@bitkyc08/opencodex 2.55.0 → 2.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/bin/ocx.mjs +10 -0
  2. package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
  3. package/gui/dist/assets/{index-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +4 -3
  6. package/src/adapters/base.ts +21 -0
  7. package/src/adapters/codebuddy/adapter.ts +2 -1
  8. package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
  9. package/src/adapters/command-code.ts +1 -1
  10. package/src/adapters/cursor/envelope-echo.ts +8 -2
  11. package/src/adapters/cursor/transport-retry.ts +46 -1
  12. package/src/adapters/cursor.ts +4 -0
  13. package/src/adapters/google.ts +7 -7
  14. package/src/adapters/kiro/adapter.ts +42 -1
  15. package/src/adapters/kiro/payload.ts +17 -3
  16. package/src/adapters/kiro/reasoning.ts +70 -7
  17. package/src/adapters/kiro/stream.ts +8 -2
  18. package/src/adapters/kiro/wire.ts +2 -1
  19. package/src/adapters/kiro-events.ts +21 -13
  20. package/src/adapters/kiro-retry.ts +23 -4
  21. package/src/adapters/openai-chat/errors.ts +116 -0
  22. package/src/adapters/openai-chat/messages.ts +346 -0
  23. package/src/adapters/openai-chat/passthrough.ts +146 -0
  24. package/src/adapters/openai-chat/response-events.ts +117 -0
  25. package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
  26. package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
  27. package/src/adapters/openai-chat/tool-schema.ts +495 -0
  28. package/src/adapters/openai-chat/wire.ts +50 -0
  29. package/src/adapters/openai-chat.ts +40 -1452
  30. package/src/adapters/openai-responses/canonical-forward.ts +202 -0
  31. package/src/adapters/openai-responses/image-gen.ts +406 -0
  32. package/src/adapters/openai-responses/internal.ts +3 -0
  33. package/src/adapters/openai-responses/passthrough.ts +642 -0
  34. package/src/adapters/openai-responses/prompt-cache.ts +83 -0
  35. package/src/adapters/openai-responses/reasoning.ts +220 -0
  36. package/src/adapters/openai-responses/request-strips.ts +185 -0
  37. package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
  38. package/src/adapters/openai-responses/tool-schema.ts +293 -0
  39. package/src/adapters/openai-responses/web-search.ts +156 -0
  40. package/src/adapters/openai-responses.ts +4 -2625
  41. package/src/bridge/errors.ts +58 -0
  42. package/src/bridge/internal.ts +174 -0
  43. package/src/bridge/response-json.ts +630 -0
  44. package/src/bridge/sse.ts +1462 -0
  45. package/src/bridge.ts +5 -2204
  46. package/src/chat/inbound.ts +12 -1
  47. package/src/claude/desktop-profile.ts +66 -9
  48. package/src/claude/outbound.ts +18 -0
  49. package/src/cli/account-main.ts +1 -1
  50. package/src/cli/capabilities.ts +2 -2
  51. package/src/cli/combo.ts +10 -1
  52. package/src/cli/index.ts +48 -5
  53. package/src/cli/registry.ts +2 -1
  54. package/src/cli/system-command.ts +4 -4
  55. package/src/clients/config-export.ts +7 -3
  56. package/src/codex/account-label.ts +14 -3
  57. package/src/codex/account-lifecycle.ts +3 -0
  58. package/src/codex/account-store.ts +184 -35
  59. package/src/codex/account-usability.ts +21 -0
  60. package/src/codex/auth-api/account-list.ts +507 -0
  61. package/src/codex/auth-api/http.ts +32 -0
  62. package/src/codex/auth-api/login-flow.ts +566 -0
  63. package/src/codex/auth-api/login-state.ts +64 -0
  64. package/src/codex/auth-api/main-account-probe.ts +331 -0
  65. package/src/codex/auth-api/pool-mode-gate.ts +274 -0
  66. package/src/codex/auth-api/pool-quota-probe.ts +512 -0
  67. package/src/codex/auth-api/reset-credit-service.ts +431 -0
  68. package/src/codex/auth-api/routes.ts +425 -0
  69. package/src/codex/auth-api/runtime-config.ts +48 -0
  70. package/src/codex/auth-api.ts +27 -3118
  71. package/src/codex/auth-context.ts +252 -35
  72. package/src/codex/catalog/aggregation.ts +80 -1
  73. package/src/codex/catalog/auto-review.ts +507 -0
  74. package/src/codex/catalog/build-entries.ts +981 -0
  75. package/src/codex/catalog/combo-member.ts +375 -0
  76. package/src/codex/catalog/derive-entry.ts +229 -0
  77. package/src/codex/catalog/effort.ts +0 -1
  78. package/src/codex/catalog/gated-native-warn.ts +63 -0
  79. package/src/codex/catalog/gather-capture.ts +533 -0
  80. package/src/codex/catalog/model-hints.ts +691 -0
  81. package/src/codex/catalog/model-visibility.ts +305 -0
  82. package/src/codex/catalog/provider-fetch.ts +52 -2942
  83. package/src/codex/catalog/provider-models.ts +685 -0
  84. package/src/codex/catalog/remote.ts +30 -0
  85. package/src/codex/catalog/restore.ts +132 -0
  86. package/src/codex/catalog/retained-sync.ts +714 -0
  87. package/src/codex/catalog/routed-gather.ts +895 -0
  88. package/src/codex/catalog/subagent-roster.ts +176 -0
  89. package/src/codex/catalog/sync.ts +52 -2698
  90. package/src/codex/cli-install-provenance.ts +7 -1
  91. package/src/codex/convergence.ts +7 -2
  92. package/src/codex/desktop-app/types.ts +11 -2
  93. package/src/codex/desktop-app/windows.ts +5 -5
  94. package/src/codex/inject/config-toml.ts +563 -0
  95. package/src/codex/inject/remove.ts +192 -0
  96. package/src/codex/inject/restore.ts +567 -0
  97. package/src/codex/inject/routing-classify.ts +109 -0
  98. package/src/codex/inject/routing-target.ts +125 -0
  99. package/src/codex/inject.ts +89 -1444
  100. package/src/codex/lineage.ts +458 -0
  101. package/src/codex/model-entitlements.ts +152 -15
  102. package/src/codex/pool-refresh-backoff.ts +161 -0
  103. package/src/codex/quota-rejection.ts +104 -15
  104. package/src/codex/routing/active-account.ts +194 -0
  105. package/src/codex/routing/cache-affinity.ts +70 -0
  106. package/src/codex/routing/cooldown-math.ts +285 -0
  107. package/src/codex/routing/health-store.ts +402 -0
  108. package/src/codex/routing/probe-lease.ts +358 -0
  109. package/src/codex/routing/selection.ts +780 -0
  110. package/src/codex/routing/thread-affinity.ts +586 -0
  111. package/src/codex/routing/transient-hold-dispatch.ts +141 -0
  112. package/src/codex/routing.ts +370 -2271
  113. package/src/codex/shim-fingerprint.ts +223 -0
  114. package/src/codex/shim-inspect.ts +175 -0
  115. package/src/codex/shim-probe.ts +367 -0
  116. package/src/codex/shim-restore-lock.ts +169 -0
  117. package/src/codex/shim-state-file.ts +151 -0
  118. package/src/codex/shim-templates.ts +265 -0
  119. package/src/codex/shim.ts +48 -1268
  120. package/src/codex/warmup.ts +1 -1
  121. package/src/combos/failover.ts +85 -0
  122. package/src/combos/request.ts +17 -10
  123. package/src/combos/types.ts +23 -2
  124. package/src/config/diagnostics.ts +705 -0
  125. package/src/config/feature-flags.ts +55 -0
  126. package/src/config/live-reconcile.ts +403 -0
  127. package/src/config/load-degrade.ts +880 -0
  128. package/src/config/mutation-lock.ts +244 -0
  129. package/src/config/openai-tier-backup.ts +268 -0
  130. package/src/config/pending-teardown.ts +31 -0
  131. package/src/config/persist-unlocked.ts +92 -0
  132. package/src/config/proxy-env.ts +188 -0
  133. package/src/config/salvage.ts +244 -0
  134. package/src/config/schema/config-schema.ts +640 -0
  135. package/src/config/schema/leaf-validators.ts +855 -0
  136. package/src/config/warn-memo.ts +28 -0
  137. package/src/config.ts +234 -4481
  138. package/src/generated/compatibility-version.json +649 -121
  139. package/src/images/loop.ts +1 -1
  140. package/src/lib/errors.ts +17 -0
  141. package/src/lib/request-execution-budget.ts +198 -23
  142. package/src/lib/spend-reservation-ledger.ts +958 -0
  143. package/src/lib/state-store-registrations.ts +6 -2
  144. package/src/lib/test-home-guard.ts +85 -1
  145. package/src/lib/upstream-retry.ts +132 -21
  146. package/src/lib/windows-elevation.ts +76 -14
  147. package/src/lib/workflow-budget.ts +553 -30
  148. package/src/oauth/index.ts +2 -2
  149. package/src/oauth/key-providers.ts +2 -2
  150. package/src/providers/kiro-models.ts +4 -3
  151. package/src/providers/label.ts +19 -1
  152. package/src/providers/model-discovery.ts +16 -0
  153. package/src/providers/quota/account-cache.ts +441 -0
  154. package/src/providers/quota/antigravity.ts +295 -0
  155. package/src/providers/quota/report-cache.ts +320 -0
  156. package/src/providers/quota/vendor-probes-key.ts +1243 -0
  157. package/src/providers/quota/vendor-probes-oauth.ts +590 -0
  158. package/src/providers/quota.ts +324 -3079
  159. package/src/providers/registry/entries-core.ts +1228 -0
  160. package/src/providers/registry/entries-extended.ts +1213 -0
  161. package/src/providers/registry/model-seeds.ts +912 -0
  162. package/src/providers/registry/types.ts +352 -0
  163. package/src/providers/registry.ts +24 -3536
  164. package/src/responses/continuation-ownership.ts +29 -0
  165. package/src/responses/reasoning-envelope.ts +6 -3
  166. package/src/responses/state/replay-fingerprint.ts +80 -0
  167. package/src/responses/state/snapshot-codec.ts +104 -0
  168. package/src/responses/state/spill-failure.ts +118 -0
  169. package/src/responses/state/spill-queue.ts +665 -0
  170. package/src/responses/state/temp-recovery.ts +257 -0
  171. package/src/responses/state.ts +82 -1143
  172. package/src/routing/identity-domains.ts +456 -0
  173. package/src/routing/probe-lease.ts +613 -0
  174. package/src/server/chat-completions.ts +3 -1
  175. package/src/server/chat-native.ts +37 -9
  176. package/src/server/index/bounded-request.ts +88 -0
  177. package/src/server/index/live-sideband.ts +601 -0
  178. package/src/server/index/serve-options.ts +1766 -0
  179. package/src/server/index/startup-warnings.ts +213 -0
  180. package/src/server/index/websocket-handler.ts +339 -0
  181. package/src/server/index.ts +45 -2552
  182. package/src/server/inspection-tee.ts +107 -0
  183. package/src/server/live.ts +46 -1
  184. package/src/server/management/combo-routes.ts +10 -1
  185. package/src/server/management/route-registry.ts +26 -23
  186. package/src/server/management/shared.ts +8 -5
  187. package/src/server/management/workflow-budget-routes.ts +133 -0
  188. package/src/server/management-api.ts +12 -0
  189. package/src/server/relay-eager.ts +2 -0
  190. package/src/server/relay.ts +14 -19
  191. package/src/server/request-log-conversation.ts +9 -7
  192. package/src/server/request-log.ts +372 -4
  193. package/src/server/response-log-body.ts +153 -0
  194. package/src/server/responses/account-change-state.ts +307 -0
  195. package/src/server/responses/adapter-continuation.ts +540 -0
  196. package/src/server/responses/adapter-delivery.ts +208 -0
  197. package/src/server/responses/adapter-dispatch.ts +1042 -0
  198. package/src/server/responses/codex-ws-wire.ts +5 -0
  199. package/src/server/responses/collaboration.ts +74 -4
  200. package/src/server/responses/combo-session-recall.ts +68 -8
  201. package/src/server/responses/compact.ts +113 -17
  202. package/src/server/responses/completion-policy.ts +33 -0
  203. package/src/server/responses/core-auth.ts +529 -0
  204. package/src/server/responses/core-codex-account.ts +907 -0
  205. package/src/server/responses/core-combo-failure.ts +210 -0
  206. package/src/server/responses/core-combo.ts +787 -0
  207. package/src/server/responses/core-errors.ts +170 -0
  208. package/src/server/responses/core-lifetime.ts +95 -0
  209. package/src/server/responses/core-normalize.ts +350 -0
  210. package/src/server/responses/core-opaque-recovery.ts +380 -0
  211. package/src/server/responses/core-options.ts +159 -0
  212. package/src/server/responses/core-replay.ts +298 -0
  213. package/src/server/responses/core.ts +192 -8893
  214. package/src/server/responses/encrypted-payload.ts +0 -1
  215. package/src/server/responses/input-admission.ts +126 -6
  216. package/src/server/responses/passthrough-delivery.ts +869 -0
  217. package/src/server/responses/passthrough-dispatch.ts +1494 -0
  218. package/src/server/responses/passthrough-error.ts +38 -2
  219. package/src/server/responses/passthrough-execution.ts +54 -0
  220. package/src/server/responses/request-prepare.ts +1080 -0
  221. package/src/server/responses/request-send-budget.ts +259 -0
  222. package/src/server/responses/request-sidecar-auth.ts +149 -0
  223. package/src/server/responses/request-spend.ts +147 -0
  224. package/src/server/responses/request-transport.ts +803 -0
  225. package/src/server/responses/response-effects.ts +157 -0
  226. package/src/server/responses/run-turn-execution.ts +476 -0
  227. package/src/server/responses/sidecar-execution.ts +463 -0
  228. package/src/server/responses/terminal-guard.ts +65 -4
  229. package/src/server/responses-image-gen-repair.ts +1 -1
  230. package/src/server/responses-undeclared-tool-guard.ts +9 -5
  231. package/src/server/workflow-refusal.ts +84 -0
  232. package/src/service/windows-ops.ts +210 -16
  233. package/src/service/windows-scheduler.ts +28 -21
  234. package/src/service.ts +1 -1
  235. package/src/types/config.ts +34 -1
  236. package/src/types/request.ts +8 -5
  237. package/src/types/tools.ts +24 -0
  238. package/src/types.ts +2 -0
  239. package/src/update/index.ts +10 -0
  240. package/src/update/stop-contract.d.mts +1 -0
  241. package/src/update/stop-contract.mjs +19 -0
  242. package/src/update/stop-decision.d.mts +1 -1
  243. package/src/update/stop-decision.mjs +12 -3
  244. package/src/usage/log.ts +147 -1
  245. package/src/usage/summary.ts +171 -21
  246. package/src/vision/anthropic-describe.ts +1 -1
  247. package/src/vision/describe.ts +5 -5
  248. package/src/web-search/anthropic-executor.ts +1 -1
  249. package/src/web-search/exa-executor.ts +1 -1
  250. package/src/web-search/executor.ts +1 -1
  251. package/src/web-search/gemini-executor.ts +1 -1
  252. package/src/web-search/loop.ts +1 -1
  253. package/src/web-search/ollama-executor.ts +1 -1
  254. package/src/web-search/parse.ts +67 -14
  255. package/src/web-search/passthrough-bridge.ts +64 -31
  256. package/src/web-search/xai-executor.ts +1 -1
@@ -4,21 +4,46 @@ import type { OcxParsedRequest } from "../../types";
4
4
  export type KiroReasoningMode = "native" | "emulated";
5
5
 
6
6
  // Kiro takes a verified native effort field for these models, and each model family names it
7
- // differently: the Sol-only `reasoning.effort` versus the Claude-specific `output_config.effort`.
8
- // Models absent from this table fall back to emulated thinking instructions.
7
+ // differently: the GPT-5.6 family's `reasoning.effort` versus the Claude-specific
8
+ // `output_config.effort`. Models absent from this table fall back to emulated thinking
9
+ // instructions.
10
+ //
11
+ // The GPT-5.6 entries are measured against the live runtime rather than inferred from the vendor
12
+ // schema: the field is accepted (HTTP 200) and the encrypted reasoning blob that comes back grows
13
+ // with the effort. On one fixed hard prompt — a primality search plus a 20-bit recurrence count —
14
+ // luna's blob measured 5,130 chars at `low`, 16,686 at `medium`, 30,670 at `high` and 48,594 at
15
+ // `max`, against 13,118 with no effort signal at all; terra's measured 34,590 and 38,106 at native
16
+ // `max` against 11,758 and 17,598 bare, two repetitions each. The channel this replaces — the
17
+ // emulated `<thinking_mode>` tag block, which was all those models used to receive — measured
18
+ // 21,202 (`low`) and 28,302 (`max`) for luna, i.e. between that model's native `medium` and
19
+ // `high`, never reaching native `max`. `gpt-5.6-sol`'s native `max` cross-checked at 30,498 on the
20
+ // same prompt. Terra's absence from this table was therefore an omission rather than a capability
21
+ // difference: what the earlier Sol-only scope recorded was not reproducible here.
9
22
  export const KIRO_NATIVE_EFFORT_FIELDS: Record<string, "reasoning" | "output_config"> = {
10
23
  "gpt-5.6-sol": "reasoning",
24
+ "gpt-5.6-terra": "reasoning",
25
+ "gpt-5.6-luna": "reasoning",
11
26
  "claude-opus-5": "output_config",
12
27
  };
13
28
 
14
29
  export const KIRO_NATIVE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
15
30
 
16
- export function kiroNativeEffortField(modelId: string): "reasoning" | "output_config" | undefined {
17
- return KIRO_NATIVE_EFFORT_FIELDS[normalizeKiroModelId(modelId)];
31
+ // The newly enabled models have evidence for these rungs only. Keep the previous
32
+ // emulation for xhigh, and never widen their native wire when the shared ladder grows.
33
+ const KIRO_LUNA_TERRA_NATIVE_EFFORTS = new Set(["low", "medium", "high", "max"]);
34
+
35
+ export function kiroNativeEffortField(
36
+ modelId: string,
37
+ effort?: string,
38
+ ): "reasoning" | "output_config" | undefined {
39
+ const model = normalizeKiroModelId(modelId);
40
+ if ((model === "gpt-5.6-luna" || model === "gpt-5.6-terra")
41
+ && effort !== undefined && !KIRO_LUNA_TERRA_NATIVE_EFFORTS.has(effort)) return undefined;
42
+ return KIRO_NATIVE_EFFORT_FIELDS[model];
18
43
  }
19
44
 
20
- export function kiroReasoningMode(modelId: string): KiroReasoningMode {
21
- return kiroNativeEffortField(modelId) ? "native" : "emulated";
45
+ export function kiroReasoningMode(modelId: string, effort?: string): KiroReasoningMode {
46
+ return kiroNativeEffortField(modelId, effort) ? "native" : "emulated";
22
47
  }
23
48
 
24
49
  export function kiroThinkingBudget(parsed: OcxParsedRequest): number | undefined {
@@ -38,7 +63,7 @@ export function kiroThinkingBudget(parsed: OcxParsedRequest): number | undefined
38
63
  }
39
64
 
40
65
  export function injectKiroThinkingTags(content: string, parsed: OcxParsedRequest): string {
41
- if (kiroReasoningMode(parsed.modelId) !== "emulated") return content;
66
+ if (kiroReasoningMode(parsed.modelId, parsed.options.reasoning) !== "emulated") return content;
42
67
  const budget = kiroThinkingBudget(parsed);
43
68
  if (!budget) return content;
44
69
  const instruction = [
@@ -54,3 +79,41 @@ export function injectKiroThinkingTags(content: string, parsed: OcxParsedRequest
54
79
  content,
55
80
  ].join("\n");
56
81
  }
82
+
83
+ /**
84
+ * The blob from a Kiro `reasoningContentEvent` has two possible homes on a replayed assistant
85
+ * turn, and the wire validates the SHAPE of each rather than its content: `signature` takes the
86
+ * emitted string verbatim, while `redactedContent` is a base64 member. The `.KTR~~…` value every
87
+ * GPT-5.6 capture returns is NOT valid base64, which is exactly why replaying it as
88
+ * `redactedContent` — what this proxy did before the field was measured — came back as
89
+ * REQUEST_BODY_INVALID ("Improperly formed request").
90
+ *
91
+ * The blob travels as ONE opaque string: adapter event, `ocxr1:` reasoning envelope, then
92
+ * `OcxAssistantMessage.kiroRedactedReasoning`. The field it arrived on therefore rides that same
93
+ * string, instead of a second parallel value that could drift from it. Provider data cannot forge
94
+ * the tag: the other channel is base64, whose alphabet has no colon.
95
+ */
96
+ export const KIRO_REASONING_SIGNATURE_TAG = "signature:";
97
+
98
+ export function tagKiroReasoningBlob(field: "signature" | "redactedContent", data: string): string {
99
+ return field === "signature" ? KIRO_REASONING_SIGNATURE_TAG + data : data;
100
+ }
101
+
102
+ /** The wire field a stored blob arrived on, and its untagged value. */
103
+ export function splitKiroReasoningBlob(value: string): { field: "signature" | "redactedContent"; data: string } {
104
+ return value.startsWith(KIRO_REASONING_SIGNATURE_TAG)
105
+ ? { field: "signature", data: value.slice(KIRO_REASONING_SIGNATURE_TAG.length) }
106
+ : { field: "redactedContent", data: value };
107
+ }
108
+
109
+ /**
110
+ * The `reasoningContent` object on an `assistantResponseMessage`. Exactly one member is set: the
111
+ * wire validates the shape, so the two cannot be substituted for each other.
112
+ */
113
+ export type KiroReasoningContent = { signature: string } | { redactedContent: string };
114
+
115
+ /** `reasoningContent` for a replayed `assistantResponseMessage`, carrying the blob verbatim. */
116
+ export function kiroReasoningContent(value: string): KiroReasoningContent {
117
+ const { field, data } = splitKiroReasoningBlob(value);
118
+ return field === "signature" ? { signature: data } : { redactedContent: data };
119
+ }
@@ -19,6 +19,7 @@ import { noteKiroTransientThrottle } from "../kiro-retry";
19
19
  import { KiroThinkingParser } from "../kiro-thinking";
20
20
  import { isCompleteKiroToolInput, kiroTruncationErrorMessage } from "../kiro-truncation";
21
21
  import { isValidKiroConversationId } from "../kiro-wire";
22
+ import { tagKiroReasoningBlob } from "./reasoning";
22
23
  import { estimateKiroTokens, kiroUpstreamContextWindow } from "./usage";
23
24
 
24
25
  // Stream parsing (shared by parseStream + parseResponse)
@@ -633,8 +634,13 @@ async function* parseKiroAttemptEvents(
633
634
  if (ev.data) {
634
635
  yield* emitRetained(stage({ type: "reasoning_raw_delta", text: ev.data }));
635
636
  }
636
- if (ev.redactedContent) {
637
- yield* emitRetained(stage({ type: "kiro_redacted_reasoning", data: ev.redactedContent }));
637
+ // The blob is replayed on the field it arrived on, so remember that field here — this is
638
+ // the only place that still knows it. See kiro/reasoning.ts for why the distinction is
639
+ // load-bearing rather than cosmetic.
640
+ if (ev.signature) {
641
+ yield* emitRetained(stage({ type: "kiro_redacted_reasoning", data: tagKiroReasoningBlob("signature", ev.signature) }));
642
+ } else if (ev.redactedContent) {
643
+ yield* emitRetained(stage({ type: "kiro_redacted_reasoning", data: tagKiroReasoningBlob("redactedContent", ev.redactedContent) }));
638
644
  }
639
645
  break;
640
646
  case "context_usage":
@@ -1,5 +1,6 @@
1
1
  import type { OcxProviderConfig } from "../../types";
2
2
  import type { KiroImage } from "../kiro-images";
3
+ import type { KiroReasoningContent } from "./reasoning";
3
4
 
4
5
  export const AMZ_TARGET = "AmazonCodeWhispererStreamingService.GenerateAssistantResponse";
5
6
  export const SDK_VERSION = "1.0.27";
@@ -51,7 +52,7 @@ export interface KiroHistoryEntry {
51
52
  assistantResponseMessage?: {
52
53
  content: string;
53
54
  toolUses?: KiroToolUse[];
54
- reasoningContent?: { redactedContent: string };
55
+ reasoningContent?: KiroReasoningContent;
55
56
  };
56
57
  }
57
58
 
@@ -3,7 +3,7 @@ import { kiroTruncationReason } from "./kiro-truncation";
3
3
 
4
4
  export type ParsedKiroEvent =
5
5
  | { type: "content"; data?: string; modelId?: string }
6
- | { type: "reasoning"; data?: string; redactedContent?: string }
6
+ | { type: "reasoning"; data?: string; signature?: string; redactedContent?: string }
7
7
  | { type: "context_usage"; contextUsagePercentage: number }
8
8
  | { type: "tool"; name?: string; toolUseId?: string; input?: string; stop?: boolean }
9
9
  | { type: "truncation"; data: string }
@@ -138,18 +138,26 @@ export function parseKiroEvent(eventType: string, payload: Uint8Array): ParsedKi
138
138
  : {}),
139
139
  };
140
140
  case "reasoningContentEvent":
141
- // `text` is plaintext reasoning; `redactedContent` is the encrypted blob the GPT-5.6 family
142
- // (sol/terra/luna) actually returns — they never send `text`. Keyed off the wire field, not
143
- // the model id. Both may be absent on a bare event.
144
- return {
145
- type: "reasoning",
146
- ...(optionalString(eventType, parsed, "text") !== undefined
147
- ? { data: optionalString(eventType, parsed, "text") }
148
- : {}),
149
- ...(optionalString(eventType, parsed, "redactedContent") !== undefined
150
- ? { redactedContent: optionalString(eventType, parsed, "redactedContent") }
151
- : {}),
152
- };
141
+ // `text` is plaintext reasoning; the GPT-5.6 family (sol/terra/luna) instead returns an
142
+ // encrypted blob, and the field it arrives on has to be replayed unchanged (see
143
+ // kiro/reasoning.ts): `signature` carries the `.KTR~~…` value verbatim and is what every
144
+ // capture of those models sent, while `redactedContent` — the base64 shape a capture has
145
+ // never shown — stays accepted for any model that sends it. Keyed off the wire field, not the
146
+ // model id. Any of the three may be absent on a bare event.
147
+ {
148
+ const text = optionalString(eventType, parsed, "text");
149
+ const signature = optionalString(eventType, parsed, "signature");
150
+ const redacted = optionalString(eventType, parsed, "redactedContent");
151
+ return {
152
+ type: "reasoning",
153
+ ...(text !== undefined ? { data: text } : {}),
154
+ ...(signature !== undefined
155
+ ? { signature }
156
+ : redacted !== undefined
157
+ ? { redactedContent: redacted }
158
+ : {}),
159
+ };
160
+ }
153
161
  case "toolUseEvent":
154
162
  return {
155
163
  type: "tool",
@@ -1,4 +1,5 @@
1
1
  import type { AdapterFetchContext, AdapterRequest } from "./base";
2
+ import type { AttemptRecoveryKind } from "../usage/log";
2
3
  import { classifyKiroHttpError, safeKiroHttpErrorMessage } from "./kiro-errors";
3
4
  import { normalizeUpstreamHttpErrorResponse } from "./upstream-http-error";
4
5
  import { readBoundedResponseBody } from "../lib/bounded-body";
@@ -159,6 +160,7 @@ async function fetchWithResetRecovery(
159
160
  url: string,
160
161
  ctx: AdapterFetchContext,
161
162
  timeoutMs: number,
163
+ notePhysicalSend: (reset: boolean) => void,
162
164
  ): Promise<Response> {
163
165
  let lastError: unknown;
164
166
  for (let attempt = 0; attempt < RESET_ATTEMPTS; attempt++) {
@@ -170,6 +172,9 @@ async function fetchWithResetRecovery(
170
172
  if (decision && (!decision.allowed || !decision.permit.use())) {
171
173
  throw new SendBudgetExhaustedError(url);
172
174
  }
175
+ // Reported after admission and before dispatch, so a refused send is never counted and an
176
+ // admitted one is counted exactly once whichever way the fetch below settles.
177
+ notePhysicalSend(attempt > 0);
173
178
  try {
174
179
  const headers = new Headers(request.headers);
175
180
  const recovered = attempt > 0;
@@ -252,14 +257,15 @@ async function fetchKiroAttempt(
252
257
  request: AdapterRequest,
253
258
  ctx: AdapterFetchContext,
254
259
  timeoutMs: number,
260
+ notePhysicalSend: (reset: boolean) => void,
255
261
  ): Promise<Response> {
256
262
  const legacy = legacyUrl(request.url);
257
263
  let response: Response;
258
264
  try {
259
- response = await fetchWithResetRecovery(request, request.url, ctx, timeoutMs);
265
+ response = await fetchWithResetRecovery(request, request.url, ctx, timeoutMs, notePhysicalSend);
260
266
  } catch (error) {
261
267
  if (!legacy || !endpointConnectFailure(error)) throw error;
262
- return fetchWithResetRecovery(request, legacy, ctx, timeoutMs);
268
+ return fetchWithResetRecovery(request, legacy, ctx, timeoutMs, notePhysicalSend);
263
269
  }
264
270
 
265
271
  if (legacy && !response.ok) {
@@ -267,7 +273,7 @@ async function fetchKiroAttempt(
267
273
  response = inspected.response;
268
274
  if (inspected.fallback) {
269
275
  cancelResponseBodyBestEffort(response);
270
- response = await fetchWithResetRecovery(request, legacy, ctx, timeoutMs);
276
+ response = await fetchWithResetRecovery(request, legacy, ctx, timeoutMs, notePhysicalSend);
271
277
  }
272
278
  }
273
279
  return response;
@@ -281,12 +287,25 @@ async function fetchKiroAttempt(
281
287
  export async function fetchKiroWithRetry(request: AdapterRequest, ctx: AdapterFetchContext = {}): Promise<Response> {
282
288
  const timeoutMs = ctx.timeoutMs ?? 200_000;
283
289
  let probeToken: symbol | undefined;
290
+ // One ordinal sequence for the whole call, across the throttle loop, the endpoint fallback
291
+ // and the reset ladder nested inside it. The caller records ordinal 1 itself, so this is what
292
+ // turns "one adapter call" back into the physical count the request actually made.
293
+ let physicalSends = 0;
294
+ let throttleRound = 0;
295
+ const notePhysicalSend = (reset: boolean): void => {
296
+ physicalSends += 1;
297
+ const recovery: AttemptRecoveryKind | undefined = reset
298
+ ? "connection-reset"
299
+ : throttleRound > 0 ? "rate-limit-429" : undefined;
300
+ ctx.onPhysicalSend?.({ ordinal: physicalSends, ...(recovery ? { recovery } : {}) });
301
+ };
284
302
  try {
285
303
  for (let attempt = 0; attempt < THROTTLE_ATTEMPTS; attempt++) {
304
+ throttleRound = attempt;
286
305
  if (!probeToken) probeToken = await enterKiroThrottleGate(ctx.abortSignal);
287
306
  else await waitForKiroCooldown(ctx.abortSignal);
288
307
 
289
- const response = await fetchKiroAttempt(request, ctx, timeoutMs);
308
+ const response = await fetchKiroAttempt(request, ctx, timeoutMs, notePhysicalSend);
290
309
  const throttle = await inspectKiroThrottle(response, ctx.abortSignal);
291
310
  if (!throttle || !throttle.transient) {
292
311
  releaseKiroThrottleProbe(probeToken);
@@ -0,0 +1,116 @@
1
+ import { isCyberPolicyCode } from "../../lib/errors";
2
+ import { redactSecretString } from "../../lib/redact";
3
+ import type { AdapterEvent, OcxUsage } from "../../types";
4
+
5
+ // 260715 (issue #126): surface upstream error detail through the web-search sidecar loop.
6
+ // loop.ts only appends a suffix to "Provider error N" when the adapter exposes
7
+ // formatErrorBody; without it, strict OpenAI-compatible backends (NVIDIA NIM pydantic
8
+ // validation, "This model only supports single tool-calls at once!", etc.) were reduced
9
+ // to a bare status code. JSON-only extraction: recognized string fields are returned,
10
+ // HTML/non-JSON bodies yield "" so raw markup is never echoed to the client.
11
+ export function formatOpenAIChatErrorBody(status: number, _headers: Headers, payloadText: string): string {
12
+ let parsed: unknown;
13
+ try {
14
+ parsed = JSON.parse(payloadText);
15
+ } catch {
16
+ return "";
17
+ }
18
+ const detail = extractErrorDetail(parsed);
19
+ if (!detail) return "";
20
+ return redactSecretString(detail).slice(0, 400);
21
+ }
22
+
23
+ function extractErrorDetail(parsed: unknown): string | undefined {
24
+ if (typeof parsed === "string") return parsed.trim() || undefined;
25
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
26
+ const obj = parsed as Record<string, unknown>;
27
+ const err = obj.error;
28
+ if (typeof err === "string" && err.trim()) return err.trim();
29
+ if (err !== null && typeof err === "object" && !Array.isArray(err)) {
30
+ const msg = (err as Record<string, unknown>).message;
31
+ if (typeof msg === "string" && msg.trim()) return msg.trim();
32
+ }
33
+ const det = obj.detail;
34
+ if (typeof det === "string" && det.trim()) return det.trim();
35
+ if (Array.isArray(det)) {
36
+ const msgs = det
37
+ .map(item => (item !== null && typeof item === "object" && typeof (item as Record<string, unknown>).msg === "string"
38
+ ? ((item as Record<string, unknown>).msg as string).trim()
39
+ : ""))
40
+ .filter(m => m.length > 0);
41
+ if (msgs.length > 0) return msgs.join("; ");
42
+ }
43
+ if (typeof obj.message === "string" && obj.message.trim()) return obj.message.trim();
44
+ if (typeof obj.title === "string" && obj.title.trim()) return obj.title.trim();
45
+ return undefined;
46
+ }
47
+
48
+ export function unwrapChatCompletionPayload(json: Record<string, unknown>): Record<string, unknown> {
49
+ if ((json.error !== undefined && json.error !== null) || Array.isArray(json.choices)) return json;
50
+ const data = json.data;
51
+ return data !== null && typeof data === "object" && !Array.isArray(data)
52
+ ? data as Record<string, unknown>
53
+ : json;
54
+ }
55
+
56
+ export interface OpenAIChatError {
57
+ message?: unknown;
58
+ code?: unknown;
59
+ type?: unknown;
60
+ status?: unknown;
61
+ metadata?: unknown;
62
+ }
63
+
64
+ export function safeUpstreamRequestId(metadata: unknown): string | undefined {
65
+ if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) return undefined;
66
+ const record = metadata as Record<string, unknown>;
67
+ const value = record.request_id ?? record.requestId;
68
+ if (typeof value !== "string") return undefined;
69
+ const requestId = value.trim();
70
+ return /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(requestId)
71
+ && redactSecretString(requestId) === requestId
72
+ ? requestId
73
+ : undefined;
74
+ }
75
+
76
+ export function upstreamErrorEvent(
77
+ error: unknown,
78
+ usage?: OcxUsage,
79
+ ): Extract<AdapterEvent, { type: "error" }> {
80
+ const details = error !== null && typeof error === "object" && !Array.isArray(error)
81
+ ? error as OpenAIChatError
82
+ : undefined;
83
+ const rawMessage = typeof error === "string"
84
+ ? error.trim() || "upstream error"
85
+ : typeof details?.message === "string" ? details.message : "upstream error";
86
+ const safeMessage = redactSecretString(rawMessage);
87
+ const requestId = safeUpstreamRequestId(details?.metadata);
88
+ const message = requestId !== undefined && !safeMessage.includes(requestId)
89
+ ? `${safeMessage} (request ID: ${requestId})`
90
+ : safeMessage;
91
+ const code = typeof details?.code === "string"
92
+ ? details.code
93
+ : typeof details?.code === "number" && Number.isFinite(details.code) && Number.isInteger(details.code)
94
+ ? String(details.code)
95
+ : undefined;
96
+ const errorType = typeof details?.type === "string" ? details.type : undefined;
97
+ const codeStatus = typeof details?.code === "number"
98
+ && Number.isInteger(details.code)
99
+ && details.code >= 100
100
+ && details.code <= 599
101
+ ? details.code
102
+ : undefined;
103
+ const status = isCyberPolicyCode(code)
104
+ ? 400
105
+ : typeof details?.status === "number" && Number.isInteger(details.status)
106
+ ? details.status
107
+ : codeStatus;
108
+ return {
109
+ type: "error",
110
+ message,
111
+ ...(usage !== undefined ? { usage } : {}),
112
+ ...(code !== undefined ? { code } : {}),
113
+ ...(errorType !== undefined ? { errorType } : {}),
114
+ ...(status !== undefined ? { status } : {}),
115
+ };
116
+ }