Files
OmniRoute/open-sse/services/combo/nativeCodexTurnPin.ts
adevwithpurpose 3cab6dc9f0 fix(combo): resolve nativeCodexTurnPin type error and connection-pin gap
PR #10573 landed with two real defects surfaced by typecheck/tests on
the combined release tip:

- TS2322: allowedConnectionIds (string[]) was built from
  compatible.map(t => t.connectionId), whose type includes null.
  Filter nulls before assigning.
- applyNativeCodexTurnPin never assigned the pinned connectionId onto
  a compatible candidate that didn't already carry it (e.g. an
  unresolved placeholder target with connectionId: null) — the pin
  was silently dropped instead of applied. Now resolves the pinned
  slot's connectionId explicitly (in original order, so
  allowedConnectionIds stays consistent regardless of pinned-first
  reordering) before building the returned target list.

Confirmed via the existing focused suites:
tests/unit/chatgpt-web-codex-turn-pin.test.ts and
tests/unit/native-codex-turn-pin-10379.test.ts (14/14 pass),
typecheck:core clean.
2026-08-18 11:14:01 -03:00

156 lines
4.9 KiB
TypeScript

import { createHash } from "node:crypto";
import type { ResolvedComboTarget } from "./types.ts";
type NativeTurnPin = {
comboName: string;
modelStr: string;
provider: string;
connectionId: string;
createdAt: number;
expiresAt: number;
};
const TTL_MS = 45 * 60_000;
const MAX_PINS = 1_000;
const pins = new Map<string, NativeTurnPin>();
function record(value: unknown): Record<string, unknown> | undefined {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: undefined;
}
function turnMetadata(body: Record<string, unknown>): Record<string, unknown> | undefined {
const metadata = record(body.client_metadata);
const raw = metadata?.["x-codex-turn-metadata"];
if (typeof raw === "string") {
try {
return record(JSON.parse(raw));
} catch {
return undefined;
}
}
return record(raw);
}
export function nativeCodexTurnKey(
body: Record<string, unknown>,
comboName: string
): string | null {
const metadata = turnMetadata(body);
const threadId = typeof metadata?.thread_id === "string" ? metadata.thread_id : "";
const turnId = typeof metadata?.turn_id === "string" ? metadata.turn_id : "";
if (!threadId || !turnId) return null;
return createHash("sha256").update(JSON.stringify({ comboName, threadId, turnId })).digest("hex");
}
function prune(now = Date.now()): void {
for (const [key, pin] of pins) if (pin.expiresAt <= now) pins.delete(key);
while (pins.size > MAX_PINS) {
const oldest = pins.keys().next().value as string | undefined;
if (!oldest) break;
pins.delete(oldest);
}
}
export function getNativeCodexTurnPin(
body: Record<string, unknown>,
comboName: string
): NativeTurnPin | null {
prune();
const key = nativeCodexTurnKey(body, comboName);
return key ? (pins.get(key) ?? null) : null;
}
export function pinNativeCodexTurn(args: {
body: Record<string, unknown>;
comboName: string;
target: ResolvedComboTarget;
connectionId: string;
}): void {
const key = nativeCodexTurnKey(args.body, args.comboName);
if (!key || !args.connectionId) return;
const existing = pins.get(key);
if (
existing &&
(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,
modelStr: args.target.modelStr,
provider: args.target.provider,
connectionId: args.connectionId,
createdAt: existing?.createdAt ?? now,
expiresAt: now + TTL_MS,
});
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 compatible = targets.filter(
(candidate) => candidate.modelStr === pin.modelStr && candidate.provider === pin.provider
);
if (compatible.length === 0) return [];
let pinnedIndex = compatible.findIndex((t) => t.connectionId === pin.connectionId);
// No candidate already carries the pinned connectionId (e.g. the caller
// resolved the target before a connection was assigned) — assign the pin
// onto the first compatible candidate so dispatch targets it directly.
if (pinnedIndex < 0) pinnedIndex = 0;
// Resolve the pinned slot's connectionId in ORIGINAL order first, so
// allowedConnectionIds reflects the same set/order regardless of which
// candidate ends up first in the returned (pinned-first) array.
const resolved = compatible.map((t, i) =>
i === pinnedIndex ? { ...t, connectionId: pin.connectionId } : t
);
const allowedConnectionIds = resolved
.map((t) => t.connectionId)
.filter((id): id is string => id !== null);
// Pinned connection first, then same-provider/model siblings as fallback
const pinned = resolved[pinnedIndex];
const siblings = resolved.filter((_, i) => i !== pinnedIndex);
const ordered = [pinned, ...siblings];
return ordered.map((target) => ({
...target,
// Allow only connections for the pinned provider+model
allowedConnectionIds,
}));
}
export function revokeNativeCodexTurnPinsForConnection(connectionId: string): number {
let revoked = 0;
for (const [key, pin] of pins) {
if (pin.connectionId !== connectionId) continue;
pins.delete(key);
revoked += 1;
}
return revoked;
}
export function clearNativeCodexTurnPinsForTests(): void {
pins.clear();
}