@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,242 @@
|
|
|
1
|
+
import { CodexWsCorrelation } from "./codex-ws-correlation";
|
|
2
|
+
import type { NativeResponseControl } from "./native-response-control";
|
|
3
|
+
import type { NativeSteeringReplayObserver } from "./native-steering-replay";
|
|
4
|
+
import {
|
|
5
|
+
injectionError, injectionFingerprint, injectionId, injectionRecord as record, injectionResults,
|
|
6
|
+
isInjectionRequest, MAX_NATIVE_INJECTIONS, MAX_NATIVE_INJECTION_BYTES, MAX_NATIVE_INJECTION_CALLS,
|
|
7
|
+
NATIVE_INJECTION_ACK_MS, NATIVE_INJECTION_TOOL_MS,
|
|
8
|
+
type FunctionResult, type InjectionFrame as Frame,
|
|
9
|
+
} from "./native-injection-protocol";
|
|
10
|
+
|
|
11
|
+
import { nativeResultKey, nativeResultMatches, nativeResultFingerprint, nativeSavedResults, nativeToolRequirement,
|
|
12
|
+
type NativeToolRequirement } from "./native-tool-results";
|
|
13
|
+
|
|
14
|
+
type Call = { requirement: NativeToolRequirement; state: "available" | "queued" | "accepted" | "failed"; result?: string; recoverable?: boolean };
|
|
15
|
+
type Submission = { frame: Frame; results: FunctionResult[]; bytes: number };
|
|
16
|
+
const ENVELOPE = new Set(["type", "input", "previous_response_id", "stream", "stream_id"]);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Injection-only multi-agent owner. Exactly one injection is on the physical wire
|
|
20
|
+
* at a time because created acknowledgements contain a response ID, not a request
|
|
21
|
+
* ID. Queued submissions, terminal delivery and caller-owned recovery are distinct.
|
|
22
|
+
*/
|
|
23
|
+
export class NativeInjectionChannel implements NativeResponseControl {
|
|
24
|
+
readonly kind = "injection" as const;
|
|
25
|
+
relayActive = false;
|
|
26
|
+
replayFactory?: () => NativeSteeringReplayObserver;
|
|
27
|
+
private replay?: NativeSteeringReplayObserver;
|
|
28
|
+
private send?: (frame: Frame) => void;
|
|
29
|
+
private onFailure?: (error: Error) => void;
|
|
30
|
+
private currentId?: string;
|
|
31
|
+
private correlation?: CodexWsCorrelation;
|
|
32
|
+
private terminal?: Frame;
|
|
33
|
+
private terminalRecorded = false;
|
|
34
|
+
private continuationSent = false;
|
|
35
|
+
private finished = false;
|
|
36
|
+
private everAttached = false;
|
|
37
|
+
private readonly seen = new Set<string>();
|
|
38
|
+
private readonly calls = new Map<string, Call>();
|
|
39
|
+
private callBytes = 0;
|
|
40
|
+
private readonly queue: Submission[] = [];
|
|
41
|
+
private queueBytes = 0;
|
|
42
|
+
private inFlight?: Submission;
|
|
43
|
+
private lastAckSequence = -1;
|
|
44
|
+
private ackTimer?: ReturnType<typeof setTimeout>;
|
|
45
|
+
private idleTimer?: ReturnType<typeof setTimeout>;
|
|
46
|
+
private readonly settings = new Map<string, string>();
|
|
47
|
+
private readonly lane: unknown;
|
|
48
|
+
|
|
49
|
+
/** Pin the original settings and lane; construction never opens a connection. */
|
|
50
|
+
constructor(initial: Frame, private readonly idleMs = 300_000,
|
|
51
|
+
private readonly deadlines = { ackMs: NATIVE_INJECTION_ACK_MS, toolMs: NATIVE_INJECTION_TOOL_MS }) {
|
|
52
|
+
if (!isInjectionRequest(initial)) injectionError("injection_not_supported", "Native injection requires explicit multi_agent.enabled.");
|
|
53
|
+
this.lane = initial.stream_id ?? undefined;
|
|
54
|
+
for (const [key, value] of Object.entries(initial)) if (!ENVELOPE.has(key)) this.settings.set(key, injectionFingerprint(value));
|
|
55
|
+
}
|
|
56
|
+
/** Report that the real dispatch boundary has selected this owner. */
|
|
57
|
+
get attached(): boolean { return this.everAttached; }
|
|
58
|
+
/** A terminal is not final until submitted results have acknowledgements. */
|
|
59
|
+
get ended(): boolean { return this.finished; }
|
|
60
|
+
|
|
61
|
+
/** Attach once, after routing/auth/admission, retaining no global response-ID lookup. */
|
|
62
|
+
attach(send: (frame: Frame) => void, fail: (error: Error) => void): () => void {
|
|
63
|
+
if (this.everAttached) throw new Error("Native injection transport is already owned.");
|
|
64
|
+
this.replay = this.replayFactory?.();
|
|
65
|
+
this.send = send; this.onFailure = fail; this.everAttached = true;
|
|
66
|
+
return () => {
|
|
67
|
+
if (this.send !== send) return;
|
|
68
|
+
this.send = undefined; this.onFailure = undefined; this.finished = true;
|
|
69
|
+
clearTimeout(this.ackTimer); clearTimeout(this.idleTimer);
|
|
70
|
+
this.correlation?.finish(); this.calls.clear(); this.seen.clear(); this.queue.length = 0;
|
|
71
|
+
this.inFlight = undefined; this.queueBytes = 0; this.callBytes = 0; this.terminal = undefined;
|
|
72
|
+
this.replay?.dispose(); this.replay = undefined;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Never reinterpret user steering as a function result, or silently mix beta modes. */
|
|
76
|
+
steer(_frame: Frame): never {
|
|
77
|
+
return injectionError("native_control_mode_mismatch", "This multi-agent turn owns an injection-only channel; start a separate turn for steering.");
|
|
78
|
+
}
|
|
79
|
+
/** Abort unknown-delivery state without HTTP fallback, resends or invented acceptance. */
|
|
80
|
+
private fail(): void {
|
|
81
|
+
this.finished = true;
|
|
82
|
+
clearTimeout(this.ackTimer); clearTimeout(this.idleTimer);
|
|
83
|
+
this.onFailure?.(new Error("Native injection transport failed or timed out; delivery is unknown. Do not automatically resend or rerun tools."));
|
|
84
|
+
}
|
|
85
|
+
/** Require the same live owner; an unbound or detached channel cannot authorize a send. */
|
|
86
|
+
private live(): void {
|
|
87
|
+
if (!this.send || this.finished) injectionError("injection_not_supported", "No live native injection transport is available on this route.");
|
|
88
|
+
}
|
|
89
|
+
/** Advertise client-owned function/custom calls and approvals, never hosted execution. */
|
|
90
|
+
private advertise(item: unknown): void {
|
|
91
|
+
const requirement = nativeToolRequirement(item);
|
|
92
|
+
if (!requirement) return;
|
|
93
|
+
const old = this.calls.get(requirement.key);
|
|
94
|
+
if (old) {
|
|
95
|
+
if (old.requirement.identity !== requirement.identity) throw new Error("Native result call identity was reused.");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const bytes = Buffer.byteLength(JSON.stringify(requirement));
|
|
99
|
+
if (this.calls.size >= MAX_NATIVE_INJECTION_CALLS || this.callBytes + bytes > 256 * 1024) throw new Error("Native injection call budget exceeded.");
|
|
100
|
+
this.calls.set(requirement.key, { requirement, state: "available" }); this.callBytes += bytes;
|
|
101
|
+
}
|
|
102
|
+
/** Queue validated saved results; reserve each call before any possibly synchronous send. */
|
|
103
|
+
inject(frame: Frame): void {
|
|
104
|
+
this.live();
|
|
105
|
+
if (frame.type !== "response.inject" || !injectionId(frame.response_id)
|
|
106
|
+
|| Object.keys(frame).some(key => !["type", "response_id", "input", "stream_id"].includes(key))) {
|
|
107
|
+
injectionError("invalid_injection", "Invalid native injection envelope.");
|
|
108
|
+
}
|
|
109
|
+
if (frame.response_id !== this.currentId || (frame.stream_id ?? undefined) !== this.lane || this.continuationSent) {
|
|
110
|
+
injectionError("injection_response_mismatch", "Injection must target the current response and lane on this connection.");
|
|
111
|
+
}
|
|
112
|
+
const results = injectionResults(frame.input);
|
|
113
|
+
for (const item of results) {
|
|
114
|
+
const call = this.calls.get(nativeResultKey(item));
|
|
115
|
+
if (!call || !nativeResultMatches(item, call.requirement)) injectionError("injection_call_not_found", "The result does not match a completed function call on this connection.");
|
|
116
|
+
if (call.state !== "available") injectionError("duplicate_injection", "This function result was already submitted; do not replay it.");
|
|
117
|
+
}
|
|
118
|
+
const text = JSON.stringify(frame);
|
|
119
|
+
const bytes = Buffer.byteLength(text);
|
|
120
|
+
if (this.queue.length >= MAX_NATIVE_INJECTIONS || bytes + this.queueBytes > MAX_NATIVE_INJECTION_BYTES) {
|
|
121
|
+
injectionError("injection_queue_full", "Native injection queue count or byte limit reached; no result was sent.");
|
|
122
|
+
}
|
|
123
|
+
// Detach from caller-owned objects before keeping data across asynchronous callbacks.
|
|
124
|
+
const copy = JSON.parse(text) as Frame;
|
|
125
|
+
const submission = { frame: copy, results: copy.input as FunctionResult[], bytes };
|
|
126
|
+
for (const item of results) this.calls.get(nativeResultKey(item))!.state = "queued";
|
|
127
|
+
this.queue.push(submission); this.queueBytes += bytes;
|
|
128
|
+
this.pump();
|
|
129
|
+
}
|
|
130
|
+
/** Dispatch one queued frame; unrelated output cannot reset its acknowledgement deadline. */
|
|
131
|
+
private pump(): void {
|
|
132
|
+
if (this.inFlight || !this.queue.length || this.finished) return;
|
|
133
|
+
this.live();
|
|
134
|
+
const submission = this.queue[0];
|
|
135
|
+
this.inFlight = submission;
|
|
136
|
+
this.ackTimer = setTimeout(() => this.fail(), this.deadlines.ackMs);
|
|
137
|
+
this.ackTimer.unref?.();
|
|
138
|
+
try {
|
|
139
|
+
this.replay?.submitted(submission.frame);
|
|
140
|
+
this.send!(submission.frame);
|
|
141
|
+
} catch {
|
|
142
|
+
this.fail();
|
|
143
|
+
injectionError("injection_delivery_unknown", "Injection dispatch failed; do not automatically resend or rerun tools.");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Match an acknowledgement to the sole in-flight frame, before releasing the next send. */
|
|
147
|
+
private acknowledge(event: Frame): void {
|
|
148
|
+
const pending = this.inFlight;
|
|
149
|
+
if (!pending || event.response_id !== this.currentId || !Number.isSafeInteger(event.sequence_number)
|
|
150
|
+
|| (event.sequence_number as number) <= this.lastAckSequence) throw new Error("Native injection acknowledgement identity mismatch.");
|
|
151
|
+
const failed = event.type === "response.inject.failed";
|
|
152
|
+
if (failed && (!record(event.error) || typeof event.error.code !== "string"
|
|
153
|
+
|| injectionFingerprint(event.input) !== injectionFingerprint(pending.results))) throw new Error("Native injection rejection does not match submitted results.");
|
|
154
|
+
this.replay?.observe(event);
|
|
155
|
+
this.lastAckSequence = event.sequence_number as number;
|
|
156
|
+
for (const item of pending.results) {
|
|
157
|
+
const call = this.calls.get(nativeResultKey(item))!;
|
|
158
|
+
call.state = failed ? "failed" : "accepted";
|
|
159
|
+
// Retain a digest, not another result body, for an explicitly rejected continuation.
|
|
160
|
+
call.recoverable = failed && record(event.error) && event.error.code === "response_already_completed";
|
|
161
|
+
if (call.recoverable) call.result = nativeResultFingerprint(item);
|
|
162
|
+
}
|
|
163
|
+
clearTimeout(this.ackTimer); this.ackTimer = undefined;
|
|
164
|
+
this.inFlight = undefined; this.queue.shift(); this.queueBytes -= pending.bytes;
|
|
165
|
+
// Do not let a synchronous fake peer publish the next ack before this event is relayed.
|
|
166
|
+
if (this.queue.length) queueMicrotask(() => { try { this.pump(); } catch { this.fail(); } });
|
|
167
|
+
}
|
|
168
|
+
/** Commit terminal replay only when no submitted injection can change its accepted inputs. */
|
|
169
|
+
private recordTerminal(): void {
|
|
170
|
+
if (this.terminal && !this.terminalRecorded) { this.replay?.observe(this.terminal); this.terminalRecorded = true; }
|
|
171
|
+
}
|
|
172
|
+
/** Recover saved, explicitly rejected results on the same socket only when the client asks. */
|
|
173
|
+
continue(frame: Frame): boolean {
|
|
174
|
+
if (!this.send || this.finished) return false;
|
|
175
|
+
if (this.queue.length || this.continuationSent) injectionError("injection_pending", "Wait for every injection acknowledgement before creating another response.");
|
|
176
|
+
if (!this.terminal && frame.previous_response_id === this.currentId) {
|
|
177
|
+
injectionError("injection_pending", "Wait for the response terminal before sending saved-result continuations.");
|
|
178
|
+
}
|
|
179
|
+
if (!this.terminal || frame.previous_response_id !== this.currentId) return false;
|
|
180
|
+
if (this.terminal.type !== "response.completed") injectionError("injection_response_failed", "The parent response did not complete successfully.");
|
|
181
|
+
if ((frame.stream_id ?? undefined) !== this.lane || frame.generate === false) injectionError("invalid_injection", "Use the same lane for an injection continuation.");
|
|
182
|
+
for (const [key, value] of Object.entries(frame)) {
|
|
183
|
+
if (!ENVELOPE.has(key) && this.settings.get(key) !== injectionFingerprint(value)) injectionError("injection_settings_changed", "A native injection continuation cannot change the pinned model or settings.");
|
|
184
|
+
}
|
|
185
|
+
for (const key of this.settings.keys()) {
|
|
186
|
+
if (!Object.hasOwn(frame, key)) injectionError("injection_settings_changed", "A native injection continuation cannot change the pinned model or settings.");
|
|
187
|
+
}
|
|
188
|
+
const results = nativeSavedResults(frame.input);
|
|
189
|
+
const required = [...this.calls.entries()].filter(([, call]) => call.state !== "accepted");
|
|
190
|
+
if (!required.length || results.length !== required.length) injectionError("invalid_injection", "Supply every outstanding saved tool result exactly once.");
|
|
191
|
+
for (const item of results) {
|
|
192
|
+
const call = this.calls.get(nativeResultKey(item));
|
|
193
|
+
if (!call || !nativeResultMatches(item, call.requirement) || call.state === "accepted" || call.state === "queued"
|
|
194
|
+
|| (call.state === "failed" && (!call.recoverable || call.result !== nativeResultFingerprint(item)))) {
|
|
195
|
+
injectionError("invalid_injection", "Continuation input must match unsent or explicitly completion-rejected tool results.");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (Buffer.byteLength(JSON.stringify(frame)) > MAX_NATIVE_INJECTION_BYTES) injectionError("invalid_injection", "Native injection continuation exceeds its byte limit.");
|
|
199
|
+
this.continuationSent = true;
|
|
200
|
+
try { this.recordTerminal(); const copy = JSON.parse(JSON.stringify(frame)) as Frame; this.replay?.submitted(copy); this.send(copy); }
|
|
201
|
+
catch { this.fail(); injectionError("injection_delivery_unknown", "Continuation delivery is unknown; do not automatically resend results."); }
|
|
202
|
+
if (!this.finished) this.armIdle(this.deadlines.ackMs);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
/** Manage response/tool liveness independently of the non-resettable acknowledgement timer. */
|
|
206
|
+
private armIdle(ms: number): void {
|
|
207
|
+
clearTimeout(this.idleTimer);
|
|
208
|
+
this.idleTimer = setTimeout(() => this.fail(), ms); this.idleTimer.unref?.();
|
|
209
|
+
}
|
|
210
|
+
/** Validate ordered upstream events while allowing acknowledgements after a response terminal. */
|
|
211
|
+
observe(event: Frame): boolean {
|
|
212
|
+
if ((event.stream_id ?? undefined) !== this.lane && !(event.type === "error" && event.stream_id == null)) throw new Error("Native injection lane mismatch.");
|
|
213
|
+
const type = event.type;
|
|
214
|
+
if (type === "error") { this.finished = true; return true; }
|
|
215
|
+
if (type === "response.inject.created" || type === "response.inject.failed") this.acknowledge(event);
|
|
216
|
+
else {
|
|
217
|
+
if (typeof type === "string" && (type.startsWith("response.inject.") || type.startsWith("response.steer."))) throw new Error("Unsupported native injection control event.");
|
|
218
|
+
const response = record(event.response) ? event.response : undefined;
|
|
219
|
+
if (type === "response.created") {
|
|
220
|
+
if (!injectionId(response?.id) || this.seen.has(response.id) || this.seen.size >= 128) throw new Error("Native injection response identity or chain limit violated.");
|
|
221
|
+
if (this.currentId && (!this.continuationSent || !this.terminal || this.queue.length
|
|
222
|
+
|| (response.previous_response_id != null && response.previous_response_id !== this.currentId))) throw new Error("Unexpected native injection successor.");
|
|
223
|
+
this.currentId = response.id; this.seen.add(response.id); this.calls.clear(); this.callBytes = 0;
|
|
224
|
+
this.terminal = undefined; this.terminalRecorded = false; this.continuationSent = false; this.lastAckSequence = -1;
|
|
225
|
+
this.correlation?.finish(); this.correlation = new CodexWsCorrelation(true, () => false);
|
|
226
|
+
} else if (!this.currentId || this.terminal) throw new Error("Unexpected native injection event outside an active response.");
|
|
227
|
+
this.correlation?.accept({ ...event, stream_id: undefined });
|
|
228
|
+
if (type === "response.output_item.done") this.advertise(event.item);
|
|
229
|
+
if (["response.completed", "response.failed", "response.incomplete"].includes(String(type))) {
|
|
230
|
+
if (!this.currentId || response?.id !== this.currentId) throw new Error("Native injection terminal identity mismatch.");
|
|
231
|
+
this.terminal = event;
|
|
232
|
+
if (Array.isArray(response.output)) for (const item of response.output) this.advertise(item);
|
|
233
|
+
} else this.replay?.observe(event);
|
|
234
|
+
}
|
|
235
|
+
const unresolved = [...this.calls.values()].some(call => call.state !== "accepted");
|
|
236
|
+
this.finished = Boolean(this.terminal && !this.queue.length && !this.continuationSent
|
|
237
|
+
&& (this.terminal.type !== "response.completed" || !unresolved));
|
|
238
|
+
if (this.finished) { this.recordTerminal(); clearTimeout(this.idleTimer); }
|
|
239
|
+
else this.armIdle(this.continuationSent ? this.deadlines.ackMs : unresolved ? this.deadlines.toolMs : this.idleMs);
|
|
240
|
+
return this.finished;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../../types";
|
|
2
|
+
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
3
|
+
import type { NativeSteeringReplayObserver } from "./native-steering-replay";
|
|
4
|
+
|
|
5
|
+
import { isInjectionRequest } from "./native-injection-protocol";
|
|
6
|
+
|
|
7
|
+
/** Shared transport ownership, not a shared steer/inject protocol state machine. */
|
|
8
|
+
export interface NativeResponseControl {
|
|
9
|
+
readonly kind?: "steering" | "injection";
|
|
10
|
+
relayActive: boolean;
|
|
11
|
+
normalizeContinuation?: (frame: Record<string, unknown>) => Record<string, unknown>;
|
|
12
|
+
replayFactory?: () => NativeSteeringReplayObserver;
|
|
13
|
+
readonly attached: boolean;
|
|
14
|
+
readonly ended: boolean;
|
|
15
|
+
attach(send: (frame: Record<string, unknown>) => void, fail: (error: Error) => void): () => void;
|
|
16
|
+
observe(frame: Record<string, unknown>): boolean;
|
|
17
|
+
steer(frame: Record<string, unknown>): void;
|
|
18
|
+
inject?(frame: Record<string, unknown>): void;
|
|
19
|
+
continue(frame: Record<string, unknown>): boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const OPENAI_API_RESPONSES_URL = "https://api.openai.com/v1/responses";
|
|
23
|
+
|
|
24
|
+
const nativeControlResponses = new WeakSet<Response>();
|
|
25
|
+
/** Mark the exact response for multi-response delivery without serializing a wire field. */
|
|
26
|
+
export function markNativeControlResponse(response: Response): Response { nativeControlResponses.add(response); return response; }
|
|
27
|
+
/** Recognize a marked native response by identity, not by caller-controlled content. */
|
|
28
|
+
export function isNativeControlResponse(response: Response): boolean { return nativeControlResponses.has(response); }
|
|
29
|
+
|
|
30
|
+
/** Preserve canonical ChatGPT eligibility; public API controls require an explicit provider WebSocket opt-in. */
|
|
31
|
+
export function nativeResponseControlEligible(provider: OcxProviderConfig, control?: NativeResponseControl): boolean {
|
|
32
|
+
if (isCanonicalOpenAiForwardProvider(provider)) return true;
|
|
33
|
+
return (control?.kind === "injection" || control?.kind === "steering") && provider.adapter === "openai-responses"
|
|
34
|
+
&& provider.upstreamWebsocket === true && provider.authMode !== "forward"
|
|
35
|
+
&& provider.baseUrl?.replace(/\/+$/, "") === "https://api.openai.com/v1";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Select by execution mode, never model name; a multi-agent request cannot acquire steering. */
|
|
39
|
+
export function nativeResponseControlMode(frame: Record<string, unknown>, flags: {
|
|
40
|
+
codexNativeInjection?: boolean; codexNativeSteering?: boolean;
|
|
41
|
+
}): "injection" | "steering" | undefined {
|
|
42
|
+
if (isInjectionRequest(frame)) return flags.codexNativeInjection === true ? "injection" : undefined;
|
|
43
|
+
return nativeSteeringUnavailableReason(frame, flags.codexNativeSteering) === undefined ? "steering" : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Explain documented execution-mode exclusions without claiming model entitlement. */
|
|
47
|
+
export function nativeSteeringUnavailableReason(frame: Record<string, unknown>, enabled?: boolean): string | undefined {
|
|
48
|
+
if (enabled !== true) return "Native steering is disabled; enable codexNativeSteering and WebSockets for a supported route.";
|
|
49
|
+
if (isInjectionRequest(frame)) return "Multi-agent execution does not support single-agent response.steer; use a later client request.";
|
|
50
|
+
if (frame.conversation != null) return "Conversation-bound responses do not support native steering.";
|
|
51
|
+
if (Array.isArray(frame.context_management) && frame.context_management.some(item =>
|
|
52
|
+
item && typeof item === "object" && (item as Record<string, unknown>).type === "compaction")) {
|
|
53
|
+
return "Automatic API compaction and native steering cannot share an active response.";
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/** Narrow JSON object envelopes independently of either native control owner. */
|
|
4
|
+
export function nativeResponseRecord(value: unknown): value is Record<string, unknown> {
|
|
5
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Compare JSON content by value: object-key order is irrelevant, array order is not. */
|
|
9
|
+
export function nativeResponseFingerprint(value: unknown): string {
|
|
10
|
+
const canonical = (item: unknown): string => Array.isArray(item) ? `[${item.map(canonical).join(",")}]`
|
|
11
|
+
: nativeResponseRecord(item) ? `{${Object.keys(item).sort().map(key => `${JSON.stringify(key)}:${canonical(item[key])}`).join(",")}}`
|
|
12
|
+
: JSON.stringify(item) ?? "null";
|
|
13
|
+
return createHash("sha256").update(canonical(value)).digest("hex");
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { nativeResponseFingerprint as injectionFingerprint, nativeResponseRecord as record } from "./native-response-json";
|
|
2
|
+
type Frame = Record<string, unknown>;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Preserve completed wire items missing from a sparse terminal, including hosted
|
|
6
|
+
* tool results and encrypted agent messages. Shared IDs must retain content and
|
|
7
|
+
* relative order; conflicting transcripts fail instead of silently losing data.
|
|
8
|
+
*/
|
|
9
|
+
export function nativeResponseOutput(done: ReadonlyMap<number, Frame>, terminal: unknown): Frame[] {
|
|
10
|
+
const observed = [...done.entries()].sort((a, b) => a[0] - b[0]).map(([, item]) => item);
|
|
11
|
+
if (terminal == null || (Array.isArray(terminal) && !terminal.length)) return observed;
|
|
12
|
+
if (!Array.isArray(terminal) || terminal.some(item => !record(item))) throw new Error("Invalid native response output.");
|
|
13
|
+
const identity = (item: Frame) => typeof item.id === "string" ? `id:${item.id}` : `body:${injectionFingerprint(item)}`;
|
|
14
|
+
const positions = new Map<string, number>();
|
|
15
|
+
observed.forEach((item, index) => {
|
|
16
|
+
const key = identity(item);
|
|
17
|
+
if (positions.has(key)) throw new Error("Duplicate native completed output identity.");
|
|
18
|
+
positions.set(key, index);
|
|
19
|
+
});
|
|
20
|
+
const result: Frame[] = [];
|
|
21
|
+
const seen = new Set<string>();
|
|
22
|
+
let cursor = 0;
|
|
23
|
+
for (const item of terminal as Frame[]) {
|
|
24
|
+
const key = identity(item);
|
|
25
|
+
if (seen.has(key)) throw new Error("Duplicate native terminal output identity.");
|
|
26
|
+
seen.add(key);
|
|
27
|
+
const position = positions.get(key);
|
|
28
|
+
if (position === undefined) { result.push(item); continue; }
|
|
29
|
+
if (position < cursor || injectionFingerprint(item) !== injectionFingerprint(observed[position])) {
|
|
30
|
+
throw new Error("Native terminal output contradicts completed wire items.");
|
|
31
|
+
}
|
|
32
|
+
while (cursor < position) result.push(observed[cursor++]);
|
|
33
|
+
result.push(item); cursor++;
|
|
34
|
+
}
|
|
35
|
+
while (cursor < observed.length) result.push(observed[cursor++]);
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { inspectResponseLogSsePayload, usageFromResponsesPayload, type RequestLogContext } from "../request-log";
|
|
2
|
+
import type { OcxUsage } from "../../types";
|
|
3
|
+
import { MAX_NATIVE_STEERING_RESPONSES } from "./native-steering";
|
|
4
|
+
|
|
5
|
+
/** Count every terminal once. Control frames may echo user input: never sample them. */
|
|
6
|
+
export function createNativeSteeringLogObserver(logCtx: RequestLogContext, onFirstOutput?: () => void): (payload: string) => void {
|
|
7
|
+
let outputSeen = false;
|
|
8
|
+
const usages = new Map<string, OcxUsage>();
|
|
9
|
+
return payload => {
|
|
10
|
+
let event: { type?: string; delta?: unknown; response?: { id?: string; usage?: unknown; incomplete_details?: { reason?: string } } };
|
|
11
|
+
try { event = JSON.parse(payload); } catch { return; }
|
|
12
|
+
if (event.type?.startsWith("response.steer.") || event.type?.startsWith("response.inject.")) return;
|
|
13
|
+
if (!outputSeen && event.type?.endsWith(".delta") && typeof event.delta === "string" && event.delta.length) {
|
|
14
|
+
outputSeen = true; onFirstOutput?.();
|
|
15
|
+
}
|
|
16
|
+
// A steered parent is not a failed logical turn. Keep its usage, but never
|
|
17
|
+
// label an eventual successful successor as an upstream failure.
|
|
18
|
+
if (!(event.type === "response.incomplete" && event.response?.incomplete_details?.reason === "steered")) {
|
|
19
|
+
inspectResponseLogSsePayload(logCtx, payload);
|
|
20
|
+
}
|
|
21
|
+
const terminal = ["response.completed", "response.failed", "response.incomplete"].includes(event.type ?? "");
|
|
22
|
+
if (terminal && typeof event.response?.id === "string") {
|
|
23
|
+
const usage = usageFromResponsesPayload(event.response.usage);
|
|
24
|
+
if (usage && (usages.has(event.response.id) || usages.size < MAX_NATIVE_STEERING_RESPONSES)) {
|
|
25
|
+
// Retain numeric counters only, never arbitrary rawUsage metadata.
|
|
26
|
+
const counters: OcxUsage = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens };
|
|
27
|
+
for (const key of ["totalTokens", "cachedInputTokens", "cacheReadInputTokens", "cacheCreationInputTokens", "reasoningOutputTokens"] as const) {
|
|
28
|
+
if (typeof usage[key] === "number") counters[key] = usage[key];
|
|
29
|
+
}
|
|
30
|
+
usages.set(event.response.id, counters);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!usages.size) return;
|
|
34
|
+
const total: OcxUsage = { inputTokens: 0, outputTokens: 0 };
|
|
35
|
+
for (const usage of usages.values()) {
|
|
36
|
+
for (const key of ["inputTokens", "outputTokens", "totalTokens", "cachedInputTokens", "cacheReadInputTokens", "cacheCreationInputTokens", "reasoningOutputTokens"] as const) {
|
|
37
|
+
const value = usage[key];
|
|
38
|
+
if (typeof value === "number" && Number.isFinite(value)) total[key] = (total[key] ?? 0) + value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
logCtx.usage = total;
|
|
42
|
+
if (logCtx.activeAttempt) logCtx.activeAttempt.usage = total;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
2
|
+
import { nativeEffortClamp, shouldApplyNativeEffortClamp } from "../../codex/catalog";
|
|
3
|
+
import { applyEffortCap, applyPinnedEffort, effortCapAppliesTo, prepareEffortNormalization, stripEmptyLadderEffort, supportedLadderFor } from "../effort-policy";
|
|
4
|
+
import { collabSurface } from "./collaboration";
|
|
5
|
+
import { mapRoutedResponsesReasoningEffort, normalizeConfiguredReasoningSummaryDelivery,
|
|
6
|
+
stripDisabledReasoningSummaries, stripDisabledVerbosity, stripUnsupportedReasoningSummaryDelivery } from "../../adapters/openai-responses/reasoning";
|
|
7
|
+
import { NativeSteeringError } from "./native-steering";
|
|
8
|
+
import { nativeResponseRecord as record } from "./native-response-json";
|
|
9
|
+
import { STEERING_MUTABLE_SETTINGS } from "./native-steering-settings";
|
|
10
|
+
|
|
11
|
+
type Frame = Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
/** Reuse normal route-specific policy on a private override, never reroute or rebuild saved tool results. */
|
|
14
|
+
export function createSteeringSettingsNormalizer(
|
|
15
|
+
parsed: OcxParsedRequest,
|
|
16
|
+
route: { provider: OcxProviderConfig; providerName: string; modelId: string },
|
|
17
|
+
config: OcxConfig,
|
|
18
|
+
headers: Headers,
|
|
19
|
+
): (frame: Frame) => Frame {
|
|
20
|
+
const selector = prepareEffortNormalization(parsed, route);
|
|
21
|
+
const surface = collabSurface(parsed);
|
|
22
|
+
return (frame: Frame): Frame => {
|
|
23
|
+
if (route.provider.authMode === "forward" && Object.hasOwn(frame, "max_output_tokens")) {
|
|
24
|
+
throw new NativeSteeringError("steering_settings_unsupported", "This subscription route does not accept max_output_tokens; omit that override.");
|
|
25
|
+
}
|
|
26
|
+
// The frame is cloned by the channel. Policy receives only generation keys,
|
|
27
|
+
// not saved results, tool declarations, credentials or caller response IDs.
|
|
28
|
+
let body: Frame = Object.fromEntries(STEERING_MUTABLE_SETTINGS.filter(key => Object.hasOwn(frame, key)).map(key => [key, frame[key]]));
|
|
29
|
+
if (Object.hasOwn(body, "reasoning")) {
|
|
30
|
+
const candidate = { ...parsed, options: { ...parsed.options,
|
|
31
|
+
reasoning: record(body.reasoning) && typeof body.reasoning.effort === "string" ? body.reasoning.effort : undefined }, _rawBody: body };
|
|
32
|
+
applyPinnedEffort(candidate, route, config, selector);
|
|
33
|
+
if (effortCapAppliesTo(surface, headers, config, parsed._compactionRequest === true)) {
|
|
34
|
+
applyEffortCap(candidate, headers, config, supportedLadderFor(route));
|
|
35
|
+
}
|
|
36
|
+
const clamp = shouldApplyNativeEffortClamp(route.providerName, route.provider, route.modelId)
|
|
37
|
+
? nativeEffortClamp(route.modelId, candidate.options.reasoning) : null;
|
|
38
|
+
if (clamp && record(body.reasoning)) body.reasoning.effort = clamp;
|
|
39
|
+
body = mapRoutedResponsesReasoningEffort(body, route.provider, route.modelId) as Frame;
|
|
40
|
+
body.reasoning = stripEmptyLadderEffort(body.reasoning, supportedLadderFor(route));
|
|
41
|
+
}
|
|
42
|
+
body = stripDisabledVerbosity(stripDisabledReasoningSummaries(
|
|
43
|
+
normalizeConfiguredReasoningSummaryDelivery(stripUnsupportedReasoningSummaryDelivery(body, route.modelId), route.provider, route.modelId),
|
|
44
|
+
route.provider, route.modelId), route.provider, route.modelId) as Frame;
|
|
45
|
+
const next = { ...frame };
|
|
46
|
+
for (const key of STEERING_MUTABLE_SETTINGS) if (Object.hasOwn(frame, key)) next[key] = body[key];
|
|
47
|
+
return next;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { nativeResponseOutput } from "./native-response-output";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Connection-local replay journal. Only input committed by response.created enters
|
|
5
|
+
* a successor's prefix. Uncommitted/rejected steering never enters the shared
|
|
6
|
+
* continuation cache. Bodies are bounded and discarded at connection teardown.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_NATIVE_STEERING_REPLAY_BYTES = 32 * 1024 * 1024;
|
|
9
|
+
type Frame = Record<string, unknown>;
|
|
10
|
+
/** Accept JSON object envelopes without treating arrays as records. */
|
|
11
|
+
function record(value: unknown): value is Frame {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
/** Normalize string input to one user message while preserving array order. */
|
|
15
|
+
function inputItems(input: unknown): unknown[] {
|
|
16
|
+
if (typeof input === "string") return [{ type: "message", role: "user", content: [{ type: "input_text", text: input }] }];
|
|
17
|
+
return Array.isArray(input) ? input : [];
|
|
18
|
+
}
|
|
19
|
+
export interface NativeSteeringReplayObserver {
|
|
20
|
+
submitted(frame: Frame): () => void;
|
|
21
|
+
observe(frame: Frame): void;
|
|
22
|
+
dispose(): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Keep bounded, connection-local input until a validated successor commits it. */
|
|
26
|
+
export class NativeSteeringReplay implements NativeSteeringReplayObserver {
|
|
27
|
+
private prefix: unknown[];
|
|
28
|
+
private bytes: number;
|
|
29
|
+
private current?: string;
|
|
30
|
+
private previousOutput: unknown[] = [];
|
|
31
|
+
private outputItems = new Map<number, Frame>();
|
|
32
|
+
private submissions: Array<{ parent: string; input: unknown[]; id?: string; bytes: number }> = [];
|
|
33
|
+
private explicitInput: unknown[] = [];
|
|
34
|
+
private explicitBytes = 0;
|
|
35
|
+
|
|
36
|
+
/** Capture the initial prefix and reject over-budget history before dispatch. */
|
|
37
|
+
constructor(input: unknown, private readonly remember: (input: unknown[], response: Frame) => void) {
|
|
38
|
+
this.prefix = [...inputItems(input)];
|
|
39
|
+
this.bytes = Buffer.byteLength(JSON.stringify(this.prefix));
|
|
40
|
+
this.check();
|
|
41
|
+
}
|
|
42
|
+
/** Reject overflow rather than silently truncating retained conversation input. */
|
|
43
|
+
private check(): void {
|
|
44
|
+
if (this.bytes > MAX_NATIVE_STEERING_REPLAY_BYTES) throw new Error("Native steering replay exceeded its bounded history budget; input was not silently truncated.");
|
|
45
|
+
}
|
|
46
|
+
/** Reserve replay bytes before send and return a rollback for synchronous failure. */
|
|
47
|
+
submitted(frame: Frame): () => void {
|
|
48
|
+
const input = inputItems(frame.input);
|
|
49
|
+
const bytes = Buffer.byteLength(JSON.stringify(input));
|
|
50
|
+
this.bytes += bytes;
|
|
51
|
+
try { this.check(); } catch (error) { this.bytes -= bytes; throw error; }
|
|
52
|
+
if (frame.type === "response.steer") {
|
|
53
|
+
const submission = { parent: String(frame.previous_response_id), input, bytes };
|
|
54
|
+
this.submissions.push(submission);
|
|
55
|
+
return () => {
|
|
56
|
+
const index = this.submissions.indexOf(submission);
|
|
57
|
+
if (index >= 0) { this.submissions.splice(index, 1); this.bytes -= bytes; }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
this.explicitInput = input;
|
|
61
|
+
this.explicitBytes = bytes;
|
|
62
|
+
return () => { this.explicitInput = []; this.bytes -= this.explicitBytes; this.explicitBytes = 0; };
|
|
63
|
+
}
|
|
64
|
+
/** Apply ordered upstream events; only created successors commit queued input. */
|
|
65
|
+
observe(frame: Frame): void {
|
|
66
|
+
const response = record(frame.response) ? frame.response : undefined;
|
|
67
|
+
const steer = record(frame.steer) ? frame.steer : undefined;
|
|
68
|
+
if (frame.type === "response.steer.accepted") {
|
|
69
|
+
const first = this.submissions.find(item => item.parent === steer?.previous_response_id && item.id === undefined);
|
|
70
|
+
if (!first || typeof steer?.id !== "string") throw new Error("Native steering replay acceptance does not match submitted input");
|
|
71
|
+
first.id = steer.id;
|
|
72
|
+
} else if (frame.type === "response.steer.failed") {
|
|
73
|
+
const index = this.submissions.findIndex(item => steer?.id !== undefined
|
|
74
|
+
? item.id === steer.id
|
|
75
|
+
: item.parent === steer?.previous_response_id && item.id === undefined);
|
|
76
|
+
if (index >= 0) {
|
|
77
|
+
const [failed] = this.submissions.splice(index, 1);
|
|
78
|
+
this.bytes -= failed.bytes;
|
|
79
|
+
}
|
|
80
|
+
} else if (frame.type === "response.created") {
|
|
81
|
+
if (this.current) {
|
|
82
|
+
const committed = this.submissions.filter(item => item.parent === this.current && item.id !== undefined);
|
|
83
|
+
// Byte-valid histories may exceed the runtime's positional-argument limit.
|
|
84
|
+
for (const item of this.previousOutput) this.prefix.push(item);
|
|
85
|
+
for (const submission of committed) {
|
|
86
|
+
for (const item of submission.input) this.prefix.push(item);
|
|
87
|
+
}
|
|
88
|
+
for (const item of this.explicitInput) this.prefix.push(item);
|
|
89
|
+
this.submissions = this.submissions.filter(item => !committed.includes(item));
|
|
90
|
+
}
|
|
91
|
+
this.explicitInput = [];
|
|
92
|
+
this.explicitBytes = 0;
|
|
93
|
+
this.previousOutput = [];
|
|
94
|
+
this.outputItems.clear();
|
|
95
|
+
this.current = String(response?.id);
|
|
96
|
+
} else if (frame.type === "response.output_item.done") {
|
|
97
|
+
const index = frame.output_index as number;
|
|
98
|
+
if (!Number.isSafeInteger(index) || index < 0 || index > 10_000 || !record(frame.item)) throw new Error("Native steering replay output identity is invalid");
|
|
99
|
+
const previous = this.outputItems.get(index);
|
|
100
|
+
if (previous !== undefined) this.bytes -= Buffer.byteLength(JSON.stringify(previous));
|
|
101
|
+
this.bytes += Buffer.byteLength(JSON.stringify(frame.item));
|
|
102
|
+
this.check();
|
|
103
|
+
this.outputItems.set(index, frame.item);
|
|
104
|
+
} else if (response && ["response.completed", "response.incomplete", "response.failed"].includes(String(frame.type))) {
|
|
105
|
+
const doneItems = [...this.outputItems.entries()].sort((a, b) => a[0] - b[0]).map(([, item]) => item);
|
|
106
|
+
const output = nativeResponseOutput(this.outputItems, response.output);
|
|
107
|
+
for (const item of doneItems) this.bytes -= Buffer.byteLength(JSON.stringify(item));
|
|
108
|
+
this.bytes += Buffer.byteLength(JSON.stringify(output));
|
|
109
|
+
this.check();
|
|
110
|
+
this.outputItems.clear();
|
|
111
|
+
this.previousOutput = output;
|
|
112
|
+
// Failed and steered parents are never presented to shared state as completed.
|
|
113
|
+
// Their output is used only when a validated successor commits that prefix.
|
|
114
|
+
if (frame.type === "response.completed") this.remember(this.prefix, { ...response, output });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Release retained input, output and queued submissions when the owner detaches. */
|
|
118
|
+
dispose(): void {
|
|
119
|
+
this.prefix = [];
|
|
120
|
+
this.previousOutput = [];
|
|
121
|
+
this.submissions = [];
|
|
122
|
+
this.explicitInput = [];
|
|
123
|
+
this.outputItems.clear();
|
|
124
|
+
this.bytes = 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { REASONING_SUMMARY_DELIVERY_VALUES } from "../../types/wire";
|
|
2
|
+
import { nativeResponseRecord as record } from "./native-response-json";
|
|
3
|
+
|
|
4
|
+
type Frame = Record<string, unknown>;
|
|
5
|
+
/** Only generation settings may change without selecting a new route or tool surface. */
|
|
6
|
+
export const STEERING_MUTABLE_SETTINGS = ["reasoning", "text", "max_output_tokens", "stream_options"] as const;
|
|
7
|
+
export const isSteeringMutableSetting = (key: string): boolean =>
|
|
8
|
+
(STEERING_MUTABLE_SETTINGS as readonly string[]).includes(key);
|
|
9
|
+
|
|
10
|
+
/** Bound nested structured-output schemas before fingerprinting or copying them. */
|
|
11
|
+
function boundedJson(value: unknown): boolean {
|
|
12
|
+
const pending = [{ value, depth: 0 }];
|
|
13
|
+
let nodes = 0;
|
|
14
|
+
while (pending.length) {
|
|
15
|
+
const current = pending.pop()!;
|
|
16
|
+
if (++nodes > 20_000 || current.depth > 64) return false;
|
|
17
|
+
if (current.value && typeof current.value === "object") {
|
|
18
|
+
for (const value of Object.values(current.value)) pending.push({ value, depth: current.depth + 1 });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
const only = (value: Frame, keys: readonly string[]) => Object.keys(value).every(key => keys.includes(key));
|
|
24
|
+
const optionalEnum = (value: unknown, choices: readonly string[]) => value === undefined || value === null
|
|
25
|
+
|| (typeof value === "string" && choices.includes(value));
|
|
26
|
+
|
|
27
|
+
/** Reject malformed overrides rather than treating them as omitted settings. */
|
|
28
|
+
export function validSteeringSettings(frame: Frame): boolean {
|
|
29
|
+
for (const key of STEERING_MUTABLE_SETTINGS) {
|
|
30
|
+
if (!Object.hasOwn(frame, key)) continue;
|
|
31
|
+
const value = frame[key];
|
|
32
|
+
if (!boundedJson(value)) return false;
|
|
33
|
+
if (value === null) continue;
|
|
34
|
+
if (key === "max_output_tokens") {
|
|
35
|
+
if (!Number.isSafeInteger(value) || (value as number) < 1) return false;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (!record(value)) return false;
|
|
39
|
+
if (key === "reasoning") {
|
|
40
|
+
if (!only(value, ["effort", "summary", "generate_summary"])
|
|
41
|
+
|| !optionalEnum(value.effort, ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"])
|
|
42
|
+
|| !optionalEnum(value.summary, ["auto", "concise", "detailed", "none"])
|
|
43
|
+
|| !optionalEnum(value.generate_summary, ["auto", "concise", "detailed", "none"])) return false;
|
|
44
|
+
} else if (key === "stream_options") {
|
|
45
|
+
if (!only(value, ["reasoning_summary_delivery", "include_usage", "include_obfuscation"])
|
|
46
|
+
|| !optionalEnum(value.reasoning_summary_delivery, REASONING_SUMMARY_DELIVERY_VALUES)
|
|
47
|
+
|| [value.include_usage, value.include_obfuscation].some(item => item !== undefined && typeof item !== "boolean")) return false;
|
|
48
|
+
} else {
|
|
49
|
+
if (!only(value, ["format", "verbosity"]) || !optionalEnum(value.verbosity, ["low", "medium", "high"])) return false;
|
|
50
|
+
if (value.format === undefined || value.format === null) continue;
|
|
51
|
+
const format = value.format;
|
|
52
|
+
if (!record(format)) return false;
|
|
53
|
+
if (format.type === "text" || format.type === "json_object") {
|
|
54
|
+
if (!only(format, ["type"])) return false;
|
|
55
|
+
} else if (format.type === "json_schema") {
|
|
56
|
+
if (!only(format, ["type", "name", "schema", "strict", "description"])
|
|
57
|
+
|| typeof format.name !== "string" || !/^[a-zA-Z0-9_-]{1,64}$/.test(format.name)
|
|
58
|
+
|| !record(format.schema)
|
|
59
|
+
|| (format.strict != null && typeof format.strict !== "boolean")
|
|
60
|
+
|| (format.description !== undefined && typeof format.description !== "string")) return false;
|
|
61
|
+
} else return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Overlay only validated generation keys on the current, already authorized wire settings. */
|
|
68
|
+
export function mergeSteeringContinuation(base: Frame, frame: Frame): Frame {
|
|
69
|
+
const outgoing: Frame = { ...base, input: frame.input, previous_response_id: frame.previous_response_id };
|
|
70
|
+
for (const key of STEERING_MUTABLE_SETTINGS) {
|
|
71
|
+
if (!Object.hasOwn(frame, key)) continue;
|
|
72
|
+
if (frame[key] === undefined) delete outgoing[key];
|
|
73
|
+
else outgoing[key] = frame[key];
|
|
74
|
+
}
|
|
75
|
+
return outgoing;
|
|
76
|
+
}
|