@coseung2/opencodex 2.8.0-cs.16 → 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.
Files changed (52) hide show
  1. package/gui/dist/assets/{index-BZGMtkmp.js → index-Ch-99jy3.js} +1 -1
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/base.ts +12 -0
  5. package/src/adapters/kiro-calibration.ts +83 -0
  6. package/src/adapters/kiro-constants.ts +11 -2
  7. package/src/adapters/kiro-errors.ts +11 -0
  8. package/src/adapters/kiro-events.ts +19 -1
  9. package/src/adapters/kiro-thinking.ts +18 -2
  10. package/src/adapters/kiro-tools.ts +12 -3
  11. package/src/adapters/kiro.ts +300 -78
  12. package/src/adapters/openai-chat.ts +1 -42
  13. package/src/adapters/openai-responses.ts +93 -14
  14. package/src/adapters/xai-schema-analysis.ts +78 -0
  15. package/src/adapters/xai-tool-schema.ts +274 -0
  16. package/src/adapters/xai-web-search.ts +138 -0
  17. package/src/bridge.ts +61 -6
  18. package/src/codex/catalog/effort.ts +4 -2
  19. package/src/codex/catalog/metadata.ts +38 -9
  20. package/src/codex/catalog/parsing.ts +17 -2
  21. package/src/codex/catalog/provider-fetch.ts +9 -3
  22. package/src/codex/catalog/sync.ts +8 -5
  23. package/src/codex/data/upstream-models.json +169 -0
  24. package/src/grok/inject.ts +1 -1
  25. package/src/lib/token-estimate.ts +42 -38
  26. package/src/lib/translator-budget.ts +34 -0
  27. package/src/oauth/index.ts +10 -4
  28. package/src/oauth/kiro.ts +71 -6
  29. package/src/oauth/store.ts +3 -1
  30. package/src/oauth/types.ts +4 -0
  31. package/src/providers/derive.ts +7 -5
  32. package/src/providers/opencode-go-transport.ts +18 -0
  33. package/src/providers/registry.ts +34 -10
  34. package/src/providers/xai-transport.ts +10 -0
  35. package/src/responses/compaction.ts +8 -1
  36. package/src/responses/namespace-aliases.ts +56 -0
  37. package/src/responses/parser.ts +12 -0
  38. package/src/responses/reasoning-envelope.ts +9 -1
  39. package/src/responses/snapshot-policy.ts +108 -0
  40. package/src/responses/state.ts +23 -10
  41. package/src/responses/turn-termination.ts +108 -0
  42. package/src/responses/xai-custom-tool-compat.ts +237 -0
  43. package/src/server/grok-responses-snapshot-repair.ts +338 -0
  44. package/src/server/index.ts +2 -1
  45. package/src/server/relay-eager.ts +1 -0
  46. package/src/server/responses/core.ts +173 -13
  47. package/src/server/responses-image-gen-repair.ts +2 -2
  48. package/src/server/sse-payload-rewrite.ts +20 -3
  49. package/src/types.ts +10 -1
  50. package/src/usage/cost.ts +0 -0
  51. package/src/usage/expected-prices.ts +7 -0
  52. package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
@@ -527,7 +527,7 @@ export function startServer(port?: number) {
527
527
  // Disabled natives stay in the catalog shape with visibility "hide" (mirrors the
528
528
  // on-disk sync; codex-rs keeps them out of the picker itself).
529
529
  const maMode = config.multiAgentMode === "v1" || config.multiAgentMode === "v2" ? config.multiAgentMode : "default";
530
- const entries = buildCatalogEntries(loadCatalogTemplate(), nativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config));
530
+ const entries = buildCatalogEntries(loadCatalogTemplate(), nativeSlugs, goOrdered, config.subagentModels, websocketsEnabled(config), maMode as "v1" | "default" | "v2", exactComboCatalogSlugs(config), config);
531
531
  return jsonResponse({ models: applyNativeVisibility(entries, disabledNativeSlugs(config)) }, 200, req, config);
532
532
  }
533
533
  // OpenAI list shape: native gpt bare + routed models namespaced "<provider>/<id>"
@@ -709,6 +709,7 @@ export function startServer(port?: number) {
709
709
  ...admissionFields(admission),
710
710
  inboundProtocol: "responses",
711
711
  };
