Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codex-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"chalk": "^5.2.0",
"diff": "^7.0.0",
"dotenv": "^16.1.4",
"express": "^5.1.0",
"fast-deep-equal": "^3.1.3",
"fast-npm-meta": "^0.4.2",
"figures": "^6.1.0",
Expand All @@ -54,6 +55,7 @@
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/diff": "^7.0.2",
"@types/express": "^5.0.1",
"@types/js-yaml": "^4.0.9",
"@types/marked-terminal": "^6.1.1",
"@types/react": "^18.0.32",
Expand Down
36 changes: 34 additions & 2 deletions codex-cli/src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import { ReviewDecision } from "./utils/agent/review";
import { AutoApprovalMode } from "./utils/auto-approval-mode";
import { checkForUpdates } from "./utils/check-updates";
import {
getApiKey,
loadConfig,
PRETTY_PRINT,
INSTRUCTIONS_FILEPATH,
} from "./utils/config";
import { getApiKey as fetchApiKey } from "./utils/get-api-key";
import { createInputItem } from "./utils/input-utils";
import { initLogger } from "./utils/logger/log";
import { isModelSupportedForResponses } from "./utils/model-utils.js";
Expand All @@ -34,6 +34,7 @@ import { spawnSync } from "child_process";
import fs from "fs";
import { render } from "ink";
import meow from "meow";
import os from "os";
import path from "path";
import React from "react";

Expand Down Expand Up @@ -265,7 +266,38 @@ const prompt = cli.input[0];
const model = cli.flags.model ?? config.model;
const imagePaths = cli.flags.image;
const provider = cli.flags.provider ?? config.provider ?? "openai";
const apiKey = getApiKey(provider);

const client = {
issuer: "https://auth.openai.com",
client_id: "app_EMoamEEZ73f0CkXaXp7hrann",
};

let apiKey = "";

// Try to load existing auth file if present
try {
const home = os.homedir();
const authDir = path.join(home, ".codex");
const authFile = path.join(authDir, "auth.json");
if (fs.existsSync(authFile)) {
const data = JSON.parse(fs.readFileSync(authFile, "utf-8"));
const lastRefreshTime = data.last_refresh
? new Date(data.last_refresh).getTime()
: 0;
const expired = Date.now() - lastRefreshTime > 28 * 24 * 60 * 60 * 1000;
if (data.OPENAI_API_KEY && !expired) {
apiKey = data.OPENAI_API_KEY;
}
}
} catch {
// ignore errors
}

if (!apiKey) {
apiKey = await fetchApiKey(client.issuer, client.client_id);
}
// Ensure the API key is available as an environment variable for legacy code
process.env["OPENAI_API_KEY"] = apiKey;

// Set of providers that don't require API keys
const NO_API_KEY_REQUIRED = new Set(["ollama"]);
Expand Down
6 changes: 2 additions & 4 deletions codex-cli/src/utils/agent/agent-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
OPENAI_TIMEOUT_MS,
OPENAI_ORGANIZATION,
OPENAI_PROJECT,
getApiKey,
getBaseUrl,
AZURE_OPENAI_API_VERSION,
} from "../config.js";
Expand Down Expand Up @@ -298,7 +297,7 @@ export class AgentLoop {
this.sessionId = getSessionId() || randomUUID().replaceAll("-", "");
// Configure OpenAI client with optional timeout (ms) from environment
const timeoutMs = OPENAI_TIMEOUT_MS;
const apiKey = getApiKey(this.provider);
const apiKey = this.config.apiKey ?? process.env["OPENAI_API_KEY"] ?? "";
const baseURL = getBaseUrl(this.provider);

this.oai = new OpenAI({
Expand Down Expand Up @@ -683,7 +682,7 @@ export class AgentLoop {
// prompts) and so that freshly generated `function_call_output`s are
// shown immediately.
// Figure out what subset of `turnInput` constitutes *new* information
// for the UI so that we dont spam the interface with repeats of the
// for the UI so that we don't spam the interface with repeats of the
// entire transcript on every iteration when response storage is
// disabled.
const deltaInput = this.disableResponseStorage
Expand Down Expand Up @@ -1525,7 +1524,6 @@ You MUST adhere to the following criteria when executing the task:
- If there is a .pre-commit-config.yaml, use \`pre-commit run --files ...\` to check that your changes pass the pre-commit checks. However, do not fix pre-existing errors on lines you didn't touch.
- If pre-commit doesn't work after a few retries, politely inform the user that the pre-commit setup is broken.
- Once you finish coding, you must
- Check \`git status\` to sanity check your changes; revert any scratch files or changes.
- Remove all inline comments you added as much as possible, even if they look normal. Check using \`git diff\`. Inline comments must be generally avoided, unless active maintainers of the repo, after long careful study of the code and the issue, will still misinterpret the code without the comments.
- Check if you accidentally add copyright or license headers. If so, remove them.
- Try to run pre-commit if it is available.
Expand Down
2 changes: 1 addition & 1 deletion codex-cli/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function getApiKey(provider: string = "openai"): string | undefined {
return process.env[providerInfo.envKey];
}

// Checking `PROVIDER_API_KEY feels more intuitive with a custom provider.
// Checking `PROVIDER_API_KEY` feels more intuitive with a custom provider.
const customApiKey = process.env[`${provider.toUpperCase()}_API_KEY`];
if (customApiKey) {
return customApiKey;
Expand Down
75 changes: 75 additions & 0 deletions codex-cli/src/utils/get-api-key-components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import SelectInput from "../components/select-input/select-input.js";
import Spinner from "../components/vendor/ink-spinner.js";
import TextInput from "../components/vendor/ink-text-input.js";
import { Box, Text } from "ink";
import React, { useState } from "react";

export type Choice = { type: "signin" } | { type: "apikey"; key: string };

export function ApiKeyPrompt({
onDone,
}: {
onDone: (choice: Choice) => void;
}): JSX.Element {
const [step, setStep] = useState<"select" | "paste">("select");
const [apiKey, setApiKey] = useState("");

if (step === "select") {
return (
<Box flexDirection="column" gap={1}>
<Box flexDirection="column">
<Text>
Sign in with ChatGPT to generate an API key or paste one you already
have.
</Text>
<Text dimColor>[use arrows to move, enter to select]</Text>
</Box>
<SelectInput
items={[
{ label: "Sign in with ChatGPT", value: "signin" },
{
label: "Paste an API key (or set as OPENAI_API_KEY)",
value: "paste",
},
]}
onSelect={(item: { value: string }) => {
if (item.value === "signin") {
onDone({ type: "signin" });
} else {
setStep("paste");
}
}}
/>
</Box>
);
}

return (
<Box flexDirection="column">
<Text>Paste your OpenAI API key and press &lt;Enter&gt;:</Text>
<TextInput
value={apiKey}
onChange={setApiKey}
onSubmit={(value: string) => {
if (value.trim() !== "") {
onDone({ type: "apikey", key: value.trim() });
}
}}
placeholder="sk-..."
mask="*"
/>
</Box>
);
}

export function WaitingForAuth(): JSX.Element {
return (
<Box flexDirection="row" marginTop={1}>
<Spinner type="ball" />
<Text>
{" "}
Waiting for authentication… <Text dimColor>ctrl + c to quit</Text>
</Text>
</Box>
);
}
Loading