@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
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
previewCodexPoolLineage,
|
|
37
37
|
applyCodexAuthContextToProvider,
|
|
38
38
|
hasCallerCodexBearer,
|
|
39
|
+
requestOwnedMainPinState,
|
|
39
40
|
} from "../../codex/auth-context";
|
|
40
41
|
import {
|
|
41
42
|
copyPreviousResponseReplayProvenance,
|
|
@@ -483,11 +484,37 @@ export async function prepareResponsesRequest(
|
|
|
483
484
|
const nativeMainReadsForbidden = previewRequestScopedMainCredential
|
|
484
485
|
|| nativeMainRecoveryBlocked
|
|
485
486
|
|| previewSelectionAdmission?.mainProfileDraining === true;
|
|
487
|
+
// The liveness answer final authentication gives its own selection options, computed from the
|
|
488
|
+
// same shared predicate so the two cannot drift apart again (#4850). `fixedAccountId` is
|
|
489
|
+
// mirrored through `route.codexAccountId` because that is literally what core-auth.ts passes
|
|
490
|
+
// as `accountId`. A reserve-authorized request is the one input where the two can differ, and
|
|
491
|
+
// it differs harmlessly: reserve plus a caller bearer is served as main either way, which is
|
|
492
|
+
// the answer this produces.
|
|
493
|
+
const previewRequestOwnedMainPin = requestOwnedMainPinState(
|
|
494
|
+
previewAuthHeaders,
|
|
495
|
+
config,
|
|
496
|
+
options.codexAuthPolicy ?? config,
|
|
497
|
+
previewRequestScopedMainCredential,
|
|
498
|
+
route.codexAccountId,
|
|
499
|
+
).preserve;
|
|
486
500
|
// Deliberately NOT fenced on ownership: final auth derives `nativeMainSelectionOnly` from the
|
|
487
501
|
// drain alone, and adding a term here would diverge from it in the other direction.
|
|
488
502
|
const previewSelectionOptions = {
|
|
489
503
|
nativeMainSelectionOnly: !nativeMainRecoveryBlocked
|
|
490
504
|
&& previewSelectionAdmission?.mainProfileDraining === true,
|
|
505
|
+
// Pool eligibility was the last part of preview still outside the fence (#4850). Without
|
|
506
|
+
// this seam `codexAccountUnusableReason` takes its default branch into
|
|
507
|
+
// `isMainAccountCredentialUsable()`, which opens the physical `auth.json` -- twice per
|
|
508
|
+
// spawn, because subagent fallback re-enters the preview through the callback below.
|
|
509
|
+
//
|
|
510
|
+
// Scoped to ownership, and carrying final auth's value rather than a constant, because
|
|
511
|
+
// preview exists to predict final auth. Under an effective main pin the request really is
|
|
512
|
+
// served by its own main credential, so main must stay eligible; without the pin final auth
|
|
513
|
+
// scores main `main_credential_unavailable` and drops it, so preview has to drop it too. A
|
|
514
|
+
// hardcoded `true` would be wrong in the second case and `false` in the first.
|
|
515
|
+
isMainAccountTokenLive: previewRequestScopedMainCredential
|
|
516
|
+
? () => previewRequestOwnedMainPin
|
|
517
|
+
: undefined,
|
|
491
518
|
// Preview must reach the same answer as the final resolution, including the uploaded-file
|
|
492
519
|
// retention (#4778): a preview that reported a quota move the request will not make would
|
|
493
520
|
// hand subagent fallback a different account than the one that actually serves.
|
|
@@ -711,9 +738,23 @@ export async function prepareResponsesRequest(
|
|
|
711
738
|
route,
|
|
712
739
|
options,
|
|
713
740
|
).requestScopedMainCredential && hasCallerCodexBearer(recoveryAuthHeaders);
|
|
741
|
+
// Recovery's own answer to the same question, against the route it may have moved
|
|
742
|
+
// to. Reconstructing the options without it is what left pool eligibility outside
|
|
743
|
+
// the fence on the first preview (#4850); recovery re-previews, so it would leave
|
|
744
|
+
// the same two reads on the one path that runs after decryption.
|
|
745
|
+
const recoveryRequestOwnedMainPin = requestOwnedMainPinState(
|
|
746
|
+
recoveryAuthHeaders,
|
|
747
|
+
config,
|
|
748
|
+
options.codexAuthPolicy ?? config,
|
|
749
|
+
recoveryRequestScopedMainCredential,
|
|
750
|
+
route.codexAccountId,
|
|
751
|
+
).preserve;
|
|
714
752
|
const recoverySelectionOptions = {
|
|
715
753
|
nativeMainSelectionOnly: !recoveryNativeMainBlocked
|
|
716
754
|
&& recoverySelectionAdmission?.mainProfileDraining === true,
|
|
755
|
+
isMainAccountTokenLive: recoveryRequestScopedMainCredential
|
|
756
|
+
? () => recoveryRequestOwnedMainPin
|
|
757
|
+
: undefined,
|
|
717
758
|
// #4778, same reason as `previewSelectionOptions` above: this preview decides
|
|
718
759
|
// which account subagent fallback scores against, and final auth passes the
|
|
719
760
|
// retention. Recovery is exactly where the two could diverge -- it re-previews
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { OPENAI_API_RESPONSES_URL } from "./native-response-control";
|
|
2
|
+
import { isInjectionRequest } from "./native-injection-protocol";
|
|
3
|
+
import type { NativeResponseControl } from "./native-response-control";
|
|
1
4
|
// Upstream WebSocket transport for the ChatGPT Codex backend.
|
|
2
5
|
//
|
|
3
6
|
// Why this exists: the Codex backend serves the responses_websockets path from
|
|
@@ -129,6 +132,8 @@ export function codexWsUpstreamFetch(
|
|
|
129
132
|
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
130
133
|
onQuota?: CodexWsQuotaObserver,
|
|
131
134
|
beforeDispatch?: (headers: Headers) => void,
|
|
135
|
+
nativeControl?: NativeResponseControl,
|
|
136
|
+
beforeContinuation?: () => Promise<void>,
|
|
132
137
|
): Promise<Response> {
|
|
133
138
|
const prepared = prepareCodexWsRequest(url, init);
|
|
134
139
|
if (!prepared) return sseFallback(url, prepareCodexHttpInit(url, init));
|
|
@@ -142,6 +147,17 @@ export function codexWsUpstreamFetch(
|
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
const { frameText, headers } = prepared;
|
|
150
|
+
// Never infer backend support from a model name or enable controls on a gateway.
|
|
151
|
+
const control = nativeControl?.kind === "injection"
|
|
152
|
+
? ((prepared.canonical || url === OPENAI_API_RESPONSES_URL) && isInjectionRequest(JSON.parse(frameText)) ? nativeControl : undefined)
|
|
153
|
+
: (prepared.canonical || url === OPENAI_API_RESPONSES_URL) ? nativeControl : undefined;
|
|
154
|
+
if (control?.kind === "injection" && url === OPENAI_API_RESPONSES_URL) {
|
|
155
|
+
const beta = headers["openai-beta"];
|
|
156
|
+
if (!beta?.split(",").some(value => value.trim() === "responses_multi_agent=v1")) {
|
|
157
|
+
headers["openai-beta"] = beta ? `${beta}, responses_multi_agent=v1` : "responses_multi_agent=v1";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
145
161
|
|
|
146
162
|
// Decide before dialing. Once the socket is open the caller already holds a
|
|
147
163
|
// streaming Response, so the oversized close can only be surfaced as a stream
|
|
@@ -169,7 +185,9 @@ export function codexWsUpstreamFetch(
|
|
|
169
185
|
}
|
|
170
186
|
let session: CodexWsSession;
|
|
171
187
|
try {
|
|
172
|
-
|
|
188
|
+
// Steering keeps a private physical connection across successor responses; it
|
|
189
|
+
// must never enter the idle-socket pool or move to a different credential.
|
|
190
|
+
const identity = control ? null : codexWsReuseIdentity(url, headers, frameText, proxy);
|
|
173
191
|
session = (identity ? codexWsPool.acquire(identity, wsUrl, headers, proxy) : null)
|
|
174
192
|
?? new CodexWsSession(wsUrl, headers, false, undefined, proxy);
|
|
175
193
|
if (!session.busy && !session.reserve()) {
|
|
@@ -181,6 +199,8 @@ export function codexWsUpstreamFetch(
|
|
|
181
199
|
}
|
|
182
200
|
return codexWsExchange({
|
|
183
201
|
session, url, init, prepared, sseFallback, onQuota, beforeDispatch,
|
|
202
|
+
nativeControl: control,
|
|
203
|
+
beforeContinuation,
|
|
184
204
|
bunVersion: typeof runtime === "string" ? runtime : runtime.version,
|
|
185
205
|
});
|
|
186
206
|
}
|
|
@@ -67,7 +67,14 @@ export async function performStopTeardown(url: URL, io: StopTeardownIo = {}): Pr
|
|
|
67
67
|
// undone (#3008).
|
|
68
68
|
const grokNote = grok.ok ? "" : ` Grok config cleanup failed: ${grok.message}`;
|
|
69
69
|
if (restore.success && grok.ok) {
|
|
70
|
-
|
|
70
|
+
// A degraded restore is a success — routing is out and the client is no longer aimed at
|
|
71
|
+
// a port that is about to disappear — but it left a provider table behind on purpose.
|
|
72
|
+
// Reporting a bare "restored" would put the caller in exactly the position #4812
|
|
73
|
+
// describes: a config they did not expect and no idea why it is there.
|
|
74
|
+
const retained = restore.retainedCodexProviderTable
|
|
75
|
+
? ` ${(await import("../codex/inject/restore")).describeRetainedCodexProviderTable(restore.retainedCodexProviderTable)}`
|
|
76
|
+
: "";
|
|
77
|
+
return { success: true, message: `Proxy stopping, native Codex restored.${retained}`, sharedTeardown: "performed" };
|
|
71
78
|
}
|
|
72
79
|
if (restore.success) {
|
|
73
80
|
return {
|
package/src/server/ws-bridge.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NativeResponseControl } from "./responses/native-response-control";
|
|
1
2
|
import type { ServerWebSocket } from "bun";
|
|
2
3
|
import { responsesJsonEventSequence } from "./responses-json-events";
|
|
3
4
|
import { FORWARD_HEADERS } from "../adapters/openai-responses";
|
|
@@ -17,6 +18,9 @@ type ResponsesTerminalReporter = (status: ResponsesTerminalStatus) => void;
|
|
|
17
18
|
type ResponsesPayloadObserver = (payload: string) => void;
|
|
18
19
|
|
|
19
20
|
export interface WsData {
|
|
21
|
+
nativeControl?: NativeResponseControl;
|
|
22
|
+
/** Content-free per-turn explanation; never a model capability assertion. */
|
|
23
|
+
nativeSteeringUnavailable?: string;
|
|
20
24
|
headers?: Headers; // base inbound forward headers only; per-turn auth refresh injects current pool tokens
|
|
21
25
|
/**
|
|
22
26
|
* Resolved once at the handshake. Auth is handshake-time only on this path, so
|
|
@@ -229,6 +233,7 @@ export async function pumpResponsesSseToWebSocket(
|
|
|
229
233
|
sseStream: ReadableStream<Uint8Array>,
|
|
230
234
|
options: {
|
|
231
235
|
isCurrent?: () => boolean;
|
|
236
|
+
untilEof?: boolean;
|
|
232
237
|
onTerminal?: ResponsesTerminalReporter;
|
|
233
238
|
onSsePayload?: ResponsesPayloadObserver;
|
|
234
239
|
} = {},
|
|
@@ -251,6 +256,7 @@ export async function pumpResponsesSseToWebSocket(
|
|
|
251
256
|
const decoder = new TextDecoder();
|
|
252
257
|
const framer = new BoundedSseFrameBuffer();
|
|
253
258
|
let terminalSeen = false;
|
|
259
|
+
let lastTerminal: ResponsesTerminalStatus | undefined;
|
|
254
260
|
|
|
255
261
|
const handlePayload = (payload: string): boolean => {
|
|
256
262
|
if (!isCurrent()) return true;
|
|
@@ -270,8 +276,10 @@ export async function pumpResponsesSseToWebSocket(
|
|
|
270
276
|
}
|
|
271
277
|
if (terminalSeen) return true;
|
|
272
278
|
sendTextFrame(ws, payload);
|
|
273
|
-
|
|
279
|
+
if (options.untilEof && type === "response.created") lastTerminal = undefined;
|
|
280
|
+
const terminalStatus = type === "error" && options.untilEof ? "failed" : terminalStatusFromType(type);
|
|
274
281
|
if (terminalStatus) {
|
|
282
|
+
if (options.untilEof) { lastTerminal = terminalStatus; return false; }
|
|
275
283
|
reportTerminal(terminalStatus);
|
|
276
284
|
terminalSeen = true;
|
|
277
285
|
void reader.cancel().catch(() => {});
|
|
@@ -294,6 +302,10 @@ export async function pumpResponsesSseToWebSocket(
|
|
|
294
302
|
const payload = parseSseBlock(decoder.decode(tail));
|
|
295
303
|
if (payload) handlePayload(payload);
|
|
296
304
|
}
|
|
305
|
+
if (options.untilEof && lastTerminal && isCurrent() && !clientCancelled) {
|
|
306
|
+
reportTerminal(lastTerminal);
|
|
307
|
+
terminalSeen = true;
|
|
308
|
+
}
|
|
297
309
|
if (!terminalSeen && isCurrent() && !clientCancelled) {
|
|
298
310
|
reportTerminal("incomplete");
|
|
299
311
|
sendProtocolError(ws, 502, "Upstream stream ended before response terminal event");
|
|
@@ -368,6 +380,7 @@ export async function sendResponseToWebSocket(
|
|
|
368
380
|
response: Response,
|
|
369
381
|
isCurrent: () => boolean,
|
|
370
382
|
options: {
|
|
383
|
+
untilEof?: boolean;
|
|
371
384
|
onTerminal?: ResponsesTerminalReporter;
|
|
372
385
|
onSsePayload?: ResponsesPayloadObserver;
|
|
373
386
|
} = {},
|
|
@@ -398,6 +411,7 @@ export async function sendResponseToWebSocket(
|
|
|
398
411
|
if (contentType.includes("text/event-stream")) {
|
|
399
412
|
await pumpResponsesSseToWebSocket(ws, response.body, {
|
|
400
413
|
isCurrent,
|
|
414
|
+
untilEof: options.untilEof,
|
|
401
415
|
onTerminal: options.onTerminal,
|
|
402
416
|
onSsePayload: options.onSsePayload,
|
|
403
417
|
});
|
|
@@ -420,6 +434,7 @@ export async function sendResponseToWebSocket(
|
|
|
420
434
|
if (looksLikeSse(prefix)) {
|
|
421
435
|
await pumpResponsesSseToWebSocket(ws, stream, {
|
|
422
436
|
isCurrent,
|
|
437
|
+
untilEof: options.untilEof,
|
|
423
438
|
onTerminal: options.onTerminal,
|
|
424
439
|
onSsePayload: options.onSsePayload,
|
|
425
440
|
});
|
package/src/service/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { restoreNativeCodexAsync } from "../codex/inject";
|
|
4
|
+
import { describeRetainedCodexProviderTable } from "../codex/inject/restore";
|
|
4
5
|
import { stripGrokConfig } from "../grok/inject";
|
|
5
6
|
import { serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
6
7
|
import { statusWinswRaw, type WinswStatus } from "../lib/winsw";
|
|
@@ -291,7 +292,15 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
291
292
|
break;
|
|
292
293
|
}
|
|
293
294
|
const restore = await restoreNativeCodexAsync();
|
|
294
|
-
if (restore.success)
|
|
295
|
+
if (restore.success) {
|
|
296
|
+
console.log("✅ service stopped + native Codex restored.");
|
|
297
|
+
// Success is not the whole answer when routing came down but the provider table
|
|
298
|
+
// stayed. Saying only "restored" here is how a user finds an unexplained
|
|
299
|
+
// opencodex table in their config weeks later (#4812).
|
|
300
|
+
if (restore.retainedCodexProviderTable) {
|
|
301
|
+
console.log(` ${describeRetainedCodexProviderTable(restore.retainedCodexProviderTable)}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
295
304
|
else console.error(`⚠️ service stopped, but native Codex restore FAILED: ${restore.message}\nRun \`ocx restore\` (or check $CODEX_HOME/config.toml) before using native Codex.`);
|
|
296
305
|
if (!restore.success) process.exitCode = 1;
|
|
297
306
|
// The Grok fence is the other managed config this command owns. Leaving it behind
|
|
@@ -339,6 +348,9 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
339
348
|
console.error(`⚠️ native Codex restore FAILED: ${restore.message}\nRun \`ocx restore\` before using native Codex.`);
|
|
340
349
|
process.exitCode = 1;
|
|
341
350
|
}
|
|
351
|
+
else if (restore.retainedCodexProviderTable) {
|
|
352
|
+
console.log(`↩️ ${describeRetainedCodexProviderTable(restore.retainedCodexProviderTable)}`);
|
|
353
|
+
}
|
|
342
354
|
const grok = stripGrokConfig();
|
|
343
355
|
if (grok.changed) console.log(`↩️ ${grok.message}`);
|
|
344
356
|
else if (!grok.ok) {
|
package/src/types/config.ts
CHANGED
|
@@ -749,6 +749,10 @@ export interface OcxConfig {
|
|
|
749
749
|
shutdownTimeoutMs?: number;
|
|
750
750
|
/** Advertise supports_websockets so Codex opens the WS endpoint. Default false; set true to opt in. */
|
|
751
751
|
websockets?: boolean;
|
|
752
|
+
/** Experimental single-lane native OpenAI WebSocket steering; default off. */
|
|
753
|
+
codexNativeSteering?: boolean;
|
|
754
|
+
/** Experimental, default-off saved function-result injection on native multi-agent WebSockets. */
|
|
755
|
+
codexNativeInjection?: boolean;
|
|
752
756
|
/**
|
|
753
757
|
* Opt-in auto-cleanup policy for archived Codex sessions (issue #42 Phase 3).
|
|
754
758
|
* Default OFF (`enabled` false / unset). Never enabled implicitly.
|
package/src/types/provider.ts
CHANGED
|
@@ -311,6 +311,19 @@ export interface OcxProviderConfig {
|
|
|
311
311
|
* preserved after it, and parallel calls stay together with the reasoning turn that produced them.
|
|
312
312
|
*/
|
|
313
313
|
requiresAdjacentResponsesToolResults?: boolean;
|
|
314
|
+
/**
|
|
315
|
+
* Responses upstream whose parser also rejects a tool call that has no matching output
|
|
316
|
+
* anywhere in the replayed input, not merely one whose result sits out of order. A call left
|
|
317
|
+
* dangling by an interrupted stream is answered with an explicit unknown-status placeholder
|
|
318
|
+
* so the thread can continue.
|
|
319
|
+
*
|
|
320
|
+
* Separate from `requiresAdjacentResponsesToolResults` on purpose: adjacency reorders items a
|
|
321
|
+
* strict parser already accepts in some order, while this synthesizes an item the client never
|
|
322
|
+
* sent. Kimi accepts a dangling call (#4726), so it must not inherit the synthesis.
|
|
323
|
+
* `statelessResponses` implies this, because an upstream that stores nothing cannot resolve
|
|
324
|
+
* the missing half from its own history either.
|
|
325
|
+
*/
|
|
326
|
+
requiresPairedResponsesToolResults?: boolean;
|
|
314
327
|
/**
|
|
315
328
|
* When enabled, a tool result that is present but empty (no usable text or content
|
|
316
329
|
* part) is rewritten to an explicit annotation before it reaches the upstream wire,
|