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: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@ jobs:
run: npm ci

- name: Run Codex compatibility tests
run: npm run test -- test/codex.test.ts test/opencode-codex.test.ts test/request-transformer.test.ts test/fetch-helpers.test.ts
run: npm run test -- test/codex.test.ts test/host-codex-prompt.test.ts test/request-transformer.test.ts test/fetch-helpers.test.ts
11 changes: 6 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Commit: 461ea2b
Branch: main

## OVERVIEW
OpenCode plugin: intercepts OpenAI SDK calls, routes through ChatGPT Codex backend with multi-account OAuth rotation.
Codex plugin: intercepts OpenAI SDK calls, routes through ChatGPT Codex backend with multi-account OAuth rotation.

## STRUCTURE
```
Expand All @@ -14,7 +14,7 @@ OpenCode plugin: intercepts OpenAI SDK calls, routes through ChatGPT Codex backe
├── lib/ # core logic (see lib/AGENTS.md)
├── test/ # vitest suites (see test/AGENTS.md)
├── scripts/ # install + build helpers
├── config/ # opencode.json examples (legacy/modern)
├── config/ # Codex.json examples (legacy/modern)
├── docs/ # architecture + user docs
├── assets/ # static assets
└── dist/ # build output (generated, do not edit)
Expand Down Expand Up @@ -64,8 +64,8 @@ npm run lint # eslint
## NOTES
- OAuth callback: `http://127.0.0.1:1455/auth/callback`.
- ChatGPT backend requires `store: false`, include `reasoning.encrypted_content`.
- Per-project accounts: `~/.opencode/projects/<project-key>/openai-codex-accounts.json` (walks up to find project root).
- Global accounts: `~/.opencode/openai-codex-accounts.json`.
- Per-project accounts: `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json` (walks up to find project root).
- Global accounts: `~/.codex/multi-auth/openai-codex-accounts.json`.
- Prompt templates synced from Codex CLI GitHub releases with ETag caching.
- 5xx server errors trigger account rotation and health penalty (same as network errors).
- API deprecation/sunset headers (RFC 8594) are logged as warnings.
Expand All @@ -83,7 +83,7 @@ Skills to load when delegating tasks in this codebase.
| `typescript-senior` | Strict mode, template literal types (`QuotaKey`), discriminated unions (`TokenResult`), Zod inference |
| `node-backend` | ESM-first, `node:crypto`, Buffer API, async patterns |
| `testing-js` | Vitest with 80% coverage threshold, `vi.mock`, `vi.useFakeTimers` |
| `mcp-builder` | Uses `@opencode-ai/plugin/tool` pattern for tool registration |
| `mcp-builder` | Uses `@codex-ai/plugin/tool` pattern for tool registration |

### Domain-Specific Skills (load when touching these areas)

