Skip to content
Open
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,19 @@ Yes. If you already use Codex, the plugin picks up the same [configuration](#com
Yes. Because the plugin uses your local Codex CLI, your existing sign-in method and config still apply.

If you need to point the built-in OpenAI provider at a different endpoint, set `openai_base_url` in your [Codex config](https://developers.openai.com/codex/config-advanced/#config-and-state-locations).

### Can I use another model provider?

Yes. The plugin reflects the active provider from your Codex config, so you can route Codex through any provider Codex supports by defining it under `[model_providers]`. For example, to use [OrcaRouter](https://www.orcarouter.ai), an OpenAI-compatible AI gateway, add this to your `~/.codex/config.toml`:

```toml
model = "anthropic/claude-sonnet-5"
model_provider = "orcarouter"

[model_providers.orcarouter]
name = "OrcaRouter"
base_url = "https://api.orcarouter.ai/v1"
env_key = "ORCAROUTER_API_KEY"
```

After that, `/codex:setup` and `/codex:status` show `OrcaRouter` as the active provider.
3 changes: 2 additions & 1 deletion plugins/codex/scripts/lib/codex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,8 @@ function buildResultStatus(turnState) {
const BUILTIN_PROVIDER_LABELS = new Map([
["openai", "OpenAI"],
["ollama", "Ollama"],
["lmstudio", "LM Studio"]
["lmstudio", "LM Studio"],
["orcarouter", "OrcaRouter"]
]);

function normalizeProviderId(value) {
Expand Down
16 changes: 16 additions & 0 deletions tests/fake-codex-fixture.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function buildAccountReadResult() {
return { account: null, requiresOpenaiAuth: true };
case "provider-no-auth":
case "env-key-provider":
case "orcarouter-provider":
return { account: null, requiresOpenaiAuth: false };
case "api-key-account-only":
return { account: { type: "apiKey" }, requiresOpenaiAuth: true };
Expand Down Expand Up @@ -104,6 +105,21 @@ function buildConfigReadResult() {
},
origins: {}
};
case "orcarouter-provider":
return {
config: {
model_provider: "orcarouter",
model_providers: {
orcarouter: {
name: "OrcaRouter",
base_url: "https://api.orcarouter.ai/v1",
env_key: "ORCAROUTER_API_KEY",
requires_openai_auth: false
}
}
},
origins: {}
};
default:
return {
config: { model_provider: "openai" },
Expand Down
37 changes: 37 additions & 0 deletions tests/runtime.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,43 @@ test("setup treats custom providers with app-server-ready config as ready", () =
assert.match(payload.auth.detail, /configured and does not require OpenAI authentication/i);
});

test("setup recognizes OrcaRouter as the active provider without OpenAI auth", () => {
const binDir = makeTempDir();
installFakeCodex(binDir, "orcarouter-provider");

const result = run("node", [SCRIPT, "setup", "--json"], {
cwd: ROOT,
env: buildEnv(binDir)
});

assert.equal(result.status, 0, result.stderr);
const payload = JSON.parse(result.stdout);
assert.equal(payload.ready, true);
assert.equal(payload.auth.loggedIn, true);
assert.equal(payload.auth.authMethod, null);
assert.equal(payload.auth.source, "app-server");
assert.equal(payload.auth.provider, "orcarouter");
assert.match(payload.auth.detail, /OrcaRouter is configured and does not require OpenAI authentication/i);
});

test("task runs when OrcaRouter is the active provider", () => {
const repo = makeTempDir();
const binDir = makeTempDir();
installFakeCodex(binDir, "orcarouter-provider");
initGitRepo(repo);
fs.writeFileSync(path.join(repo, "README.md"), "hello\n");
run("git", ["add", "README.md"], { cwd: repo });
run("git", ["commit", "-m", "init"], { cwd: repo });

const result = run("node", [SCRIPT, "task", "check orcarouter provider"], {
cwd: repo,
env: buildEnv(binDir)
});

assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /Handled the requested task/);
});

test("setup reports not ready when app-server config read fails", () => {
const binDir = makeTempDir();
installFakeCodex(binDir, "config-read-fails");
Expand Down