@coseung2/opencodex 2.8.0-cs.15 → 2.8.0-cs.17
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-BhXIu7c0.js → index-Ch-99jy3.js} +2 -2
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/packages/ocx-notch/README.md +3 -1
- package/src/adapters/base.ts +12 -0
- package/src/adapters/identity.ts +1 -1
- package/src/adapters/kiro-calibration.ts +83 -0
- package/src/adapters/kiro-constants.ts +11 -2
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +12 -3
- package/src/adapters/kiro.ts +300 -78
- package/src/adapters/openai-chat.ts +1 -42
- package/src/adapters/openai-responses.ts +126 -9
- package/src/adapters/xai-schema-analysis.ts +78 -0
- package/src/adapters/xai-tool-schema.ts +274 -0
- package/src/adapters/xai-web-search.ts +138 -0
- package/src/bridge.ts +61 -6
- package/src/cli/observe.ts +18 -3
- package/src/codex/app-server-processes.ts +3 -5
- package/src/codex/catalog/effort.ts +4 -2
- package/src/codex/catalog/metadata.ts +42 -9
- package/src/codex/catalog/parsing.ts +17 -2
- package/src/codex/catalog/provider-fetch.ts +9 -3
- package/src/codex/catalog/sync.ts +11 -5
- package/src/codex/data/upstream-models.json +169 -0
- package/src/grok/inject.ts +1 -1
- package/src/lib/errors.ts +18 -0
- package/src/lib/token-estimate.ts +42 -38
- package/src/lib/translator-budget.ts +34 -0
- package/src/oauth/index.ts +10 -4
- package/src/oauth/kiro.ts +71 -6
- package/src/oauth/store.ts +3 -1
- package/src/oauth/types.ts +4 -0
- package/src/providers/derive.ts +7 -5
- package/src/providers/opencode-go-transport.ts +59 -0
- package/src/providers/quota.ts +68 -60
- package/src/providers/registry.ts +41 -10
- package/src/providers/xai-transport.ts +10 -0
- package/src/responses/compaction.ts +8 -1
- package/src/responses/namespace-aliases.ts +56 -0
- package/src/responses/parser.ts +12 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/snapshot-policy.ts +108 -0
- package/src/responses/state.ts +23 -10
- package/src/responses/turn-termination.ts +108 -0
- package/src/responses/xai-custom-tool-compat.ts +237 -0
- package/src/server/grok-responses-snapshot-repair.ts +338 -0
- package/src/server/index.ts +2 -1
- package/src/server/relay-eager.ts +1 -0
- package/src/server/request-log-conversation.ts +8 -0
- package/src/server/request-log.ts +5 -4
- package/src/server/responses/core.ts +233 -16
- package/src/server/responses-image-gen-repair.ts +2 -2
- package/src/server/sse-payload-rewrite.ts +20 -3
- package/src/types.ts +10 -1
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +7 -0
- package/src/usage/log.ts +1 -2
- package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
import { parseRequest } from "../../responses/parser";
|
|
10
10
|
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
|
|
11
11
|
import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
|
|
12
|
+
import { XaiToolSchemaCompatibilityError } from "../../adapters/xai-tool-schema";
|
|
12
13
|
import {
|
|
13
14
|
expandPreviousResponseInput,
|
|
14
15
|
previousResponseProviderState,
|
|
15
16
|
previousResponseReplayFailure,
|
|
16
17
|
rememberResponseState,
|
|
17
18
|
} from "../../responses/state";
|
|
19
|
+
import { bindTurnTerminationScope, rememberDeliveredFinalAnswer } from "../../responses/turn-termination";
|
|
18
20
|
import { routeModel, type RouteResult } from "../../router";
|
|
19
21
|
import {
|
|
20
22
|
advanceComboAfterFailure,
|
|
@@ -100,11 +102,17 @@ import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve"
|
|
|
100
102
|
import type { InboundWire } from "../../providers/registry";
|
|
101
103
|
import { hasKeyPoolFailover, rotateProviderTransportOn429 } from "../../providers/key-failover";
|
|
102
104
|
import { shouldAttemptImageTierRetry } from "../image-retry";
|
|
103
|
-
import { resolveProviderTransport } from "../../providers/xai-transport";
|
|
105
|
+
import { isXaiResponsesDestination, resolveProviderTransport } from "../../providers/xai-transport";
|
|
106
|
+
import { isOpenCodeMuseResponses, resolveOpenCodeGoTransport } from "../../providers/opencode-go-transport";
|
|
107
|
+
import { declaredNamespaceAliases } from "../../responses/namespace-aliases";
|
|
104
108
|
import type { WsData } from "../ws-bridge";
|
|
105
109
|
import { trackActiveTurnLease, trackStreamLifetime } from "../lifecycle";
|
|
106
110
|
import { redactSecretString } from "../../lib/redact";
|
|
107
111
|
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
112
|
+
import {
|
|
113
|
+
isRateLimitOrQuotaFailureMessage,
|
|
114
|
+
upstreamErrorMessageFromPayload,
|
|
115
|
+
} from "../../lib/errors";
|
|
108
116
|
import type { AdmissionLease } from "../../lib/admission";
|
|
109
117
|
import { supportedLadderFor } from "../effort-policy";
|
|
110
118
|
import { isThreadSpawnRequest } from "../effort-policy";
|
|
@@ -130,6 +138,7 @@ import {
|
|
|
130
138
|
import {
|
|
131
139
|
conversationIdFromResponsesRequest,
|
|
132
140
|
normalizeLogConversationId,
|
|
141
|
+
sessionLaneIdFromRequest,
|
|
133
142
|
sessionIdHeaderFromRequest,
|
|
134
143
|
} from "../request-log-conversation";
|
|
135
144
|
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
@@ -152,12 +161,18 @@ import {
|
|
|
152
161
|
createResponsesItemIdPayloadRewrite,
|
|
153
162
|
hasResponsesItemIdRepair,
|
|
154
163
|
} from "../responses-item-id-repair";
|
|
164
|
+
import { createGrokResponsesSparseTerminalPayloadRewrite } from "../grok-responses-snapshot-repair";
|
|
155
165
|
import {
|
|
156
166
|
createImageGenCallRestoreRewrite,
|
|
157
167
|
imageGenToolCallAliases,
|
|
158
168
|
restoreImageGenCallsInJson,
|
|
159
169
|
} from "../responses-image-gen-repair";
|
|
160
170
|
import { composeSsePayloadRewrites, relaySseWithPayloadRewrite } from "../sse-payload-rewrite";
|
|
171
|
+
import {
|
|
172
|
+
createXaiCustomToolPayloadRewrite,
|
|
173
|
+
restoreXaiCustomCallsInJson,
|
|
174
|
+
xaiResponsesCustomToolNames,
|
|
175
|
+
} from "../../responses/xai-custom-tool-compat";
|
|
161
176
|
import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
|
|
162
177
|
|
|
163
178
|
import { buildToolBridgeMaps, collabSurface, injectDeveloperMessage, multiAgentGuidanceText } from "./collaboration";
|
|
@@ -252,8 +267,40 @@ async function shouldRetryCodexPoolAccountModel400(
|
|
|
252
267
|
}
|
|
253
268
|
|
|
254
269
|
/** Pre-stream quota/billing rejections that warrant one alternate-account attempt (#584). */
|
|
255
|
-
function
|
|
256
|
-
|
|
270
|
+
function codexQuotaFailureMessage(body: string): string | undefined {
|
|
271
|
+
try {
|
|
272
|
+
const payload = JSON.parse(body) as unknown;
|
|
273
|
+
const canonical = upstreamErrorMessageFromPayload(payload);
|
|
274
|
+
if (canonical !== undefined) return canonical;
|
|
275
|
+
if (typeof payload === "string") return payload;
|
|
276
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return undefined;
|
|
277
|
+
const record = payload as Record<string, unknown>;
|
|
278
|
+
if (typeof record.message === "string") return record.message;
|
|
279
|
+
return typeof record.error === "string" ? record.error : undefined;
|
|
280
|
+
} catch {
|
|
281
|
+
// Plain-text gateways remain supported. Valid JSON is inspected only at recognized
|
|
282
|
+
// message fields so echoed request content elsewhere cannot trigger account cooldown.
|
|
283
|
+
return body;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function shouldRetryCodexPoolAccountQuota(
|
|
288
|
+
response: Response,
|
|
289
|
+
signal?: AbortSignal,
|
|
290
|
+
): Promise<boolean> {
|
|
291
|
+
if (response.status === 402 || response.status === 429) return true;
|
|
292
|
+
if (response.status < 500 || response.status >= 600) return false;
|
|
293
|
+
try {
|
|
294
|
+
// Reject malformed UTF-8 instead of matching quota words around replacement characters.
|
|
295
|
+
const body = await readBoundedResponseBody(response.clone(), { signal, fatalUtf8: true });
|
|
296
|
+
const message = body.displaySafe && !body.truncated
|
|
297
|
+
? codexQuotaFailureMessage(body.text)
|
|
298
|
+
: undefined;
|
|
299
|
+
return message !== undefined
|
|
300
|
+
&& isRateLimitOrQuotaFailureMessage(message);
|
|
301
|
+
} catch {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
257
304
|
}
|
|
258
305
|
|
|
259
306
|
interface CodexPoolAccountRetryArgs {
|
|
@@ -351,6 +398,19 @@ async function retryCodexPoolOnAlternateAccount(
|
|
|
351
398
|
) throw error;
|
|
352
399
|
}
|
|
353
400
|
if (retryAuthCtx?.kind !== "pool" && retryAuthCtx?.kind !== "main-pool") {
|
|
401
|
+
// A body-confirmed quota response may arrive under HTTP 5xx. Without an alternate,
|
|
402
|
+
// the ordinary terminal recorder sees only that wire status and would misclassify it
|
|
403
|
+
// as transient, leaving the exhausted account immediately selectable next turn.
|
|
404
|
+
if (outcomeStatus !== firstResponse.status && (outcomeStatus === 429 || outcomeStatus === 402)) {
|
|
405
|
+
recordCodexUpstreamOutcome(config, firstAuthCtx.accountId, outcomeStatus, {
|
|
406
|
+
...codexQuotaOutcomeMeta(firstResponse),
|
|
407
|
+
threadId: req.headers.get("x-codex-parent-thread-id"),
|
|
408
|
+
modelId: route.modelId,
|
|
409
|
+
probeLeaseId: codexProbeLeaseId(firstAuthCtx),
|
|
410
|
+
probeQuotaScope: codexProbeQuotaScope(firstAuthCtx),
|
|
411
|
+
writerGeneration: firstAuthCtx.writerGeneration,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
354
414
|
return { kind: "no-alternate" };
|
|
355
415
|
}
|
|
356
416
|
|
|
@@ -826,6 +886,7 @@ async function applyFinalRouteRequestNormalization(args: {
|
|
|
826
886
|
}
|
|
827
887
|
// Settle the wire once so logging, fast-mode, auth, and sidecars read the adapter
|
|
828
888
|
// this request will actually use (#404).
|
|
889
|
+
route.provider = resolveOpenCodeGoTransport(route.provider, sessionLaneIdFromRequest(req.headers));
|
|
829
890
|
route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
|
|
830
891
|
logCtx.model = route.modelId;
|
|
831
892
|
logCtx.provider = route.providerName;
|
|
@@ -837,8 +898,11 @@ async function applyFinalRouteRequestNormalization(args: {
|
|
|
837
898
|
// Virtual model rewriting: Pro aliases → base model + reasoning.mode="pro".
|
|
838
899
|
applyOpenAiVirtualModel(parsed, route, logCtx);
|
|
839
900
|
|
|
840
|
-
// Fast mode override for OpenAI-
|
|
841
|
-
|
|
901
|
+
// Fast mode override for OpenAI-shaped routes. xAI OAuth's subscription Responses lane has no
|
|
902
|
+
// caller-owned Priority/Fast contract, so switching Grok to its native wire must not make a
|
|
903
|
+
// global OpenAI fastMode leak service_tier upstream.
|
|
904
|
+
const callerFastTierAllowed = !(route.providerName === "xai" && route.provider.authMode === "oauth");
|
|
905
|
+
if (config.fastMode !== undefined && route.provider.adapter === "openai-responses" && callerFastTierAllowed) {
|
|
842
906
|
const tier = config.fastMode ? "priority" : undefined;
|
|
843
907
|
if (parsed._rawBody && typeof parsed._rawBody === "object") {
|
|
844
908
|
if (tier) (parsed._rawBody as Record<string, unknown>).service_tier = tier;
|
|
@@ -1278,6 +1342,7 @@ async function handleResponsesInner(
|
|
|
1278
1342
|
cursorConversationId: parsed._cursorConversationId,
|
|
1279
1343
|
});
|
|
1280
1344
|
}
|
|
1345
|
+
bindTurnTerminationScope(parsed, logCtx.conversationId);
|
|
1281
1346
|
logCtx.requestedModel = parsed.modelId;
|
|
1282
1347
|
logCtx.requestedEffort = parsed.options.reasoning;
|
|
1283
1348
|
logCtx.requestedServiceTier = parsed.options.serviceTier;
|
|
@@ -1584,9 +1649,6 @@ async function handleResponsesInner(
|
|
|
1584
1649
|
}
|
|
1585
1650
|
|
|
1586
1651
|
if ("passthrough" in adapter && adapter.passthrough && !routedCompaction) {
|
|
1587
|
-
const imageGenCallAliases = route.provider.authMode === "forward"
|
|
1588
|
-
? new Map<string, { namespace: string; name: string }>()
|
|
1589
|
-
: imageGenToolCallAliases(toolBridgeMaps.toolNsMap, parsed._rawBody, translatorBudget);
|
|
1590
1652
|
// Local continuation cache for the ChatGPT passthrough. Codex WS turns chain with
|
|
1591
1653
|
// previous_response_id, ocx converts them to internal HTTP requests, and the ChatGPT Codex
|
|
1592
1654
|
// REST backend rejects the parameter — the adapter strips it in forward mode, so the ONLY
|
|
@@ -1608,7 +1670,27 @@ async function handleResponsesInner(
|
|
|
1608
1670
|
+ `(model ${parsed.modelId}); forwarding without it — earlier turns may be missing from this request`,
|
|
1609
1671
|
);
|
|
1610
1672
|
}
|
|
1611
|
-
let request
|
|
1673
|
+
let request: Awaited<ReturnType<typeof adapter.buildRequest>>;
|
|
1674
|
+
try {
|
|
1675
|
+
request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
releaseCodexAuthContextProbeLease(authCtx);
|
|
1678
|
+
if (error instanceof XaiToolSchemaCompatibilityError) {
|
|
1679
|
+
return formatErrorResponse(400, "invalid_request_error", redactSecretString(error.message));
|
|
1680
|
+
}
|
|
1681
|
+
throw error;
|
|
1682
|
+
}
|
|
1683
|
+
const xaiCustomToolNames = isXaiResponsesDestination(route.provider)
|
|
1684
|
+
? xaiResponsesCustomToolNames(parsed._rawBody)
|
|
1685
|
+
: new Set<string>();
|
|
1686
|
+
// Reuse the existing client-only namespace rewrite and its bounded SSE relay. The
|
|
1687
|
+
// inspection/cache branch retains raw names for continuation replay. Resolve all
|
|
1688
|
+
// declaration ownership before admitting a Muse dotted alias.
|
|
1689
|
+
const imageGenCallAliases = route.provider.authMode === "forward"
|
|
1690
|
+
? new Map<string, { namespace: string; name: string }>()
|
|
1691
|
+
: isOpenCodeMuseResponses(parsed.modelId, request.url)
|
|
1692
|
+
? declaredNamespaceAliases(parsed.context.tools ?? [], translatorBudget)
|
|
1693
|
+
: imageGenToolCallAliases(toolBridgeMaps.toolNsMap, parsed._rawBody, translatorBudget);
|
|
1612
1694
|
recordAdapterReasoning(logCtx, request);
|
|
1613
1695
|
const passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
|
|
1614
1696
|
? request.usageLog.inputTokens
|
|
@@ -1663,6 +1745,66 @@ async function handleResponsesInner(
|
|
|
1663
1745
|
request.releaseBodyObservation?.();
|
|
1664
1746
|
}
|
|
1665
1747
|
|
|
1748
|
+
// Native Responses providers return from this passthrough branch before the generic recovery
|
|
1749
|
+
// loop below. Preserve the same OAuth contract here: one pre-stream 401 forces one credential
|
|
1750
|
+
// refresh and one rebuilt replay. This is required for xAI now that Grok 4.6/4.5 subscription
|
|
1751
|
+
// traffic uses the provider's native Responses wire.
|
|
1752
|
+
if (upstreamResponse.status === 401 && isOAuth401ReplayProvider && sentOAuthSnapshot) {
|
|
1753
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed */ }
|
|
1754
|
+
let refreshed: OAuthAccessSnapshot;
|
|
1755
|
+
try {
|
|
1756
|
+
refreshed = await forceRefreshOAuthAccessSnapshot(sentOAuthSnapshot);
|
|
1757
|
+
} catch (err) {
|
|
1758
|
+
upstream.abort();
|
|
1759
|
+
releaseCodexAuthContextProbeLease(authCtx);
|
|
1760
|
+
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
1761
|
+
}
|
|
1762
|
+
sentOAuthSnapshot = refreshed;
|
|
1763
|
+
if (route.providerName === "kiro") parsed._kiroAuthContext = { ...(refreshed.kiro ?? {}) };
|
|
1764
|
+
const refreshedProvider = resolveProviderTransport(
|
|
1765
|
+
route.providerName,
|
|
1766
|
+
{ ...route.provider, apiKey: refreshed.accessToken },
|
|
1767
|
+
parsed.options.promptCacheKey,
|
|
1768
|
+
route.providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(route.providerName) : undefined,
|
|
1769
|
+
);
|
|
1770
|
+
route.provider = refreshedProvider;
|
|
1771
|
+
const refreshedAdapter = resolveAdapter(
|
|
1772
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider, inboundWire),
|
|
1773
|
+
config.cacheRetention,
|
|
1774
|
+
);
|
|
1775
|
+
if (!("passthrough" in refreshedAdapter) || !refreshedAdapter.passthrough) {
|
|
1776
|
+
upstream.abort();
|
|
1777
|
+
return formatErrorResponse(502, "upstream_error", "OAuth refresh changed the provider wire unexpectedly");
|
|
1778
|
+
}
|
|
1779
|
+
logCtx.providerAdapter = refreshedAdapter.name;
|
|
1780
|
+
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, refreshedAdapter.name, logCtx.accountLogLabel);
|
|
1781
|
+
try {
|
|
1782
|
+
request = await refreshedAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
|
|
1783
|
+
recordAdapterReasoning(logCtx, request);
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
upstream.abort();
|
|
1786
|
+
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
1787
|
+
return formatErrorResponse(400, "invalid_request_error", redactSecretString(err instanceof Error ? err.message : String(err)));
|
|
1788
|
+
}
|
|
1789
|
+
try {
|
|
1790
|
+
upstreamResponse = await fetchWithTransientRetry(
|
|
1791
|
+
recovery => {
|
|
1792
|
+
noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, recovery ?? "oauth-401");
|
|
1793
|
+
return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
|
|
1794
|
+
method: request.method,
|
|
1795
|
+
headers: request.headers,
|
|
1796
|
+
body: request.body,
|
|
1797
|
+
}, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider, options.codexWsRuntimeIdentity));
|
|
1798
|
+
},
|
|
1799
|
+
{ abortSignal: upstream.signal, label: safeHostLabel(request.url) },
|
|
1800
|
+
);
|
|
1801
|
+
} catch (err) {
|
|
1802
|
+
return transportFailureResponse(err);
|
|
1803
|
+
} finally {
|
|
1804
|
+
request.releaseBodyObservation?.();
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1666
1808
|
if (usesCodexForwardPoolAuth(authCtx, route.provider) && !authCtx.fixedAccount) {
|
|
1667
1809
|
let poolRetryOutcome: number | undefined;
|
|
1668
1810
|
if (await shouldRetryCodexPoolAccountModel400(
|
|
@@ -1671,9 +1813,14 @@ async function handleResponsesInner(
|
|
|
1671
1813
|
options.abortSignal,
|
|
1672
1814
|
)) {
|
|
1673
1815
|
poolRetryOutcome = 400;
|
|
1674
|
-
} else if (shouldRetryCodexPoolAccountQuota(
|
|
1816
|
+
} else if (!authCtx.fixedAccount && await shouldRetryCodexPoolAccountQuota(
|
|
1817
|
+
upstreamResponse,
|
|
1818
|
+
options.abortSignal,
|
|
1819
|
+
)) {
|
|
1675
1820
|
// Pre-stream only: once SSE has begun, mid-stream quota stays terminal.
|
|
1676
|
-
|
|
1821
|
+
// ChatGPT sometimes wraps quota exhaustion in a generic 5xx. Normalize only
|
|
1822
|
+
// body-confirmed cases to quota evidence so cooldown and rotation both apply.
|
|
1823
|
+
poolRetryOutcome = upstreamResponse.status >= 500 ? 429 : upstreamResponse.status;
|
|
1677
1824
|
}
|
|
1678
1825
|
|
|
1679
1826
|
if (poolRetryOutcome !== undefined) {
|
|
@@ -1808,10 +1955,20 @@ async function handleResponsesInner(
|
|
|
1808
1955
|
// The bundled known-bad runtime remains on tee by default on both platforms.
|
|
1809
1956
|
if (isEventStream && upstreamResponse.body) {
|
|
1810
1957
|
const repairConfig = route.provider.responsesItemIdRepair;
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1958
|
+
const xaiCustomToolRewrite = createXaiCustomToolPayloadRewrite(xaiCustomToolNames);
|
|
1959
|
+
const grokSparseTerminalRewrite = logCtx.surface === "grok"
|
|
1960
|
+
? createGrokResponsesSparseTerminalPayloadRewrite(translatorBudget)
|
|
1961
|
+
: undefined;
|
|
1962
|
+
const needsClientRewrite = imageGenCallAliases.size > 0
|
|
1963
|
+
|| hasResponsesItemIdRepair(repairConfig)
|
|
1964
|
+
|| xaiCustomToolRewrite !== undefined
|
|
1965
|
+
|| grokSparseTerminalRewrite !== undefined;
|
|
1966
|
+
// Compose opt-in payload rewrites into one parse/stringify pass. Provider-shape restoration
|
|
1967
|
+
// runs before generic item-id repair so the client sees the correct custom-tool identity.
|
|
1813
1968
|
const payloadRewrites = [
|
|
1814
1969
|
createImageGenCallRestoreRewrite(imageGenCallAliases),
|
|
1970
|
+
xaiCustomToolRewrite,
|
|
1971
|
+
grokSparseTerminalRewrite,
|
|
1815
1972
|
hasResponsesItemIdRepair(repairConfig)
|
|
1816
1973
|
? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
|
|
1817
1974
|
: undefined,
|
|
@@ -1974,7 +2131,8 @@ async function handleResponsesInner(
|
|
|
1974
2131
|
rememberPassthroughResponse(JSON.parse(text) as { id?: unknown; output?: unknown; status?: unknown });
|
|
1975
2132
|
} catch { /* non-JSON despite content-type; recording is best-effort */ }
|
|
1976
2133
|
}
|
|
1977
|
-
|
|
2134
|
+
const restoredCustomTools = restoreXaiCustomCallsInJson(text, xaiCustomToolNames);
|
|
2135
|
+
return new Response(restoreImageGenCallsInJson(restoredCustomTools, imageGenCallAliases), {
|
|
1978
2136
|
status: upstreamResponse.status,
|
|
1979
2137
|
statusText: upstreamResponse.statusText,
|
|
1980
2138
|
headers,
|
|
@@ -2333,6 +2491,62 @@ async function handleResponsesInner(
|
|
|
2333
2491
|
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
2334
2492
|
}
|
|
2335
2493
|
|
|
2494
|
+
// Some adapter inputs already contain the completed answer and require no inference at all.
|
|
2495
|
+
// Kiro uses this for replayed history ending in a delivered final answer. Keep this OUTSIDE the
|
|
2496
|
+
// ordinary empty-completion path: an outputless done is intentional here and must never trigger
|
|
2497
|
+
// another request for the closed task.
|
|
2498
|
+
const localTerminal = adapter.localTerminal?.(parsed);
|
|
2499
|
+
if (localTerminal) {
|
|
2500
|
+
const terminalEvents: AdapterEvent[] = [{
|
|
2501
|
+
type: "done",
|
|
2502
|
+
endTurn: true,
|
|
2503
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
2504
|
+
}];
|
|
2505
|
+
const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
2506
|
+
if (parsed.stream) {
|
|
2507
|
+
const localSse = bridgeToResponsesSSE(
|
|
2508
|
+
(async function* () { yield* terminalEvents; })(),
|
|
2509
|
+
parsed.modelId,
|
|
2510
|
+
toolNsMap,
|
|
2511
|
+
freeformToolNames,
|
|
2512
|
+
toolSearchToolNames,
|
|
2513
|
+
undefined,
|
|
2514
|
+
2_000,
|
|
2515
|
+
{
|
|
2516
|
+
translatorBudget,
|
|
2517
|
+
...(options.forceEmptyResponseId ? { responseId: "" } : {}),
|
|
2518
|
+
...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
|
|
2519
|
+
onUsage: usage => {
|
|
2520
|
+
logCtx.usageFromBridge = true;
|
|
2521
|
+
if (usage) {
|
|
2522
|
+
logCtx.usage = usage;
|
|
2523
|
+
if (logCtx.activeAttempt) logCtx.activeAttempt.usage = usage;
|
|
2524
|
+
}
|
|
2525
|
+
},
|
|
2526
|
+
},
|
|
2527
|
+
);
|
|
2528
|
+
const localTurnAc = new AbortController();
|
|
2529
|
+
const tracked = trackStreamLifetime(localSse, localTurnAc, undefined, options.turnAdmissionLease);
|
|
2530
|
+
return new Response(tracked, {
|
|
2531
|
+
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" },
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
const json = buildResponseJSON(terminalEvents, parsed.modelId, {
|
|
2535
|
+
translatorBudget,
|
|
2536
|
+
toolNsMap,
|
|
2537
|
+
freeformToolNames,
|
|
2538
|
+
toolSearchToolNames,
|
|
2539
|
+
onUsage: usage => {
|
|
2540
|
+
logCtx.usageFromBridge = true;
|
|
2541
|
+
if (usage) {
|
|
2542
|
+
logCtx.usage = usage;
|
|
2543
|
+
if (logCtx.activeAttempt) logCtx.activeAttempt.usage = usage;
|
|
2544
|
+
}
|
|
2545
|
+
},
|
|
2546
|
+
});
|
|
2547
|
+
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2336
2550
|
const upstream = new AbortController();
|
|
2337
2551
|
const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal);
|
|
2338
2552
|
const connectMs = config.connectTimeoutMs ?? 200_000;
|
|
@@ -2821,13 +3035,15 @@ async function handleResponsesInner(
|
|
|
2821
3035
|
// PRE-compaction history, and a later previous_response_id expansion would rehydrate the
|
|
2822
3036
|
// giant stale chain Codex just replaced.
|
|
2823
3037
|
...(routedCompaction ? {} : {
|
|
2824
|
-
onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) =>
|
|
3038
|
+
onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => {
|
|
3039
|
+
if (activeAdapter.name === "kiro") rememberDeliveredFinalAnswer(parsed, response);
|
|
2825
3040
|
rememberResponseState(
|
|
2826
3041
|
parsed._rawBody,
|
|
2827
3042
|
response,
|
|
2828
3043
|
continuationStateForResponse(providerState),
|
|
2829
3044
|
activeAdapter.name === "kiro" ? { force: true } : undefined,
|
|
2830
|
-
)
|
|
3045
|
+
);
|
|
3046
|
+
},
|
|
2831
3047
|
}),
|
|
2832
3048
|
},
|
|
2833
3049
|
);
|
|
@@ -2887,6 +3103,7 @@ async function handleResponsesInner(
|
|
|
2887
3103
|
});
|
|
2888
3104
|
// See the streaming branch: compaction turns skip the continuation cache.
|
|
2889
3105
|
if (!routedCompaction) {
|
|
3106
|
+
if (activeAdapter.name === "kiro") rememberDeliveredFinalAnswer(parsed, json);
|
|
2890
3107
|
rememberResponseState(
|
|
2891
3108
|
parsed._rawBody,
|
|
2892
3109
|
json,
|
|
@@ -60,7 +60,7 @@ export function imageGenToolCallAliases(
|
|
|
60
60
|
return aliases;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
63
|
+
/** Restore exact client tool identities. Callers own destination-scoped alias admission. */
|
|
64
64
|
function restoreImageGenCalls(
|
|
65
65
|
value: unknown,
|
|
66
66
|
aliases: ReadonlyMap<string, NamespacedTool>,
|
|
@@ -86,7 +86,7 @@ function restoreImageGenCalls(
|
|
|
86
86
|
|
|
87
87
|
const name = typeof value.name === "string" ? value.name : undefined;
|
|
88
88
|
const target = value.type === "function_call" && name ? aliases.get(name) : undefined;
|
|
89
|
-
if (target) {
|
|
89
|
+
if (target && (value.namespace === undefined || value.namespace === target.namespace)) {
|
|
90
90
|
restored.name = target.name;
|
|
91
91
|
restored.namespace = target.namespace;
|
|
92
92
|
changed = true;
|
|
@@ -7,7 +7,7 @@ import type { TranslatorBudget } from "../lib/translator-budget";
|
|
|
7
7
|
* parse/stringify pass so a tee'd stream is not re-framed twice per event.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export type SsePayloadRewrite = (payload: string) => string;
|
|
10
|
+
export type SsePayloadRewrite = ((payload: string) => string) & { dispose?: () => void };
|
|
11
11
|
|
|
12
12
|
/** Split one complete SSE event block while retaining its original blank-line delimiter. */
|
|
13
13
|
export function nextSseBlock(buffer: string): { block: string; delimiter: string; rest: string } | null {
|
|
@@ -54,11 +54,19 @@ export function replaceSseDataPayload(block: string, payload: string): string {
|
|
|
54
54
|
export function composeSsePayloadRewrites(...rewrites: SsePayloadRewrite[]): SsePayloadRewrite {
|
|
55
55
|
if (rewrites.length === 0) return (payload) => payload;
|
|
56
56
|
if (rewrites.length === 1) return rewrites[0]!;
|
|
57
|
-
|
|
57
|
+
const composed = ((payload: string) => {
|
|
58
58
|
let next = payload;
|
|
59
59
|
for (const rewrite of rewrites) next = rewrite(next);
|
|
60
60
|
return next;
|
|
61
|
-
};
|
|
61
|
+
}) as SsePayloadRewrite;
|
|
62
|
+
if (rewrites.some(rewrite => rewrite.dispose !== undefined)) {
|
|
63
|
+
composed.dispose = () => {
|
|
64
|
+
for (const rewrite of rewrites) {
|
|
65
|
+
try { rewrite.dispose?.(); } catch { /* rewrite teardown is best-effort */ }
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return composed;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
/**
|
|
@@ -122,6 +130,12 @@ export function relaySseWithPayloadRewrite(
|
|
|
122
130
|
buffer = "";
|
|
123
131
|
bufferBytes = 0;
|
|
124
132
|
};
|
|
133
|
+
let rewriteDisposed = false;
|
|
134
|
+
const disposeRewrite = (): void => {
|
|
135
|
+
if (rewriteDisposed) return;
|
|
136
|
+
rewriteDisposed = true;
|
|
137
|
+
try { rewrite.dispose?.(); } catch { /* stream teardown must not throw */ }
|
|
138
|
+
};
|
|
125
139
|
|
|
126
140
|
const emitProcessedBlocks = (
|
|
127
141
|
controller: ReadableStreamDefaultController<Uint8Array>,
|
|
@@ -156,6 +170,7 @@ export function relaySseWithPayloadRewrite(
|
|
|
156
170
|
appendBuffer(decoder.decode());
|
|
157
171
|
emitProcessedBlocks(controller, true);
|
|
158
172
|
releaseBuffer();
|
|
173
|
+
disposeRewrite();
|
|
159
174
|
controller.close();
|
|
160
175
|
return;
|
|
161
176
|
}
|
|
@@ -163,12 +178,14 @@ export function relaySseWithPayloadRewrite(
|
|
|
163
178
|
emitProcessedBlocks(controller);
|
|
164
179
|
} catch (error) {
|
|
165
180
|
releaseBuffer();
|
|
181
|
+
disposeRewrite();
|
|
166
182
|
try { await reader.cancel(error); } catch { /* already closed */ }
|
|
167
183
|
controller.error(error);
|
|
168
184
|
}
|
|
169
185
|
},
|
|
170
186
|
cancel(reason) {
|
|
171
187
|
releaseBuffer();
|
|
188
|
+
disposeRewrite();
|
|
172
189
|
reader.cancel(reason).catch(() => {});
|
|
173
190
|
},
|
|
174
191
|
});
|
package/src/types.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface OcxParsedRequest {
|
|
|
28
28
|
*/
|
|
29
29
|
_cursorIsolateConversation?: boolean;
|
|
30
30
|
/** Account-scoped, non-secret Kiro request metadata selected with the OAuth access token. */
|
|
31
|
-
_kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion">;
|
|
31
|
+
_kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion" | "authType">;
|
|
32
32
|
/** Provider-private continuation metadata resolved from the Responses previous_response_id chain. */
|
|
33
33
|
_providerContinuation?: OcxProviderContinuationState;
|
|
34
34
|
/**
|
|
@@ -85,6 +85,12 @@ export interface OcxAssistantMessage {
|
|
|
85
85
|
phase?: OcxMessagePhase;
|
|
86
86
|
model?: string;
|
|
87
87
|
timestamp: number;
|
|
88
|
+
/**
|
|
89
|
+
* Kiro `reasoningContent.redactedContent` for THIS assistant turn — an opaque encrypted blob
|
|
90
|
+
* Kiro replays to preserve model reasoning across turns. Provider-specific and unrenderable, so
|
|
91
|
+
* it rides the message rather than a content part: any other adapter simply ignores it.
|
|
92
|
+
*/
|
|
93
|
+
kiroRedactedReasoning?: string;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
export interface OcxDeveloperMessage {
|
|
@@ -254,6 +260,9 @@ export type AdapterEvent =
|
|
|
254
260
|
// opaque redacted_thinking blocks. Both must be replayed verbatim or tool-use turns 400.
|
|
255
261
|
| { type: "thinking_signature"; signature: string }
|
|
256
262
|
| { type: "redacted_thinking"; data: string }
|
|
263
|
+
// Kiro reasoning round-trip: the encrypted `redactedContent` blob for the CURRENT assistant turn.
|
|
264
|
+
// Never rendered — it only rides the reasoning item's envelope so the next request can replay it.
|
|
265
|
+
| { type: "kiro_redacted_reasoning"; data: string }
|
|
257
266
|
| { type: "reasoning_raw_delta"; text: string }
|
|
258
267
|
| { type: "tool_call_start"; id: string; name: string }
|
|
259
268
|
| { type: "tool_call_delta"; arguments: string }
|
package/src/usage/cost.ts
CHANGED
|
Binary file
|
|
@@ -29,6 +29,9 @@ export interface ExpectedPriceOverlay {
|
|
|
29
29
|
status: ExpectedPriceStatus;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const GPT6_ASTRA: Cost4 = { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 };
|
|
33
|
+
const ASTRA_API_PRICING = "https://developers.openai.com/api/docs/models/gpt-6-astra";
|
|
34
|
+
|
|
32
35
|
const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
|
|
33
36
|
const GEMINI_36_FLASH: Cost4 = { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 };
|
|
34
37
|
const MINIMAX_M21_HIGHSPEED: Cost4 = { input: 0.6, output: 2.4, cacheRead: 0.03, cacheWrite: 0.375 };
|
|
@@ -59,6 +62,9 @@ const KIMI_PRICING = "https://platform.kimi.ai/docs/pricing (official table; cac
|
|
|
59
62
|
const QWEN38_ROUTEWAY_PRICING = "https://routeway.ai/models/qwen3.8-max-preview (temporary reseller proxy; NOT Alibaba Token Plan billing; cacheWrite unpublished -> 0)";
|
|
60
63
|
|
|
61
64
|
export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
65
|
+
{ provider: "openai-apikey", modelId: "gpt-6-astra", cost4: GPT6_ASTRA, source: ASTRA_API_PRICING, verifiedAt: "2026-09-07", status: "verified" },
|
|
66
|
+
// Dollar display is an API-reference comparison, not a conversion of subscription credits.
|
|
67
|
+
{ provider: "openai", modelId: "gpt-6-astra", cost4: GPT6_ASTRA, source: `API-reference comparison estimate: ${ASTRA_API_PRICING}`, verifiedAt: "2026-09-07", status: "verified-derived" },
|
|
62
68
|
// claude-opus-5 is exposed by three providers but absent from the jawcode bundle, so
|
|
63
69
|
// cost resolution returned null and the Logs `~$` column rendered an em dash. The
|
|
64
70
|
// model-level vendor fallback only searches jawcode metadata, never overlays, so one
|
|
@@ -150,6 +156,7 @@ export function findExpectedPriceOverlay(
|
|
|
150
156
|
* Models not listed here fall back to 1× (no multiplier).
|
|
151
157
|
*/
|
|
152
158
|
export const PRIORITY_MULTIPLIERS: Readonly<Record<string, number>> = {
|
|
159
|
+
"gpt-6-astra": 2,
|
|
153
160
|
"gpt-5.6-sol": 2,
|
|
154
161
|
"gpt-5.6-terra": 1.6,
|
|
155
162
|
"gpt-5.6-luna": 0.4,
|
package/src/usage/log.ts
CHANGED
|
@@ -147,8 +147,7 @@ export function usageTotalTokens(usage: OcxUsage | undefined): number | undefine
|
|
|
147
147
|
* fallback for paths that only know the configured provider name (e.g. "cursor-mykey").
|
|
148
148
|
*/
|
|
149
149
|
function isEstimatedUsageProvider(providerOrAdapter: string): boolean {
|
|
150
|
-
return providerOrAdapter === "
|
|
151
|
-
|| providerOrAdapter === "cursor" || providerOrAdapter.startsWith("cursor-");
|
|
150
|
+
return providerOrAdapter === "cursor" || providerOrAdapter.startsWith("cursor-");
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
export function usageForFinalLog(provider: string, usage: OcxUsage | undefined): OcxUsage | undefined {
|
|
Binary file
|