712
+ if (req.headers.get("x-opencodex-grok") === "1") logCtx.surface = "grok";
712
713
  let logged = false;
713
714
  const finalizeNativePassthroughLog = (
714
715
  status: number,
@@ -283,6 +283,7 @@ export function relaySseEagerBounded(
283
283
  if (!cancelled) {
284
284
  try { controllerRef?.close(); } catch { /* already closed/errored */ }
285
285
  }
286
+ try { rewrite?.dispose?.(); } catch { /* rewrite teardown must not block lifecycle cleanup */ }
286
287
  try { hooks.disposeInspection?.(); } catch { /* inspection teardown must not block lifecycle cleanup */ }
287
288
  fireDone();
288
289
  }
@@ -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,8 +102,9 @@ 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";
104
- import { resolveOpenCodeGoTransport } from "../../providers/opencode-go-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";
105
108
  import type { WsData } from "../ws-bridge";
106
109
  import { trackActiveTurnLease, trackStreamLifetime } from "../lifecycle";
107
110
  import { redactSecretString } from "../../lib/redact";
@@ -158,12 +161,18 @@ import {
158
161
  createResponsesItemIdPayloadRewrite,
159
162
  hasResponsesItemIdRepair,
160
163
  } from "../responses-item-id-repair";
164
+ import { createGrokResponsesSparseTerminalPayloadRewrite } from "../grok-responses-snapshot-repair";
161
165
  import {
162
166
  createImageGenCallRestoreRewrite,
163
167
  imageGenToolCallAliases,
164
168
  restoreImageGenCallsInJson,
165
169
  } from "../responses-image-gen-repair";
166
170
  import { composeSsePayloadRewrites, relaySseWithPayloadRewrite } from "../sse-payload-rewrite";
171
+ import {
172
+ createXaiCustomToolPayloadRewrite,
173
+ restoreXaiCustomCallsInJson,
174
+ xaiResponsesCustomToolNames,
175
+ } from "../../responses/xai-custom-tool-compat";
167
176
  import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
168
177
 
169
178
  import { buildToolBridgeMaps, collabSurface, injectDeveloperMessage, multiAgentGuidanceText } from "./collaboration";
@@ -889,8 +898,11 @@ async function applyFinalRouteRequestNormalization(args: {
889
898
  // Virtual model rewriting: Pro aliases → base model + reasoning.mode="pro".
890
899
  applyOpenAiVirtualModel(parsed, route, logCtx);
891
900
 
892
- // Fast mode override for OpenAI-routed models.
893
- if (config.fastMode !== undefined && route.provider.adapter === "openai-responses") {
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) {
894
906
  const tier = config.fastMode ? "priority" : undefined;
895
907
  if (parsed._rawBody && typeof parsed._rawBody === "object") {
896
908
  if (tier) (parsed._rawBody as Record<string, unknown>).service_tier = tier;
@@ -1330,6 +1342,7 @@ async function handleResponsesInner(
1330
1342
  cursorConversationId: parsed._cursorConversationId,
1331
1343
  });
1332
1344
  }
1345
+ bindTurnTerminationScope(parsed, logCtx.conversationId);
1333
1346
  logCtx.requestedModel = parsed.modelId;
1334
1347
  logCtx.requestedEffort = parsed.options.reasoning;
1335
1348
  logCtx.requestedServiceTier = parsed.options.serviceTier;
@@ -1636,9 +1649,6 @@ async function handleResponsesInner(
1636
1649
  }
1637
1650
 
1638
1651
  if ("passthrough" in adapter && adapter.passthrough && !routedCompaction) {
1639
- const imageGenCallAliases = route.provider.authMode === "forward"
1640
- ? new Map<string, { namespace: string; name: string }>()
1641
- : imageGenToolCallAliases(toolBridgeMaps.toolNsMap, parsed._rawBody, translatorBudget);
1642
1652
  // Local continuation cache for the ChatGPT passthrough. Codex WS turns chain with
1643
1653
  // previous_response_id, ocx converts them to internal HTTP requests, and the ChatGPT Codex
1644
1654
  // REST backend rejects the parameter — the adapter strips it in forward mode, so the ONLY
@@ -1660,7 +1670,27 @@ async function handleResponsesInner(
1660
1670
  + `(model ${parsed.modelId}); forwarding without it — earlier turns may be missing from this request`,
1661
1671
  );
1662
1672
  }
1663
- let request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
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);
1664
1694
  recordAdapterReasoning(logCtx, request);
1665
1695
  const passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
1666
1696
  ? request.usageLog.inputTokens
@@ -1715,6 +1745,66 @@ async function handleResponsesInner(
1715
1745
  request.releaseBodyObservation?.();
1716
1746
  }
1717
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
+
1718
1808
  if (usesCodexForwardPoolAuth(authCtx, route.provider) && !authCtx.fixedAccount) {
1719
1809
  let poolRetryOutcome: number | undefined;
1720
1810
  if (await shouldRetryCodexPoolAccountModel400(
@@ -1865,10 +1955,20 @@ async function handleResponsesInner(
1865
1955
  // The bundled known-bad runtime remains on tee by default on both platforms.
1866
1956
  if (isEventStream && upstreamResponse.body) {
1867
1957
  const repairConfig = route.provider.responsesItemIdRepair;
1868
- const needsClientRewrite = imageGenCallAliases.size > 0 || hasResponsesItemIdRepair(repairConfig);
1869
- // Compose opt-in payload rewrites into one parse/stringify pass (image-gen restore first).
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.
1870
1968
  const payloadRewrites = [
1871
1969
  createImageGenCallRestoreRewrite(imageGenCallAliases),
1970
+ xaiCustomToolRewrite,
1971
+ grokSparseTerminalRewrite,
1872
1972
  hasResponsesItemIdRepair(repairConfig)
1873
1973
  ? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
1874
1974
  : undefined,
@@ -2031,7 +2131,8 @@ async function handleResponsesInner(
2031
2131
  rememberPassthroughResponse(JSON.parse(text) as { id?: unknown; output?: unknown; status?: unknown });
2032
2132
  } catch { /* non-JSON despite content-type; recording is best-effort */ }
2033
2133
  }
2034
- return new Response(restoreImageGenCallsInJson(text, imageGenCallAliases), {
2134
+ const restoredCustomTools = restoreXaiCustomCallsInJson(text, xaiCustomToolNames);
2135
+ return new Response(restoreImageGenCallsInJson(restoredCustomTools, imageGenCallAliases), {
2035
2136
  status: upstreamResponse.status,
2036
2137
  statusText: upstreamResponse.statusText,
2037
2138
  headers,
@@ -2390,6 +2491,62 @@ async function handleResponsesInner(
2390
2491
  return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
2391
2492
  }
2392
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
+
2393
2550
  const upstream = new AbortController();
2394
2551
  const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal);
2395
2552
  const connectMs = config.connectTimeoutMs ?? 200_000;
@@ -2878,13 +3035,15 @@ async function handleResponsesInner(
2878
3035
  // PRE-compaction history, and a later previous_response_id expansion would rehydrate the
2879
3036
  // giant stale chain Codex just replaced.
2880
3037
  ...(routedCompaction ? {} : {
2881
- onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) =>
3038
+ onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => {
3039
+ if (activeAdapter.name === "kiro") rememberDeliveredFinalAnswer(parsed, response);
2882
3040
  rememberResponseState(
2883
3041
  parsed._rawBody,
2884
3042
  response,
2885
3043
  continuationStateForResponse(providerState),
2886
3044
  activeAdapter.name === "kiro" ? { force: true } : undefined,
2887
- ),
3045
+ );
3046
+ },
2888
3047
  }),
2889
3048
  },
2890
3049
  );
@@ -2944,6 +3103,7 @@ async function handleResponsesInner(
2944
3103
  });
2945
3104
  // See the streaming branch: compaction turns skip the continuation cache.
2946
3105
  if (!routedCompaction) {
3106
+ if (activeAdapter.name === "kiro") rememberDeliveredFinalAnswer(parsed, json);
2947
3107
  rememberResponseState(
2948
3108
  parsed._rawBody,
2949
3109
  json,
@@ -60,7 +60,7 @@ export function imageGenToolCallAliases(
60
60
  return aliases;
61
61
  }
62
62
 
63
- /** Recursively restore only exact image-gen function-call aliases in a parsed Responses payload. */
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
- return (payload) => {
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,