Expand Down Expand Up @@ -121,3 +121,4 @@ delegate_task(category="...", load_skills=["typescript-senior", "node-backend",
// Git/GitHub operations
delegate_task(category="quick", load_skills=["git-master", "github"])
```

18 changes: 0 additions & 18 deletions assets/opencode-logo-ornate-dark.svg

This file was deleted.

10 changes: 5 additions & 5 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

These files are example model/provider templates for `codex-multi-auth`.

## Files
## Primary (Codex-named)

| File | Purpose |
| --- | --- |
| [`opencode-modern.json`](./opencode-modern.json) | Modern variant-based template |
| [`opencode-legacy.json`](./opencode-legacy.json) | Legacy explicit-model template |
| [`minimal-opencode.json`](./minimal-opencode.json) | Minimal debug template |
| [`codex-modern.json`](./codex-modern.json) | Modern variant-based template |
| [`codex-legacy.json`](./codex-legacy.json) | Legacy explicit-model template |
| [`minimal-codex.json`](./minimal-codex.json) | Minimal debug template |
Comment thread
ndycode marked this conversation as resolved.

## Notes

- Template filenames are kept for backward compatibility.
- These templates are optional for the OAuth account manager flow.
- Core auth commands use `codex auth login`.

Expand All @@ -28,3 +27,4 @@ These files are example model/provider templates for `codex-multi-auth`.
- [`../docs/configuration.md`](../docs/configuration.md)
- [`../docs/getting-started.md`](../docs/getting-started.md)
- [`../docs/reference/settings.md`](../docs/reference/settings.md)

2 changes: 1 addition & 1 deletion config/opencode-legacy.json → config/codex-legacy.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://opencode.ai/config.json",
"$schema": "./schema/config.schema.json",
"plugin": [
"codex-multi-auth"
],
Expand Down
4 changes: 3 additions & 1 deletion config/opencode-modern.json → config/codex-modern.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://opencode.ai/config.json",
"$schema": "./schema/config.schema.json",
"plugin": [
"codex-multi-auth"
],
Expand Down Expand Up @@ -237,3 +237,5 @@
}
}
}


Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://opencode.ai/config.json",
"$schema": "./schema/config.schema.json",
"plugin": ["codex-multi-auth"],

"provider": {
Expand All @@ -11,3 +11,5 @@
},
"model": "openai/gpt-5-codex"
}


21 changes: 21 additions & 0 deletions config/schema/config.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://codex-multi-auth.local/schema/config.schema.json",
"title": "codex-multi-auth config template",
"type": "object",
"properties": {
"plugin": {
"type": "array",
"items": { "type": "string" }
},
"provider": {
"type": "object",
"additionalProperties": true
},
"model": {
"type": "string"
}
},
"required": ["plugin", "provider"],
"additionalProperties": true
}
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import tsparser from "@typescript-eslint/parser";

export default [
{
ignores: ["dist/**", "coverage/**", "node_modules/**", "winston/**", ".tmp*/**", "*.cjs", "*.mjs"],
ignores: ["dist/**", "coverage/**", "node_modules/**", "winston/**", ".tmp*/**", "vendor/**", "*.cjs", "*.mjs"],
Comment thread
ndycode marked this conversation as resolved.
},
{
files: ["index.ts", "lib/**/*.ts"],
Expand Down Expand Up @@ -74,3 +74,4 @@ export default [
},
},
];

14 changes: 8 additions & 6 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@

*/

import { tool } from "@opencode-ai/plugin/tool";
import type { Plugin, PluginInput } from "@opencode-ai/plugin";
import type { Auth } from "@opencode-ai/sdk";
import { tool } from "@codex-ai/plugin/tool";
import type { Plugin, PluginInput } from "@codex-ai/plugin";
import type { Auth } from "@codex-ai/sdk";
import {
createAuthorizationFlow,
exchangeAuthorizationCode,
Expand Down Expand Up @@ -182,7 +182,7 @@ import {
prewarmCodexInstructions,
type ModelFamily,
} from "./lib/prompts/codex.js";
import { prewarmOpenCodeCodexPrompt } from "./lib/prompts/opencode-codex.js";
import { prewarmHostCodexPrompt } from "./lib/prompts/host-codex-prompt.js";
import type {
AccountIdSource,
OAuthAuthDetails,
Expand Down Expand Up @@ -659,7 +659,7 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
const skipHydrate =
process.env.VITEST_WORKER_ID !== undefined ||
process.env.NODE_ENV === "test" ||
process.env.OPENCODE_SKIP_EMAIL_HYDRATE === "1";
process.env.CODEX_SKIP_EMAIL_HYDRATE === "1";
if (skipHydrate) return storage;

const accountsCopy = storage.accounts.map((account) =>
Expand Down Expand Up @@ -1143,7 +1143,7 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
const configuredModels = Object.keys(userConfig.models ?? {});
prewarmCodexInstructions(configuredModels);
if (codexMode) {
prewarmOpenCodeCodexPrompt();
prewarmHostCodexPrompt();
}
}

Expand Down Expand Up @@ -4117,3 +4117,5 @@ while (attempted.size < Math.max(1, accountCount)) {
export const OpenAIAuthPlugin = OpenAIOAuthPlugin;

export default OpenAIOAuthPlugin;


8 changes: 5 additions & 3 deletions lib/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ lib/
├── proactive-refresh.ts # token refresh before expiry
├── prompts/
│ ├── codex.ts # model-family prompts, GitHub ETag cache
│ ├── codex-opencode-bridge.ts # tool remapping instructions
│ └── opencode-codex.ts # OpenCode-specific prompts
│ ├── codex-host-bridge.ts # tool remapping instructions
│ └── host-codex-prompt.ts # Codex-specific prompts
├── recovery.ts # session recovery
├── recovery/
│ ├── constants.ts
Expand Down Expand Up @@ -74,7 +74,7 @@ lib/
| SSE parsing | `request/response-handler.ts` | `response.done` extraction |
| Rate limit backoff | `request/rate-limit-backoff.ts` | exponential + jitter |
| Model family detection | `prompts/codex.ts` | GPT-5.x, Codex variants |
| Bridge prompts | `prompts/codex-opencode-bridge.ts` | tool remapping instructions |
| Bridge prompts | `prompts/codex-host-bridge.ts` | tool remapping instructions |
| Account selection | `rotation.ts` | hybrid health + token bucket |
| Account rate limits | `accounts/rate-limits.ts` | per-account tracking |
| Storage format | `storage.ts` | V3 with migration from V1/V2 |
Expand All @@ -98,3 +98,5 @@ lib/
- Never import from `dist/`; use source paths.
- Never suppress type errors.
- Never hardcode OAuth ports (use `REDIRECT_URI` constant).


3 changes: 2 additions & 1 deletion lib/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Auth } from "@opencode-ai/sdk";
import type { Auth } from "@codex-ai/sdk";
import { createLogger } from "./logger.js";
import {
loadAccounts,
Expand Down Expand Up @@ -825,3 +825,4 @@ export function formatCooldown(
const reason = account.cooldownReason ? ` (${account.cooldownReason})` : "";
return `${formatWaitTime(remaining)}${reason}`;
}

6 changes: 3 additions & 3 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import { UI_COPY } from "./ui/copy.js";
export function isNonInteractiveMode(): boolean {
if (process.env.FORCE_INTERACTIVE_MODE === "1") return false;
if (!input.isTTY || !output.isTTY) return true;
if (process.env.OPENCODE_TUI === "1") return true;
if (process.env.OPENCODE_DESKTOP === "1") return true;
if (process.env.TERM_PROGRAM === "opencode") return true;
if (process.env.CODEX_TUI === "1") return true;
if (process.env.CODEX_DESKTOP === "1") return true;
if ((process.env.TERM_PROGRAM ?? "").trim().toLowerCase() === "codex") return true;
if (process.env.ELECTRON_RUN_AS_NODE === "1") return true;
return false;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
39 changes: 27 additions & 12 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
import type { PluginConfig } from "./types.js";
import { logWarn } from "./logger.js";
import { PluginConfigSchema, getValidationErrors } from "./schemas.js";
import { getCodexHomeDir, getCodexMultiAuthDir, getLegacyOpenCodeDir } from "./runtime-paths.js";
import { getCodexHomeDir, getCodexMultiAuthDir, getLegacyCodexDir } from "./runtime-paths.js";
import {
getUnifiedSettingsPath,
loadUnifiedPluginConfigSync,
Expand All @@ -12,13 +12,20 @@ import {

const CONFIG_DIR = getCodexMultiAuthDir();
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
const LEGACY_CODEX_CONFIG_PATH = join(getCodexHomeDir(), "codex-multi-auth-config.json");
const LEGACY_OPENCODE_CONFIG_PATH = join(
getLegacyOpenCodeDir(),
const CODEX_HOME_DIR = getCodexHomeDir();
const LEGACY_CODEX_DIR = getLegacyCodexDir();
const IS_CUSTOM_CODEX_HOME = CODEX_HOME_DIR !== LEGACY_CODEX_DIR;
const LEGACY_CODEX_HOME_CONFIG_PATH = join(CODEX_HOME_DIR, "codex-multi-auth-config.json");
const LEGACY_CODEX_HOME_AUTH_CONFIG_PATH = join(
CODEX_HOME_DIR,
"openai-codex-auth-config.json",
);
const LEGACY_CODEX_CONFIG_PATH = join(
LEGACY_CODEX_DIR,
"codex-multi-auth-config.json",
);
const LEGACY_OPENCODE_AUTH_CONFIG_PATH = join(
getLegacyOpenCodeDir(),
const LEGACY_CODEX_AUTH_CONFIG_PATH = join(
LEGACY_CODEX_DIR,
"openai-codex-auth-config.json",
);
Comment thread
ndycode marked this conversation as resolved.
const TUI_COLOR_PROFILES = new Set(["truecolor", "ansi16", "ansi256"]);
Expand Down Expand Up @@ -68,6 +75,14 @@ function resolvePluginConfigPath(): string | null {
return CONFIG_PATH;
}

if (IS_CUSTOM_CODEX_HOME && existsSync(LEGACY_CODEX_HOME_CONFIG_PATH)) {
logConfigWarnOnce(
`Using legacy config path ${LEGACY_CODEX_HOME_CONFIG_PATH}. ` +
`Please migrate to ${CONFIG_PATH}.`,
);
return LEGACY_CODEX_HOME_CONFIG_PATH;
}
Comment thread
ndycode marked this conversation as resolved.

if (existsSync(LEGACY_CODEX_CONFIG_PATH)) {
logConfigWarnOnce(
`Using legacy config path ${LEGACY_CODEX_CONFIG_PATH}. ` +
Expand All @@ -76,20 +91,20 @@ function resolvePluginConfigPath(): string | null {
return LEGACY_CODEX_CONFIG_PATH;
}

if (existsSync(LEGACY_OPENCODE_CONFIG_PATH)) {
if (IS_CUSTOM_CODEX_HOME && existsSync(LEGACY_CODEX_HOME_AUTH_CONFIG_PATH)) {
logConfigWarnOnce(
`Using legacy config path ${LEGACY_OPENCODE_CONFIG_PATH}. ` +
`Using legacy config path ${LEGACY_CODEX_HOME_AUTH_CONFIG_PATH}. ` +
`Please migrate to ${CONFIG_PATH}.`,
);
return LEGACY_OPENCODE_CONFIG_PATH;
return LEGACY_CODEX_HOME_AUTH_CONFIG_PATH;
}

if (existsSync(LEGACY_OPENCODE_AUTH_CONFIG_PATH)) {
if (existsSync(LEGACY_CODEX_AUTH_CONFIG_PATH)) {
logConfigWarnOnce(
`Using legacy config path ${LEGACY_OPENCODE_AUTH_CONFIG_PATH}. ` +
`Using legacy config path ${LEGACY_CODEX_AUTH_CONFIG_PATH}. ` +
`Please migrate to ${CONFIG_PATH}.`,
);
return LEGACY_OPENCODE_AUTH_CONFIG_PATH;
return LEGACY_CODEX_AUTH_CONFIG_PATH;
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Token Count: ~450 tokens (~90% reduction vs full host prompt)
*/

export const CODEX_OPENCODE_BRIDGE = `# Codex Host Bridge
export const CODEX_HOST_BRIDGE = `# Codex Host Bridge

You are running Codex through a host terminal coding runtime. The host provides specific tools to help you work efficiently.

Expand Down Expand Up @@ -148,15 +148,15 @@ Sandbox policies, approval mechanisms, final answer formatting, git commit proto
- \`request_user_input\` is Plan-mode only; do not call it in Default mode.
- When uncertain, prefer non-destructive verification first (e.g., confirm file existence with \`list\`, then delete with \`bash\`).`;

export interface CodexOpenCodeBridgeMeta {
export interface CodexHostBridgeMeta {
estimatedTokens: number;
reductionVsCurrent: string;
reductionVsToolRemap: string;
protects: string[];
omits: string[];
}

export const CODEX_OPENCODE_BRIDGE_META: CodexOpenCodeBridgeMeta = {
export const CODEX_HOST_BRIDGE_META: CodexHostBridgeMeta = {
estimatedTokens: 550,
reductionVsCurrent: "88%",
reductionVsToolRemap: "10%",
Expand All @@ -176,3 +176,4 @@ export const CODEX_OPENCODE_BRIDGE_META: CodexOpenCodeBridgeMeta = {
"Git protocols (in Codex)",
],
};

Loading