Skip to content
Closed
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
18 changes: 17 additions & 1 deletion src/server/responses/main-hard-lock-routing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { OcxConfig } from "../../types";
import { resolveComboId } from "../../combos";
import { isBareOpenAiFamilyModel } from "../../router";
import { isBareOpenAiFamilyModel, previewRouteModel } from "../../router";
import { resolveChosenTarget } from "./shadow-target-availability";
import { getRoutingProfile, resolvePolicyProfileId } from "../../routing/profile";
import { NATIVE_RESERVE_MODEL } from "../../codex/catalog/native-models";
import {
getMainAccountHardLockStatus,
Expand Down Expand Up @@ -95,6 +97,20 @@ export function applyMainHardLockFallbackOverride(
return null;
}

// Combos and policies keep their own dispatch admission and declared failover/evidence.
// Other chosen targets must resolve now; a removed qualifier cannot become a default send.
const policyId = resolvePolicyProfileId(config, fallback.model);
if (!isConfiguredComboSelector(config, fallback.model)
&& !(policyId && getRoutingProfile(config, policyId))) {
try {
const target = resolveChosenTarget(fallback.model, model => previewRouteModel(config, model));
if ("unavailable" in target) return null;
} catch {
// An unavailable target keeps the source lock's refusal without touching the body.
return null;
}
}

parsed.raw.model = fallback.model;
if (fallback.reasoningEffort !== undefined) {
const reasoning = parsed.raw.reasoning;
Expand Down
5 changes: 5 additions & 0 deletions structure/codex-account-controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ protects:
- A malformed section (blank or padded model, undeclared effort) disables the reroute entirely —
the same disable-don't-widen reading as the compaction override — while the load and write
boundaries report the malformation. The lock's own refusal behavior is untouched either way.
- Before rewriting, concrete targets resolve against the current configuration through the
shared chosen-target check. Unavailable targets and qualified selectors that only reach the
terminal default provider retain the source lock's refusal. Bare default targets, declared
slash-containing model ids, configured combos and policy profiles keep their ordinary routing
semantics; policies retain request-evidence evaluation at dispatch preparation.

The dashboard row mirrors the shadow-call row: the model pick, the effort and the switch each
save immediately, and the switch writes `enabled: false` — a remembered pause that keeps the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { applyMainHardLockFallbackOverride } from "../../src/server/responses/main-hard-lock-routing";
import { resolveMainAccountHardLockFallback } from "../../src/codex/main-account-hard-lock";
import { captureMainQuotaWriter, clearMainAccountInfoCache, observeMainQuotaIdentity } from "../../src/codex/main-account-cache";
import { captureMainQuotaWriter, clearMainAccountInfoCache, observeMainQuotaCredential, observeMainQuotaIdentity } from "../../src/codex/main-account-cache";
import { clearAccountQuota, setAccountQuotaFromParsed, type StoredAccountQuota } from "../../src/codex/quota";
import { saveCodexAccountCredential } from "../../src/codex/account-store";
import { NATIVE_RESERVE_MODEL } from "../../src/codex/catalog/native-models";
Expand Down Expand Up @@ -57,7 +57,7 @@ function observe(quota: Omit<StoredAccountQuota, "updatedAt">): void {
}

function config(overrides: Partial<OcxConfig> = {}): OcxConfig {
return { providers: {}, codexAccounts: [], ...overrides } as OcxConfig;
return { providers: { deepseek: { adapter: "openai-responses", authMode: "key", baseUrl: "https://deepseek.example.test/v1" } }, codexAccounts: [], ...overrides } as OcxConfig;
}

function withPoolAccount(target: OcxConfig): OcxConfig {
Expand Down Expand Up @@ -165,6 +165,7 @@ describe("applyMainHardLockFallbackOverride", () => {
deleteAllScopedHealth("pool-a");
const directCfg = withPoolAccount(config({
providers: {
deepseek: { adapter: "openai-responses", authMode: "key", baseUrl: "https://deepseek.example.test/v1" },
openai: {
adapter: "openai-responses",
authMode: "forward",
Expand Down Expand Up @@ -226,6 +227,90 @@ describe("applyMainHardLockFallbackOverride", () => {
expect(applyMainHardLockFallbackOverride({ model: 42 }, cfg)).toBeNull();
});

test("unavailable chosen targets keep the blocked source and reasoning intact", () => {
observe({ weeklyPercent: 100 });
const gateway = { adapter: "openai-responses", authMode: "key", baseUrl: "https://gateway.example.test/v1" } as const;
for (const target of ["private/secret-model", "former/secret-model", "combo/removed", "policy/removed", "retired-account/gpt-5.3"]) {
for (const providers of [{ gateway }, {}]) {
const cfg = config({ providers, defaultProvider: "gateway", codexMainAccountHardLockFallback: { model: target, reasoningEffort: "low" } } as Partial<OcxConfig>);
const body = { model: "gpt-5.3", reasoning: { effort: "high" } };
expect(applyMainHardLockFallbackOverride(body, cfg)).toBeNull();
expect(body).toEqual({ model: "gpt-5.3", reasoning: { effort: "high" } });
}
}
const cfg = config({ providers: { gateway, private: { ...gateway, disabled: true } }, defaultProvider: "gateway", codexMainAccountHardLockFallback: { model: "private/secret-model" } } as Partial<OcxConfig>);
expect(applyMainHardLockFallbackOverride({ model: "gpt-5.3" }, cfg)).toBeNull();
});

test("target admission is rechecked after provider removal, re-addition and alias changes", () => {
observe({ weeklyPercent: 100 });
const provider = { adapter: "openai-responses", authMode: "key", baseUrl: "https://chosen.example.test/v1", alias: "chosen" } as const;
const cfg = config({ providers: { private: provider }, defaultProvider: "private", codexMainAccountHardLockFallback: { model: "chosen/secret-model" } } as Partial<OcxConfig>);
const apply = () => applyMainHardLockFallbackOverride({ model: "gpt-5.3" }, cfg);
expect(apply()).not.toBeNull();
delete cfg.providers.private;
expect(apply()).toBeNull();
cfg.providers.private = provider;
expect(apply()).not.toBeNull();
cfg.providers.private = { ...provider, alias: "renamed" };
expect(apply()).toBeNull();
cfg.codexMainAccountHardLockFallback = { model: "private/secret-model" };
expect(apply()).not.toBeNull();
});

test("bare fallback and explicitly declared slash model ids stay valid", () => {
observe({ weeklyPercent: 100 });
const provider = { adapter: "openai-responses", authMode: "key", baseUrl: "https://chosen.example.test/v1", models: ["vendor/native-model"] } as const;
for (const model of ["bare-model", "vendor/native-model", "private/vendor/native-model"]) {
const cfg = config({ providers: { private: { ...provider, models: [...provider.models] } }, defaultProvider: "private", codexMainAccountHardLockFallback: { model } } as Partial<OcxConfig>);
expect(applyMainHardLockFallbackOverride({ model: "gpt-5.3" }, cfg)?.targetModel).toBe(model);
}
});

test("configured combo aliases, policy profiles and account namespaces keep their admission", () => {
observe({ weeklyPercent: 100 });
const cfg = config({
providers: { private: { adapter: "openai-responses", authMode: "key", baseUrl: "https://chosen.example.test/v1" },
openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex" } },
combos: { rescue: { alias: "chosen-combo", targets: [{ provider: "private", model: "secret-model" }] } },
routingProfiles: { rescue: { alias: "chosen-policy", candidates: [{ provider: "private", model: "secret-model" }], require: { tools: true } } },
codexAccountNamespaces: { pinned: "pool-a" },
} as Partial<OcxConfig>);
for (const model of ["combo/rescue", "chosen-combo", "policy/rescue", "chosen-policy", "pinned/gpt-5.3"]) {
cfg.codexMainAccountHardLockFallback = { model };
expect(applyMainHardLockFallbackOverride({ model: "gpt-5.3" }, cfg)?.targetModel).toBe(model);
}
});

test("a stale qualified fallback never sends to the enabled default provider", async () => {
observeMainQuotaCredential("fixture-caller", "fallback-account-a");
observe({ weeklyPercent: 100 });
const cfg = config({
providers: {
openai: { adapter: "openai-responses", authMode: "forward", codexAccountMode: "direct", baseUrl: "https://chatgpt.com/backend-api/codex" },
gateway: { adapter: "openai-responses", authMode: "key", apiKey: "fixture-key", baseUrl: "https://gateway.example.test/v1" },
},
defaultProvider: "gateway",
codexMainAccountHardLockFallback: { model: "private/secret-model" },
} as Partial<OcxConfig>);
let sends = 0;
const sentUrls: string[] = [];
globalThis.fetch = (async (input: unknown) => {
sends += 1;
sentUrls.push(String(input));
return Response.json({ id: "resp_fixture", status: "completed", output: [] });
}) as typeof fetch;
const response = await handleResponses(new Request("http://localhost/v1/responses", {
method: "POST",
headers: { "content-type": "application/json", authorization: "Bearer fixture-caller", "chatgpt-account-id": "fallback-account-a" },
body: JSON.stringify({ model: "gpt-5.3", stream: false, input: "fixture" }),
}), cfg, { model: "", provider: "" });
await response.text();
expect(sentUrls).toEqual([]);
expect(sends).toBe(0);
expect(response.status).toBeGreaterThanOrEqual(400);
});

test("handleResponses strips caller credentials, preserves requestedModel on combo fallback, and yields to shadow/compaction turns", async () => {
observe({ weeklyPercent: 100 });
const cfg = config({
Expand Down
Loading