@bitkyc08/opencodex 2.47.0-preview.20260908 → 2.48.0-preview.20260908
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-B6EFoatP.js +115 -0
- package/gui/dist/assets/index-D5SiRo8X.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/openai-responses.ts +16 -2
- package/src/adapters/opencode-go-additional-tools.ts +35 -0
- package/src/adapters/routed-agent-messages.ts +46 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/codex/auth-api.ts +211 -67
- package/src/codex/features.ts +3 -1
- package/src/codex/multi-agent-mode-policy.ts +24 -0
- package/src/codex/reset-credit-auto-redeem.ts +54 -13
- package/src/codex/routing.ts +85 -4
- package/src/codex/shim.ts +10 -1
- package/src/config/atomic-write.ts +2 -3
- package/src/config/initialize.ts +11 -6
- package/src/generated/compatibility-version.json +35 -23
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/providers/default-aliases.ts +4 -1
- package/src/providers/registry.ts +6 -1
- package/src/router.ts +5 -2
- package/src/server/claude-messages.ts +21 -4
- package/src/server/live.ts +8 -19
- package/src/server/management/agent-settings-routes.ts +3 -0
- package/src/server/responses/collaboration.ts +23 -12
- package/src/server/responses/combo-session-recall.ts +89 -0
- package/src/server/responses/compact.ts +19 -1
- package/src/server/responses/core.ts +189 -51
- package/src/types/config.ts +3 -3
- package/gui/dist/assets/index-B8ZZ0ivI.css +0 -1
- package/gui/dist/assets/index-CNTWhC5F.js +0 -115
- package/src/adapters/opencode-go.ts +0 -35
|
@@ -15,6 +15,7 @@ import { nativeContextLimits } from "../../codex/catalog";
|
|
|
15
15
|
import { describeUpstreamConnectFailure } from "./upstream-error";
|
|
16
16
|
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
17
17
|
import { applyAccountQuotaFromUpstreamHeaders as applyCapturedCodexQuota } from "../../codex/quota";
|
|
18
|
+
import { isCodexAccountGenerationLive } from "../../codex/account-store";
|
|
18
19
|
import { isCodexWsQuotaObservedResponse } from "./ws-upstream";
|
|
19
20
|
import {
|
|
20
21
|
multiAgentGuidanceEnabled,
|
|
@@ -58,6 +59,10 @@ import {
|
|
|
58
59
|
providerContinuationRouteScope,
|
|
59
60
|
sameProviderContinuationOwner,
|
|
60
61
|
} from "../../responses/provider-continuation";
|
|
62
|
+
import {
|
|
63
|
+
rememberComboForLane,
|
|
64
|
+
recallComboForLane,
|
|
65
|
+
} from "./combo-session-recall";
|
|
61
66
|
import {
|
|
62
67
|
comboRouteDecisionTrace,
|
|
63
68
|
NoEligiblePolicyCandidateError,
|
|
@@ -78,6 +83,7 @@ import {
|
|
|
78
83
|
comboRequestHasImageInput,
|
|
79
84
|
concreteComboRequestBody,
|
|
80
85
|
getCombo,
|
|
86
|
+
resolveComboId,
|
|
81
87
|
isComboTargetInCooldown,
|
|
82
88
|
NoAvailableComboTargetsError,
|
|
83
89
|
noteComboSuccess,
|
|
@@ -310,6 +316,7 @@ import {
|
|
|
310
316
|
consumeForInspection,
|
|
311
317
|
consumeForResponseLogMetadata,
|
|
312
318
|
createSseInspector,
|
|
319
|
+
terminalStatusFromParsed,
|
|
313
320
|
isEagerRelaySseResponse,
|
|
314
321
|
isNativePassthroughSseResponse,
|
|
315
322
|
markEagerRelaySseResponse,
|
|
@@ -340,6 +347,7 @@ import {
|
|
|
340
347
|
} from "../responses-item-id-repair";
|
|
341
348
|
import {
|
|
342
349
|
createReasoningSummaryChannelPayloadRewrite,
|
|
350
|
+
rewriteReasoningSummaryInJson,
|
|
343
351
|
rewriteReasoningSummaryInJsonString,
|
|
344
352
|
routeUsesContentChannelReasoning,
|
|
345
353
|
} from "../responses-reasoning-summary-rewrite";
|
|
@@ -1004,8 +1012,12 @@ export function usesCodexForwardPoolAuth(
|
|
|
1004
1012
|
function codexWsQuotaObserver(authCtx: CodexAuthContext, provider: OcxProviderConfig): CodexWsQuotaObserver | undefined {
|
|
1005
1013
|
if (!isCanonicalOpenAiForwardProvider(provider) || !usesCodexForwardPoolAuth(authCtx, provider)) return undefined;
|
|
1006
1014
|
const { accountId, writerGeneration } = authCtx;
|
|
1015
|
+
const credentialGeneration = authCtx.kind === "pool" ? authCtx.generation : undefined;
|
|
1007
1016
|
const mainWriter = authCtx.kind === "main-pool" ? authCtx.mainQuotaWriter : undefined;
|
|
1008
|
-
return headers =>
|
|
1017
|
+
return headers => {
|
|
1018
|
+
if (credentialGeneration !== undefined && !isCodexAccountGenerationLive(accountId, credentialGeneration)) return;
|
|
1019
|
+
applyCapturedCodexQuota(accountId, headers, writerGeneration, mainWriter);
|
|
1020
|
+
};
|
|
1009
1021
|
}
|
|
1010
1022
|
|
|
1011
1023
|
export function preAuthUpstreamHostCircuitKey(
|
|
@@ -1661,6 +1673,8 @@ export interface HandleResponsesOptions {
|
|
|
1661
1673
|
onCodexAuthContextResolved?: (context: CodexAuthContext | undefined) => void;
|
|
1662
1674
|
/** Internal deterministic seam for account-gated native fallback tests. */
|
|
1663
1675
|
resolveCodexModelEntitlements?: typeof resolveCodexModelEntitlements;
|
|
1676
|
+
/** Internal: validated final client-visible model, after completed terminal success only. */
|
|
1677
|
+
onResponseComplete?: (model: string) => void;
|
|
1664
1678
|
recordTerminalOutcomes?: boolean;
|
|
1665
1679
|
setTerminalOutcomeRecorder?: (recorder: ((status: ResponsesTerminalStatus, httpStatusOverride?: number) => void) | undefined) => void;
|
|
1666
1680
|
onNativePassthroughTerminal?: (status: ResponsesTerminalStatus) => void;
|
|
@@ -1861,34 +1875,54 @@ export function createChildPassthroughCallbackGate(options: HandleResponsesOptio
|
|
|
1861
1875
|
let state: "pending" | "committed" | "discarded" = "pending";
|
|
1862
1876
|
let pending: Pending | undefined;
|
|
1863
1877
|
let accepted = false;
|
|
1878
|
+
let pendingModel: string | undefined;
|
|
1879
|
+
let completionAccepted = false;
|
|
1880
|
+
let completionRejected = false;
|
|
1864
1881
|
const publish = (value: Pending): void => {
|
|
1865
1882
|
if (value.kind === "terminal") options.onNativePassthroughTerminal?.(value.status);
|
|
1866
1883
|
else options.onNativePassthroughCancel?.();
|
|
1867
1884
|
};
|
|
1885
|
+
const publishCompletion = (): void => {
|
|
1886
|
+
if (state !== "committed" || completionRejected || pendingModel === undefined) return;
|
|
1887
|
+
const model = pendingModel;
|
|
1888
|
+
pendingModel = undefined;
|
|
1889
|
+
options.onResponseComplete?.(model);
|
|
1890
|
+
};
|
|
1868
1891
|
const receive = (value: Pending): void => {
|
|
1869
1892
|
if (state === "discarded" || accepted) return;
|
|
1870
1893
|
accepted = true;
|
|
1894
|
+
if (value.kind === "cancel" || value.status !== "completed") {
|
|
1895
|
+
completionRejected = true;
|
|
1896
|
+
pendingModel = undefined;
|
|
1897
|
+
}
|
|
1871
1898
|
if (state === "committed") return publish(value);
|
|
1872
1899
|
pending ??= value;
|
|
1873
1900
|
};
|
|
1874
1901
|
return {
|
|
1875
1902
|
onTerminal: (status: ResponsesTerminalStatus) => receive({ kind: "terminal", status }),
|
|
1876
1903
|
onCancel: () => receive({ kind: "cancel" }),
|
|
1904
|
+
onResponseComplete: (model: string) => {
|
|
1905
|
+
if (state === "discarded" || completionRejected || completionAccepted || !model.trim()) return;
|
|
1906
|
+
completionAccepted = true;
|
|
1907
|
+
pendingModel = model;
|
|
1908
|
+
publishCompletion();
|
|
1909
|
+
},
|
|
1877
1910
|
commit: () => {
|
|
1878
1911
|
if (state !== "pending") return;
|
|
1879
1912
|
state = "committed";
|
|
1880
1913
|
if (pending) publish(pending);
|
|
1881
1914
|
pending = undefined;
|
|
1915
|
+
publishCompletion();
|
|
1882
1916
|
},
|
|
1883
1917
|
discard: () => {
|
|
1884
1918
|
state = "discarded";
|
|
1885
1919
|
pending = undefined;
|
|
1920
|
+
pendingModel = undefined;
|
|
1886
1921
|
},
|
|
1887
1922
|
};
|
|
1888
1923
|
}
|
|
1889
1924
|
|
|
1890
1925
|
|
|
1891
|
-
|
|
1892
1926
|
export function buildComboChildHeaders(parentHeaders: HeadersInit): Headers {
|
|
1893
1927
|
const childHeaders = new Headers(parentHeaders);
|
|
1894
1928
|
// Combo children re-serialize already-decoded JSON. Keeping transport metadata from
|
|
@@ -2689,9 +2723,22 @@ export async function handleComboResponses(
|
|
|
2689
2723
|
(logCtx.attempts ??= []).push(attempt);
|
|
2690
2724
|
attemptRetained = true;
|
|
2691
2725
|
};
|
|
2726
|
+
const completedTarget = { provider: pick.target.provider, model: pick.target.model };
|
|
2727
|
+
const writerGeneration = pick.writerGeneration;
|
|
2692
2728
|
let consumedChildFailure: ConsumedComboFailure | undefined;
|
|
2693
2729
|
const callbackGate = createChildPassthroughCallbackGate({
|
|
2694
2730
|
...options,
|
|
2731
|
+
onResponseComplete: model => {
|
|
2732
|
+
// The live config can change while the child is streaming. Never retain credentials.
|
|
2733
|
+
const currentCombo = getCombo(config, comboId);
|
|
2734
|
+
const provider = config.providers[completedTarget.provider];
|
|
2735
|
+
if (Object.hasOwn(config.providers, completedTarget.provider)
|
|
2736
|
+
&& provider && provider.disabled !== true
|
|
2737
|
+
&& currentCombo?.targets.some(target => targetKey(target) === targetKey(completedTarget))) {
|
|
2738
|
+
rememberComboForLane(sessionLaneIdFromRequest(req.headers), comboId, completedTarget, model, writerGeneration);
|
|
2739
|
+
}
|
|
2740
|
+
options.onResponseComplete?.(model);
|
|
2741
|
+
},
|
|
2695
2742
|
onNativePassthroughTerminal: status => {
|
|
2696
2743
|
// A committed stream can acquire terminal metadata after preflight copied
|
|
2697
2744
|
// the child log. Publish it before the outer logger finalizes, but only
|
|
@@ -2732,6 +2779,7 @@ export async function handleComboResponses(
|
|
|
2732
2779
|
onStoredPool401ReplayDispatched: () => { storedPool401ReplayDispatched = true; },
|
|
2733
2780
|
onNativePassthroughTerminal: callbackGate.onTerminal,
|
|
2734
2781
|
onNativePassthroughCancel: callbackGate.onCancel,
|
|
2782
|
+
onResponseComplete: callbackGate.onResponseComplete,
|
|
2735
2783
|
});
|
|
2736
2784
|
} catch (error) {
|
|
2737
2785
|
callbackGate.discard();
|
|
@@ -3104,6 +3152,23 @@ async function handleResponsesInner(
|
|
|
3104
3152
|
effort: comboEffortRow.effort,
|
|
3105
3153
|
};
|
|
3106
3154
|
}
|
|
3155
|
+
// Compaction may send the last client-visible bare model after a combo switch.
|
|
3156
|
+
// Configured selectors take precedence; otherwise recall before combo dispatch (#3891).
|
|
3157
|
+
if (!options.comboAttempt && body && typeof body === "object" && !Array.isArray(body)) {
|
|
3158
|
+
const rawModel = (body as { model?: unknown }).model;
|
|
3159
|
+
const rawInput = (body as { input?: unknown }).input;
|
|
3160
|
+
const isCompactionTrigger = Array.isArray(rawInput)
|
|
3161
|
+
&& rawInput.some((item: unknown) =>
|
|
3162
|
+
typeof item === "object" && item !== null && (item as { type?: string }).type === "compaction_trigger");
|
|
3163
|
+
if (typeof rawModel === "string" && !rawModel.includes("/") && isCompactionTrigger
|
|
3164
|
+
&& !comboRows.fastRow && !comboEffortRow
|
|
3165
|
+
&& !resolveComboId(config, rawModel)) {
|
|
3166
|
+
const recalledComboId = recallComboForLane(config, sessionLaneIdFromRequest(req.headers), rawModel);
|
|
3167
|
+
if (recalledComboId) {
|
|
3168
|
+
(body as Record<string, unknown>).model = `combo/${recalledComboId}`;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3107
3172
|
const comboId = !options.comboAttempt ? comboIdFromRawBody(body, config) : null;
|
|
3108
3173
|
if (comboId && Object.hasOwn(config.combos ?? {}, comboId)) {
|
|
3109
3174
|
options.onRequestBodyRead?.();
|
|
@@ -3595,14 +3660,16 @@ async function handleResponsesInner(
|
|
|
3595
3660
|
// The canonical ChatGPT backend rejects previous_response_id, so a local replay miss leaves no
|
|
3596
3661
|
// safe way to recover the omitted history. Fail before auth, adapter construction, or upstream
|
|
3597
3662
|
// I/O instead of stripping the id and silently forwarding a context-free delta (#702).
|
|
3663
|
+
// Codex recognizes previous_response_not_found on WebSocket errors and reconnects with its
|
|
3664
|
+
// full input. A generic invalid_request_error instead terminates the task after cache expiry.
|
|
3598
3665
|
if (
|
|
3599
3666
|
hasUnexpandedPreviousResponse
|
|
3600
3667
|
&& isCanonicalOpenAiForwardProvider(route.provider)
|
|
3601
3668
|
) {
|
|
3602
3669
|
return formatErrorResponse(
|
|
3603
3670
|
400,
|
|
3604
|
-
"
|
|
3605
|
-
"OpenAI forward continuation state is unavailable or expired;
|
|
3671
|
+
"previous_response_not_found",
|
|
3672
|
+
"OpenAI forward continuation state is unavailable or expired; resend the full conversation without previous_response_id.",
|
|
3606
3673
|
);
|
|
3607
3674
|
}
|
|
3608
3675
|
|
|
@@ -4305,6 +4372,17 @@ async function handleResponsesInner(
|
|
|
4305
4372
|
}
|
|
4306
4373
|
|
|
4307
4374
|
const recordTerminalOutcomes = options.recordTerminalOutcomes !== false;
|
|
4375
|
+
let responseCompletionNotified = false;
|
|
4376
|
+
let responseCompletionCancelled = false;
|
|
4377
|
+
const cancelResponseCompletion = (): void => { responseCompletionCancelled = true; };
|
|
4378
|
+
const notifyResponseComplete = (response: { status?: unknown; model?: unknown }): void => {
|
|
4379
|
+
if (responseCompletionNotified || responseCompletionCancelled
|
|
4380
|
+
|| options.abortSignal?.aborted || req.signal.aborted
|
|
4381
|
+
|| response.status !== "completed"
|
|
4382
|
+
|| typeof response.model !== "string" || !response.model.trim()) return;
|
|
4383
|
+
responseCompletionNotified = true;
|
|
4384
|
+
options.onResponseComplete?.(response.model);
|
|
4385
|
+
};
|
|
4308
4386
|
|
|
4309
4387
|
const continuationStateForResponse = (
|
|
4310
4388
|
emitted?: OcxProviderContinuationState,
|
|
@@ -4582,9 +4660,26 @@ async function handleResponsesInner(
|
|
|
4582
4660
|
// check sees nothing undeclared, and the refused turn enters continuation state anyway. So the
|
|
4583
4661
|
// rejection is sticky for the whole turn, set from every parsed payload on the inspection side.
|
|
4584
4662
|
let inspectionSawUndeclaredTool = false;
|
|
4663
|
+
let inspectedTerminal: ResponsesTerminalStatus | null = null;
|
|
4664
|
+
let inspectedCompletionSeen = false;
|
|
4665
|
+
let firstTerminalAllowsRecall = false;
|
|
4585
4666
|
const passiveQuotaObserved = hasPassiveAccountQuota(route.providerName)
|
|
4586
4667
|
&& route.provider.authMode === "oauth";
|
|
4587
4668
|
const noteInspectedPayload = (payload: unknown) => {
|
|
4669
|
+
// First terminal stays authoritative even in metadata-only inspection, which
|
|
4670
|
+
// intentionally continues parsing after a failed/incomplete terminal.
|
|
4671
|
+
const terminal = terminalStatusFromParsed(payload);
|
|
4672
|
+
if (inspectedTerminal === null && terminal !== null) {
|
|
4673
|
+
inspectedTerminal = terminal;
|
|
4674
|
+
// The client boundary accepts a terminal by event type, even without a
|
|
4675
|
+
// response object. Such a terminal must permanently decline recall.
|
|
4676
|
+
if (terminal === "completed" && payload && typeof payload === "object"
|
|
4677
|
+
&& "response" in payload && payload.response && typeof payload.response === "object"
|
|
4678
|
+
&& !Array.isArray(payload.response) && "model" in payload.response) {
|
|
4679
|
+
firstTerminalAllowsRecall = typeof payload.response.model === "string"
|
|
4680
|
+
&& payload.response.model.trim().length > 0;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4588
4683
|
// Meta reports subscription usage ONLY as an in-stream event; there is no endpoint
|
|
4589
4684
|
// to poll (003 §E probed 17 paths, all 404). Observed here rather than behind a
|
|
4590
4685
|
// dedicated inspector handler because onParsedPayload already reaches every
|
|
@@ -4606,8 +4701,7 @@ async function handleResponsesInner(
|
|
|
4606
4701
|
// Gated on the same flag as the guard itself: with no readable catalog (or a forward-auth
|
|
4607
4702
|
// provider) every name looks undeclared, and flipping this would stop recording continuation
|
|
4608
4703
|
// state for exactly the passthrough traffic the guard deliberately stands down for.
|
|
4609
|
-
if (
|
|
4610
|
-
if (undeclaredToolCallName(
|
|
4704
|
+
if (undeclaredToolGuardActive && !inspectionSawUndeclaredTool && undeclaredToolCallName(
|
|
4611
4705
|
restoreAuthorizedBareNamespaceToolCalls(payload),
|
|
4612
4706
|
declaredWireToolNames,
|
|
4613
4707
|
declaredNamelessClientCallTypes,
|
|
@@ -4615,33 +4709,61 @@ async function handleResponsesInner(
|
|
|
4615
4709
|
) !== undefined) {
|
|
4616
4710
|
inspectionSawUndeclaredTool = true;
|
|
4617
4711
|
}
|
|
4712
|
+
// The snapshot callback opts the inspector into output reconstruction. Compaction
|
|
4713
|
+
// has no continuation cache, so use the parsed terminal here without adding retention.
|
|
4714
|
+
if (!rememberPassthroughResponse && payload && typeof payload === "object"
|
|
4715
|
+
&& "type" in payload && payload.type === "response.completed"
|
|
4716
|
+
&& "response" in payload && payload.response && typeof payload.response === "object"
|
|
4717
|
+
&& !Array.isArray(payload.response)) {
|
|
4718
|
+
rememberPassthroughResponseChecked(payload.response as Record<string, unknown>);
|
|
4719
|
+
}
|
|
4618
4720
|
};
|
|
4619
|
-
const rememberPassthroughResponseChecked =
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4721
|
+
const rememberPassthroughResponseChecked = (
|
|
4722
|
+
response: { id?: unknown; output?: unknown; status?: unknown; model?: unknown },
|
|
4723
|
+
) => {
|
|
4724
|
+
if (inspectionSawUndeclaredTool) return;
|
|
4725
|
+
const restored = restoreRoutedCustomCalls(
|
|
4726
|
+
restoreAuthorizedBareNamespaceToolCalls(restoreRoutedNamespaceCalls(response, routedNamespaceToolAliases).value),
|
|
4727
|
+
routedCustomToolNames,
|
|
4728
|
+
routedCustomToolRepairNames,
|
|
4729
|
+
declaredWireToolNames,
|
|
4730
|
+
).value;
|
|
4731
|
+
const restoredResponse = (functionRepairSchemas.size > 0
|
|
4732
|
+
? JSON.parse(normalizeFunctionCompletionJson(JSON.stringify(restored)))
|
|
4733
|
+
: restored) as { id?: unknown; output?: unknown; status?: unknown };
|
|
4734
|
+
// Replay overlap compares the items the client echoes, including visible reasoning shape.
|
|
4735
|
+
const replayResponse = parsed.options.hideThinkingSummary !== true
|
|
4736
|
+
&& routeUsesContentChannelReasoning(route.provider, route.modelId)
|
|
4737
|
+
? rewriteReasoningSummaryInJson(restoredResponse) as typeof restoredResponse
|
|
4738
|
+
: restoredResponse;
|
|
4739
|
+
if (
|
|
4740
|
+
undeclaredToolGuardActive
|
|
4741
|
+
&& undeclaredToolCallNameInResponse(
|
|
4742
|
+
restoredResponse,
|
|
4626
4743
|
declaredWireToolNames,
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4744
|
+
declaredNamelessClientCallTypes,
|
|
4745
|
+
providerExecutedCallTypes,
|
|
4746
|
+
) !== undefined
|
|
4747
|
+
) {
|
|
4748
|
+
return;
|
|
4749
|
+
}
|
|
4750
|
+
rememberPassthroughResponse?.(replayResponse);
|
|
4751
|
+
const firstCompletion = !inspectedCompletionSeen;
|
|
4752
|
+
inspectedCompletionSeen = true;
|
|
4753
|
+
if (firstCompletion && (inspectedTerminal === null || firstTerminalAllowsRecall)) {
|
|
4754
|
+
// A model-less first completion permanently declines recall; later terminal
|
|
4755
|
+
// frames are hidden by the client boundary and cannot supply its identity.
|
|
4756
|
+
// Native inspection sees the pre-rewrite model. Only an actual terminal
|
|
4757
|
+
// model can seed recall; an absent model never falls back to the pick.
|
|
4758
|
+
if (typeof response.model === "string" && response.model.trim()) {
|
|
4759
|
+
notifyResponseComplete({
|
|
4760
|
+
status: response.status,
|
|
4761
|
+
model: parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
|
|
4762
|
+
? parsed._responseModelId : response.model,
|
|
4763
|
+
});
|
|
4641
4764
|
}
|
|
4642
|
-
rememberPassthroughResponse(restoredResponse);
|
|
4643
4765
|
}
|
|
4644
|
-
|
|
4766
|
+
};
|
|
4645
4767
|
recordAdapterReasoning(logCtx, request);
|
|
4646
4768
|
recordAdapterTier(logCtx, request);
|
|
4647
4769
|
const actualHostKey = upstreamHostHealthKey(
|
|
@@ -5649,7 +5771,7 @@ async function handleResponsesInner(
|
|
|
5649
5771
|
const inspector = createSseInspector({
|
|
5650
5772
|
onTerminal: reportNativeTerminal,
|
|
5651
5773
|
logCtx,
|
|
5652
|
-
onCompletedResponse: rememberPassthroughResponseChecked,
|
|
5774
|
+
onCompletedResponse: rememberPassthroughResponse ? rememberPassthroughResponseChecked : undefined,
|
|
5653
5775
|
onParsedPayload: noteInspectedPayload,
|
|
5654
5776
|
onFirstOutput: options.onFirstOutput,
|
|
5655
5777
|
pinCompletedResponseIdToFirstSeen: githubCopilotRepairEnabled,
|
|
@@ -5679,7 +5801,10 @@ async function handleResponsesInner(
|
|
|
5679
5801
|
reportNativeTerminal("failed", 502);
|
|
5680
5802
|
}
|
|
5681
5803
|
},
|
|
5682
|
-
onClientCancel: () =>
|
|
5804
|
+
onClientCancel: () => {
|
|
5805
|
+
responseCompletionCancelled = true;
|
|
5806
|
+
options.onNativePassthroughCancel?.();
|
|
5807
|
+
},
|
|
5683
5808
|
onDone: () => unregisterTurn(turnAc),
|
|
5684
5809
|
}, {
|
|
5685
5810
|
clientGoneSignal: options.abortSignal,
|
|
@@ -5741,8 +5866,11 @@ async function handleResponsesInner(
|
|
|
5741
5866
|
turnAc.signal,
|
|
5742
5867
|
() => unregisterTurn(turnAc),
|
|
5743
5868
|
logCtx,
|
|
5744
|
-
() =>
|
|
5745
|
-
|
|
5869
|
+
() => {
|
|
5870
|
+
responseCompletionCancelled = true;
|
|
5871
|
+
options.onNativePassthroughCancel?.();
|
|
5872
|
+
},
|
|
5873
|
+
rememberPassthroughResponse ? rememberPassthroughResponseChecked : undefined,
|
|
5746
5874
|
options.onFirstOutput,
|
|
5747
5875
|
inspectionConsumerOptions,
|
|
5748
5876
|
);
|
|
@@ -5752,7 +5880,7 @@ async function handleResponsesInner(
|
|
|
5752
5880
|
logCtx,
|
|
5753
5881
|
turnAc.signal,
|
|
5754
5882
|
() => unregisterTurn(turnAc),
|
|
5755
|
-
rememberPassthroughResponseChecked,
|
|
5883
|
+
rememberPassthroughResponse ? rememberPassthroughResponseChecked : undefined,
|
|
5756
5884
|
options.onFirstOutput,
|
|
5757
5885
|
inspectionConsumerOptions,
|
|
5758
5886
|
);
|
|
@@ -5767,7 +5895,10 @@ async function handleResponsesInner(
|
|
|
5767
5895
|
const clientBody = relaySseWithFailedTail(
|
|
5768
5896
|
rewrittenBody,
|
|
5769
5897
|
upstream,
|
|
5770
|
-
reason =>
|
|
5898
|
+
reason => {
|
|
5899
|
+
responseCompletionCancelled = true;
|
|
5900
|
+
clientGone.abort(reason);
|
|
5901
|
+
},
|
|
5771
5902
|
{ upstreamError: logCtx.upstreamError },
|
|
5772
5903
|
);
|
|
5773
5904
|
return markNativePassthroughSseResponse(new Response(clientBody, {
|
|
@@ -5847,13 +5978,11 @@ async function handleResponsesInner(
|
|
|
5847
5978
|
}
|
|
5848
5979
|
}
|
|
5849
5980
|
commitReasoningReplayServingRoute();
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
} catch { /* non-JSON despite content-type; recording is best-effort */ }
|
|
5856
|
-
}
|
|
5981
|
+
try {
|
|
5982
|
+
rememberPassthroughResponseChecked(
|
|
5983
|
+
JSON.parse(text) as { id?: unknown; output?: unknown; status?: unknown; model?: unknown },
|
|
5984
|
+
);
|
|
5985
|
+
} catch { /* non-JSON despite content-type; recording is best-effort */ }
|
|
5857
5986
|
// #875: the transport-neutral reliability policy forced a bounded JSON
|
|
5858
5987
|
// upstream for a client that asked for SSE. Reframe the completed JSON
|
|
5859
5988
|
// as the canonical terminal SSE sequence (created → output_item.done →
|
|
@@ -6187,10 +6316,12 @@ async function handleResponsesInner(
|
|
|
6187
6316
|
continuationStateForResponse(providerState),
|
|
6188
6317
|
responseStateOptions(adapterNeedsForcedContinuation(adapter.name)),
|
|
6189
6318
|
);
|
|
6319
|
+
notifyResponseComplete(response);
|
|
6190
6320
|
},
|
|
6191
6321
|
});
|
|
6192
6322
|
if (imgResponse.body) {
|
|
6193
6323
|
const imgTurnAc = new AbortController();
|
|
6324
|
+
imgTurnAc.signal.addEventListener("abort", cancelResponseCompletion, { once: true });
|
|
6194
6325
|
return new Response(trackStreamLifetime(imgResponse.body, imgTurnAc, undefined, options.turnAdmissionLease), {
|
|
6195
6326
|
status: imgResponse.status,
|
|
6196
6327
|
headers: imgResponse.headers,
|
|
@@ -6261,12 +6392,16 @@ async function handleResponsesInner(
|
|
|
6261
6392
|
streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
|
|
6262
6393
|
on429: rotateSidecarProviderOn429,
|
|
6263
6394
|
retryOn429Policy: rateLimitRetryPolicyFor(route.provider),
|
|
6264
|
-
onCompletedResponse:
|
|
6395
|
+
onCompletedResponse: response => {
|
|
6396
|
+
commitReasoningReplayServingRoute();
|
|
6397
|
+
notifyResponseComplete(response);
|
|
6398
|
+
},
|
|
6265
6399
|
});
|
|
6266
6400
|
// Register the sidecar stream as an active turn so drainAndShutdown waits for (or aborts)
|
|
6267
6401
|
// in-flight web-search turns instead of skipping them during graceful shutdown.
|
|
6268
6402
|
if (wsResponse.body) {
|
|
6269
6403
|
const wsTurnAc = new AbortController();
|
|
6404
|
+
wsTurnAc.signal.addEventListener("abort", cancelResponseCompletion, { once: true });
|
|
6270
6405
|
return new Response(trackStreamLifetime(wsResponse.body, wsTurnAc, undefined, options.turnAdmissionLease), {
|
|
6271
6406
|
status: wsResponse.status,
|
|
6272
6407
|
headers: wsResponse.headers,
|
|
@@ -6490,6 +6625,7 @@ async function handleResponsesInner(
|
|
|
6490
6625
|
const sseStream = bridgeToResponsesSSE(
|
|
6491
6626
|
guardedSource, parsed._responseModelId ?? parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
|
|
6492
6627
|
() => {
|
|
6628
|
+
cancelResponseCompletion();
|
|
6493
6629
|
runTurnAbort.abort();
|
|
6494
6630
|
queue.close();
|
|
6495
6631
|
}, 2_000,
|
|
@@ -6526,6 +6662,7 @@ async function handleResponsesInner(
|
|
|
6526
6662
|
responseStateOptions(adapterNeedsForcedContinuation(adapter.name)),
|
|
6527
6663
|
);
|
|
6528
6664
|
}
|
|
6665
|
+
notifyResponseComplete(response);
|
|
6529
6666
|
},
|
|
6530
6667
|
},
|
|
6531
6668
|
);
|
|
@@ -6602,6 +6739,7 @@ async function handleResponsesInner(
|
|
|
6602
6739
|
if (adapterResponseReachedServingTerminal(events, json)) {
|
|
6603
6740
|
commitReasoningReplayServingRoute();
|
|
6604
6741
|
}
|
|
6742
|
+
notifyResponseComplete(json);
|
|
6605
6743
|
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
6606
6744
|
}
|
|
6607
6745
|
|
|
@@ -6649,10 +6787,11 @@ async function handleResponsesInner(
|
|
|
6649
6787
|
toolBridgeMaps.toolNsMap,
|
|
6650
6788
|
toolBridgeMaps.freeformToolNames,
|
|
6651
6789
|
toolBridgeMaps.toolSearchToolNames,
|
|
6652
|
-
|
|
6790
|
+
cancelResponseCompletion,
|
|
6653
6791
|
2_000,
|
|
6654
6792
|
{
|
|
6655
6793
|
translatorBudget,
|
|
6794
|
+
onCompletedResponse: notifyResponseComplete,
|
|
6656
6795
|
...(options.forceEmptyResponseId ? { responseId: "" } : {}),
|
|
6657
6796
|
...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
|
|
6658
6797
|
},
|
|
@@ -6673,12 +6812,9 @@ async function handleResponsesInner(
|
|
|
6673
6812
|
},
|
|
6674
6813
|
);
|
|
6675
6814
|
}
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
})),
|
|
6680
|
-
{ headers: { "Content-Type": "application/json" } },
|
|
6681
|
-
);
|
|
6815
|
+
const json = buildResponseJSON(terminalEvents, parsed._responseModelId ?? parsed.modelId, { translatorBudget });
|
|
6816
|
+
notifyResponseComplete(json);
|
|
6817
|
+
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
6682
6818
|
}
|
|
6683
6819
|
// One request-scoped transient-retry budget owner, declared here so BOTH the initial send
|
|
6684
6820
|
// and the later recovery refetches (429, key/account rotation, OAuth replay) share it. A
|
|
@@ -7660,7 +7796,7 @@ async function handleResponsesInner(
|
|
|
7660
7796
|
const { toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
7661
7797
|
const sseStream = bridgeToResponsesSSE(
|
|
7662
7798
|
guardedEventStream, parsed._responseModelId ?? parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
|
|
7663
|
-
() => upstream.abort(), 2_000,
|
|
7799
|
+
() => { cancelResponseCompletion(); upstream.abort(); }, 2_000,
|
|
7664
7800
|
{
|
|
7665
7801
|
translatorBudget,
|
|
7666
7802
|
replayCacheScope: parsed._reasoningReplayScope,
|
|
@@ -7695,6 +7831,7 @@ async function handleResponsesInner(
|
|
|
7695
7831
|
responseStateOptions(activeAdapter.name === "kiro"),
|
|
7696
7832
|
);
|
|
7697
7833
|
}
|
|
7834
|
+
notifyResponseComplete(response);
|
|
7698
7835
|
},
|
|
7699
7836
|
},
|
|
7700
7837
|
);
|
|
@@ -7774,6 +7911,7 @@ async function handleResponsesInner(
|
|
|
7774
7911
|
if (adapterResponseReachedServingTerminal(events, json)) {
|
|
7775
7912
|
commitReasoningReplayServingRoute();
|
|
7776
7913
|
}
|
|
7914
|
+
notifyResponseComplete(json);
|
|
7777
7915
|
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
7778
7916
|
}
|
|
7779
7917
|
|
package/src/types/config.ts
CHANGED
|
@@ -469,8 +469,8 @@ export interface OcxConfig {
|
|
|
469
469
|
*/
|
|
470
470
|
syncCodexSubagentDefaults?: boolean;
|
|
471
471
|
/**
|
|
472
|
-
* Optional reasoning effort
|
|
473
|
-
*
|
|
472
|
+
* Optional reasoning effort reported as advisory metadata in v2 sub-agent guidance.
|
|
473
|
+
* It does not prescribe spawn overrides. Only meaningful while `injectionModel` is set; validated against
|
|
474
474
|
* the Codex ladder (src/reasoning-effort.ts CODEX_REASONING_LEVELS) at the API boundary.
|
|
475
475
|
*/
|
|
476
476
|
injectionEffort?: string;
|
|
@@ -513,7 +513,7 @@ export interface OcxConfig {
|
|
|
513
513
|
streamMode?: "auto" | "legacy-tee" | "eager-relay";
|
|
514
514
|
/**
|
|
515
515
|
* Custom override for the injected v2 multi-agent guidance body (the text inside
|
|
516
|
-
* the <
|
|
516
|
+
* the <opencodex_subagent_guidance> tags). After guidance is enabled and the v2 surface and
|
|
517
517
|
* catalog-state gates pass, a configured injectionModel is sufficient to render it;
|
|
518
518
|
* otherwise an eligible roster or fallback is required. Placeholders: `{{model}}` -> the
|
|
519
519
|
* effective preferred model for the request (a bare native model is account-qualified
|