@bitkyc08/opencodex 2.57.0 → 2.58.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/gui/dist/assets/{index-Cz7CLdif.js → index-BbrHOIY0.js} +2 -2
- package/gui/dist/index.html +1 -1
- package/package.json +2 -2
- package/src/adapters/codebuddy/scaffold-guard.ts +5 -4
- package/src/adapters/command-code.ts +13 -4
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +65 -1
- package/src/adapters/cursor/live-transport.ts +5 -1
- package/src/adapters/cursor/protobuf-events.ts +110 -11
- package/src/adapters/cursor/protobuf-request.ts +19 -1
- package/src/adapters/cursor/text-toolcall.ts +230 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/types.ts +5 -0
- package/src/adapters/cursor.ts +55 -5
- package/src/adapters/google-http.ts +38 -13
- package/src/adapters/mimo-free.ts +32 -17
- package/src/adapters/ollama-native.ts +42 -8
- package/src/adapters/openai-responses/passthrough.ts +30 -4
- package/src/adapters/openai-responses/request-strips.ts +43 -0
- package/src/adapters/physical-send.ts +50 -0
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +1 -1
- package/src/claude/outbound.ts +14 -4
- package/src/cli/config-command.ts +35 -18
- package/src/cli/dispatch.ts +17 -4
- package/src/cli/index.ts +44 -2
- package/src/cli/system-command.ts +70 -1
- package/src/cli/uninstall-client-state.ts +12 -0
- package/src/codex/auth-context.ts +42 -8
- package/src/codex/desktop-switches.ts +145 -0
- package/src/codex/history-job.ts +5 -1
- package/src/codex/history-provider.ts +33 -4
- package/src/codex/history-worker.ts +14 -1
- package/src/codex/inject/remove.ts +145 -7
- package/src/codex/inject/restore.ts +204 -32
- package/src/codex/inject.ts +3 -7
- package/src/codex/loopback-target.ts +9 -0
- package/src/codex/native-profile-startup.ts +64 -20
- package/src/config/atomic-write.ts +83 -8
- package/src/config/schema/config-schema.ts +2 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +132 -68
- package/src/lib/bounded-subprocess.ts +62 -10
- package/src/lib/windows-secret-acl.ts +151 -15
- package/src/lib/windows-user-principal.ts +5 -1
- package/src/providers/derive.ts +6 -0
- package/src/providers/model-discovery.ts +19 -7
- package/src/providers/registry/entries-core.ts +11 -0
- package/src/providers/registry/entries-extended.ts +50 -28
- package/src/providers/registry/model-seeds.ts +67 -17
- package/src/providers/registry/types.ts +9 -0
- package/src/responses/spill-store.ts +17 -0
- package/src/responses/state/body-policy.ts +25 -0
- package/src/responses/state/spill-queue.ts +8 -6
- package/src/responses/state.ts +3 -22
- package/src/router.ts +4 -0
- package/src/server/auth-cors.ts +1 -0
- package/src/server/index/websocket-handler.ts +48 -1
- package/src/server/management/config-routes.ts +27 -5
- package/src/server/models-capabilities.ts +24 -3
- package/src/server/responses/codex-ws-exchange.ts +65 -4
- package/src/server/responses/combo-stream-preflight.ts +68 -5
- package/src/server/responses/core-combo.ts +26 -0
- package/src/server/responses/core-options.ts +3 -0
- package/src/server/responses/fetch-helpers.ts +4 -1
- package/src/server/responses/native-injection-protocol.ts +42 -0
- package/src/server/responses/native-injection-replay.ts +105 -0
- package/src/server/responses/native-injection.ts +242 -0
- package/src/server/responses/native-response-control.ts +56 -0
- package/src/server/responses/native-response-json.ts +14 -0
- package/src/server/responses/native-response-output.ts +37 -0
- package/src/server/responses/native-steering-log.ts +44 -0
- package/src/server/responses/native-steering-policy.ts +49 -0
- package/src/server/responses/native-steering-replay.ts +126 -0
- package/src/server/responses/native-steering-settings.ts +76 -0
- package/src/server/responses/native-steering.ts +400 -0
- package/src/server/responses/native-tool-results.ts +130 -0
- package/src/server/responses/passthrough-delivery.ts +11 -0
- package/src/server/responses/passthrough-dispatch.ts +33 -1
- package/src/server/responses/request-prepare.ts +41 -0
- package/src/server/responses/ws-upstream.ts +21 -1
- package/src/server/stop-teardown.ts +8 -1
- package/src/server/ws-bridge.ts +16 -1
- package/src/service/cli.ts +13 -1
- package/src/types/config.ts +4 -0
- package/src/types/provider.ts +13 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { isSteeringMutableSetting, validSteeringSettings } from "./native-steering-settings";
|
|
2
|
+
import type { NativeSteeringReplayObserver } from "./native-steering-replay";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { CODEX_WS_ID_MAX_BYTES, CodexWsCorrelation } from "./codex-ws-correlation";
|
|
5
|
+
import { codexWsCreateFrameExceedsLimit } from "./codex-ws-wire";
|
|
6
|
+
|
|
7
|
+
export const MAX_NATIVE_STEERS = 32;
|
|
8
|
+
export const MAX_NATIVE_STEERING_RESPONSES = 128;
|
|
9
|
+
export const NATIVE_STEERING_WAIT_MS = 90_000;
|
|
10
|
+
export const NATIVE_STEERING_TOOL_WAIT_MS = 30 * 60_000;
|
|
11
|
+
|
|
12
|
+
type Frame = Record<string, unknown>;
|
|
13
|
+
type Send = (frame: Frame) => void;
|
|
14
|
+
|
|
15
|
+
/** Narrow JSON object envelopes while excluding arrays and null. */
|
|
16
|
+
function record(value: unknown): value is Frame {
|
|
17
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
/** Require a bounded, nonempty protocol identity without control characters. */
|
|
20
|
+
function validId(value: unknown): value is string {
|
|
21
|
+
return typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= CODEX_WS_ID_MAX_BYTES
|
|
22
|
+
&& !/[\u0000-\u001f\u007f]/.test(value);
|
|
23
|
+
}
|
|
24
|
+
/** Serialize JSON settings deterministically so key order cannot change equality. */
|
|
25
|
+
function stable(value: unknown): string {
|
|
26
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
27
|
+
if (record(value)) return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stable(value[key])}`).join(",")}}`;
|
|
28
|
+
return JSON.stringify(value) ?? "null";
|
|
29
|
+
}
|
|
30
|
+
/** Retain only a digest of pinned settings instead of their request payloads. */
|
|
31
|
+
function fingerprint(value: unknown): string { return createHash("sha256").update(stable(value)).digest("hex"); }
|
|
32
|
+
|
|
33
|
+
/** Typed, content-free local protocol rejection. */
|
|
34
|
+
export class NativeSteeringError extends Error {
|
|
35
|
+
/** Create a content-free protocol error with a stable downstream rejection code. */
|
|
36
|
+
constructor(readonly code: string, message: string) { super(message); this.name = "NativeSteeringError"; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Validate the narrow public steer envelope, not the much wider create schema. */
|
|
40
|
+
export function validateSteeringFrame(frame: Frame): void {
|
|
41
|
+
const fail = () => { throw new NativeSteeringError("invalid_input", "Steering requires user-only input and a previous_response_id; extra envelope fields are not supported."); };
|
|
42
|
+
if (frame.type !== "response.steer" || !validId(frame.previous_response_id)
|
|
43
|
+
|| Object.keys(frame).some(key => !["type", "previous_response_id", "input"].includes(key))) fail();
|
|
44
|
+
if (typeof frame.input === "string") return;
|
|
45
|
+
if (!Array.isArray(frame.input) || !frame.input.length) { fail(); return; }
|
|
46
|
+
for (const item of frame.input) {
|
|
47
|
+
if (!record(item) || item.role !== "user" || (item.type !== undefined && item.type !== "message")
|
|
48
|
+
|| Object.keys(item).some(key => !["type", "role", "content"].includes(key))) { fail(); return; }
|
|
49
|
+
if (typeof item.content === "string") continue;
|
|
50
|
+
if (!Array.isArray(item.content) || !item.content.length) { fail(); return; }
|
|
51
|
+
for (const part of item.content) {
|
|
52
|
+
if (!record(part) || !["input_text", "input_image", "input_file"].includes(String(part.type))
|
|
53
|
+
|| (part.type === "input_text" && typeof part.text !== "string")) fail();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type Parent = {
|
|
59
|
+
unacknowledged: Array<{ deadline: number }>;
|
|
60
|
+
accepted: Set<string>;
|
|
61
|
+
ended: boolean;
|
|
62
|
+
successorDeadline?: number;
|
|
63
|
+
toolDeadline?: number;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Only client-owned results can use the early-continuation path. */
|
|
67
|
+
function outputRequirement(item: unknown): Frame | undefined {
|
|
68
|
+
if (!record(item) || typeof item.type !== "string") return;
|
|
69
|
+
if (item.type === "mcp_approval_request") {
|
|
70
|
+
if (!validId(item.id)) throw new Error("Native steering approval identity is invalid");
|
|
71
|
+
return { type: "mcp_approval_response", approval_request_id: item.id };
|
|
72
|
+
}
|
|
73
|
+
const types: Record<string, string> = {
|
|
74
|
+
function_call: "function_call_output", custom_tool_call: "custom_tool_call_output",
|
|
75
|
+
local_shell_call: "local_shell_call_output", shell_call: "shell_call_output",
|
|
76
|
+
computer_call: "computer_call_output", apply_patch_call: "apply_patch_call_output",
|
|
77
|
+
};
|
|
78
|
+
const type = Object.hasOwn(types, item.type) ? types[item.type] : undefined;
|
|
79
|
+
if (!type) return;
|
|
80
|
+
if (!validId(item.call_id)) throw new Error("Native steering tool-call identity is invalid");
|
|
81
|
+
return { type, call_id: item.call_id };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Match a saved result to its required stub, allowing an omitted optional name. */
|
|
85
|
+
function matchesRequirement(item: Frame, stub: Frame): boolean {
|
|
86
|
+
// The wire may label a required result with its tool name, but the ordinary
|
|
87
|
+
// function/custom output schema identifies the result by call_id, not name.
|
|
88
|
+
return Object.entries(stub).every(([key, value]) =>
|
|
89
|
+
key === "name" && item[key] === undefined || stable(item[key]) === stable(value));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* One downstream turn owns one dedicated native upstream socket, including all
|
|
94
|
+
* automatic successors and required-input continuations. Never registered by a
|
|
95
|
+
* caller-supplied response ID in global state; never lent to another account.
|
|
96
|
+
*
|
|
97
|
+
* Opt-in single-lane implementation. Continuations may supply saved tool results and new user messages
|
|
98
|
+
* and validated generation overrides, but cannot change routing or tools. General new turns still use normal dispatch.
|
|
99
|
+
*/
|
|
100
|
+
export class NativeSteeringChannel {
|
|
101
|
+
readonly kind = "steering" as const;
|
|
102
|
+
normalizeContinuation?: (frame: Frame) => Frame;
|
|
103
|
+
relayActive = false;
|
|
104
|
+
replayFactory?: () => NativeSteeringReplayObserver;
|
|
105
|
+
private replay?: NativeSteeringReplayObserver;
|
|
106
|
+
private send?: Send;
|
|
107
|
+
private onFailure?: (error: Error) => void;
|
|
108
|
+
private timer?: ReturnType<typeof setTimeout>;
|
|
109
|
+
private timerDeadline?: number;
|
|
110
|
+
private idleDeadline?: number;
|
|
111
|
+
private continuationDeadline?: number;
|
|
112
|
+
private readonly parents = new Map<string, Parent>();
|
|
113
|
+
private readonly settings = new Map<string, string>();
|
|
114
|
+
private currentId?: string;
|
|
115
|
+
private correlation?: CodexWsCorrelation;
|
|
116
|
+
private pendingParent?: string;
|
|
117
|
+
private continuationSent = false;
|
|
118
|
+
private lane: unknown;
|
|
119
|
+
private finished = false;
|
|
120
|
+
private everAttached = false;
|
|
121
|
+
private required: Frame[] = [];
|
|
122
|
+
private readonly advertised = new Map<string, Frame>();
|
|
123
|
+
private advertisedBytes = 0;
|
|
124
|
+
|
|
125
|
+
/** Pin the initial lane and setting digests without opening a transport. */
|
|
126
|
+
constructor(initial: Frame, private readonly idleMs = 300_000) {
|
|
127
|
+
if (record(initial.multi_agent) && initial.multi_agent.enabled === true) {
|
|
128
|
+
throw new NativeSteeringError("native_control_mode_mismatch", "Multi-agent responses cannot use the single-agent steering channel.");
|
|
129
|
+
}
|
|
130
|
+
this.lane = initial.stream_id;
|
|
131
|
+
for (const [key, value] of Object.entries(initial)) {
|
|
132
|
+
if (!["type", "input", "previous_response_id", "stream", "stream_id"].includes(key)) this.settings.set(key, fingerprint(value));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Report whether native dispatch ever bound a physical connection to this owner. */
|
|
136
|
+
get attached(): boolean { return this.everAttached; }
|
|
137
|
+
/** Report whether ordered terminal handling has finished the native chain. */
|
|
138
|
+
get ended(): boolean { return this.finished; }
|
|
139
|
+
/** Count unacknowledged or accepted submissions that still own the response chain. */
|
|
140
|
+
get hasOutstanding(): boolean {
|
|
141
|
+
return [...this.parents.values()].some(parent => parent.unacknowledged.length > 0 || parent.accepted.size > 0);
|
|
142
|
+
}
|
|
143
|
+
/** Report whether the server has requested a saved-result continuation. */
|
|
144
|
+
get awaitingContinuation(): boolean { return this.pendingParent !== undefined; }
|
|
145
|
+
|
|
146
|
+
/** Bind only after native credentials have been selected and admission passed. */
|
|
147
|
+
attach(send: Send, onFailure: (error: Error) => void): () => void {
|
|
148
|
+
if (this.send || this.currentId) throw new Error("native steering transport already owned");
|
|
149
|
+
this.replay = this.replayFactory?.();
|
|
150
|
+
this.everAttached = true;
|
|
151
|
+
this.finished = false;
|
|
152
|
+
this.send = send;
|
|
153
|
+
this.onFailure = onFailure;
|
|
154
|
+
return () => {
|
|
155
|
+
if (this.send !== send) return;
|
|
156
|
+
this.send = undefined;
|
|
157
|
+
this.onFailure = undefined;
|
|
158
|
+
this.clearTimer();
|
|
159
|
+
this.idleDeadline = undefined;
|
|
160
|
+
this.continuationDeadline = undefined;
|
|
161
|
+
this.correlation?.finish();
|
|
162
|
+
this.correlation = undefined;
|
|
163
|
+
this.parents.clear();
|
|
164
|
+
this.required = [];
|
|
165
|
+
this.advertised.clear();
|
|
166
|
+
this.advertisedBytes = 0;
|
|
167
|
+
this.replay?.dispose();
|
|
168
|
+
this.replay = undefined;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Drop the physical timer without altering any protocol-stage deadline. */
|
|
173
|
+
private clearTimer(): void {
|
|
174
|
+
clearTimeout(this.timer);
|
|
175
|
+
this.timer = undefined;
|
|
176
|
+
this.timerDeadline = undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Earliest absolute control deadline wins, independently of ordinary stream activity. */
|
|
180
|
+
private nextDeadline(): number | undefined {
|
|
181
|
+
let deadline: number | undefined;
|
|
182
|
+
const include = (value: number | undefined) => {
|
|
183
|
+
if (value !== undefined) deadline = deadline === undefined ? value : Math.min(deadline, value);
|
|
184
|
+
};
|
|
185
|
+
for (const [id, parent] of this.parents) {
|
|
186
|
+
include(parent.unacknowledged[0]?.deadline);
|
|
187
|
+
if (parent.ended && (parent.accepted.size || parent.unacknowledged.length)) {
|
|
188
|
+
if (id === this.currentId && this.continuationSent) continue;
|
|
189
|
+
include(this.pendingParent === id ? parent.toolDeadline : parent.successorDeadline);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (this.continuationSent) include(this.continuationDeadline);
|
|
193
|
+
if (this.currentId && !this.parents.get(this.currentId)?.ended) include(this.idleDeadline);
|
|
194
|
+
return deadline;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Settle once as unknown delivery; expiry never retries or invents a server rejection. */
|
|
198
|
+
private expire(): Error {
|
|
199
|
+
const error = new Error("Native steering continuation timed out; queued-input delivery is unknown. Do not automatically replay tools or steering input.");
|
|
200
|
+
if (!this.finished) {
|
|
201
|
+
this.finished = true;
|
|
202
|
+
this.clearTimer();
|
|
203
|
+
this.replay?.dispose();
|
|
204
|
+
this.onFailure?.(error);
|
|
205
|
+
}
|
|
206
|
+
return error;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Late wire activity must not win a race with an expired but not-yet-fired timer. */
|
|
210
|
+
private assertTimely(): void {
|
|
211
|
+
const deadline = this.nextDeadline();
|
|
212
|
+
if (this.finished || (deadline !== undefined && performance.now() >= deadline)) throw this.expire();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Arm one unrefed timer for the existing deadline, never for now plus a fresh wait. */
|
|
216
|
+
private armTimer(): void {
|
|
217
|
+
const deadline = this.finished || !this.send ? undefined : this.nextDeadline();
|
|
218
|
+
if (deadline === this.timerDeadline) return;
|
|
219
|
+
this.clearTimer();
|
|
220
|
+
if (deadline === undefined) return;
|
|
221
|
+
this.timerDeadline = deadline;
|
|
222
|
+
this.timer = setTimeout(() => {
|
|
223
|
+
this.clearTimer();
|
|
224
|
+
if (this.finished || !this.send) return;
|
|
225
|
+
const next = this.nextDeadline();
|
|
226
|
+
if (next !== undefined && performance.now() >= next) this.expire();
|
|
227
|
+
else this.armTimer(); // Early callbacks cannot shorten the monotonic bound.
|
|
228
|
+
}, Math.max(0, deadline - performance.now()));
|
|
229
|
+
this.timer.unref?.();
|
|
230
|
+
}
|
|
231
|
+
/** Check the live owner and byte limit before journaling and sending one control. */
|
|
232
|
+
private liveSend(frame: Frame): void {
|
|
233
|
+
if (!this.send || this.finished) throw new NativeSteeringError("steering_not_supported", "No active native WebSocket steering transport; this route may be disabled or using HTTP fallback.");
|
|
234
|
+
if (codexWsCreateFrameExceedsLimit(JSON.stringify(frame))) throw new NativeSteeringError("invalid_input", "Native steering frame exceeds the upstream byte limit.");
|
|
235
|
+
const rollback = this.replay?.submitted(frame);
|
|
236
|
+
try { this.send(frame); } catch (error) { rollback?.(); throw error; }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Send user-only input to this connection's active response under the pending cap. */
|
|
240
|
+
steer(frame: Frame): void {
|
|
241
|
+
validateSteeringFrame(frame);
|
|
242
|
+
if (!this.send || this.finished) { this.liveSend(frame); return; }
|
|
243
|
+
this.assertTimely();
|
|
244
|
+
const target = this.parents.get(frame.previous_response_id as string);
|
|
245
|
+
if (!target || frame.previous_response_id !== this.currentId || target.ended) {
|
|
246
|
+
throw new NativeSteeringError("response_not_active", "The target response is not active on this connection.");
|
|
247
|
+
}
|
|
248
|
+
if ([...this.parents.values()].reduce((n, p) => n + p.unacknowledged.length + p.accepted.size, 0) >= MAX_NATIVE_STEERS) {
|
|
249
|
+
throw new NativeSteeringError("too_many_pending_steers", "The native steering pending-submission limit was reached.");
|
|
250
|
+
}
|
|
251
|
+
// Count before send: fake transports, and some runtimes, deliver synchronously.
|
|
252
|
+
const submission = { deadline: performance.now() + NATIVE_STEERING_WAIT_MS };
|
|
253
|
+
target.unacknowledged.push(submission);
|
|
254
|
+
this.armTimer();
|
|
255
|
+
try { this.liveSend(frame); } catch (error) {
|
|
256
|
+
const index = target.unacknowledged.indexOf(submission);
|
|
257
|
+
if (index >= 0) target.unacknowledged.splice(index, 1);
|
|
258
|
+
this.armTimer();
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Retain bounded call or approval identities that authorize early saved results. */
|
|
264
|
+
private advertise(item: unknown): void {
|
|
265
|
+
const stub = outputRequirement(item);
|
|
266
|
+
if (!stub) return;
|
|
267
|
+
const key = JSON.stringify(stub);
|
|
268
|
+
if (this.advertised.has(key)) return;
|
|
269
|
+
const bytes = Buffer.byteLength(key);
|
|
270
|
+
if (this.advertised.size >= 1024 || this.advertisedBytes + bytes > 256 * 1024) {
|
|
271
|
+
throw new Error("Native steering advertised-input budget exceeded");
|
|
272
|
+
}
|
|
273
|
+
this.advertised.set(key, stub);
|
|
274
|
+
this.advertisedBytes += bytes;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Returns false only when an ordinary create may use normal dispatch. */
|
|
278
|
+
continue(frame: Frame): boolean {
|
|
279
|
+
if (this.finished || !this.send) return false;
|
|
280
|
+
this.assertTimely();
|
|
281
|
+
const parent = this.currentId ? this.parents.get(this.currentId) : undefined;
|
|
282
|
+
if (!parent?.ended || !this.currentId || frame.previous_response_id !== this.currentId) {
|
|
283
|
+
if (this.hasOutstanding || this.continuationSent) throw new NativeSteeringError("steering_continuation_required", "Queued steering owns this connection; wait for the successor or send the required-input continuation, or explicitly stop the turn.");
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
if (this.continuationSent) throw new NativeSteeringError("duplicate_continuation", "A required-input continuation was already sent for this parent.");
|
|
287
|
+
// The client is allowed to return saved results before response.steer.pending.
|
|
288
|
+
// In that case only calls actually advertised by this response authorize it.
|
|
289
|
+
const required = this.pendingParent ? this.required : [...this.advertised.values()];
|
|
290
|
+
if (!required.length) throw new NativeSteeringError("steering_continuation_required", "Wait for the automatic successor or server-identified required input.");
|
|
291
|
+
if (frame.stream_id !== this.lane || frame.generate === false) throw new NativeSteeringError("invalid_input", "The continuation must use the same WebSocket lane and generate a response.");
|
|
292
|
+
for (const [key, value] of Object.entries(frame)) {
|
|
293
|
+
if (["type", "input", "previous_response_id", "stream", "stream_id"].includes(key)) continue;
|
|
294
|
+
if (!isSteeringMutableSetting(key) && this.settings.get(key) !== fingerprint(value)) throw new NativeSteeringError("steering_settings_changed", "The native steering continuation cannot change routing, tools or non-generation settings; start a separate turn instead.");
|
|
295
|
+
}
|
|
296
|
+
if (!validSteeringSettings(frame)) throw new NativeSteeringError("invalid_input", "Invalid or oversized native steering generation settings.");
|
|
297
|
+
const input = frame.input;
|
|
298
|
+
if (!Array.isArray(input) || !input.length) throw new NativeSteeringError("invalid_input", "Supply the saved results for the required_input stubs exactly once; do not resend steering text.");
|
|
299
|
+
const used = new Set<number>();
|
|
300
|
+
for (const item of input) {
|
|
301
|
+
// An explicit continuation may carry new user input after its saved results.
|
|
302
|
+
// Reuse the narrow user-only validator so privileged roles cannot bypass routing.
|
|
303
|
+
if (record(item) && item.role === "user") {
|
|
304
|
+
validateSteeringFrame({ type: "response.steer", previous_response_id: this.currentId, input: [item] });
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const match = record(item) ? required.findIndex((stub, i) => !used.has(i) && matchesRequirement(item, stub)) : -1;
|
|
308
|
+
if (match < 0) throw new NativeSteeringError("invalid_input", "Continuation input must match the pending tool-output or approval stubs.");
|
|
309
|
+
used.add(match);
|
|
310
|
+
}
|
|
311
|
+
if (used.size !== required.length) throw new NativeSteeringError("invalid_input", "Every required tool output or approval must be supplied exactly once.");
|
|
312
|
+
// Snapshot before asynchronous pacing; later caller mutation must not alter the authorized frame.
|
|
313
|
+
frame = structuredClone(frame);
|
|
314
|
+
if (this.normalizeContinuation) frame = this.normalizeContinuation(frame);
|
|
315
|
+
this.continuationSent = true;
|
|
316
|
+
this.continuationDeadline = performance.now() + NATIVE_STEERING_WAIT_MS;
|
|
317
|
+
this.armTimer();
|
|
318
|
+
try { this.liveSend(frame); } catch (error) {
|
|
319
|
+
this.continuationSent = false;
|
|
320
|
+
this.continuationDeadline = undefined;
|
|
321
|
+
this.armTimer();
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Called on the ordered upstream wire, BEFORE the event is published to SSE. */
|
|
328
|
+
observe(event: Frame): boolean {
|
|
329
|
+
this.assertTimely();
|
|
330
|
+
const type = event.type;
|
|
331
|
+
if (!(type === "error" && event.stream_id == null) && (event.stream_id ?? undefined) !== (this.lane ?? undefined)) throw new Error("native steering WebSocket lane mismatch");
|
|
332
|
+
const response = record(event.response) ? event.response : undefined;
|
|
333
|
+
if (type === "response.created") {
|
|
334
|
+
const id = response?.id;
|
|
335
|
+
if (!validId(id) || this.parents.has(id) || this.parents.size >= MAX_NATIVE_STEERING_RESPONSES) throw new Error("native steering response identity or chain limit violated");
|
|
336
|
+
if (this.currentId) {
|
|
337
|
+
const parent = this.parents.get(this.currentId)!;
|
|
338
|
+
if (!parent.ended || (!parent.accepted.size && !this.continuationSent)) throw new Error("unexpected native steering successor");
|
|
339
|
+
if (response?.previous_response_id != null && response.previous_response_id !== this.currentId) throw new Error("native steering successor parent mismatch");
|
|
340
|
+
parent.accepted.clear(); // response.created, not accepted, is the commit point.
|
|
341
|
+
}
|
|
342
|
+
this.currentId = id;
|
|
343
|
+
this.parents.set(id, { unacknowledged: [], accepted: new Set(), ended: false });
|
|
344
|
+
this.pendingParent = undefined;
|
|
345
|
+
this.required = [];
|
|
346
|
+
this.advertised.clear();
|
|
347
|
+
this.advertisedBytes = 0;
|
|
348
|
+
this.continuationSent = false;
|
|
349
|
+
this.continuationDeadline = undefined;
|
|
350
|
+
this.correlation?.finish();
|
|
351
|
+
this.correlation = new CodexWsCorrelation(true, () => false);
|
|
352
|
+
}
|
|
353
|
+
if (typeof type === "string" && type.startsWith("response.steer.")) {
|
|
354
|
+
const steer = record(event.steer) ? event.steer : undefined;
|
|
355
|
+
const parent = typeof steer?.previous_response_id === "string" ? this.parents.get(steer.previous_response_id) : undefined;
|
|
356
|
+
if (!parent) throw new Error("native steering acknowledgement has an unknown parent");
|
|
357
|
+
if (type === "response.steer.accepted") {
|
|
358
|
+
if (!validId(steer?.id) || parent.unacknowledged.length < 1 || parent.accepted.has(steer.id)) throw new Error("unexpected native steering acceptance");
|
|
359
|
+
parent.unacknowledged.shift();
|
|
360
|
+
parent.accepted.add(steer.id);
|
|
361
|
+
} else if (type === "response.steer.failed") {
|
|
362
|
+
if (steer?.id !== undefined) {
|
|
363
|
+
if (!validId(steer.id) || !parent.accepted.delete(steer.id)) throw new Error("unexpected native steering failure");
|
|
364
|
+
} else {
|
|
365
|
+
if (parent.unacknowledged.length < 1) throw new Error("unexpected native steering rejection");
|
|
366
|
+
parent.unacknowledged.shift();
|
|
367
|
+
}
|
|
368
|
+
} else if (type === "response.steer.pending") {
|
|
369
|
+
if (!validId(steer?.id) || !parent.accepted.has(steer.id) || !parent.ended
|
|
370
|
+
|| steer.previous_response_id !== this.currentId) throw new Error("unexpected native steering pending event");
|
|
371
|
+
if (event.reason === "waiting_for_required_input") {
|
|
372
|
+
if (!Array.isArray(event.required_input) || !event.required_input.length || event.required_input.length > 1024
|
|
373
|
+
|| event.required_input.some(item => !record(item) || typeof item.type !== "string" || item.type === "message")
|
|
374
|
+
|| Buffer.byteLength(JSON.stringify(event.required_input)) > 256 * 1024) throw new Error("native steering required-input budget or schema violated");
|
|
375
|
+
if (this.pendingParent && stable(this.required) !== stable(event.required_input)) throw new Error("native steering required-input stubs changed");
|
|
376
|
+
parent.toolDeadline ??= performance.now() + NATIVE_STEERING_TOOL_WAIT_MS;
|
|
377
|
+
this.pendingParent = this.currentId;
|
|
378
|
+
this.required = event.required_input as Frame[];
|
|
379
|
+
}
|
|
380
|
+
// Unknown reasons are preserved, not converted into a create or success.
|
|
381
|
+
} else throw new Error("unsupported native steering control event");
|
|
382
|
+
} else {
|
|
383
|
+
this.correlation?.accept({ ...event, stream_id: undefined });
|
|
384
|
+
if (type === "response.output_item.done") this.advertise(event.item);
|
|
385
|
+
if (type === "response.completed" || type === "response.failed" || type === "response.incomplete") {
|
|
386
|
+
if (!this.currentId || response?.id !== this.currentId) throw new Error("native steering terminal identity mismatch");
|
|
387
|
+
const parent = this.parents.get(this.currentId)!;
|
|
388
|
+
parent.ended = true;
|
|
389
|
+
parent.successorDeadline ??= performance.now() + NATIVE_STEERING_WAIT_MS;
|
|
390
|
+
if (Array.isArray(response.output)) for (const item of response.output) this.advertise(item);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (type === "error") this.finished = true;
|
|
394
|
+
else this.finished = this.currentId !== undefined && this.parents.get(this.currentId)!.ended && !this.hasOutstanding && !this.continuationSent;
|
|
395
|
+
if (this.currentId && !this.parents.get(this.currentId)!.ended) this.idleDeadline = performance.now() + this.idleMs;
|
|
396
|
+
this.armTimer();
|
|
397
|
+
this.replay?.observe(event);
|
|
398
|
+
return this.finished;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
injectionError, injectionFingerprint, injectionId, injectionRecord as record,
|
|
3
|
+
MAX_NATIVE_INJECTION_CALLS, type InjectionFrame as Frame,
|
|
4
|
+
} from "./native-injection-protocol";
|
|
5
|
+
|
|
6
|
+
export type NativeToolOutput = string | Frame[];
|
|
7
|
+
export type NativeToolResult = {
|
|
8
|
+
type: "function_call_output" | "custom_tool_call_output";
|
|
9
|
+
call_id: string;
|
|
10
|
+
output: NativeToolOutput;
|
|
11
|
+
id?: string;
|
|
12
|
+
caller?: Frame | null;
|
|
13
|
+
};
|
|
14
|
+
export type NativeApprovalResult = {
|
|
15
|
+
type: "mcp_approval_response";
|
|
16
|
+
approval_request_id: string;
|
|
17
|
+
approve: boolean;
|
|
18
|
+
reason?: string | null;
|
|
19
|
+
id?: string;
|
|
20
|
+
};
|
|
21
|
+
export type NativeSavedResult = NativeToolResult | NativeApprovalResult;
|
|
22
|
+
export type NativeToolRequirement = { key: string; type: NativeSavedResult["type"]; identity: string; caller: string };
|
|
23
|
+
export const MAX_NATIVE_RESULT_PARTS = 1024;
|
|
24
|
+
|
|
25
|
+
/** Fixed diagnostics deliberately exclude caller identifiers and saved result bodies. */
|
|
26
|
+
function invalid(): never {
|
|
27
|
+
return injectionError("invalid_injection", "Invalid saved tool result, content part, caller or approval decision.");
|
|
28
|
+
}
|
|
29
|
+
/** Reject unknown fields rather than silently discarding them or widening the wire schema. */
|
|
30
|
+
function keys(value: Frame, allowed: string[]): void {
|
|
31
|
+
if (Object.keys(value).some(key => !allowed.includes(key))) invalid();
|
|
32
|
+
}
|
|
33
|
+
/** References remain opaque: no local file reads, URL downloads or cross-account uploads. */
|
|
34
|
+
function source(value: unknown): boolean {
|
|
35
|
+
return typeof value === "string" && value.length > 0;
|
|
36
|
+
}
|
|
37
|
+
/** Validate a supplied caller while treating absent and explicit direct callers alike. */
|
|
38
|
+
function caller(value: unknown): string {
|
|
39
|
+
if (value == null) return injectionFingerprint({ type: "direct" });
|
|
40
|
+
if (!record(value)) return invalid();
|
|
41
|
+
if (value.type === "direct") keys(value, ["type"]);
|
|
42
|
+
else if (value.type === "program" && injectionId(value.caller_id)) keys(value, ["type", "caller_id"]);
|
|
43
|
+
else return invalid();
|
|
44
|
+
return injectionFingerprint(value);
|
|
45
|
+
}
|
|
46
|
+
/** Bound rich result shape without coercing it to text or fetching its content. */
|
|
47
|
+
function output(value: unknown): void {
|
|
48
|
+
if (typeof value === "string") return;
|
|
49
|
+
if (!Array.isArray(value) || value.length > MAX_NATIVE_RESULT_PARTS) invalid();
|
|
50
|
+
for (const part of value) {
|
|
51
|
+
if (!record(part)) invalid();
|
|
52
|
+
if (part.prompt_cache_breakpoint !== undefined) {
|
|
53
|
+
if (!record(part.prompt_cache_breakpoint) || part.prompt_cache_breakpoint.mode !== "explicit") invalid();
|
|
54
|
+
keys(part.prompt_cache_breakpoint, ["mode"]);
|
|
55
|
+
}
|
|
56
|
+
if (part.type === "input_text") {
|
|
57
|
+
keys(part, ["type", "text", "prompt_cache_breakpoint"]);
|
|
58
|
+
if (typeof part.text !== "string") invalid();
|
|
59
|
+
} else if (part.type === "input_image") {
|
|
60
|
+
keys(part, ["type", "image_url", "file_id", "detail", "prompt_cache_breakpoint"]);
|
|
61
|
+
if (!["auto", "low", "high", "original"].includes(String(part.detail))) invalid();
|
|
62
|
+
if (Number(source(part.image_url)) + Number(source(part.file_id)) !== 1) invalid();
|
|
63
|
+
for (const key of ["image_url", "file_id"]) if (part[key] != null && !source(part[key])) invalid();
|
|
64
|
+
if (part.file_id != null && !injectionId(part.file_id)) invalid();
|
|
65
|
+
} else if (part.type === "input_file") {
|
|
66
|
+
keys(part, ["type", "file_id", "file_url", "file_data", "filename", "detail", "prompt_cache_breakpoint"]);
|
|
67
|
+
if ([part.file_id, part.file_url, part.file_data].filter(source).length !== 1) invalid();
|
|
68
|
+
for (const key of ["file_id", "file_url", "file_data", "filename"]) if (part[key] != null && !source(part[key])) invalid();
|
|
69
|
+
if (part.file_id != null && !injectionId(part.file_id)) invalid();
|
|
70
|
+
if (part.file_data != null && !source(part.filename)) invalid();
|
|
71
|
+
if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail))) invalid();
|
|
72
|
+
} else invalid();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Separate call IDs from approval IDs so identical spellings cannot authorize each other. */
|
|
76
|
+
export function nativeResultKey(value: NativeSavedResult): string {
|
|
77
|
+
return JSON.stringify([value.type === "mcp_approval_response" ? "approval" : "call",
|
|
78
|
+
value.type === "mcp_approval_response" ? value.approval_request_id : value.call_id]);
|
|
79
|
+
}
|
|
80
|
+
/** Parse the wider continuation schema; this does NOT grant response.inject support. */
|
|
81
|
+
export function nativeSavedResults(value: unknown): NativeSavedResult[] {
|
|
82
|
+
if (!Array.isArray(value) || !value.length || value.length > MAX_NATIVE_INJECTION_CALLS) invalid();
|
|
83
|
+
const seen = new Set<string>();
|
|
84
|
+
for (const item of value) {
|
|
85
|
+
if (!record(item) || (item.id !== undefined && !injectionId(item.id))) invalid();
|
|
86
|
+
if (item.type === "mcp_approval_response") {
|
|
87
|
+
keys(item, ["type", "approval_request_id", "approve", "reason", "id"]);
|
|
88
|
+
if (!injectionId(item.approval_request_id) || typeof item.approve !== "boolean"
|
|
89
|
+
|| (item.reason != null && typeof item.reason !== "string")) invalid();
|
|
90
|
+
} else {
|
|
91
|
+
keys(item, ["type", "call_id", "output", "caller", "id"]);
|
|
92
|
+
if (!["function_call_output", "custom_tool_call_output"].includes(String(item.type)) || !injectionId(item.call_id)) invalid();
|
|
93
|
+
output(item.output); caller(item.caller);
|
|
94
|
+
}
|
|
95
|
+
const key = nativeResultKey(item as NativeSavedResult);
|
|
96
|
+
if (seen.has(key)) injectionError("duplicate_injection", "Each saved result or approval must occur exactly once.");
|
|
97
|
+
seen.add(key);
|
|
98
|
+
}
|
|
99
|
+
return value as NativeSavedResult[];
|
|
100
|
+
}
|
|
101
|
+
/** Bind a client-owned call or approval to its type, caller and server-supplied identity. */
|
|
102
|
+
export function nativeToolRequirement(item: unknown): NativeToolRequirement | undefined {
|
|
103
|
+
if (!record(item)) return;
|
|
104
|
+
let type: NativeSavedResult["type"];
|
|
105
|
+
let key: string;
|
|
106
|
+
if (item.type === "mcp_approval_request") {
|
|
107
|
+
if (!injectionId(item.id)) invalid();
|
|
108
|
+
type = "mcp_approval_response"; key = JSON.stringify(["approval", item.id]);
|
|
109
|
+
} else {
|
|
110
|
+
if (item.type !== "function_call" && item.type !== "custom_tool_call") return;
|
|
111
|
+
if (!injectionId(item.call_id) || (item.id !== undefined && !injectionId(item.id))) invalid();
|
|
112
|
+
type = item.type === "function_call" ? "function_call_output" : "custom_tool_call_output";
|
|
113
|
+
key = JSON.stringify(["call", item.call_id]);
|
|
114
|
+
}
|
|
115
|
+
const origin = caller(item.caller);
|
|
116
|
+
// Retain only a bounded digest of provenance, never another copy of the call body.
|
|
117
|
+
return { key, type, caller: origin, identity: injectionFingerprint({ type, id: item.id, name: item.name,
|
|
118
|
+
server_label: item.server_label, arguments: item.arguments, input: item.input, caller: origin, agent: item.agent }) };
|
|
119
|
+
}
|
|
120
|
+
/** Approval decisions must be supplied by the caller; no default or synthetic approval exists. */
|
|
121
|
+
export function nativeResultMatches(item: NativeSavedResult, required: NativeToolRequirement): boolean {
|
|
122
|
+
return nativeResultKey(item) === required.key && item.type === required.type
|
|
123
|
+
&& (item.type === "mcp_approval_response" || caller(item.caller) === required.caller);
|
|
124
|
+
}
|
|
125
|
+
/** Compare content rather than object identity; retain content-array order and caller identity. */
|
|
126
|
+
export function nativeResultFingerprint(item: NativeSavedResult): string {
|
|
127
|
+
return injectionFingerprint(item.type === "mcp_approval_response"
|
|
128
|
+
? { type: item.type, approval_request_id: item.approval_request_id, approve: item.approve, reason: item.reason }
|
|
129
|
+
: { type: item.type, call_id: item.call_id, output: item.output, caller: caller(item.caller) });
|
|
130
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNativeControlResponse } from "./native-response-control";
|
|
1
2
|
import type { ResponsesRequestContext, ResponsesAdmissionState } from "./core-options";
|
|
2
3
|
import type { PreparedResponsesRequest } from "./request-prepare";
|
|
3
4
|
import type { ResponsesTransport } from "./request-transport";
|
|
@@ -312,6 +313,16 @@ export async function deliverPassthroughResponse(
|
|
|
312
313
|
});
|
|
313
314
|
}
|
|
314
315
|
|
|
316
|
+
if (options.nativeControl && isNativeControlResponse(upstreamResponse) && upstreamResponse.body) {
|
|
317
|
+
// A native chain carries several response terminals. Ordinary SSE repair,
|
|
318
|
+
// cancellation-on-terminal and local previous-response replay are single-response
|
|
319
|
+
// contracts and would truncate it. Keep the bounded upstream as the sole reader.
|
|
320
|
+
options.nativeControl.relayActive = true;
|
|
321
|
+
commitReasoningReplayServingRoute(nativeExchange.request.headers);
|
|
322
|
+
const body = trackStreamLifetime(upstreamResponse.body, upstream, undefined, options.turnAdmissionLease);
|
|
323
|
+
return new Response(body, { status: upstreamResponse.status, headers });
|
|
324
|
+
}
|
|
325
|
+
|
|
315
326
|
// Bun#32111 workaround: passthrough SSE uses tee()+native relay to avoid the
|
|
316
327
|
// async-pull segfault on Windows. Branch[0] goes directly to the Response (Bun
|
|
317
328
|
// native relay, never enters JS Sink.write); branch[1] is consumed in the
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createSteeringSettingsNormalizer } from "./native-steering-policy";
|
|
2
|
+
import { nativeResponseControlEligible } from "./native-response-control";
|
|
3
|
+
import { NativeInjectionReplay } from "./native-injection-replay";
|
|
4
|
+
import { NativeSteeringReplay } from "./native-steering-replay";
|
|
1
5
|
import type {
|
|
2
6
|
ResponsesRequestContext,
|
|
3
7
|
ResponsesAdmissionState,
|
|
@@ -10,7 +14,7 @@ import type { ResponsesSendBudget } from "./request-send-budget";
|
|
|
10
14
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
11
15
|
import { codexSafetyBufferingFilterOptions, terminalStatusFromParsed } from "../relay";
|
|
12
16
|
import { imageGenToolCallAliases } from "../responses-image-gen-repair";
|
|
13
|
-
import { rememberResponseState } from "../../responses/state";
|
|
17
|
+
import { rememberResponseState, isBodyNonPersistable } from "../../responses/state";
|
|
14
18
|
import {
|
|
15
19
|
currentTurnWireToolCatalogBody,
|
|
16
20
|
hasExplicitWireToolCatalog,
|
|
@@ -251,6 +255,19 @@ export async function preparePassthroughExchange(
|
|
|
251
255
|
? (response: { id?: unknown; output?: unknown; status?: unknown }) =>
|
|
252
256
|
rememberResponseState(parsed._rawBody, response, undefined, responseStateOptions(true))
|
|
253
257
|
: undefined;
|
|
258
|
+
if (options.nativeControl && nativeResponseControlEligible(route.provider, options.nativeControl)
|
|
259
|
+
&& options.inboundTransport === "websocket" && !options.comboAttempt) {
|
|
260
|
+
const body = parsed._rawBody as Record<string, unknown>;
|
|
261
|
+
if (options.nativeControl.kind === "steering") {
|
|
262
|
+
options.nativeControl.normalizeContinuation = createSteeringSettingsNormalizer(parsed, route, config, req.headers);
|
|
263
|
+
}
|
|
264
|
+
const Replay = options.nativeControl.kind === "injection" ? NativeInjectionReplay : NativeSteeringReplay;
|
|
265
|
+
options.nativeControl.replayFactory = () => new Replay(body.input, (input, response) => {
|
|
266
|
+
if (passthroughRecordEligible && !isBodyNonPersistable(body)) {
|
|
267
|
+
rememberResponseState({ ...body, input }, response, undefined, responseStateOptions(true));
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
254
271
|
if (parsed.previousResponseId && !parsed._previousResponseInputExpanded) {
|
|
255
272
|
console.warn(
|
|
256
273
|
`[responses] previous_response_id ${parsed.previousResponseId} not found in local replay state `
|
|
@@ -772,6 +789,9 @@ export async function preparePassthroughExchange(
|
|
|
772
789
|
body: request.body,
|
|
773
790
|
}, recovery), upstream.signal, connectMs, parsed.stream,
|
|
774
791
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
792
|
+
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
793
|
+
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
794
|
+
? options.nativeControl : undefined,
|
|
775
795
|
dispatchOverride: oauthDispatch(request),
|
|
776
796
|
providerName: route.providerName,
|
|
777
797
|
modelId: route.modelId,
|
|
@@ -868,6 +888,9 @@ export async function preparePassthroughExchange(
|
|
|
868
888
|
body: request.body,
|
|
869
889
|
}, innerRecovery), upstream.signal, connectMs, parsed.stream,
|
|
870
890
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
891
|
+
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
892
|
+
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
893
|
+
? options.nativeControl : undefined,
|
|
871
894
|
dispatchOverride: oauthDispatch(request),
|
|
872
895
|
providerName: route.providerName,
|
|
873
896
|
modelId: route.modelId,
|
|
@@ -974,6 +997,9 @@ export async function preparePassthroughExchange(
|
|
|
974
997
|
// here on is a genuine transport attempt.
|
|
975
998
|
storedPoolReplayDispatchNotifier(
|
|
976
999
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
1000
|
+
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1001
|
+
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1002
|
+
? options.nativeControl : undefined,
|
|
977
1003
|
dispatchOverride: oauthDispatch(request),
|
|
978
1004
|
providerName: route.providerName,
|
|
979
1005
|
modelId: route.modelId,
|
|
@@ -1095,6 +1121,9 @@ export async function preparePassthroughExchange(
|
|
|
1095
1121
|
body: request.body,
|
|
1096
1122
|
}, recovery), upstream.signal, connectMs, parsed.stream,
|
|
1097
1123
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
1124
|
+
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1125
|
+
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1126
|
+
? options.nativeControl : undefined,
|
|
1098
1127
|
dispatchOverride: oauthDispatch(request),
|
|
1099
1128
|
providerName: route.providerName,
|
|
1100
1129
|
modelId: route.modelId,
|
|
@@ -1217,6 +1246,9 @@ export async function preparePassthroughExchange(
|
|
|
1217
1246
|
body: request.body,
|
|
1218
1247
|
}, recovery), upstream.signal, connectMs, parsed.stream,
|
|
1219
1248
|
providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
1249
|
+
nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
|
|
1250
|
+
&& responseEffects.plaintextV2AgentMessageToolNames.size === 0
|
|
1251
|
+
? options.nativeControl : undefined,
|
|
1220
1252
|
dispatchOverride: oauthDispatch(request),
|
|
1221
1253
|
providerName: route.providerName,
|
|
1222
1254
|
modelId: route.modelId,
|