@bitkyc08/opencodex 2.81.0 → 2.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-BqHF5Trd.js} +1 -1
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/{index-RsoaAxXY.js → index-D3FBCNiP.js} +13 -13
- package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DTBC8n7-.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound.ts +77 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +2 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +5 -0
- package/src/cli/doctor.ts +16 -3
- package/src/cli/index.ts +14 -9
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integrations.ts +16 -3
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/routes.ts +10 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/build-entries.ts +18 -0
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/effort.ts +26 -5
- package/src/codex/catalog/gather-capture.ts +5 -0
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/provider-models.ts +43 -35
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -220
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +8 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/config-schema.ts +4 -0
- package/src/generated/compatibility-version.json +303 -171
- package/src/generated/model-metadata.ts +2 -2
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +29 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +18 -2
- package/src/integrations/writer.ts +9 -3
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/request-execution-budget.ts +8 -0
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/oauth/anthropic-account-refusal.ts +39 -1
- package/src/oauth/anthropic-routing.ts +1 -1
- package/src/oauth/anthropic-send-ownership.ts +3 -3
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/index.ts +6 -2
- package/src/oauth/store.ts +1 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +21 -0
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota.ts +1 -1
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +15 -2
- package/src/responses/state/metrics.ts +8 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +12 -5
- package/src/responses/state.ts +18 -10
- package/src/responses/task-input.ts +17 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +1 -0
- package/src/server/chat-native.ts +28 -12
- package/src/server/claude-messages.ts +43 -6
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +2 -1
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +1 -0
- package/src/server/management/agent-settings-routes.ts +93 -32
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +59 -8
- package/src/server/management/context.ts +2 -2
- package/src/server/management/shared.ts +3 -3
- package/src/server/messages-native-oauth.ts +9 -8
- package/src/server/messages-native.ts +49 -13
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +31 -2
- package/src/server/responses/adapter-continuation.ts +2 -2
- package/src/server/responses/adapter-dispatch.ts +34 -13
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +20 -3
- package/src/server/responses/core-combo.ts +22 -14
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-replay.ts +73 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +24 -2
- package/src/server/responses/passthrough-dispatch.ts +19 -2
- package/src/server/responses/request-prepare.ts +4 -0
- package/src/server/responses/request-send-budget.ts +112 -49
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +2 -0
- package/src/server/responses/sidecar-execution.ts +25 -16
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +3 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/config.ts +21 -0
- package/src/types/request.ts +5 -0
- package/src/types/wire.ts +1 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-7yhoys8a.js +0 -54
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
import { codexWsReuseAcrossTurnsEnabled } from "../../config/codex-ws-reuse-setting";
|
|
2
3
|
import { registerOptionalShutdownHook } from "../../lib/optional-shutdown-hooks";
|
|
3
4
|
import { CODEX_RESPONSES_HTTP_URL, CODEX_WS_FRAME_HEADERS } from "./codex-ws-request";
|
|
4
5
|
import { CODEX_WS_ID_MAX_BYTES } from "./codex-ws-correlation";
|
|
@@ -7,11 +8,15 @@ import { CodexWsSession } from "./codex-ws-session";
|
|
|
7
8
|
export const CODEX_WS_POOL_MAX_SESSIONS = 32;
|
|
8
9
|
export const CODEX_WS_POOL_IDLE_MS = 30_000;
|
|
9
10
|
export const CODEX_WS_POOL_MAX_AGE_MS = 5 * 60_000;
|
|
11
|
+
export const CODEX_WS_POOL_WAIT_MS = 750;
|
|
12
|
+
/** Retained sockets per account+thread while cross-turn reuse is on. */
|
|
13
|
+
export const CODEX_WS_POOL_SCOPE_LIMIT = 2;
|
|
14
|
+
const CODEX_WS_POOL_WAIT_DEPTH = 2;
|
|
10
15
|
const MUTABLE_HEADERS = new Set<string>(CODEX_WS_FRAME_HEADERS);
|
|
11
16
|
let processKey: Buffer | undefined;
|
|
12
17
|
let poolSequence = 0;
|
|
13
18
|
|
|
14
|
-
export interface CodexWsReuseIdentity { key: string; scope: string }
|
|
19
|
+
export interface CodexWsReuseIdentity { key: string; scope: string; routeTag: string }
|
|
15
20
|
function value(value: unknown): value is string {
|
|
16
21
|
return typeof value === "string" && value.trim().length > 0
|
|
17
22
|
&& !/[\u0000-\u001f\u007f]/.test(value) && Buffer.byteLength(value) <= CODEX_WS_ID_MAX_BYTES;
|
|
@@ -49,19 +54,23 @@ export function codexWsReuseIdentity(url: string, headers: Record<string, string
|
|
|
49
54
|
const immutable = Object.entries(headers).filter(([name]) => !MUTABLE_HEADERS.has(name)).sort(([a], [b]) => a.localeCompare(b));
|
|
50
55
|
if (immutable.length > 128 || immutable.some(([, field]) => !value(field))
|
|
51
56
|
|| immutable.reduce((bytes, [name, field]) => bytes + Buffer.byteLength(name) + Buffer.byteLength(field), 0) > 32 * 1024) return null;
|
|
52
|
-
const
|
|
57
|
+
const reuseAcrossTurns = codexWsReuseAcrossTurnsEnabled();
|
|
58
|
+
const scope = digest(reuseAcrossTurns ? [url, account, thread] : [url, account, thread, turn]);
|
|
53
59
|
const lite = metadata.ws_request_header_x_openai_internal_codex_responses_lite;
|
|
54
60
|
if (lite !== undefined && lite !== "true" && lite !== "false") return null;
|
|
55
61
|
// `dialUrl` is the socket destination after plugin rewrites; absent means the canonical one.
|
|
56
|
-
|
|
62
|
+
const routeTag = digest(["route", authorization, proxy ?? null, dialUrl ?? null]);
|
|
63
|
+
return { scope, routeTag, key: digest([scope, authorization, body.model, body.service_tier ?? null, lite ?? null, immutable, proxy ?? null, dialUrl ?? null]) };
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
interface Entry { identity: CodexWsReuseIdentity; session: CodexWsSession; createdAt: number; idleAt: number; retired: boolean }
|
|
60
|
-
interface
|
|
67
|
+
interface Waiter { key: string; resolve: (session: CodexWsSession | null) => void; timer: ReturnType<typeof setTimeout>; onAbort: () => void; signal?: AbortSignal }
|
|
68
|
+
interface PoolOptions { now?: () => number; maxSessions?: number; idleMs?: number; maxAgeMs?: number; waitMs?: number }
|
|
61
69
|
|
|
62
70
|
/** Bounded retained sockets only. Busy/capacity misses keep the existing one-shot path. */
|
|
63
71
|
export class CodexWsPool {
|
|
64
72
|
private readonly entries = new Map<string, Entry>();
|
|
73
|
+
private readonly waiters: Waiter[] = [];
|
|
65
74
|
private timer?: ReturnType<typeof setTimeout>;
|
|
66
75
|
private detachShutdown?: () => void;
|
|
67
76
|
private readonly hookKey = `codex-upstream-ws-pool-${++poolSequence}`;
|
|
@@ -69,29 +78,74 @@ export class CodexWsPool {
|
|
|
69
78
|
private readonly maxSessions: number;
|
|
70
79
|
private readonly idleMs: number;
|
|
71
80
|
private readonly maxAgeMs: number;
|
|
81
|
+
private readonly waitMs: number;
|
|
72
82
|
constructor(options: PoolOptions = {}) {
|
|
73
83
|
this.now = options.now ?? Date.now;
|
|
74
84
|
this.maxSessions = options.maxSessions ?? CODEX_WS_POOL_MAX_SESSIONS;
|
|
75
85
|
this.idleMs = options.idleMs ?? CODEX_WS_POOL_IDLE_MS;
|
|
76
86
|
this.maxAgeMs = options.maxAgeMs ?? CODEX_WS_POOL_MAX_AGE_MS;
|
|
87
|
+
this.waitMs = options.waitMs ?? CODEX_WS_POOL_WAIT_MS;
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
acquire(identity: CodexWsReuseIdentity, url: string, headers: Record<string, string>, proxy?: string): CodexWsSession | null {
|
|
80
|
-
this.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
return this.claim(identity, url, headers, proxy).session;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Before any frame is sent, wait for the matching busy socket instead of dialing a throwaway.
|
|
96
|
+
* The queue is FIFO and at most two deep. An abort or the deadline returns null.
|
|
97
|
+
*/
|
|
98
|
+
acquireWaiting(
|
|
99
|
+
identity: CodexWsReuseIdentity,
|
|
100
|
+
url: string,
|
|
101
|
+
headers: Record<string, string>,
|
|
102
|
+
proxy: string | undefined,
|
|
103
|
+
signal?: AbortSignal,
|
|
104
|
+
): Promise<CodexWsSession | null> {
|
|
105
|
+
const claimed = this.claim(identity, url, headers, proxy);
|
|
106
|
+
if (claimed.session || !claimed.busy) return Promise.resolve(claimed.session);
|
|
107
|
+
if (signal?.aborted) return Promise.resolve(null);
|
|
108
|
+
if (this.waiters.filter(waiter => waiter.key === identity.key).length >= CODEX_WS_POOL_WAIT_DEPTH) {
|
|
109
|
+
return Promise.resolve(null);
|
|
85
110
|
}
|
|
111
|
+
return new Promise(resolve => {
|
|
112
|
+
const waiter: Waiter = {
|
|
113
|
+
key: identity.key,
|
|
114
|
+
resolve,
|
|
115
|
+
signal,
|
|
116
|
+
timer: setTimeout(() => this.finishWaiter(waiter, null), this.waitMs),
|
|
117
|
+
onAbort: () => this.finishWaiter(waiter, null),
|
|
118
|
+
};
|
|
119
|
+
waiter.timer.unref?.();
|
|
120
|
+
signal?.addEventListener("abort", waiter.onAbort, { once: true });
|
|
121
|
+
this.waiters.push(waiter);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private claim(
|
|
126
|
+
identity: CodexWsReuseIdentity,
|
|
127
|
+
url: string,
|
|
128
|
+
headers: Record<string, string>,
|
|
129
|
+
proxy?: string,
|
|
130
|
+
): { session: CodexWsSession | null; busy: boolean } {
|
|
131
|
+
this.sweep();
|
|
132
|
+
this.retireSiblings(identity);
|
|
86
133
|
const existing = this.entries.get(identity.key);
|
|
87
134
|
if (existing) {
|
|
88
|
-
if (existing.retired
|
|
89
|
-
if (existing.session.
|
|
135
|
+
if (existing.retired) return { session: null, busy: false };
|
|
136
|
+
if (existing.session.busy) return { session: null, busy: true };
|
|
137
|
+
if (existing.session.reserve()) { this.arm(); return { session: existing.session, busy: false }; }
|
|
90
138
|
this.remove(existing);
|
|
139
|
+
} else if (codexWsReuseAcrossTurnsEnabled() && this.scopeCount(identity.scope) >= CODEX_WS_POOL_SCOPE_LIMIT) {
|
|
140
|
+
const oldest = [...this.entries.values()]
|
|
141
|
+
.filter(entry => entry.identity.scope === identity.scope && !entry.session.busy)
|
|
142
|
+
.sort((left, right) => left.idleAt - right.idleAt)[0];
|
|
143
|
+
if (!oldest) return { session: null, busy: false };
|
|
144
|
+
this.remove(oldest);
|
|
91
145
|
}
|
|
92
146
|
if (this.entries.size >= this.maxSessions) {
|
|
93
147
|
const oldest = [...this.entries.values()].filter(entry => !entry.session.busy).sort((a, b) => a.idleAt - b.idleAt)[0];
|
|
94
|
-
if (!oldest) return null;
|
|
148
|
+
if (!oldest) return { session: null, busy: false };
|
|
95
149
|
this.remove(oldest);
|
|
96
150
|
}
|
|
97
151
|
const createdAt = this.now();
|
|
@@ -100,13 +154,45 @@ export class CodexWsPool {
|
|
|
100
154
|
session.reserve();
|
|
101
155
|
this.entries.set(identity.key, entry);
|
|
102
156
|
this.detachShutdown ??= registerOptionalShutdownHook(this.hookKey, () => this.dispose());
|
|
103
|
-
return session;
|
|
157
|
+
return { session, busy: false };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private retireSiblings(identity: CodexWsReuseIdentity): void {
|
|
161
|
+
const reuseAcrossTurns = codexWsReuseAcrossTurnsEnabled();
|
|
162
|
+
for (const entry of this.entries.values()) {
|
|
163
|
+
if (entry.identity.scope !== identity.scope || entry.identity.key === identity.key) continue;
|
|
164
|
+
const routeChanged = entry.identity.routeTag !== identity.routeTag;
|
|
165
|
+
if (reuseAcrossTurns && !routeChanged) continue;
|
|
166
|
+
entry.retired = true;
|
|
167
|
+
if (!entry.session.busy) this.remove(entry);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private scopeCount(scope: string): number {
|
|
172
|
+
let count = 0;
|
|
173
|
+
for (const entry of this.entries.values()) if (entry.identity.scope === scope && !entry.retired) count += 1;
|
|
174
|
+
return count;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private finishWaiter(waiter: Waiter, session: CodexWsSession | null): void {
|
|
178
|
+
const index = this.waiters.indexOf(waiter);
|
|
179
|
+
if (index < 0) return;
|
|
180
|
+
this.waiters.splice(index, 1);
|
|
181
|
+
clearTimeout(waiter.timer);
|
|
182
|
+
waiter.signal?.removeEventListener("abort", waiter.onAbort);
|
|
183
|
+
waiter.resolve(session);
|
|
104
184
|
}
|
|
105
185
|
|
|
106
186
|
private changed(entry: Entry): void {
|
|
107
187
|
if (this.entries.get(entry.identity.key) !== entry) return;
|
|
108
188
|
if (entry.session.closed) this.entries.delete(entry.identity.key);
|
|
109
189
|
else if (!entry.session.busy) {
|
|
190
|
+
const waiter = this.waiters.find(candidate => candidate.key === entry.identity.key);
|
|
191
|
+
if (waiter && !entry.retired && entry.session.reserve()) {
|
|
192
|
+
this.finishWaiter(waiter, entry.session);
|
|
193
|
+
this.arm();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
110
196
|
entry.idleAt = this.now();
|
|
111
197
|
if (entry.retired || entry.idleAt - entry.createdAt >= this.maxAgeMs) this.remove(entry);
|
|
112
198
|
}
|
|
@@ -152,6 +238,7 @@ export class CodexWsPool {
|
|
|
152
238
|
this.detachShutdown = undefined;
|
|
153
239
|
const entries = [...this.entries.values()];
|
|
154
240
|
this.entries.clear();
|
|
241
|
+
for (const waiter of [...this.waiters]) this.finishWaiter(waiter, null);
|
|
155
242
|
for (const entry of entries) entry.session.dispose(new DOMException("codex websocket pool shutdown", "AbortError"));
|
|
156
243
|
}
|
|
157
244
|
|
|
@@ -49,6 +49,7 @@ const WS_CLOSE_MESSAGE_TOO_BIG = 1009;
|
|
|
49
49
|
|
|
50
50
|
const codexWsUpstreamResponses = new WeakSet<Response>();
|
|
51
51
|
const quotaObservedResponses = new WeakSet<Response>();
|
|
52
|
+
const codexWsPreludeProjections = new WeakSet<Response>();
|
|
52
53
|
|
|
53
54
|
/** Quota arrived directly at its captured account; do not replay old HTTP prelude headers. */
|
|
54
55
|
export function isCodexWsQuotaObservedResponse(response: Response): boolean {
|
|
@@ -60,6 +61,14 @@ export function isCodexWsUpstreamResponse(response: Response): boolean {
|
|
|
60
61
|
return codexWsUpstreamResponses.has(response);
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/** Pre-response projection; its headers retain the exchange's prelude snapshot. */
|
|
65
|
+
export function markCodexWsPreludeProjection(response: Response): void {
|
|
66
|
+
codexWsPreludeProjections.add(response);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isCodexWsPreludeProjection(response: Response): boolean {
|
|
70
|
+
return codexWsPreludeProjections.has(response);
|
|
71
|
+
}
|
|
63
72
|
|
|
64
73
|
export function markCodexWsResponse(response: Response, observed: boolean): void {
|
|
65
74
|
codexWsUpstreamResponses.add(response);
|
|
@@ -10,5 +10,7 @@ export function comboRequestedEffortLabel(
|
|
|
10
10
|
const forcedTransition = forcedEffort != null && forcedEffort !== originalRequestedEffort
|
|
11
11
|
? `->${forcedEffort}`
|
|
12
12
|
: "";
|
|
13
|
-
|
|
13
|
+
const segments = `${originalRequestedEffort}${forcedTransition}${laterTransitions}`.split("->");
|
|
14
|
+
// A pin that kept the effort it was given is not a transition; drop the repeated segment.
|
|
15
|
+
return segments.filter((segment, index) => index === 0 || segment !== segments[index - 1]).join("->");
|
|
14
16
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
computeQuotaCooldown,
|
|
7
7
|
formatCodexProviderForLog,
|
|
8
8
|
} from "../../codex/routing";
|
|
9
|
+
import { claimMainQuotaDispatchForWs, isMainQuotaDispatchLive, renewMainQuotaDispatchForAttempt, type MainQuotaDispatch } from "../../codex/main-account-cache";
|
|
9
10
|
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
10
11
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
11
12
|
import { isCodexAccountGenerationLive } from "../../codex/account-store";
|
|
@@ -121,8 +122,28 @@ export function usesCodexForwardPoolAuth(
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
|
|
125
|
+
/** Live proof that this plain-main response used the observed main credential. */
|
|
126
|
+
export function liveMainQuotaDispatch(
|
|
127
|
+
authCtx: CodexAuthContext, provider: OcxProviderConfig,
|
|
128
|
+
): MainQuotaDispatch | undefined {
|
|
129
|
+
if (authCtx.kind !== "main" || !authCtx.mainQuotaDispatch) return undefined;
|
|
130
|
+
if (!isCanonicalOpenAiForwardProvider(provider)
|
|
131
|
+
|| provider.authMode !== "forward" || provider.adapter !== "openai-responses") return undefined;
|
|
132
|
+
return isMainQuotaDispatchLive(authCtx.mainQuotaDispatch) ? authCtx.mainQuotaDispatch : undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
124
135
|
export function codexWsQuotaObserver(authCtx: CodexAuthContext, provider: OcxProviderConfig, modelId?: string): CodexWsQuotaObserver | undefined {
|
|
125
|
-
if (!isCanonicalOpenAiForwardProvider(provider)
|
|
136
|
+
if (!isCanonicalOpenAiForwardProvider(provider)) return undefined;
|
|
137
|
+
if (!usesCodexForwardPoolAuth(authCtx, provider)) {
|
|
138
|
+
const captured = liveMainQuotaDispatch(authCtx, provider);
|
|
139
|
+
if (!captured || authCtx.kind !== "main") return undefined;
|
|
140
|
+
const dispatch = authCtx.mainQuotaDispatch = renewMainQuotaDispatchForAttempt(captured);
|
|
141
|
+
return headers => {
|
|
142
|
+
claimMainQuotaDispatchForWs(dispatch);
|
|
143
|
+
if (!isMainQuotaDispatchLive(dispatch)) return;
|
|
144
|
+
applyCapturedCodexQuota(MAIN_CODEX_ACCOUNT_ID, headers, dispatch.configGeneration, dispatch.writer, { modelId });
|
|
145
|
+
};
|
|
146
|
+
}
|
|
126
147
|
const { accountId, writerGeneration } = authCtx;
|
|
127
148
|
const credentialGeneration = authCtx.kind === "pool" ? authCtx.generation : undefined;
|
|
128
149
|
const mainWriter = authCtx.kind === "main-pool" ? authCtx.mainQuotaWriter : undefined;
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
type RequestLogContext,
|
|
33
33
|
} from "../request-log";
|
|
34
34
|
import { captureRouteStaticPolicy, routeConcreteModel } from "../../router";
|
|
35
|
-
import { comboDefaultEffort, concreteComboRequestBody, getCombo } from "../../combos";
|
|
35
|
+
import { comboDefaultEffort, concreteComboRequestBody, getCombo, type JevDecision } from "../../combos";
|
|
36
36
|
import { supportedLadderFor } from "../effort-policy";
|
|
37
37
|
import { applyDroidReasoningDefault } from "../droid-reasoning-default";
|
|
38
38
|
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
@@ -94,6 +94,7 @@ export interface ComboProtocolLanes {
|
|
|
94
94
|
target: ComboTarget,
|
|
95
95
|
targetRoute: RouteResult,
|
|
96
96
|
targetSendBudget: TransientSendBudget | undefined,
|
|
97
|
+
initialDecision?: Pick<JevDecision, "effort">,
|
|
97
98
|
): NativeComboChildPlan | undefined;
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -101,6 +102,7 @@ interface CandidateVerdict {
|
|
|
101
102
|
skip?: ProtocolFeature[];
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/** Build opt-in native eligibility and child plans; only the initial plan applies a JEV choice. */
|
|
104
106
|
export function createComboProtocolLanes(input: {
|
|
105
107
|
source: ComboProtocolSource | undefined;
|
|
106
108
|
req: Request;
|
|
@@ -203,7 +205,7 @@ export function createComboProtocolLanes(input: {
|
|
|
203
205
|
400, unrepresentableMessage(features), "invalid_request_error", "unsupported_feature",
|
|
204
206
|
), "chat");
|
|
205
207
|
},
|
|
206
|
-
nativeChild: (target, targetRoute, targetSendBudget) => {
|
|
208
|
+
nativeChild: (target, targetRoute, targetSendBudget, initialDecision) => {
|
|
207
209
|
// Without the request's execution budget a native child could not share its sends, so it
|
|
208
210
|
// keeps the bridge rather than opening a tracker of its own.
|
|
209
211
|
if (!isRequestExecutionBudget(targetSendBudget)) return undefined;
|
|
@@ -214,7 +216,22 @@ export function createComboProtocolLanes(input: {
|
|
|
214
216
|
provider: targetRoute.provider,
|
|
215
217
|
modelId: targetRoute.modelId,
|
|
216
218
|
});
|
|
217
|
-
|
|
219
|
+
if (initialDecision) {
|
|
220
|
+
// A validated JEV choice owns only this first dispatch, including explicit no-effort.
|
|
221
|
+
// Do not run ordinary force defaults for null: they require a non-null default.
|
|
222
|
+
delete body.service_tier;
|
|
223
|
+
delete body.thinking_budget;
|
|
224
|
+
delete body.thinking;
|
|
225
|
+
const reasoning = body.reasoning;
|
|
226
|
+
if (reasoning && typeof reasoning === "object" && !Array.isArray(reasoning)) {
|
|
227
|
+
const preserved = { ...reasoning as Rec };
|
|
228
|
+
delete preserved.effort;
|
|
229
|
+
if (Object.keys(preserved).length > 0) body.reasoning = preserved;
|
|
230
|
+
else delete body.reasoning;
|
|
231
|
+
}
|
|
232
|
+
if (initialDecision.effort === null) delete body.reasoning_effort;
|
|
233
|
+
else body.reasoning_effort = initialDecision.effort;
|
|
234
|
+
} else applyComboEffort(body, config, comboId, target, targetRoute);
|
|
218
235
|
return { route, body, sendBudget: targetSendBudget };
|
|
219
236
|
},
|
|
220
237
|
};
|
|
@@ -717,30 +717,32 @@ export async function executeComboResponses(
|
|
|
717
717
|
const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
|
|
718
718
|
// The inherited spend tracker observes this parent log, before the child has its own label.
|
|
719
719
|
logCtx.spendPoolId = targetRoute.providerName;
|
|
720
|
-
//
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
720
|
+
// Derive the target's allowance before booking its initial send; the booking occupies it.
|
|
721
|
+
const targetSendBudget = comboSendScope
|
|
722
|
+
? comboTargetSendBudget(comboSendScope, combo.targets.length - 1 - comboTargetsDispatched)
|
|
723
|
+
: options.sendBudget;
|
|
724
|
+
// Every target prepays one send. Only its child may consume that booking; later targets
|
|
725
|
+
// remain bounded by the shared total, transition ledger and per-target holdback.
|
|
726
|
+
// `countedExternally` is required: the child charges its own physical sends against this
|
|
727
|
+
// booking through the exact permit, and charging here as well would halve the cap.
|
|
724
728
|
const hopDecision = comboSendScope?.reserveDispatch({
|
|
725
729
|
sendClass: firstComboTarget ? "initial" : "combo-failover",
|
|
726
730
|
targetKey: `${pick.target.provider}/${pick.target.model}`,
|
|
727
731
|
countedExternally: true,
|
|
728
732
|
});
|
|
729
|
-
if (hopDecision && hopDecision.allowed)
|
|
730
|
-
else if (hopDecision && firstComboTarget) {
|
|
733
|
+
if (hopDecision && !hopDecision.allowed && firstComboTarget) {
|
|
731
734
|
// A refused initial reservation authorizes no child send and has no upstream failure to return.
|
|
732
735
|
return formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, "request send budget exhausted before combo dispatch");
|
|
733
736
|
}
|
|
734
|
-
else if (hopDecision) {
|
|
737
|
+
else if (hopDecision && !hopDecision.allowed) {
|
|
735
738
|
// Out of budget is not this target's failure. The established exhaustion contract is to
|
|
736
739
|
// return the last real upstream answer with its status, headers and any quota body
|
|
737
740
|
// intact rather than to mint a synthetic error, and a later target only exists because
|
|
738
741
|
// an earlier one already recorded one.
|
|
739
742
|
return exhaustedFailure();
|
|
740
743
|
}
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
: options.sendBudget;
|
|
744
|
+
const initialSend = hopDecision?.allowed ? { permit: hopDecision.permit, producerOwned: false } : undefined;
|
|
745
|
+
try {
|
|
744
746
|
comboTargetsDispatched += 1;
|
|
745
747
|
const childLog: RequestLogContext = {
|
|
746
748
|
model: pick.target.model,
|
|
@@ -764,7 +766,7 @@ export async function executeComboResponses(
|
|
|
764
766
|
initialJevDecision ? initialJevDecision.effort : comboDefaultEffort(config, comboId),
|
|
765
767
|
initialJevDecision?.effort === null ? [] : targetReasoningEfforts,
|
|
766
768
|
combo.reasoningEffortMode,
|
|
767
|
-
initialJevDecision
|
|
769
|
+
initialJevDecision ? (initialJevDecision.effort === null ? "fallback" : "force") : combo.defaultEffortMode,
|
|
768
770
|
);
|
|
769
771
|
if (initialJevDecision) {
|
|
770
772
|
delete childBody.service_tier;
|
|
@@ -860,7 +862,7 @@ export async function executeComboResponses(
|
|
|
860
862
|
&& targetEligible(target)
|
|
861
863
|
&& !isComboTargetInCooldown(comboId, target),
|
|
862
864
|
);
|
|
863
|
-
const nativeChild = protocolLanes?.nativeChild(pick.target, targetRoute, targetSendBudget);
|
|
865
|
+
const nativeChild = protocolLanes?.nativeChild(pick.target, targetRoute, targetSendBudget, initialJevDecision);
|
|
864
866
|
response = nativeChild ? await dispatchNativeComboChild({
|
|
865
867
|
source: options.protocolSource!,
|
|
866
868
|
plan: nativeChild,
|
|
@@ -890,6 +892,7 @@ export async function executeComboResponses(
|
|
|
890
892
|
// parent arrived with.
|
|
891
893
|
sendBudget: targetSendBudget,
|
|
892
894
|
comboAttempt: true,
|
|
895
|
+
comboInitialSend: initialSend,
|
|
893
896
|
comboDispatchPermit: hopDecision?.allowed ? hopDecision.permit : undefined,
|
|
894
897
|
comboReplaySnapshot,
|
|
895
898
|
deferCodexResetDerivedCooldown,
|
|
@@ -909,8 +912,8 @@ export async function executeComboResponses(
|
|
|
909
912
|
onNativePassthroughCancel: callbackGate.onCancel,
|
|
910
913
|
onResponseComplete: callbackGate.onResponseComplete,
|
|
911
914
|
});
|
|
912
|
-
//
|
|
913
|
-
restoreOriginalRequestedEffort(childLog,
|
|
915
|
+
// Both lanes applied the initial choice before provider pins/caps; retain those transitions.
|
|
916
|
+
restoreOriginalRequestedEffort(childLog, initialJevDecision?.effort);
|
|
914
917
|
} catch (error) {
|
|
915
918
|
callbackGate.discard();
|
|
916
919
|
if (options.abortSignal?.aborted) {
|
|
@@ -1185,6 +1188,11 @@ export async function executeComboResponses(
|
|
|
1185
1188
|
// Waiting or recovery may have observed cancellation after the check above.
|
|
1186
1189
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
1187
1190
|
}
|
|
1191
|
+
} finally {
|
|
1192
|
+
// External receipts make release a no-op after real work. runTurn owns its asynchronous
|
|
1193
|
+
// producer; core transfers cleanup there before returning a streaming response.
|
|
1194
|
+
if (!initialSend?.producerOwned) initialSend?.permit.release();
|
|
1195
|
+
}
|
|
1188
1196
|
}
|
|
1189
1197
|
const failure = exhaustedFailure();
|
|
1190
1198
|
if (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
2
|
+
import { finalizeTranslatorBudgetResponse } from "../../lib/translator-budget";
|
|
2
3
|
import {
|
|
3
4
|
isNativePassthroughSseResponse,
|
|
4
5
|
markNativePassthroughSseResponse,
|
|
@@ -37,42 +38,8 @@ export const UPSTREAM_JSON_BODY_READ_OPTIONS = {
|
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
export function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBudget): Response {
|
|
41
|
-
|
|
42
|
-
budget.dispose();
|
|
43
|
-
return response;
|
|
44
|
-
}
|
|
45
|
-
const reader = response.body.getReader();
|
|
46
|
-
let finalized = false;
|
|
47
|
-
const finalize = () => {
|
|
48
|
-
if (finalized) return;
|
|
49
|
-
finalized = true;
|
|
50
|
-
budget.dispose();
|
|
51
|
-
};
|
|
52
|
-
const body = new ReadableStream<Uint8Array>({
|
|
53
|
-
async pull(controller) {
|
|
54
|
-
try {
|
|
55
|
-
const result = await reader.read();
|
|
56
|
-
if (result.done) {
|
|
57
|
-
finalize();
|
|
58
|
-
controller.close();
|
|
59
|
-
} else {
|
|
60
|
-
controller.enqueue(result.value);
|
|
61
|
-
}
|
|
62
|
-
} catch (error) {
|
|
63
|
-
finalize();
|
|
64
|
-
controller.error(error);
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
async cancel(reason) {
|
|
68
|
-
try { await reader.cancel(reason); } finally { finalize(); }
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
const finalizedResponse = new Response(body, {
|
|
72
|
-
status: response.status,
|
|
73
|
-
statusText: response.statusText,
|
|
74
|
-
headers: response.headers,
|
|
75
|
-
});
|
|
41
|
+
export function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBudget, signal?: AbortSignal): Response {
|
|
42
|
+
const finalizedResponse = finalizeTranslatorBudgetResponse(response, budget, signal);
|
|
76
43
|
if (isNativePassthroughSseResponse(response)) {
|
|
77
44
|
markNativePassthroughSseResponse(finalizedResponse);
|
|
78
45
|
}
|
|
@@ -16,6 +16,7 @@ import type { ExplicitOpenAiCallerAuth } from "../../providers/openai-sidecar";
|
|
|
16
16
|
import type { CallerDirectAuth } from "../../providers/caller-authorization";
|
|
17
17
|
import type { CompactionRoutingOverride } from "./compaction-routing";
|
|
18
18
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
19
|
+
import type { NativeReasoningOwner } from "../../responses/reasoning-replay-cache";
|
|
19
20
|
import type { TransientSendBudget } from "../../lib/upstream-retry";
|
|
20
21
|
import type { RequestLogContext } from "../request-log";
|
|
21
22
|
import type { UpstreamHostAdmissionLease } from "../../codex/upstream-host-health";
|
|
@@ -134,6 +135,8 @@ export interface HandleResponsesOptions {
|
|
|
134
135
|
* it. Omitted means a genuine Responses inbound.
|
|
135
136
|
*/
|
|
136
137
|
inboundWire?: InboundWire;
|
|
138
|
+
nativeReasoningReplay?: ReadonlyMap<string, string>;
|
|
139
|
+
nativeReasoningMint?: { owner?: NativeReasoningOwner };
|
|
137
140
|
/** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
|
|
138
141
|
droidDefaultEffort?: string;
|
|
139
142
|
/** PF-07: the Chat source a combo child may send natively; set only by the Chat ingress. */
|
|
@@ -158,6 +161,8 @@ export interface HandleResponsesOptions {
|
|
|
158
161
|
callerDirectAuth?: CallerDirectAuth | null;
|
|
159
162
|
/** Internal recursion guard; callers outside this module must not set it. */
|
|
160
163
|
comboAttempt?: boolean;
|
|
164
|
+
/** Child-owned prepaid initial send; capacity remains charged until dispatch or unsent release. */
|
|
165
|
+
comboInitialSend?: { permit: SingleUseDispatchPermit; producerOwned?: boolean };
|
|
161
166
|
/** Exact externally booked combo hop, used for its child's spend preflight and send reports. */
|
|
162
167
|
comboDispatchPermit?: SingleUseDispatchPermit;
|
|
163
168
|
/** Internal handoff: this combo was selected by shadow-call interception. */
|
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
reasoningReplayCodexCredentialIdentity,
|
|
20
20
|
reasoningReplayKeyCredentialIdentity,
|
|
21
21
|
durableReplayDestinationIdentity,
|
|
22
|
+
nativeReasoningTag,
|
|
23
|
+
nativeReasoningTagMatches,
|
|
24
|
+
type NativeReasoningOwner,
|
|
22
25
|
bindReasoningReplayScope,
|
|
23
26
|
reasoningReplayServingIdentityChanged,
|
|
24
27
|
reasoningReplayItemStoreChanged,
|
|
@@ -118,6 +121,16 @@ export function bindRouteReasoningReplayScope(args: {
|
|
|
118
121
|
...args,
|
|
119
122
|
modelId: parsed.modelId,
|
|
120
123
|
});
|
|
124
|
+
const owner = nativeReasoningOwnerForRoute(args);
|
|
125
|
+
if (owner) parsed._nativeReasoningBoundOwner = owner;
|
|
126
|
+
else delete parsed._nativeReasoningBoundOwner;
|
|
127
|
+
if (parsed._nativeReasoningReplay?.size) {
|
|
128
|
+
for (const [blob, tag] of parsed._nativeReasoningReplay) {
|
|
129
|
+
if (!nativeReasoningTagMatches(nativeReasoningTag(owner, blob), tag)) {
|
|
130
|
+
stripNativeReasoningBlob(parsed, blob);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
121
134
|
const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
|
|
122
135
|
const continuationOwner = providerContinuationOwnerFromReplayIdentity(
|
|
123
136
|
replayIdentity && continuationDestinationIdentity
|
|
@@ -156,6 +169,66 @@ export function bindRouteReasoningReplayScope(args: {
|
|
|
156
169
|
bindProviderContinuationForRoute(parsed, continuationOwner);
|
|
157
170
|
}
|
|
158
171
|
|
|
172
|
+
function stripNativeReasoningBlob(parsed: OcxParsedRequest, blob: string): void {
|
|
173
|
+
const input = (parsed._rawBody as { input?: unknown } | undefined)?.input;
|
|
174
|
+
if (Array.isArray(input)) {
|
|
175
|
+
for (const item of input) {
|
|
176
|
+
if (item && typeof item === "object" && item.type === "reasoning" && item.encrypted_content === blob) {
|
|
177
|
+
delete item.encrypted_content;
|
|
178
|
+
delete item.id;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const message of parsed.context.messages) {
|
|
183
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
184
|
+
for (const part of message.content) {
|
|
185
|
+
if (part.type !== "thinking" || typeof part.signature !== "string") continue;
|
|
186
|
+
try {
|
|
187
|
+
const item: unknown = JSON.parse(part.signature);
|
|
188
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
189
|
+
const reasoning = item as Record<string, unknown>;
|
|
190
|
+
if (reasoning.type !== "reasoning" || reasoning.encrypted_content !== blob) continue;
|
|
191
|
+
delete reasoning.encrypted_content;
|
|
192
|
+
delete reasoning.id;
|
|
193
|
+
part.signature = JSON.stringify(reasoning);
|
|
194
|
+
delete part.itemId;
|
|
195
|
+
} catch { /* A provider signature is not the parser's JSON carrier. */ }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function nativeReasoningOwnerForRoute(args: {
|
|
201
|
+
provider: OcxProviderConfig;
|
|
202
|
+
oauthCredentialSnapshot?: Pick<OAuthAccessSnapshot, "accountId" | "generation">;
|
|
203
|
+
codexAuthContext?: CodexAuthContext;
|
|
204
|
+
}): NativeReasoningOwner | undefined {
|
|
205
|
+
const { provider } = args;
|
|
206
|
+
const salt = thoughtSignatureReplaySalt();
|
|
207
|
+
if (!salt || salt.length < 16) return undefined;
|
|
208
|
+
const destination = durableReplayDestinationIdentity(JSON.stringify([
|
|
209
|
+
provider.baseUrl.trim().replace(/\/+$/, ""), provider.responsesPath ?? "",
|
|
210
|
+
]));
|
|
211
|
+
let credential: string | undefined;
|
|
212
|
+
if (provider.authMode === "oauth") {
|
|
213
|
+
const snapshot = args.oauthCredentialSnapshot;
|
|
214
|
+
if (snapshot?.accountId && snapshot.generation !== undefined) credential = durableReplayCredentialIdentity(
|
|
215
|
+
"oauth", `${snapshot.accountId}\0${snapshot.generation}`, provider.headers, salt,
|
|
216
|
+
);
|
|
217
|
+
} else if (provider.authMode === "forward") {
|
|
218
|
+
const context = args.codexAuthContext;
|
|
219
|
+
if (context && (context.kind === "pool" || context.kind === "main-pool")
|
|
220
|
+
&& context.accountId && context.writerGeneration !== undefined
|
|
221
|
+
&& (context.kind !== "pool" || context.generation !== undefined)) {
|
|
222
|
+
credential = durableReplayCredentialIdentity("codex", JSON.stringify([
|
|
223
|
+
context.accountId, context.kind === "pool" ? context.generation : null, context.writerGeneration,
|
|
224
|
+
]), provider.headers, salt);
|
|
225
|
+
}
|
|
226
|
+
} else if (provider.authMode !== "local") {
|
|
227
|
+
credential = durableReplayCredentialIdentity("key", nonEmptyProviderApiKey(provider), provider.headers, salt);
|
|
228
|
+
}
|
|
229
|
+
return destination && credential ? { destination, credential } : undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
159
232
|
|
|
160
233
|
function routeReasoningReplayIdentity(args: {
|
|
161
234
|
providerName: string;
|
|
@@ -29,8 +29,6 @@ import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
|
|
|
29
29
|
import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
|
|
30
30
|
import { runWithCompactionRecovery } from "./compaction-recovery";
|
|
31
31
|
|
|
32
|
-
/** Public Responses entry and compatibility exports. Implementations live with their owners. */
|
|
33
|
-
|
|
34
32
|
/**
|
|
35
33
|
* Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
|
|
36
34
|
* wire, image/web-search bridges, and the terminal-guard continuation.
|
|
@@ -66,13 +64,15 @@ export async function handleResponses(
|
|
|
66
64
|
// Once at ingress, spend observer included: a combo child inherits the parent's holder.
|
|
67
65
|
sendBudget: options.sendBudget ?? createInferenceSendBudget(req, logCtx),
|
|
68
66
|
}, handleResponsesInner);
|
|
69
|
-
const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget) : response;
|
|
67
|
+
const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget, abortSignal) : response;
|
|
70
68
|
if (!accountLoad.lease) { release(); return finalResponse; }
|
|
71
69
|
return finalizeAccountLease(finalResponse, release);
|
|
72
70
|
} catch (error) {
|
|
73
71
|
release();
|
|
74
72
|
if (ownsBudget) translatorBudget.dispose();
|
|
75
73
|
throw error;
|
|
74
|
+
} finally {
|
|
75
|
+
if (!options.comboInitialSend?.producerOwned) options.comboInitialSend?.permit.release();
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|