fix(backend,combo,cursor): header budget, Codex failover, kv_after_text (#10573)

* fix(combo): allow fill-first failover across Codex OAuth connections

applyNativeCodexTurnPin previously narrowed the target pool to the
single pinned connection, making same-provider failover impossible when
the pinned connection was rejected by pre-dispatch checks. Return all
compatible connections (same provider + model) with the pinned connection
first, so the combo engine can fall over to siblings. Also allow
pinNativeCodexTurn to update connectionId for failover recovery while
still rejecting provider/model changes.

Fixes #10379

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* fix(test): replace as any with properly typed ResolvedComboTarget literal

Addresses ESLint no-explicit-any error in tests/.

Signed-off-by: Minxi Hou <houminxi@gmail.com>

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
This commit is contained in:
Bob.Hou
2026-08-18 21:58:22 +08:00
committed by GitHub
parent 30265ef7f8
commit fb89fafc3a
2 changed files with 177 additions and 12 deletions

View File

@@ -74,12 +74,12 @@ export function pinNativeCodexTurn(args: {
const existing = pins.get(key);
if (
existing &&
(existing.modelStr !== args.target.modelStr ||
existing.provider !== args.target.provider ||
existing.connectionId !== args.connectionId)
(existing.modelStr !== args.target.modelStr || existing.provider !== args.target.provider)
) {
throw new Error("Native Codex turn target changed after output was emitted");
}
// ConnectionId changes are allowed (failover to sibling connection)
// as long as provider + model stay the same.
const now = Date.now();
pins.set(key, {
comboName: args.comboName,
@@ -92,21 +92,37 @@ export function pinNativeCodexTurn(args: {
prune(now);
}
/**
* Apply a native Codex turn pin to the target list.
*
* Returns all compatible targets (same provider + model) with the pinned
* connection preferred first. This allows fill-first failover: if the
* pinned connection is rejected by a pre-dispatch gate, the combo engine
* tries the next compatible connection instead of returning 503.
*
* Provider + model remain locked for the turn — only the connection
* can fall over.
*/
export function applyNativeCodexTurnPin(
targets: ResolvedComboTarget[],
pin: NativeTurnPin
): ResolvedComboTarget[] {
const target = targets.find(
const compatible = targets.filter(
(candidate) => candidate.modelStr === pin.modelStr && candidate.provider === pin.provider
);
if (!target) return [];
return [
{
...target,
connectionId: pin.connectionId,
allowedConnectionIds: [pin.connectionId],
},
];
if (compatible.length === 0) return [];
const pinned = compatible.find((t) => t.connectionId === pin.connectionId);
const siblings = compatible.filter((t) => t.connectionId !== pin.connectionId);
// Pinned connection first, then same-provider/model siblings as fallback
const ordered = pinned ? [pinned, ...siblings] : compatible;
return ordered.map((target) => ({
...target,
// Allow only connections for the pinned provider+model
allowedConnectionIds: compatible.map((t) => t.connectionId),
}));
}
export function revokeNativeCodexTurnPinsForConnection(connectionId: string): number {

View File

@@ -0,0 +1,149 @@
// #10379: Native Codex turn pin must allow fill-first failover across
// compatible connections for the same provider + model.
import { test } from "node:test";
import assert from "node:assert/strict";
const {
applyNativeCodexTurnPin,
pinNativeCodexTurn,
getNativeCodexTurnPin,
clearNativeCodexTurnPinsForTests,
} = await import("../../open-sse/services/combo/nativeCodexTurnPin.ts");
const BODY = {
client_metadata: {
"x-codex-turn-metadata": JSON.stringify({ thread_id: "t1", turn_id: "turn1" }),
},
};
function makeTarget(connectionId: string, model = "gpt-5.6-sol", provider = "codex") {
return {
kind: "model" as const,
stepId: `step-${connectionId}`,
executionKey: `ek-${connectionId}`,
modelStr: model,
provider,
providerId: null,
connectionId,
weight: 1,
label: null,
};
}
test("pinned connection is preferred but siblings are included as fallback", () => {
clearNativeCodexTurnPinsForTests();
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-1"),
connectionId: "conn-1",
});
const pin = getNativeCodexTurnPin(BODY, "test-combo")!;
const targets = [makeTarget("conn-1"), makeTarget("conn-2"), makeTarget("conn-3")];
const result = applyNativeCodexTurnPin(targets, pin);
assert.equal(result.length, 3, "all compatible connections should be returned");
assert.equal(result[0].connectionId, "conn-1", "pinned connection should be first");
assert.deepEqual(result[0].allowedConnectionIds, ["conn-1", "conn-2", "conn-3"]);
});
test("fallback connections share the same allowedConnectionIds", () => {
clearNativeCodexTurnPinsForTests();
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-2"),
connectionId: "conn-2",
});
const pin = getNativeCodexTurnPin(BODY, "test-combo")!;
const targets = [makeTarget("conn-1"), makeTarget("conn-2"), makeTarget("conn-3")];
const result = applyNativeCodexTurnPin(targets, pin);
assert.equal(result[0].connectionId, "conn-2");
assert.equal(result[1].connectionId, "conn-1");
assert.equal(result[2].connectionId, "conn-3");
for (const t of result) {
assert.deepEqual(t.allowedConnectionIds, ["conn-1", "conn-2", "conn-3"]);
}
});
test("incompatible targets (different provider/model) are excluded", () => {
clearNativeCodexTurnPinsForTests();
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-1"),
connectionId: "conn-1",
});
const pin = getNativeCodexTurnPin(BODY, "test-combo")!;
const targets = [
makeTarget("conn-1"),
makeTarget("conn-2"),
makeTarget("conn-other", "different-model", "other-provider"),
];
const result = applyNativeCodexTurnPin(targets, pin);
assert.equal(result.length, 2, "incompatible target should be excluded");
assert.deepEqual(result[0].allowedConnectionIds, ["conn-1", "conn-2"]);
});
test("empty result when no compatible targets exist", () => {
clearNativeCodexTurnPinsForTests();
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-1"),
connectionId: "conn-1",
});
const pin = getNativeCodexTurnPin(BODY, "test-combo")!;
const targets = [makeTarget("conn-x", "other-model", "other-provider")];
const result = applyNativeCodexTurnPin(targets, pin);
assert.equal(result.length, 0);
});
test("pinNativeCodexTurn allows connectionId change for same provider+model", () => {
clearNativeCodexTurnPinsForTests();
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-1"),
connectionId: "conn-1",
});
// Should NOT throw when only connectionId changes
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-2"),
connectionId: "conn-2",
});
const pin = getNativeCodexTurnPin(BODY, "test-combo")!;
assert.equal(pin.connectionId, "conn-2", "pin should update to new connection");
});
test("pinNativeCodexTurn rejects provider/model change", () => {
clearNativeCodexTurnPinsForTests();
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-1"),
connectionId: "conn-1",
});
assert.throws(
() =>
pinNativeCodexTurn({
body: BODY,
comboName: "test-combo",
target: makeTarget("conn-1", "different-model", "codex"),
connectionId: "conn-1",
}),
/Native Codex turn target changed/
);
});