@yansigit/opencodex 2.31.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 (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,1936 @@
1
+ import { decodeEventStream } from "../lib/eventstream-decoder";
2
+ import { estimateTokens } from "../lib/token-estimate";
3
+ import { debugProviderDiagnostic } from "../lib/debug";
4
+ import { resolveKiroApiRegion, resolveKiroProfileArn } from "../oauth/kiro";
5
+ import { KIRO_MODEL_CONTEXT_WINDOWS, normalizeKiroModelId } from "../providers/kiro-models";
6
+ import { modelRecordValue } from "../reasoning-effort";
7
+ import { parseKiroEvent } from "./kiro-events";
8
+ import {
9
+ classifyKiroEventError,
10
+ classifyKiroHttpError,
11
+ classifyKiroStreamError,
12
+ safeKiroErrorMessage,
13
+ safeKiroHttpErrorMessage,
14
+ type KiroErrorClassification,
15
+ } from "./kiro-errors";
16
+ import { KiroThinkingParser } from "./kiro-thinking";
17
+ import { isCompleteKiroToolInput, kiroTruncationErrorMessage } from "./kiro-truncation";
18
+ import { createKiroToolNameRegistry, fallbackToolUseId, fingerprint, invocationId, isValidKiroConversationId, mapModelId, normalizeToolId, osTag, stableConversationId } from "./kiro-wire";
19
+ import { namespacedToolName } from "../types";
20
+ import {
21
+ isTranslatorBudgetExceededError,
22
+ releaseTranslatedEvent,
23
+ retainTranslatedEvent,
24
+ type TranslatorBudget,
25
+ } from "../lib/translator-budget";
26
+ import type {
27
+ AdapterEvent,
28
+ OcxAssistantMessage,
29
+ OcxContentPart,
30
+ OcxMessage,
31
+ OcxParsedRequest,
32
+ OcxProviderConfig,
33
+ OcxTextContent,
34
+ OcxToolCall,
35
+ OcxToolResultMessage,
36
+ OcxUsage,
37
+ } from "../types";
38
+ import type { ProviderAdapter } from "./base";
39
+ import type { AdapterFetchContext, AdapterRequest } from "./base";
40
+ import { extractKiroImages, normalizeKiroImages, type KiroImage } from "./kiro-images";
41
+ import { sniffImageDimensions } from "./anthropic-image-guard";
42
+ import { fetchKiroWithRetry, noteKiroTransientThrottle } from "./kiro-retry";
43
+ import { convertKiroToolContext } from "./kiro-tools";
44
+ import { identifyRoutedModel } from "./identity";
45
+ import { buildNonOpenAIToolCatalogNudgeFromNames } from "./tool-catalog-nudge";
46
+ import {
47
+ KIRO_COMPLETION_INSTRUCTIONS,
48
+ KIRO_COMPLETION_RETRY_MESSAGE,
49
+ KIRO_COMPLETION_TOOL_NAME,
50
+ KIRO_CONTINUATION_MESSAGE,
51
+ KIRO_EMPTY_TOOL_RESULT_MESSAGE,
52
+ KIRO_TOOL_RESULT_CARRIER_MESSAGE,
53
+ MAX_KIRO_INJECTED_INSTRUCTION_CHARS,
54
+ type KiroCompletionMode,
55
+ } from "./kiro-constants";
56
+
57
+ const AMZ_TARGET = "AmazonCodeWhispererStreamingService.GenerateAssistantResponse";
58
+ const SDK_VERSION = "1.0.27";
59
+ const NODE_VERSION = "22.21.1";
60
+ const KIRO_IDE_VERSION = "1.0.0";
61
+ const KIRO_FALLBACK_SERIALIZATION_ENVELOPE_BYTES = 64 * 1024;
62
+ type KiroWireClient = "ide" | "cli";
63
+
64
+ function kiroCliPlatform(): "linux" | "macos" | "windows" {
65
+ return process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux";
66
+ }
67
+
68
+ function kiroCliUserAgent(includeAppVersion: boolean): string {
69
+ return [
70
+ "aws-sdk-rust/1.3.15",
71
+ "ua/2.1",
72
+ "api/codewhispererstreaming/0.1.17975",
73
+ `os/${kiroCliPlatform()}`,
74
+ "lang/rust/1.92.0",
75
+ ...(includeAppVersion ? ["md/appVersion-2.14.2"] : []),
76
+ "m/F",
77
+ "app/AmazonQ-For-CLI",
78
+ ].join(" ");
79
+ }
80
+
81
+ // Payload construction (conversationState)
82
+ interface KiroToolUse {
83
+ name: string;
84
+ input: Record<string, unknown>; // OBJECT, not stringified
85
+ toolUseId: string;
86
+ }
87
+ interface KiroToolResult {
88
+ content: Array<{ text: string }>;
89
+ status: string;
90
+ toolUseId: string;
91
+ }
92
+ interface KiroUserInputMessage {
93
+ content: string;
94
+ modelId?: string;
95
+ origin?: string;
96
+ userInputMessageContext?: {
97
+ tools?: unknown[];
98
+ toolResults?: KiroToolResult[];
99
+ };
100
+ images?: KiroImage[];
101
+ }
102
+ interface KiroHistoryEntry {
103
+ userInputMessage?: KiroUserInputMessage;
104
+ assistantResponseMessage?: {
105
+ content: string;
106
+ toolUses?: KiroToolUse[];
107
+ reasoningContent?: { redactedContent: string };
108
+ };
109
+ }
110
+
111
+ function kiroToolWireNames(tools: readonly unknown[]): string[] {
112
+ return tools
113
+ .map(tool => {
114
+ const spec = (tool as { toolSpecification?: { name?: unknown } }).toolSpecification;
115
+ return typeof spec?.name === "string" ? spec.name : undefined;
116
+ })
117
+ .filter((name): name is string => typeof name === "string");
118
+ }
119
+
120
+ function userContentText(content: string | OcxContentPart[]): string {
121
+ if (typeof content === "string") return content;
122
+ return content.map(p => (p.type === "text" ? p.text : "")).filter(Boolean).join("\n");
123
+ }
124
+
125
+ function usageContentText(content: string | OcxContentPart[]): string {
126
+ if (typeof content === "string") return content;
127
+ return content
128
+ .map(p => {
129
+ if (p.type === "text") return p.text;
130
+ if (p.type === "image") return `[image:${p.detail ?? "auto"}]`;
131
+ return "";
132
+ })
133
+ .filter(Boolean)
134
+ .join("\n");
135
+ }
136
+ function serializeForUsage(value: unknown): string {
137
+ try { return JSON.stringify(value); } catch { return String(value); }
138
+ }
139
+ function currentTurnUsageMessages(messages: OcxMessage[]): OcxMessage[] {
140
+ return messages.slice(messages.map(m => m.role).lastIndexOf("assistant") + 1).filter(m => m.role !== "assistant");
141
+ }
142
+ function kiroPayloadMessages(parsed: OcxParsedRequest): OcxMessage[] {
143
+ return parsed.context.messages;
144
+ }
145
+
146
+ function messageUsageText(msg: OcxMessage): string {
147
+ switch (msg.role) {
148
+ case "user":
149
+ case "developer":
150
+ return usageContentText(msg.content);
151
+ case "toolResult":
152
+ return [
153
+ msg.toolName,
154
+ msg.toolCallId,
155
+ msg.isError ? "error" : "success",
156
+ usageContentText(msg.content),
157
+ ].filter(Boolean).join("\n");
158
+ case "assistant":
159
+ return "";
160
+ }
161
+ }
162
+
163
+ function messageLogText(msg: OcxMessage): string {
164
+ if (msg.role !== "assistant") return messageUsageText(msg);
165
+ return msg.content.map(part => {
166
+ if (part.type === "text") return part.text;
167
+ if (part.type === "toolCall") return [part.name, part.id, serializeForUsage(part.arguments)].join("\n");
168
+ return part.thinking;
169
+ }).filter(Boolean).join("\n");
170
+ }
171
+
172
+ function estimateKiroImageTokens(image: KiroImage): number {
173
+ const dimensions = sniffImageDimensions(image.source.bytes);
174
+ if (dimensions) {
175
+ return Math.max(256, Math.ceil(dimensions.width * dimensions.height / 750));
176
+ }
177
+ const decodedBytes = Math.floor(image.source.bytes.length * 3 / 4);
178
+ return Math.max(256, Math.ceil(decodedBytes / 512));
179
+ }
180
+
181
+ function estimateKiroTokens(text: string, modelId?: string): number {
182
+ return estimateTokens(text, modelId ? `kiro/${modelId}` : "kiro");
183
+ }
184
+
185
+ function estimateKiroPayloadInputTokens(payload: Record<string, unknown>, modelId: string): number {
186
+ const conversationState = (payload as {
187
+ conversationState?: {
188
+ history?: KiroHistoryEntry[];
189
+ currentMessage?: KiroHistoryEntry;
190
+ };
191
+ }).conversationState;
192
+ if (!conversationState) return 0;
193
+
194
+ const parts: string[] = [];
195
+ let imageTokens = 0;
196
+ const entries = [
197
+ ...(conversationState.history ?? []),
198
+ ...(conversationState.currentMessage ? [conversationState.currentMessage] : []),
199
+ ];
200
+ for (const entry of entries) {
201
+ const user = entry.userInputMessage;
202
+ if (user) {
203
+ if (user.content) parts.push(user.content);
204
+ for (const image of user.images ?? []) imageTokens += estimateKiroImageTokens(image);
205
+ const context = user.userInputMessageContext;
206
+ if (context?.tools?.length) parts.push(serializeForUsage(context.tools));
207
+ if (context?.toolResults?.length) parts.push(serializeForUsage(context.toolResults));
208
+ }
209
+ const assistant = entry.assistantResponseMessage;
210
+ if (assistant) {
211
+ if (assistant.content) parts.push(assistant.content);
212
+ if (assistant.toolUses?.length) parts.push(serializeForUsage(assistant.toolUses));
213
+ }
214
+ }
215
+ return estimateKiroTokens(parts.join("\n"), modelId) + imageTokens;
216
+ }
217
+
218
+ function shouldCountStablePromptOverhead(parsed: OcxParsedRequest): boolean {
219
+ return !parsed.previousResponseId && !parsed.context.messages.some(m => m.role === "assistant");
220
+ }
221
+
222
+ function estimateKiroInputTokens(parsed: OcxParsedRequest): number {
223
+ const parts = currentTurnUsageMessages(parsed.context.messages)
224
+ .map(messageUsageText)
225
+ .filter(Boolean);
226
+
227
+ if (shouldCountStablePromptOverhead(parsed)) {
228
+ if (parsed.context.systemPrompt?.length) parts.push(...parsed.context.systemPrompt);
229
+ if (parsed.context.tools?.length) parts.push(serializeForUsage(parsed.context.tools));
230
+ }
231
+
232
+ return estimateKiroTokens(parts.join("\n"), parsed.modelId);
233
+ }
234
+
235
+ function estimateKiroLogInputTokens(parsed: OcxParsedRequest): number {
236
+ const parts = parsed.context.messages.map(messageLogText).filter(Boolean);
237
+ if (parsed.context.systemPrompt?.length) parts.push(...parsed.context.systemPrompt);
238
+ if (parsed.context.tools?.length) parts.push(serializeForUsage(parsed.context.tools));
239
+ return Math.max(estimateKiroInputTokens(parsed), estimateKiroTokens(parts.join("\n"), parsed.modelId));
240
+ }
241
+
242
+ function kiroUpstreamContextWindow(modelId: string | undefined): number | undefined {
243
+ if (!modelId) return undefined;
244
+ const normalizedModelId = normalizeKiroModelId(modelId);
245
+ if (normalizedModelId === "auto") return undefined;
246
+ const window = modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, modelId)
247
+ ?? modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, normalizedModelId);
248
+ return typeof window === "number" && Number.isFinite(window) && window > 0 ? window : undefined;
249
+ }
250
+
251
+ function kiroRuntimeEndpoint(provider: OcxProviderConfig, region: string): string {
252
+ const configured = new URL(provider.baseUrl);
253
+ if (
254
+ /^runtime\.[a-z]{2}(?:-[a-z]+)+-\d\.kiro\.dev$/i.test(configured.hostname)
255
+ && configured.pathname === "/"
256
+ ) {
257
+ return `https://runtime.${region}.kiro.dev/`;
258
+ }
259
+ return configured.toString();
260
+ }
261
+
262
+ export type KiroReasoningMode = "native" | "emulated";
263
+
264
+ // Kiro takes a verified native effort field for these models, and each model family names it
265
+ // differently: the Sol-only `reasoning.effort` versus the Claude-specific `output_config.effort`.
266
+ // Models absent from this table fall back to emulated thinking instructions.
267
+ const KIRO_NATIVE_EFFORT_FIELDS: Record<string, "reasoning" | "output_config"> = {
268
+ "gpt-5.6-sol": "reasoning",
269
+ "claude-opus-5": "output_config",
270
+ };
271
+
272
+ const KIRO_NATIVE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
273
+
274
+ function kiroNativeEffortField(modelId: string): "reasoning" | "output_config" | undefined {
275
+ return KIRO_NATIVE_EFFORT_FIELDS[normalizeKiroModelId(modelId)];
276
+ }
277
+
278
+ export function kiroReasoningMode(modelId: string): KiroReasoningMode {
279
+ return kiroNativeEffortField(modelId) ? "native" : "emulated";
280
+ }
281
+
282
+ function kiroThinkingBudget(parsed: OcxParsedRequest): number | undefined {
283
+ const effort = parsed.options.reasoning;
284
+ if (!effort || effort === "none") return undefined;
285
+ const maxTokens = parsed.options.maxOutputTokens || 4096;
286
+ const percent: Record<string, number> = {
287
+ minimal: 0.10,
288
+ low: 0.20,
289
+ medium: 0.50,
290
+ high: 0.80,
291
+ xhigh: 0.90,
292
+ max: 0.95,
293
+ };
294
+ const ratio = percent[effort];
295
+ return ratio === undefined ? undefined : Math.max(1, Math.floor(maxTokens * ratio));
296
+ }
297
+
298
+ function injectKiroThinkingTags(content: string, parsed: OcxParsedRequest): string {
299
+ if (kiroReasoningMode(parsed.modelId) !== "emulated") return content;
300
+ const budget = kiroThinkingBudget(parsed);
301
+ if (!budget) return content;
302
+ const instruction = [
303
+ "Think in English for better reasoning quality.",
304
+ "Be thorough and systematic, consider edge cases, challenge assumptions, and verify reasoning before answering.",
305
+ "After thinking, respond in the user's language.",
306
+ ].join("\n");
307
+ return [
308
+ "<thinking_mode>enabled</thinking_mode>",
309
+ `<max_thinking_length>${budget}</max_thinking_length>`,
310
+ `<thinking_instruction>${instruction}</thinking_instruction>`,
311
+ "",
312
+ content,
313
+ ].join("\n");
314
+ }
315
+
316
+ function validateKiroCapabilities(parsed: OcxParsedRequest): void {
317
+ const choice = parsed.options.toolChoice;
318
+ if (choice !== undefined && choice !== "auto" && choice !== "none") {
319
+ throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
320
+ }
321
+ if (parsed.options.parallelToolCalls === true) {
322
+ throw new Error("Kiro does not support parallel tool calls");
323
+ }
324
+ if (parsed.options.serviceTier !== undefined) {
325
+ throw new Error("Kiro does not support service tiers");
326
+ }
327
+ const raw = parsed._rawBody as Record<string, unknown> | undefined;
328
+ if (parsed._structuredOutput || raw?.text !== undefined) {
329
+ throw new Error("Kiro does not support Responses text controls or structured output");
330
+ }
331
+ }
332
+
333
+ type KiroTurn =
334
+ | { kind: "user"; content: string; images: KiroImage[]; toolResults: KiroToolResult[] }
335
+ | { kind: "assistant"; content: string; toolUses: KiroToolUse[]; redactedReasoning?: string };
336
+
337
+ function appendTurnText(target: string, next: string): string {
338
+ if (!next) return target;
339
+ return target ? `${target}\n\n${next}` : next;
340
+ }
341
+
342
+ function validateKiroConversationState(history: KiroHistoryEntry[], currentMessage: KiroHistoryEntry): void {
343
+ const entries = [...history, currentMessage];
344
+ const pendingToolUses = new Set<string>();
345
+ let previousRole: "user" | "assistant" | undefined;
346
+
347
+ for (const entry of entries) {
348
+ const user = entry.userInputMessage;
349
+ const assistant = entry.assistantResponseMessage;
350
+ if (Boolean(user) === Boolean(assistant)) {
351
+ throw new Error("Kiro conversation entries must contain exactly one message role");
352
+ }
353
+ const role = user ? "user" : "assistant";
354
+ if (role === previousRole) throw new Error("Kiro conversation roles must alternate");
355
+ previousRole = role;
356
+
357
+ if (user) {
358
+ const hasPayload = Boolean(user.content.trim())
359
+ || Boolean(user.images?.length)
360
+ || Boolean(user.userInputMessageContext?.toolResults?.length);
361
+ if (!hasPayload) throw new Error("Kiro user messages must not be empty");
362
+ for (const result of user.userInputMessageContext?.toolResults ?? []) {
363
+ if (!pendingToolUses.delete(result.toolUseId)) {
364
+ throw new Error(`Kiro tool result has no matching tool use ${JSON.stringify(result.toolUseId)}`);
365
+ }
366
+ if (!result.content.some(part => part.text.trim())) {
367
+ throw new Error(`Kiro tool result must not be empty ${JSON.stringify(result.toolUseId)}`);
368
+ }
369
+ }
370
+ continue;
371
+ }
372
+
373
+ const toolUses = assistant?.toolUses ?? [];
374
+ if (!assistant?.content.trim() && toolUses.length === 0) {
375
+ throw new Error("Kiro assistant messages must not be empty");
376
+ }
377
+ for (const toolUse of toolUses) {
378
+ if (pendingToolUses.has(toolUse.toolUseId)) {
379
+ throw new Error(`Kiro conversation contains duplicate tool use ${JSON.stringify(toolUse.toolUseId)}`);
380
+ }
381
+ pendingToolUses.add(toolUse.toolUseId);
382
+ }
383
+ }
384
+ if (pendingToolUses.size > 0) throw new Error("Kiro conversation contains an unanswered tool use");
385
+ }
386
+
387
+ function boundedInjectedInstruction(text: string, used: { value: number }): string | undefined {
388
+ const remaining = MAX_KIRO_INJECTED_INSTRUCTION_CHARS - used.value;
389
+ if (remaining <= 0 || !text) return undefined;
390
+ let result = text.length <= remaining ? text : text.slice(0, remaining);
391
+ // Never end the slice on a lone high surrogate: encoding it substitutes
392
+ // U+FFFD into the injected instruction. One step back keeps a valid pair
393
+ // out instead of a broken half.
394
+ if (result.length > 0) {
395
+ const last = result.charCodeAt(result.length - 1);
396
+ if (last >= 0xd800 && last <= 0xdbff) result = result.slice(0, -1);
397
+ }
398
+ used.value += result.length;
399
+ return result.length > 0 ? result : undefined;
400
+ }
401
+
402
+ /** Test-only: exercise the surrogate-safe instruction bound directly. */
403
+ export function boundedInjectedInstructionForTests(text: string, used: { value: number }): string | undefined {
404
+ return boundedInjectedInstruction(text, used);
405
+ }
406
+
407
+ function kiroCompletionTool(): Record<string, unknown> {
408
+ return {
409
+ toolSpecification: {
410
+ name: KIRO_COMPLETION_TOOL_NAME,
411
+ description: "Finish the task and return the complete user-facing final answer. Call only when no more work or tool calls are needed.",
412
+ inputSchema: {
413
+ json: {
414
+ type: "object",
415
+ properties: {
416
+ answer: {
417
+ type: "string",
418
+ description: "The complete final answer to show the user.",
419
+ },
420
+ },
421
+ required: ["answer"],
422
+ },
423
+ },
424
+ },
425
+ };
426
+ }
427
+
428
+ export function buildKiroPayload(
429
+ parsed: OcxParsedRequest,
430
+ profileArn: string | undefined,
431
+ forcedCompletionMode?: KiroCompletionMode,
432
+ wireClient: KiroWireClient = "ide",
433
+ ): {
434
+ payload: Record<string, unknown>;
435
+ nameMap: Map<string, string>;
436
+ conversationId: string;
437
+ completionMode: KiroCompletionMode;
438
+ } {
439
+ validateKiroCapabilities(parsed);
440
+ const modelId = mapModelId(parsed.modelId);
441
+ const registry = createKiroToolNameRegistry();
442
+ const toolContext = convertKiroToolContext(parsed, registry);
443
+ const ordinaryTools = toolContext.tools;
444
+ const completionMode: KiroCompletionMode = forcedCompletionMode
445
+ ?? (ordinaryTools.length > 0 ? "required" : "disabled");
446
+ const kiroTools = completionMode === "disabled"
447
+ ? ordinaryTools
448
+ : [...ordinaryTools, kiroCompletionTool()];
449
+ const nameMap = toolContext.nameMap;
450
+ const systemParts: string[] = [];
451
+ const injectedChars = { value: 0 };
452
+ // Name the Kiro model id actually sent on the wire without leaking the proxy identity upstream.
453
+ if (parsed.context.systemPrompt?.length) {
454
+ systemParts.push(identifyRoutedModel(parsed.context.systemPrompt.join("\n\n"), modelId));
455
+ }
456
+ for (const addition of toolContext.systemAdditions) {
457
+ const boundedAddition = boundedInjectedInstruction(addition, injectedChars);
458
+ if (boundedAddition) systemParts.push(boundedAddition);
459
+ }
460
+ // Kiro renames tools to satisfy its wire constraints, so resolve neighbor names through the
461
+ // registry's existing aliases; a bare-name comparison would forbid tools this turn actually
462
+ // advertises. Read the recorded mapping instead of calling `alias()`, which would REGISTER a
463
+ // name for a tool that was never advertised and pollute the collision domain.
464
+ const advertisedAlias = new Map<string, string>();
465
+ for (const [alias, wireName] of registry.nameMap) advertisedAlias.set(wireName, alias);
466
+ const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeFromNames(
467
+ kiroToolWireNames(kiroTools),
468
+ name => advertisedAlias.get(name) ?? name,
469
+ );
470
+ const boundedNudge = toolCatalogNudge ? boundedInjectedInstruction(toolCatalogNudge, injectedChars) : undefined;
471
+ if (boundedNudge) systemParts.push(boundedNudge);
472
+ if (completionMode !== "disabled") {
473
+ const boundedCompletion = boundedInjectedInstruction(KIRO_COMPLETION_INSTRUCTIONS, injectedChars);
474
+ if (boundedCompletion) systemParts.push(boundedCompletion);
475
+ }
476
+ const systemPrefix = systemParts.length > 0 ? `${systemParts.join("\n\n")}\n\n` : "";
477
+ const turns: KiroTurn[] = [];
478
+ const priorCalls = new Map<string, { wireName: string }>();
479
+ const pushUser = (content: string, images: KiroImage[] = [], toolResults: KiroToolResult[] = []): void => {
480
+ const last = turns.at(-1);
481
+ if (last?.kind === "user") {
482
+ last.content = appendTurnText(last.content, content);
483
+ last.images.push(...images);
484
+ last.toolResults.push(...toolResults);
485
+ } else {
486
+ turns.push({ kind: "user", content, images: [...images], toolResults: [...toolResults] });
487
+ }
488
+ };
489
+ const pushAssistant = (content: string, toolUses: KiroToolUse[], redactedReasoning?: string): void => {
490
+ const last = turns.at(-1);
491
+ if (last?.kind === "assistant") {
492
+ last.content = appendTurnText(last.content, content);
493
+ last.toolUses.push(...toolUses);
494
+ // Merged turns keep the newest blob: it covers the reasoning up to the merged turn's end.
495
+ if (redactedReasoning) last.redactedReasoning = redactedReasoning;
496
+ } else {
497
+ turns.push({ kind: "assistant", content, toolUses: [...toolUses], ...(redactedReasoning ? { redactedReasoning } : {}) });
498
+ }
499
+ };
500
+
501
+ for (const msg of kiroPayloadMessages(parsed)) {
502
+ if (msg.role === "user" || msg.role === "developer") {
503
+ const text = userContentText((msg as { content: string | OcxContentPart[] }).content);
504
+ const images = extractKiroImages((msg as { content: string | OcxContentPart[] }).content);
505
+ pushUser(text, images);
506
+ } else if (msg.role === "assistant") {
507
+ const aMsg = msg as OcxAssistantMessage;
508
+ const text = (aMsg.content || [])
509
+ .filter((b): b is OcxTextContent => b.type === "text")
510
+ .map(b => b.text)
511
+ .join("");
512
+ const toolCalls = (aMsg.content || [])
513
+ .filter((b): b is OcxToolCall => b.type === "toolCall");
514
+ const toolUses: KiroToolUse[] = toolCalls.map(tc => {
515
+ const toolUseId = normalizeToolId(tc.id);
516
+ if (!toolUseId) throw new Error("Kiro history contains a tool call with an empty id");
517
+ if (priorCalls.has(toolUseId)) throw new Error(`Kiro history contains duplicate tool call id ${JSON.stringify(tc.id)}`);
518
+ const wireName = namespacedToolName(tc.namespace, tc.name);
519
+ const name = registry.alias(wireName);
520
+ priorCalls.set(toolUseId, { wireName });
521
+ return { name, input: (tc.arguments ?? {}) as Record<string, unknown>, toolUseId };
522
+ });
523
+ if (!text && toolUses.length === 0) {
524
+ const hasReasoning = aMsg.content.some(part => part.type === "thinking" && part.thinking.trim());
525
+ if (hasReasoning) continue;
526
+ }
527
+ pushAssistant(text, toolUses, aMsg.kiroRedactedReasoning);
528
+ } else if (msg.role === "toolResult") {
529
+ const tr = msg as OcxToolResultMessage;
530
+ if (tr.containsEncryptedContent) {
531
+ throw new Error(`Kiro cannot translate encrypted output for tool call ${JSON.stringify(tr.toolCallId)}`);
532
+ }
533
+ const text = userContentText(tr.content);
534
+ const resultText = text.trim() ? text : KIRO_EMPTY_TOOL_RESULT_MESSAGE;
535
+ const images = extractKiroImages(tr.content);
536
+ const toolUseId = normalizeToolId(tr.toolCallId);
537
+ if (!priorCalls.has(toolUseId)) {
538
+ throw new Error(`Kiro history contains an orphaned tool result for call ${JSON.stringify(tr.toolCallId)}`);
539
+ }
540
+ // Carrier text is a placeholder for an OTHERWISE EMPTY tool-result turn, not a prefix.
541
+ // Passing it here would push proxy filler AHEAD of a human instruction that Claude Code
542
+ // sends in the same turn (mid-turn steering / queued_command, issue #543), burying the
543
+ // newest user intent behind boilerplate. Backfill below only when nothing else speaks.
544
+ pushUser("", images, [{
545
+ content: [{ text: resultText }],
546
+ status: tr.isError ? "error" : "success",
547
+ toolUseId,
548
+ }]);
549
+ }
550
+ }
551
+
552
+ if (turns.length === 0 || turns[0].kind === "assistant") {
553
+ turns.unshift({ kind: "user", content: KIRO_CONTINUATION_MESSAGE, images: [], toolResults: [] });
554
+ }
555
+ if (turns.at(-1)?.kind === "assistant") {
556
+ turns.push({
557
+ kind: "user",
558
+ content: completionMode === "text_fallback" ? KIRO_COMPLETION_RETRY_MESSAGE : KIRO_CONTINUATION_MESSAGE,
559
+ images: [],
560
+ toolResults: [],
561
+ });
562
+ }
563
+
564
+ // Give tool-result turns a carrier sentence ONLY when they carry no other text. This runs
565
+ // before the pop below so the current turn is covered too: skipping it there would ship an
566
+ // empty current content, which validateKiroConversationState accepts (tool results count as
567
+ // payload) and would therefore fail silently.
568
+ for (const turn of turns) {
569
+ if (turn.kind === "user" && !turn.content.trim() && turn.toolResults.length > 0) {
570
+ turn.content = KIRO_TOOL_RESULT_CARRIER_MESSAGE;
571
+ }
572
+ }
573
+
574
+ const currentTurn = turns.pop();
575
+ if (!currentTurn || currentTurn.kind !== "user") throw new Error("Kiro request must end with a user turn");
576
+ const toEntry = (turn: KiroTurn): KiroHistoryEntry => turn.kind === "assistant"
577
+ ? {
578
+ assistantResponseMessage: {
579
+ content: turn.content,
580
+ ...(turn.toolUses.length > 0 ? { toolUses: turn.toolUses } : {}),
581
+ ...(turn.redactedReasoning ? { reasoningContent: { redactedContent: turn.redactedReasoning } } : {}),
582
+ },
583
+ }
584
+ : {
585
+ userInputMessage: {
586
+ content: turn.content,
587
+ modelId,
588
+ origin: wireClient === "cli" ? "KIRO_CLI" : "AI_EDITOR",
589
+ ...(turn.images.length > 0 ? { images: turn.images } : {}),
590
+ ...(turn.toolResults.length > 0 ? { userInputMessageContext: { toolResults: turn.toolResults } } : {}),
591
+ },
592
+ };
593
+ const history = turns.map(toEntry);
594
+ const currentEntry = toEntry(currentTurn);
595
+ const currentUim = currentEntry.userInputMessage!;
596
+
597
+ if (systemPrefix) {
598
+ const firstUser = history.find(e => e.userInputMessage)?.userInputMessage;
599
+ if (firstUser) firstUser.content = systemPrefix + firstUser.content;
600
+ else currentUim.content = systemPrefix + currentUim.content;
601
+ }
602
+ if (kiroTools.length > 0) {
603
+ currentUim.userInputMessageContext = { ...(currentUim.userInputMessageContext ?? {}), tools: kiroTools };
604
+ }
605
+ if (completionMode === "text_fallback") {
606
+ if (currentUim.content !== KIRO_COMPLETION_RETRY_MESSAGE) {
607
+ currentUim.content = appendTurnText(currentUim.content, KIRO_COMPLETION_RETRY_MESSAGE);
608
+ }
609
+ } else if (!currentUim.userInputMessageContext?.toolResults && currentUim.content !== KIRO_CONTINUATION_MESSAGE) {
610
+ currentUim.content = injectKiroThinkingTags(currentUim.content, parsed);
611
+ }
612
+
613
+ validateKiroConversationState(history, currentEntry);
614
+ const conversationId = stableConversationId(parsed);
615
+ const payload: Record<string, unknown> = {
616
+ conversationState: {
617
+ chatTriggerType: "MANUAL",
618
+ ...(wireClient === "cli" ? {
619
+ agentContinuationId: crypto.randomUUID(),
620
+ agentTaskType: "vibe",
621
+ } : {}),
622
+ conversationId,
623
+ currentMessage: { userInputMessage: currentUim },
624
+ ...(history.length > 0 ? { history } : {}),
625
+ },
626
+ };
627
+ const effort = parsed.options.reasoning;
628
+ const effortField = kiroNativeEffortField(parsed.modelId);
629
+ if (effortField && effort && effort !== "none") {
630
+ if (!KIRO_NATIVE_EFFORTS.includes(effort)) {
631
+ throw new Error(`Kiro ${normalizeKiroModelId(parsed.modelId)} does not support reasoning effort ${JSON.stringify(effort)}`);
632
+ }
633
+ payload.additionalModelRequestFields = { [effortField]: { effort } };
634
+ }
635
+ if (profileArn) payload.profileArn = profileArn;
636
+ return { payload, nameMap, conversationId, completionMode };
637
+ }
638
+
639
+ // Stream parsing (shared by parseStream + parseResponse)
640
+ // CodeWhisperer GenerateAssistantResponse ALWAYS returns an AWS eventstream body (there is no
641
+ // non-streaming wire mode), so the streaming bridge and non-streaming Responses path decode the
642
+ // same way — parseResponse just collects what parseStream yields.
643
+ interface KiroAttemptParseResult {
644
+ terminal?: AdapterEvent;
645
+ needsFallback?: boolean;
646
+ usage?: OcxUsage;
647
+ providerState?: { kiro: { conversationId: string } };
648
+ assistantText: string;
649
+ sawReasoning: boolean;
650
+ }
651
+
652
+ interface KiroAttemptResult extends KiroAttemptParseResult {
653
+ releaseRetained(): void;
654
+ }
655
+
656
+ interface KiroAttemptRetention {
657
+ trackReplacement(previousBytes: number, nextBytes: number): void;
658
+ retainEvent(event: AdapterEvent, bytes: number): void;
659
+ releaseEvent(event: AdapterEvent): void;
660
+ releaseAll(): void;
661
+ }
662
+
663
+ function createKiroAttemptRetention(budget: TranslatorBudget): KiroAttemptRetention {
664
+ let retainedBytes = 0;
665
+ const eventBytes = new Map<AdapterEvent, number>();
666
+ return {
667
+ trackReplacement(previousBytes, nextBytes) {
668
+ retainedBytes = Math.max(0, retainedBytes - previousBytes) + nextBytes;
669
+ },
670
+ retainEvent(event, bytes) {
671
+ retainedBytes += bytes;
672
+ eventBytes.set(event, bytes);
673
+ },
674
+ releaseEvent(event) {
675
+ const bytes = eventBytes.get(event);
676
+ if (bytes === undefined) return;
677
+ eventBytes.delete(event);
678
+ retainedBytes = Math.max(0, retainedBytes - bytes);
679
+ budget.releaseRetained(bytes, { kind: "retained_collectors" });
680
+ },
681
+ releaseAll() {
682
+ if (retainedBytes > 0) budget.releaseRetained(retainedBytes, { kind: "retained_collectors" });
683
+ retainedBytes = 0;
684
+ eventBytes.clear();
685
+ },
686
+ };
687
+ }
688
+
689
+ interface KiroFallbackAttempt {
690
+ response: Response;
691
+ inputTokens: number;
692
+ contextInputEstimate: number;
693
+ nameMap: Map<string, string>;
694
+ conversationId: string;
695
+ releaseRequestBody?: () => void;
696
+ }
697
+
698
+ function appendedUtf8Bytes(previous: string, previousBytes: number, fragment: string): number {
699
+ let nextBytes = previousBytes + Buffer.byteLength(fragment);
700
+ const previousLast = previous.charCodeAt(previous.length - 1);
701
+ const fragmentFirst = fragment.charCodeAt(0);
702
+ if (previousLast >= 0xd800 && previousLast <= 0xdbff
703
+ && fragmentFirst >= 0xdc00 && fragmentFirst <= 0xdfff) {
704
+ nextBytes -= 2;
705
+ }
706
+ return nextBytes;
707
+ }
708
+
709
+ /** Exact UTF-8 size JSON.stringify() will use for a string, without materializing that copy. */
710
+ function jsonStringSerializedUtf8Bytes(value: string): number {
711
+ let bytes = 2; // Opening and closing quotes.
712
+ for (let index = 0; index < value.length; index++) {
713
+ const code = value.charCodeAt(index);
714
+ if (code === 0x22 || code === 0x5c) {
715
+ bytes += 2;
716
+ } else if (code === 0x08 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d) {
717
+ bytes += 2;
718
+ } else if (code < 0x20) {
719
+ bytes += 6;
720
+ } else if (code <= 0x7f) {
721
+ bytes += 1;
722
+ } else if (code <= 0x7ff) {
723
+ bytes += 2;
724
+ } else if (code >= 0xd800 && code <= 0xdbff) {
725
+ const next = value.charCodeAt(index + 1);
726
+ if (next >= 0xdc00 && next <= 0xdfff) {
727
+ bytes += 4;
728
+ index++;
729
+ } else {
730
+ bytes += 6;
731
+ }
732
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
733
+ bytes += 6;
734
+ } else {
735
+ bytes += 3;
736
+ }
737
+ }
738
+ return bytes;
739
+ }
740
+
741
+ interface KiroContextWindowState {
742
+ value?: number;
743
+ }
744
+
745
+ type KiroFallbackFactory = (
746
+ conversationId: string | undefined,
747
+ assistantText: string,
748
+ sawReasoning: boolean,
749
+ budget: TranslatorBudget,
750
+ ) => Promise<KiroFallbackAttempt>;
751
+
752
+ function mergeKiroUsage(
753
+ first: OcxUsage | undefined,
754
+ second: OcxUsage | undefined,
755
+ preserveFirstContextGrowth = false,
756
+ ): OcxUsage | undefined {
757
+ if (!first) return second;
758
+ if (!second) return first;
759
+ const sumOptional = (key: keyof OcxUsage): number | undefined => {
760
+ const a = first[key];
761
+ const b = second[key];
762
+ return typeof a === "number" || typeof b === "number"
763
+ ? (typeof a === "number" ? a : 0) + (typeof b === "number" ? b : 0)
764
+ : undefined;
765
+ };
766
+ const totalTokens = typeof first.totalTokens === "number" && typeof second.totalTokens === "number"
767
+ ? first.totalTokens + second.totalTokens
768
+ : undefined;
769
+ const carriedContextTotal = preserveFirstContextGrowth && typeof first.contextTotalTokens === "number"
770
+ ? first.contextTotalTokens + second.outputTokens
771
+ : undefined;
772
+ const combinedOutputTokens = first.outputTokens + second.outputTokens;
773
+ return {
774
+ inputTokens: first.inputTokens + second.inputTokens,
775
+ outputTokens: combinedOutputTokens,
776
+ ...(typeof first.contextTotalTokens === "number" || typeof second.contextTotalTokens === "number"
777
+ ? {
778
+ contextTotalTokens: Math.max(
779
+ first.contextTotalTokens ?? 0,
780
+ second.contextTotalTokens ?? 0,
781
+ carriedContextTotal ?? 0,
782
+ combinedOutputTokens,
783
+ ),
784
+ }
785
+ : {}),
786
+ ...(totalTokens !== undefined ? { totalTokens } : {}),
787
+ ...(sumOptional("cachedInputTokens") !== undefined ? { cachedInputTokens: sumOptional("cachedInputTokens") } : {}),
788
+ ...(sumOptional("cacheReadInputTokens") !== undefined ? { cacheReadInputTokens: sumOptional("cacheReadInputTokens") } : {}),
789
+ ...(sumOptional("cacheCreationInputTokens") !== undefined ? { cacheCreationInputTokens: sumOptional("cacheCreationInputTokens") } : {}),
790
+ ...(sumOptional("reasoningOutputTokens") !== undefined ? { reasoningOutputTokens: sumOptional("reasoningOutputTokens") } : {}),
791
+ ...(first.estimated || second.estimated ? { estimated: true } : {}),
792
+ };
793
+ }
794
+
795
+ function retryableKiroIncomplete(
796
+ reason: string,
797
+ message: string,
798
+ usage: OcxUsage,
799
+ providerState: { kiro: { conversationId: string } } | undefined,
800
+ retryable = true,
801
+ ): AdapterEvent {
802
+ return {
803
+ type: "incomplete",
804
+ reason,
805
+ message,
806
+ usage,
807
+ retryable,
808
+ endTurn: false,
809
+ ...(providerState ? { providerState } : {}),
810
+ };
811
+ }
812
+
813
+ /**
814
+ * Catch-path retryability for #519: only transport/socket failures with no emitted output
815
+ * are replay-safe. Malformed event payloads (`invalid Kiro …`) and any post-output failure
816
+ * stay terminal — same spirit as cursor's emittedOutput gate.
817
+ */
818
+ export function isRetryableKiroStreamCatchError(err: unknown, emittedOutput: boolean): boolean {
819
+ if (emittedOutput) return false;
820
+ const message = err instanceof Error ? err.message : String(err);
821
+ if (/^invalid Kiro\b/i.test(message)) return false;
822
+ // Include Smithy/eventstream truncation (`eventstream: truncated message at end of stream`):
823
+ // partial frame + clean EOF with zero output is the same replay-safe class as a socket close.
824
+ return /socket connection was closed|connection(?: was)? closed unexpectedly|ECONNRESET|EPIPE|UND_ERR_|fetch failed|decoder failed|premature close|other side closed|unexpected EOF|network connection lost|terminated|truncated message at end of stream|eventstream:\s*truncated/i
825
+ .test(message);
826
+ }
827
+
828
+ /** Native clean-stop reason eligible for bounded private-completion validation. */
829
+ const KIRO_END_TURN_STOP_REASON = "END_TURN";
830
+
831
+ async function* parseKiroAttempt(
832
+ response: Response,
833
+ budget: TranslatorBudget,
834
+ mode: KiroCompletionMode,
835
+ modelId: string | undefined,
836
+ inputTokens: number,
837
+ contextWindowState: KiroContextWindowState,
838
+ nameMap: Map<string, string> | undefined,
839
+ conversationId: string | undefined,
840
+ contextInputEstimate?: number,
841
+ /** True when an earlier attempt already flushed visible content to the client (#520). */
842
+ priorEmittedOutput = false,
843
+ ): AsyncGenerator<AdapterEvent, KiroAttemptResult> {
844
+ // `required` mode holds staged commentary until a real tool call or terminal metadata identifies
845
+ // the attempt boundary. Anything the inner parser leaves behind is flushed before the terminal.
846
+ const deferred: AdapterEvent[] = [];
847
+ const retention = createKiroAttemptRetention(budget);
848
+ const attempt = parseKiroAttemptEvents(
849
+ response,
850
+ budget,
851
+ mode,
852
+ modelId,
853
+ inputTokens,
854
+ contextWindowState,
855
+ nameMap,
856
+ conversationId,
857
+ deferred,
858
+ retention,
859
+ contextInputEstimate,
860
+ priorEmittedOutput,
861
+ );
862
+ let handedOff = false;
863
+ try {
864
+ const result = yield* attempt;
865
+ for (const event of deferred.splice(0)) {
866
+ try { yield event; } finally { retention.releaseEvent(event); }
867
+ }
868
+ handedOff = true;
869
+ return { ...result, releaseRetained: () => retention.releaseAll() };
870
+ } finally {
871
+ if (!handedOff) retention.releaseAll();
872
+ }
873
+ }
874
+
875
+ async function* parseKiroAttemptEvents(
876
+ response: Response,
877
+ budget: TranslatorBudget,
878
+ mode: KiroCompletionMode,
879
+ modelId: string | undefined,
880
+ inputTokens: number,
881
+ contextWindowState: KiroContextWindowState,
882
+ nameMap: Map<string, string> | undefined,
883
+ conversationId: string | undefined,
884
+ deferred: AdapterEvent[],
885
+ retention: KiroAttemptRetention,
886
+ contextInputEstimate?: number,
887
+ priorEmittedOutput = false,
888
+ ): AsyncGenerator<AdapterEvent, KiroAttemptParseResult> {
889
+ const emptyResult = (): KiroAttemptParseResult => ({ assistantText: "", sawReasoning: false });
890
+ if (!response.body) {
891
+ return {
892
+ ...emptyResult(),
893
+ terminal: { type: "error", message: "Kiro response has no body", status: 502, errorType: "upstream_error" },
894
+ };
895
+ }
896
+
897
+ let open: { id: string; name: string; chunks: string[]; completion: boolean } | null = null;
898
+ let openCallId: string | undefined;
899
+ const closeOpenCall = () => {
900
+ if (!openCallId) return;
901
+ budget.closeCall(openCallId);
902
+ openCallId = undefined;
903
+ };
904
+ let outputChars = "";
905
+ let outputCharsBytes = 0;
906
+ let contextUsagePercentage: number | undefined;
907
+ let returnedConversationId = conversationId;
908
+ let assistantText = "";
909
+ let assistantTextBytes = 0;
910
+ let sawText = false;
911
+ let sawReasoning = false;
912
+ let sawRealTool = false;
913
+ let completionAnswer: string | undefined;
914
+ let completionCalls = 0;
915
+ let authoritativeUsage: OcxUsage | undefined;
916
+ let stopReason: string | undefined;
917
+ const fallbackEvents: AdapterEvent[] = [];
918
+ const thinking = new KiroThinkingParser(budget);
919
+
920
+ const retainedEventBytes = (event: AdapterEvent): number => Buffer.byteLength(JSON.stringify(event));
921
+ const retainEvent = (event: AdapterEvent): void => {
922
+ const bytes = retainedEventBytes(event);
923
+ budget.chargeRetained(bytes, { kind: "retained_collectors" });
924
+ retention.retainEvent(event, bytes);
925
+ };
926
+ const emitRetained = async function* (events: Iterable<AdapterEvent>): AsyncGenerator<AdapterEvent> {
927
+ for (const event of events) {
928
+ try { yield event; } finally { retention.releaseEvent(event); }
929
+ }
930
+ };
931
+
932
+ const providerState = (): { kiro: { conversationId: string } } | undefined =>
933
+ returnedConversationId ? { kiro: { conversationId: returnedConversationId } } : undefined;
934
+
935
+ const contextUsageTotalFloor = (): number | undefined => {
936
+ if (contextUsagePercentage === undefined || !contextWindowState.value) return undefined;
937
+ const floor = Math.ceil(contextWindowState.value * Math.min(contextUsagePercentage, 100) / 100);
938
+ return Number.isFinite(floor) && floor > 0 ? floor : undefined;
939
+ };
940
+ const usage = (): OcxUsage => {
941
+ const base = authoritativeUsage ?? {
942
+ inputTokens,
943
+ outputTokens: estimateKiroTokens(outputChars, modelId),
944
+ estimated: true,
945
+ };
946
+ const estimatedContextTotal = contextInputEstimate !== undefined
947
+ ? contextInputEstimate + base.outputTokens
948
+ : undefined;
949
+ const authoritativeTurnTotal = base.inputTokens + base.outputTokens;
950
+ const contextTotal = Math.max(
951
+ estimatedContextTotal ?? 0,
952
+ contextUsageTotalFloor() ?? 0,
953
+ authoritativeTurnTotal,
954
+ );
955
+ return contextTotal > 0 ? { ...base, contextTotalTokens: contextTotal } : base;
956
+ };
957
+
958
+ const classifiedTerminal = (failure: KiroErrorClassification): AdapterEvent => {
959
+ // Upstream exception/error frames can arrive after commentary was already staged (and will be
960
+ // flushed before this terminal is yielded). Replaying after that content would duplicate it.
961
+ const emittedOutput = priorEmittedOutput
962
+ || sawText
963
+ || sawReasoning
964
+ || sawRealTool
965
+ || assistantText.length > 0
966
+ || deferred.length > 0
967
+ || completionAnswer !== undefined
968
+ || completionCalls > 0
969
+ || open !== null
970
+ || fallbackEvents.length > 0;
971
+ if (failure.status === 429 && failure.retryable) noteKiroTransientThrottle();
972
+ return {
973
+ type: "error",
974
+ message: failure.message,
975
+ status: failure.status,
976
+ errorType: failure.errorType,
977
+ code: failure.code,
978
+ retryable: emittedOutput ? false : failure.retryable,
979
+ usage: usage(),
980
+ };
981
+ };
982
+
983
+ const protocolTerminal = (message: string, malformedCompletion = false): AdapterEvent => {
984
+ if (mode === "text_fallback" && malformedCompletion) {
985
+ return retryableKiroIncomplete(
986
+ "malformed_kiro_completion",
987
+ message,
988
+ usage(),
989
+ providerState(),
990
+ // First-attempt progress was already flushed before this bounded fallback (#520).
991
+ !priorEmittedOutput,
992
+ );
993
+ }
994
+ return {
995
+ type: "error",
996
+ message,
997
+ status: 502,
998
+ errorType: "upstream_error",
999
+ code: malformedCompletion ? "invalid_kiro_completion" : "kiro_stream_protocol_error",
1000
+ retryable: false,
1001
+ usage: usage(),
1002
+ };
1003
+ };
1004
+
1005
+ const classifyTool = (
1006
+ tool: { id: string; name: string; chunks: string[]; completion: boolean },
1007
+ ): AdapterEvent | undefined => {
1008
+ if (tool.name !== KIRO_COMPLETION_TOOL_NAME) {
1009
+ tool.completion = false;
1010
+ return completionAnswer !== undefined || completionCalls > 0
1011
+ ? protocolTerminal("Kiro returned a real tool call alongside a private final answer")
1012
+ : undefined;
1013
+ }
1014
+ if (mode === "disabled") {
1015
+ return protocolTerminal("Kiro returned the reserved private final-answer tool while explicit completion was disabled");
1016
+ }
1017
+ tool.completion = true;
1018
+ if (completionAnswer !== undefined || completionCalls > 0) {
1019
+ return protocolTerminal("Kiro returned more than one private final-answer tool call", true);
1020
+ }
1021
+ if (sawRealTool) {
1022
+ return protocolTerminal("Kiro returned a private final answer alongside a real tool call");
1023
+ }
1024
+ return undefined;
1025
+ };
1026
+
1027
+ const beginTool = (
1028
+ id: string,
1029
+ name: string,
1030
+ ): { tool?: { id: string; name: string; chunks: string[]; completion: boolean }; terminal?: AdapterEvent } => {
1031
+ const next = { id, name, chunks: [], completion: false };
1032
+ const terminal = classifyTool(next);
1033
+ return terminal ? { terminal } : { tool: next };
1034
+ };
1035
+
1036
+ // In `required` mode Kiro's stop reason only arrives on the terminal metadata event, so staged
1037
+ // commentary is held until either a real tool call proves the turn continues (flush as
1038
+ // commentary) or the stream ends (relabel as the final answer when END_TURN says so). A heartbeat
1039
+ // stands in for each held event so the bridge's stall watchdog stays armed.
1040
+ const defer = (event: AdapterEvent): AdapterEvent[] => {
1041
+ if (sawRealTool) return [...deferred.splice(0), event];
1042
+ if (event.type !== "text_delta" && deferred.length === 0) return [event];
1043
+ deferred.push(event);
1044
+ retainEvent(event);
1045
+ return [{ type: "heartbeat" }];
1046
+ };
1047
+
1048
+ const stage = (event: AdapterEvent): AdapterEvent[] => {
1049
+ if (event.type === "text_delta") {
1050
+ const nextAssistantTextBytes = appendedUtf8Bytes(assistantText, assistantTextBytes, event.text);
1051
+ const assistantReservation = budget.reserveTransient(nextAssistantTextBytes, { kind: "retained_collectors" });
1052
+ assistantText += event.text;
1053
+ assistantReservation.commitRetained();
1054
+ budget.releaseRetained(assistantTextBytes, { kind: "retained_collectors" });
1055
+ retention.trackReplacement(assistantTextBytes, nextAssistantTextBytes);
1056
+ assistantTextBytes = nextAssistantTextBytes;
1057
+ if (event.text.trim()) sawText = true;
1058
+ const nextOutputCharsBytes = appendedUtf8Bytes(outputChars, outputCharsBytes, event.text);
1059
+ const outputReservation = budget.reserveTransient(nextOutputCharsBytes, { kind: "retained_collectors" });
1060
+ outputChars += event.text;
1061
+ outputReservation.commitRetained();
1062
+ budget.releaseRetained(outputCharsBytes, { kind: "retained_collectors" });
1063
+ retention.trackReplacement(outputCharsBytes, nextOutputCharsBytes);
1064
+ outputCharsBytes = nextOutputCharsBytes;
1065
+ const phased = mode === "disabled"
1066
+ ? event
1067
+ : { ...event, phase: "commentary" as const };
1068
+ if (mode === "text_fallback") {
1069
+ fallbackEvents.push(phased);
1070
+ retainEvent(phased);
1071
+ return [];
1072
+ }
1073
+ return mode === "required" ? defer(phased) : [phased];
1074
+ }
1075
+ if (event.type === "reasoning_raw_delta" || event.type === "thinking_delta") {
1076
+ const text = event.type === "reasoning_raw_delta" ? event.text : event.thinking;
1077
+ if (text.trim()) sawReasoning = true;
1078
+ const nextOutputCharsBytes = appendedUtf8Bytes(outputChars, outputCharsBytes, text);
1079
+ const reasoningReservation = budget.reserveTransient(nextOutputCharsBytes, { kind: "retained_collectors" });
1080
+ outputChars += text;
1081
+ reasoningReservation.commitRetained();
1082
+ budget.releaseRetained(outputCharsBytes, { kind: "retained_collectors" });
1083
+ retention.trackReplacement(outputCharsBytes, nextOutputCharsBytes);
1084
+ outputCharsBytes = nextOutputCharsBytes;
1085
+ }
1086
+ if (mode === "text_fallback" && event.type !== "heartbeat") {
1087
+ fallbackEvents.push(event);
1088
+ retainEvent(event);
1089
+ return [];
1090
+ }
1091
+ return mode === "required" ? defer(event) : [event];
1092
+ };
1093
+
1094
+ const parseCompletion = (chunks: string[]): string | Error => {
1095
+ const raw = chunks.join("").trim();
1096
+ let value: unknown;
1097
+ try {
1098
+ value = JSON.parse(raw || "{}");
1099
+ } catch {
1100
+ return new Error("Kiro returned invalid JSON for the private final-answer tool");
1101
+ }
1102
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1103
+ return new Error("Kiro returned a non-object value for the private final-answer tool");
1104
+ }
1105
+ const answer = (value as { answer?: unknown }).answer;
1106
+ if (typeof answer !== "string" || !answer.trim()) {
1107
+ return new Error("Kiro returned an empty final answer");
1108
+ }
1109
+ return answer;
1110
+ };
1111
+
1112
+ const flushOpen = (): { events: AdapterEvent[]; terminal?: AdapterEvent } => {
1113
+ if (!open) return { events: [] };
1114
+ const tool = open;
1115
+ open = null;
1116
+ closeOpenCall();
1117
+ const input = tool.chunks.join("");
1118
+ if (!isCompleteKiroToolInput(input)) {
1119
+ return { events: [], terminal: protocolTerminal(kiroTruncationErrorMessage("incomplete tool input JSON"), tool.completion) };
1120
+ }
1121
+ if (tool.completion) {
1122
+ completionCalls++;
1123
+ if (completionCalls > 1) {
1124
+ return { events: [], terminal: protocolTerminal("Kiro returned more than one private final-answer tool call", true) };
1125
+ }
1126
+ if (sawRealTool) {
1127
+ return { events: [], terminal: protocolTerminal("Kiro returned a private final answer alongside a real tool call") };
1128
+ }
1129
+ const answer = parseCompletion(tool.chunks);
1130
+ if (answer instanceof Error) return { events: [], terminal: protocolTerminal(answer.message, true) };
1131
+ completionAnswer = answer;
1132
+ return { events: [] };
1133
+ }
1134
+ if (completionAnswer !== undefined || completionCalls > 0) {
1135
+ return { events: [], terminal: protocolTerminal("Kiro returned a real tool call alongside a private final answer") };
1136
+ }
1137
+ sawRealTool = true;
1138
+ const restored = nameMap?.get(tool.name) ?? tool.name;
1139
+ return {
1140
+ events: [
1141
+ { type: "tool_call_start", id: tool.id, name: restored },
1142
+ ...tool.chunks.filter(Boolean).map(argumentsChunk => ({ type: "tool_call_delta", arguments: argumentsChunk }) as AdapterEvent),
1143
+ { type: "tool_call_end" },
1144
+ ],
1145
+ };
1146
+ };
1147
+
1148
+ try {
1149
+ for await (const msg of decodeEventStream(response.body)) {
1150
+ const mt = msg.headers[":message-type"];
1151
+ if (mt === "exception" || mt === "error") {
1152
+ open = null;
1153
+ return {
1154
+ assistantText,
1155
+ sawReasoning,
1156
+ terminal: classifiedTerminal(classifyKiroStreamError(msg.headers, new TextDecoder().decode(msg.payload))),
1157
+ };
1158
+ }
1159
+ if (mt !== "event") {
1160
+ open = null;
1161
+ return {
1162
+ assistantText,
1163
+ sawReasoning,
1164
+ terminal: protocolTerminal(`Kiro response protocol error: unsupported Smithy message type ${JSON.stringify(mt ?? "missing")}`),
1165
+ };
1166
+ }
1167
+ const eventType = msg.headers[":event-type"];
1168
+ if (!eventType) {
1169
+ open = null;
1170
+ return { assistantText, sawReasoning, terminal: protocolTerminal("Kiro response protocol error: event is missing :event-type") };
1171
+ }
1172
+ const ev = parseKiroEvent(eventType, msg.payload);
1173
+ if (!ev) continue;
1174
+ switch (ev.type) {
1175
+ case "metadata":
1176
+ if (ev.usage) authoritativeUsage = ev.usage;
1177
+ if (ev.contextUsagePercentage !== undefined && ev.contextUsagePercentage > 0) {
1178
+ contextUsagePercentage = ev.contextUsagePercentage;
1179
+ }
1180
+ if (ev.stopReason !== undefined) stopReason = ev.stopReason;
1181
+ break;
1182
+ case "message_metadata":
1183
+ if (isValidKiroConversationId(ev.conversationId)) returnedConversationId = ev.conversationId;
1184
+ break;
1185
+ case "content":
1186
+ if (ev.modelId) {
1187
+ contextWindowState.value = kiroUpstreamContextWindow(ev.modelId) ?? contextWindowState.value;
1188
+ }
1189
+ if (open) {
1190
+ open = null;
1191
+ return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage("content arrived before tool stop")) };
1192
+ }
1193
+ if (ev.data) {
1194
+ for (const contentEvent of thinking.feed(ev.data)) {
1195
+ yield* emitRetained(stage(contentEvent));
1196
+ }
1197
+ }
1198
+ break;
1199
+ case "reasoning":
1200
+ for (const contentEvent of thinking.flush()) {
1201
+ yield* emitRetained(stage(contentEvent));
1202
+ }
1203
+ if (ev.data) {
1204
+ yield* emitRetained(stage({ type: "reasoning_raw_delta", text: ev.data }));
1205
+ }
1206
+ if (ev.redactedContent) {
1207
+ yield* emitRetained(stage({ type: "kiro_redacted_reasoning", data: ev.redactedContent }));
1208
+ }
1209
+ break;
1210
+ case "context_usage":
1211
+ if (ev.contextUsagePercentage > 0) contextUsagePercentage = ev.contextUsagePercentage;
1212
+ break;
1213
+ case "tool": {
1214
+ for (const contentEvent of thinking.flush()) {
1215
+ yield* emitRetained(stage(contentEvent));
1216
+ }
1217
+ if (!open) {
1218
+ if (ev.stop === true) {
1219
+ return { assistantText, sawReasoning, terminal: protocolTerminal("Kiro response protocol error: tool stop received without an open tool call") };
1220
+ }
1221
+ if (!ev.toolUseId || !ev.name) {
1222
+ return { assistantText, sawReasoning, terminal: protocolTerminal("Kiro response protocol error: new tool event is missing toolUseId or name") };
1223
+ }
1224
+ const started = beginTool(ev.toolUseId, ev.name);
1225
+ if (started.terminal) return { assistantText, sawReasoning, terminal: started.terminal };
1226
+ open = started.tool!;
1227
+ budget.openCall(open.id);
1228
+ openCallId = open.id;
1229
+ } else if (
1230
+ (ev.toolUseId && ev.toolUseId !== open.id)
1231
+ || (ev.name && open.name !== "unknown" && ev.name !== open.name)
1232
+ ) {
1233
+ closeOpenCall();
1234
+ open = null;
1235
+ return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage("tool input changed identity before stop")) };
1236
+ }
1237
+ if (open && open.name === "unknown" && ev.name) {
1238
+ open.name = ev.name;
1239
+ const terminal = classifyTool(open);
1240
+ if (terminal) {
1241
+ open = null;
1242
+ return { assistantText, sawReasoning, terminal };
1243
+ }
1244
+ }
1245
+ if (open && ev.input !== undefined) {
1246
+ const previousCallBytes = open.chunks.reduce((total, chunk) => total + Buffer.byteLength(chunk), 0);
1247
+ const nextCallBytes = previousCallBytes + Buffer.byteLength(ev.input);
1248
+ const callReservation = budget.reserveTransient(nextCallBytes, { kind: "tool_args", callId: open.id });
1249
+ open.chunks.push(ev.input);
1250
+ callReservation.commitRetained();
1251
+ budget.releaseRetained(previousCallBytes, { kind: "tool_args", callId: open.id });
1252
+ const nextOutputCharsBytes = appendedUtf8Bytes(outputChars, outputCharsBytes, ev.input);
1253
+ const toolOutputReservation = budget.reserveTransient(nextOutputCharsBytes, { kind: "retained_collectors" });
1254
+ outputChars += ev.input;
1255
+ toolOutputReservation.commitRetained();
1256
+ budget.releaseRetained(outputCharsBytes, { kind: "retained_collectors" });
1257
+ retention.trackReplacement(outputCharsBytes, nextOutputCharsBytes);
1258
+ outputCharsBytes = nextOutputCharsBytes;
1259
+ }
1260
+ if (ev.stop === true) {
1261
+ const flushed = flushOpen();
1262
+ if (flushed.terminal) return { assistantText, sawReasoning, terminal: flushed.terminal };
1263
+ for (const event of flushed.events) {
1264
+ yield* emitRetained(stage(event));
1265
+ }
1266
+ } else {
1267
+ yield { type: "heartbeat" };
1268
+ }
1269
+ break;
1270
+ }
1271
+ case "invalid_state":
1272
+ open = null;
1273
+ return { assistantText, sawReasoning, terminal: classifiedTerminal(classifyKiroEventError(undefined, ev.message ?? "Kiro entered an invalid state")) };
1274
+ case "error":
1275
+ open = null;
1276
+ return { assistantText, sawReasoning, terminal: classifiedTerminal(classifyKiroEventError(ev.reason, ev.message)) };
1277
+ case "truncation":
1278
+ open = null;
1279
+ return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage(ev.data)) };
1280
+ }
1281
+ }
1282
+
1283
+ for (const contentEvent of thinking.flush()) {
1284
+ yield* emitRetained(stage(contentEvent));
1285
+ }
1286
+ if (open) {
1287
+ const input = open.chunks.join("");
1288
+ if (!isCompleteKiroToolInput(input)) {
1289
+ const privateTool = open.completion;
1290
+ open = null;
1291
+ return {
1292
+ assistantText,
1293
+ sawReasoning,
1294
+ terminal: protocolTerminal(kiroTruncationErrorMessage("stream ended before tool stop"), privateTool),
1295
+ };
1296
+ }
1297
+ const flushed = flushOpen();
1298
+ if (flushed.terminal) return { assistantText, sawReasoning, terminal: flushed.terminal };
1299
+ for (const event of flushed.events) {
1300
+ yield* emitRetained(stage(event));
1301
+ }
1302
+ }
1303
+
1304
+ const finalUsage = usage();
1305
+ const finalProviderState = providerState();
1306
+ if (contextUsagePercentage !== undefined) {
1307
+ debugProviderDiagnostic("kiro", "context_usage", {
1308
+ contextUsagePercentage,
1309
+ ...(contextWindowState.value ? { upstreamContextWindow: contextWindowState.value } : {}),
1310
+ });
1311
+ }
1312
+ // Native stop metadata proves that this inference ended, but it does not prove that ordinary
1313
+ // text is a final answer. Kiro has emitted END_TURN for progress prose, so tool-enabled turns
1314
+ // still require the private completion call to distinguish commentary from completion (#531).
1315
+ const normalizedStopReason = stopReason?.trim().toUpperCase();
1316
+ const nativeCompletionStop = (normalizedStopReason === KIRO_END_TURN_STOP_REASON
1317
+ || normalizedStopReason === "STOP_SEQUENCE")
1318
+ && sawText
1319
+ && !sawRealTool
1320
+ && completionAnswer === undefined
1321
+ && completionCalls === 0;
1322
+
1323
+ debugProviderDiagnostic("kiro", "attempt_complete", {
1324
+ mode,
1325
+ sawText,
1326
+ sawReasoning,
1327
+ sawRealTool,
1328
+ completionCalls,
1329
+ nativeCompletionStop,
1330
+ ...(stopReason !== undefined ? { stopReason } : {}),
1331
+ assistantChars: assistantText.length,
1332
+ });
1333
+
1334
+ if (mode === "required") {
1335
+ yield* emitRetained(deferred.splice(0));
1336
+ }
1337
+
1338
+ if (mode === "text_fallback") {
1339
+ if (completionAnswer !== undefined) {
1340
+ yield* emitRetained(fallbackEvents);
1341
+ yield { type: "text_delta", text: completionAnswer, phase: "final_answer" };
1342
+ return {
1343
+ assistantText,
1344
+ sawReasoning,
1345
+ terminal: { type: "done", usage: finalUsage, endTurn: true, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
1346
+ };
1347
+ }
1348
+ if (sawRealTool) {
1349
+ yield* emitRetained(fallbackEvents);
1350
+ return {
1351
+ assistantText,
1352
+ sawReasoning,
1353
+ terminal: { type: "done", usage: finalUsage, endTurn: false, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
1354
+ };
1355
+ }
1356
+ if (sawText) {
1357
+ for (const event of fallbackEvents) {
1358
+ try {
1359
+ if (event.type !== "text_delta") yield event;
1360
+ else yield { ...event, phase: "final_answer" };
1361
+ } finally {
1362
+ retention.releaseEvent(event);
1363
+ }
1364
+ }
1365
+ return {
1366
+ assistantText,
1367
+ sawReasoning,
1368
+ terminal: { type: "done", usage: finalUsage, endTurn: true, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
1369
+ };
1370
+ }
1371
+ yield* emitRetained(fallbackEvents);
1372
+ return {
1373
+ assistantText,
1374
+ sawReasoning,
1375
+ terminal: retryableKiroIncomplete(
1376
+ sawReasoning ? "reasoning_only_kiro_fallback" : "empty_kiro_fallback",
1377
+ sawReasoning
1378
+ ? "Kiro produced reasoning but no final answer on its bounded completion retry"
1379
+ : "Kiro produced no final answer on its bounded completion retry",
1380
+ finalUsage,
1381
+ finalProviderState,
1382
+ // First-attempt progress was already flushed before this bounded fallback (#520).
1383
+ !priorEmittedOutput,
1384
+ ),
1385
+ };
1386
+ }
1387
+
1388
+ if (completionAnswer !== undefined) {
1389
+ yield { type: "text_delta", text: completionAnswer, phase: "final_answer" };
1390
+ return {
1391
+ assistantText,
1392
+ sawReasoning,
1393
+ terminal: { type: "done", usage: finalUsage, endTurn: true, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
1394
+ };
1395
+ }
1396
+ if (sawRealTool) {
1397
+ return {
1398
+ assistantText,
1399
+ sawReasoning,
1400
+ terminal: { type: "done", usage: finalUsage, endTurn: false, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
1401
+ };
1402
+ }
1403
+ if (mode === "required" && nativeCompletionStop) {
1404
+ return {
1405
+ assistantText,
1406
+ sawReasoning,
1407
+ needsFallback: true,
1408
+ usage: finalUsage,
1409
+ providerState: finalProviderState,
1410
+ };
1411
+ }
1412
+
1413
+ // An explicit non-completion stop reason has already terminated this inference. Converting it into
1414
+ // another model request would hide truncation behind a second paid call, and for context
1415
+ // exhaustion it would resubmit a request that cannot fit. Only a MISSING stop reason falls
1416
+ // through to the bounded compatibility fallback below.
1417
+ //
1418
+ // END_TURN and STOP_SEQUENCE with text take the bounded validation path above; reaching here
1419
+ // with either means the turn produced no replayable text.
1420
+ if (mode === "required" && normalizedStopReason !== undefined) {
1421
+ const providerStateField = finalProviderState ? { providerState: finalProviderState } : {};
1422
+ const incomplete = (reason: string, retryable: boolean) => ({
1423
+ assistantText,
1424
+ sawReasoning,
1425
+ terminal: {
1426
+ type: "incomplete" as const,
1427
+ reason,
1428
+ message: `Kiro stopped with ${normalizedStopReason} before an explicit final answer`,
1429
+ usage: finalUsage,
1430
+ retryable,
1431
+ endTurn: false,
1432
+ ...providerStateField,
1433
+ },
1434
+ });
1435
+
1436
+ if (normalizedStopReason === "MODEL_CONTEXT_WINDOW_EXCEEDED") {
1437
+ // Reuse the existing context-length contract (kiro-errors.ts) instead of inventing an
1438
+ // incomplete reason: an unrecognized incomplete becomes a retryable 529 in Claude
1439
+ // outbound, and `max_output_tokens` would make responses/state.ts cache this partial
1440
+ // for continuation replay. Both invite a retry that cannot succeed.
1441
+ return {
1442
+ assistantText,
1443
+ sawReasoning,
1444
+ terminal: {
1445
+ type: "error" as const,
1446
+ message: "Kiro stopped because the model context window was exhausted",
1447
+ status: 400,
1448
+ errorType: "invalid_request_error",
1449
+ code: "context_length_exceeded",
1450
+ retryable: false,
1451
+ usage: finalUsage,
1452
+ },
1453
+ };
1454
+ }
1455
+ if (normalizedStopReason === "MAX_TOKENS") return incomplete("max_output_tokens", true);
1456
+ if (normalizedStopReason === "CONTENT_FILTERED" || normalizedStopReason === "GUARDRAIL_INTERVENED") {
1457
+ return incomplete("content_filter", false);
1458
+ }
1459
+ if (normalizedStopReason === "MALFORMED_TOOL_USE") return incomplete("kiro_malformed_tool_use", false);
1460
+ if (normalizedStopReason === "MALFORMED_MODEL_OUTPUT") return incomplete("kiro_malformed_model_output", false);
1461
+ // TOOL_USE here means Kiro claimed a tool call it never emitted.
1462
+ if (normalizedStopReason === "TOOL_USE") return incomplete("kiro_tool_use_without_call", false);
1463
+ if (normalizedStopReason === KIRO_END_TURN_STOP_REASON || normalizedStopReason === "STOP_SEQUENCE") {
1464
+ return incomplete(`kiro_${normalizedStopReason.toLowerCase()}_without_text`, false);
1465
+ }
1466
+ return incomplete(`kiro_${normalizedStopReason.toLowerCase() || "unknown_stop"}`, false);
1467
+ }
1468
+ // Kiro text has no trustworthy final/progress marker. When completion is required, ordinary
1469
+ // text and reasoning remain unfinished until the one bounded fallback validates the turn.
1470
+ if (mode === "required" && (sawText || sawReasoning)) {
1471
+ return { assistantText, sawReasoning, needsFallback: true, usage: finalUsage, providerState: finalProviderState };
1472
+ }
1473
+ if (!sawText && !sawReasoning) {
1474
+ return {
1475
+ assistantText,
1476
+ sawReasoning,
1477
+ terminal: retryableKiroIncomplete(
1478
+ "empty_kiro_stream",
1479
+ "Kiro returned a successful but empty response stream",
1480
+ finalUsage,
1481
+ finalProviderState,
1482
+ ),
1483
+ };
1484
+ }
1485
+ return {
1486
+ assistantText,
1487
+ sawReasoning,
1488
+ terminal: {
1489
+ type: "done",
1490
+ usage: finalUsage,
1491
+ endTurn: mode === "disabled" ? sawText : false,
1492
+ ...(finalProviderState ? { providerState: finalProviderState } : {}),
1493
+ },
1494
+ };
1495
+ } catch (err) {
1496
+ if (isTranslatorBudgetExceededError(err)) {
1497
+ closeOpenCall();
1498
+ return {
1499
+ assistantText,
1500
+ sawReasoning,
1501
+ terminal: {
1502
+ type: "error",
1503
+ status: 502,
1504
+ errorType: "upstream_error",
1505
+ code: "translation_buffer_limit",
1506
+ message: "upstream translation buffer exceeded the safe limit",
1507
+ },
1508
+ };
1509
+ }
1510
+ // Mid-stream socket closes after response.created / heartbeats only must stay retryable:
1511
+ // nothing was relayed to the client, so a string-body replay is safe (see #519 / cursor's
1512
+ // emittedOutput gate). Once any assistant text, reasoning, tool, or deferred content exists
1513
+ // — including content flushed by a prior attempt before a bounded fallback — fail closed;
1514
+ // the client may already have partial output. Protocol parse throws stay non-retryable even
1515
+ // with zero output.
1516
+ const emittedOutput = priorEmittedOutput
1517
+ || sawText
1518
+ || sawReasoning
1519
+ || sawRealTool
1520
+ || assistantText.length > 0
1521
+ || deferred.length > 0
1522
+ || completionAnswer !== undefined
1523
+ || completionCalls > 0
1524
+ || open !== null
1525
+ || fallbackEvents.length > 0;
1526
+ return {
1527
+ assistantText,
1528
+ sawReasoning,
1529
+ terminal: {
1530
+ type: "error",
1531
+ message: safeKiroErrorMessage({}, err instanceof Error ? err.message : String(err)),
1532
+ status: 502,
1533
+ errorType: "server_error",
1534
+ code: "kiro_stream_protocol_error",
1535
+ retryable: isRetryableKiroStreamCatchError(err, emittedOutput),
1536
+ usage: usage(),
1537
+ },
1538
+ };
1539
+ } finally {
1540
+ thinking.dispose();
1541
+ closeOpenCall();
1542
+ }
1543
+ }
1544
+
1545
+ export async function* parseKiroStream(
1546
+ response: Response,
1547
+ budget: TranslatorBudget,
1548
+ modelId?: string,
1549
+ inputTokens = 0,
1550
+ contextWindow?: number,
1551
+ nameMap?: Map<string, string>,
1552
+ conversationId?: string,
1553
+ completionMode: KiroCompletionMode = "disabled",
1554
+ fallbackFactory?: KiroFallbackFactory,
1555
+ contextInputEstimate?: number,
1556
+ ): AsyncGenerator<AdapterEvent> {
1557
+ const contextWindowState: KiroContextWindowState = { value: contextWindow };
1558
+ const firstResult = yield* parseKiroAttempt(
1559
+ response,
1560
+ budget,
1561
+ completionMode,
1562
+ modelId,
1563
+ inputTokens,
1564
+ contextWindowState,
1565
+ nameMap,
1566
+ conversationId,
1567
+ contextInputEstimate,
1568
+ false,
1569
+ );
1570
+ try {
1571
+ if (!firstResult.needsFallback) {
1572
+ if (firstResult.terminal) yield firstResult.terminal;
1573
+ return;
1574
+ }
1575
+ if (!fallbackFactory) {
1576
+ yield retryableKiroIncomplete(
1577
+ "uncompleted_kiro_response",
1578
+ "Kiro produced progress without an explicit final answer and no bounded retry transport was available",
1579
+ firstResult.usage ?? { inputTokens, outputTokens: 0, estimated: true },
1580
+ firstResult.providerState,
1581
+ );
1582
+ return;
1583
+ }
1584
+
1585
+ yield { type: "heartbeat" };
1586
+ // First attempt already flushed deferred progress before this point. Gate fallback
1587
+ // setup/HTTP failures the same way as the second-stream catch so a replay cannot
1588
+ // duplicate visible commentary (#520).
1589
+ const priorEmittedOutput = Boolean(firstResult.assistantText.trim()) || firstResult.sawReasoning;
1590
+ let firstAssistantText = firstResult.assistantText;
1591
+ const firstHadAssistantText = firstAssistantText.length > 0;
1592
+ let fallback: KiroFallbackAttempt;
1593
+ try {
1594
+ fallback = await fallbackFactory(
1595
+ firstResult.providerState?.kiro.conversationId ?? conversationId,
1596
+ firstAssistantText,
1597
+ firstResult.sawReasoning,
1598
+ budget,
1599
+ );
1600
+ } catch (err) {
1601
+ firstAssistantText = "";
1602
+ firstResult.assistantText = "";
1603
+ firstResult.releaseRetained();
1604
+ if (isTranslatorBudgetExceededError(err)) {
1605
+ yield {
1606
+ type: "error",
1607
+ message: "upstream translation buffer exceeded the safe limit",
1608
+ status: 502,
1609
+ errorType: "upstream_error",
1610
+ code: "translation_buffer_limit",
1611
+ usage: firstResult.usage,
1612
+ };
1613
+ return;
1614
+ }
1615
+ yield {
1616
+ type: "error",
1617
+ message: safeKiroErrorMessage({}, err instanceof Error ? err.message : String(err)),
1618
+ status: err instanceof Error && err.name === "TimeoutError" ? 504 : 502,
1619
+ errorType: "upstream_error",
1620
+ retryable: !priorEmittedOutput,
1621
+ usage: firstResult.usage,
1622
+ };
1623
+ return;
1624
+ }
1625
+ // The factory has finished using the live first-attempt alias and has retained its own retry
1626
+ // serialization through the fetch boundary. The discarded parser collectors can now release
1627
+ // before the second attempt begins on the same turn budget.
1628
+ firstAssistantText = "";
1629
+ firstResult.assistantText = "";
1630
+ firstResult.releaseRetained();
1631
+ fallback.releaseRequestBody?.();
1632
+ if (!fallback.response.ok) {
1633
+ const payload = await fallback.response.text().catch(() => "");
1634
+ const failure = classifyKiroHttpError(fallback.response.status, fallback.response.headers, payload);
1635
+ yield {
1636
+ type: "error",
1637
+ message: failure.message,
1638
+ status: failure.status,
1639
+ errorType: failure.errorType,
1640
+ code: failure.code,
1641
+ retryable: priorEmittedOutput ? false : failure.retryable,
1642
+ usage: firstResult.usage,
1643
+ };
1644
+ return;
1645
+ }
1646
+
1647
+ const secondResult = yield* parseKiroAttempt(
1648
+ fallback.response,
1649
+ budget,
1650
+ "text_fallback",
1651
+ modelId,
1652
+ fallback.inputTokens,
1653
+ contextWindowState,
1654
+ fallback.nameMap,
1655
+ fallback.conversationId,
1656
+ fallback.contextInputEstimate,
1657
+ // First attempt already flushed deferred progress to the client before this fallback.
1658
+ // A zero-output transport failure here must stay non-retryable to avoid duplicating that text.
1659
+ priorEmittedOutput,
1660
+ );
1661
+ try {
1662
+ if (!secondResult.terminal) {
1663
+ yield retryableKiroIncomplete(
1664
+ "empty_kiro_fallback",
1665
+ "Kiro's bounded completion retry ended without a terminal result",
1666
+ mergeKiroUsage(firstResult.usage, secondResult.usage, firstHadAssistantText)
1667
+ ?? { inputTokens, outputTokens: 0, estimated: true },
1668
+ secondResult.providerState ?? firstResult.providerState,
1669
+ !priorEmittedOutput,
1670
+ );
1671
+ return;
1672
+ }
1673
+ if (secondResult.terminal.type === "done" || secondResult.terminal.type === "incomplete") {
1674
+ yield {
1675
+ ...secondResult.terminal,
1676
+ // Belt-and-suspenders: never advertise a replay-safe incomplete after flushed progress.
1677
+ ...(secondResult.terminal.type === "incomplete" && priorEmittedOutput
1678
+ ? { retryable: false as const }
1679
+ : {}),
1680
+ usage: mergeKiroUsage(firstResult.usage, secondResult.terminal.usage, firstHadAssistantText),
1681
+ providerState: secondResult.terminal.providerState ?? firstResult.providerState,
1682
+ };
1683
+ return;
1684
+ }
1685
+ yield {
1686
+ ...secondResult.terminal,
1687
+ ...(secondResult.terminal.type === "error"
1688
+ ? { usage: mergeKiroUsage(firstResult.usage, secondResult.terminal.usage, firstHadAssistantText) }
1689
+ : {}),
1690
+ };
1691
+ } finally {
1692
+ secondResult.releaseRetained();
1693
+ }
1694
+ } finally {
1695
+ firstResult.releaseRetained();
1696
+ }
1697
+ }
1698
+
1699
+ // Adapter
1700
+ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter {
1701
+ // Per-request closure (resolveAdapter builds a fresh adapter per request — server.ts:440 — so this
1702
+ // is race-free) carrying the heuristic input-token estimate from buildRequest into the stream.
1703
+ let inputTokens = 0;
1704
+ let contextInputEstimate = 0;
1705
+ let modelId: string | undefined;
1706
+ let contextWindow: number | undefined;
1707
+ let toolNameMap: Map<string, string> | undefined;
1708
+ let conversationId: string | undefined;
1709
+ let completionMode: KiroCompletionMode = "disabled";
1710
+ let requestSnapshot: OcxParsedRequest | undefined;
1711
+ let firstRequestBodyBytes = 0;
1712
+ let requestAbortSignal: AbortSignal | undefined;
1713
+
1714
+ const build = async (
1715
+ parsed: OcxParsedRequest,
1716
+ forcedCompletionMode?: KiroCompletionMode,
1717
+ ): Promise<{
1718
+ request: AdapterRequest;
1719
+ nameMap: Map<string, string>;
1720
+ conversationId: string;
1721
+ completionMode: KiroCompletionMode;
1722
+ inputTokens: number;
1723
+ contextInputEstimate: number;
1724
+ }> => {
1725
+ if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
1726
+ throw new Error("kiro token missing — run ocx login kiro");
1727
+ }
1728
+ const region = resolveKiroApiRegion(parsed._kiroAuthContext);
1729
+ const resolvedProfileArn = resolveKiroProfileArn(parsed._kiroAuthContext);
1730
+ const isApiKey = provider.apiKey.trim().startsWith("ksk_");
1731
+ const profileArn = isApiKey ? undefined : resolvedProfileArn;
1732
+ // Builder ID and Kiro API keys have no profile ARN and are accepted only on Kiro's CLI
1733
+ // request path. Enterprise profiles retain the existing IDE-shaped request.
1734
+ const wireClient: KiroWireClient = isApiKey || !profileArn ? "cli" : "ide";
1735
+ const fp = fingerprint().slice(0, 64);
1736
+ const headers: Record<string, string> = wireClient === "cli" ? {
1737
+ authorization: `Bearer ${provider.apiKey}`,
1738
+ "content-type": "application/x-amz-json-1.0",
1739
+ accept: "*/*",
1740
+ "x-amz-target": AMZ_TARGET,
1741
+ "user-agent": kiroCliUserAgent(true),
1742
+ "x-amz-user-agent": kiroCliUserAgent(false),
1743
+ "x-amzn-codewhisperer-optout": "true",
1744
+ "amz-sdk-request": "attempt=1; max=3",
1745
+ "amz-sdk-invocation-id": invocationId(),
1746
+ ...(isApiKey ? { tokentype: "API_KEY" } : {}),
1747
+ } : {
1748
+ authorization: `Bearer ${provider.apiKey}`,
1749
+ "content-type": "application/x-amz-json-1.0",
1750
+ accept: "application/vnd.amazon.eventstream",
1751
+ "x-amz-target": AMZ_TARGET,
1752
+ "user-agent": `aws-sdk-js/${SDK_VERSION} ua/2.1 os/${osTag()} lang/js md/nodejs#${NODE_VERSION} api/codewhispererstreaming#${SDK_VERSION} m/E KiroIDE-${KIRO_IDE_VERSION}-${fp}`,
1753
+ "x-amz-user-agent": `aws-sdk-js/${SDK_VERSION} KiroIDE-${KIRO_IDE_VERSION}-${fp}`,
1754
+ "x-amzn-codewhisperer-optout": "true",
1755
+ "x-amzn-kiro-agent-mode": "vibe",
1756
+ "amz-sdk-invocation-id": invocationId(),
1757
+ };
1758
+ if (profileArn) headers["x-amzn-kiro-profile-arn"] = profileArn;
1759
+ const built = buildKiroPayload(parsed, profileArn, forcedCompletionMode, wireClient);
1760
+ await normalizeKiroImages(built.payload);
1761
+ const contextInputEstimate = estimateKiroPayloadInputTokens(built.payload, parsed.modelId);
1762
+ const body = JSON.stringify(built.payload);
1763
+ debugProviderDiagnostic("kiro", "request", {
1764
+ region,
1765
+ requestedModel: parsed.modelId,
1766
+ completionMode: built.completionMode,
1767
+ bodyBytes: new TextEncoder().encode(body).length,
1768
+ messageCount: kiroPayloadMessages(parsed).length,
1769
+ toolCount: parsed.context.tools?.length ?? 0,
1770
+ hasProfileArn: Boolean(profileArn),
1771
+ wireClient,
1772
+ hasPreviousResponseId: Boolean(parsed.previousResponseId),
1773
+ });
1774
+ return {
1775
+ request: {
1776
+ url: kiroRuntimeEndpoint(provider, region),
1777
+ method: "POST",
1778
+ headers,
1779
+ body,
1780
+ usageLog: { inputTokens: estimateKiroLogInputTokens(parsed), estimated: true },
1781
+ },
1782
+ nameMap: built.nameMap,
1783
+ conversationId: built.conversationId,
1784
+ completionMode: built.completionMode,
1785
+ inputTokens: estimateKiroInputTokens(parsed),
1786
+ contextInputEstimate,
1787
+ };
1788
+ };
1789
+
1790
+ const fallbackFactory: KiroFallbackFactory = async (
1791
+ returnedConversationId,
1792
+ assistantText,
1793
+ _sawReasoning,
1794
+ budget,
1795
+ ) => {
1796
+ if (!requestSnapshot) throw new Error("Kiro completion retry lost its request state");
1797
+ if (requestAbortSignal?.aborted) {
1798
+ throw requestAbortSignal.reason instanceof Error
1799
+ ? requestAbortSignal.reason
1800
+ : new DOMException("Kiro request was cancelled", "AbortError");
1801
+ }
1802
+ const retryParsed = structuredClone(requestSnapshot);
1803
+ retryParsed._providerContinuation = {
1804
+ ...(retryParsed._providerContinuation ?? {}),
1805
+ ...(returnedConversationId ? { kiro: { conversationId: returnedConversationId } } : {}),
1806
+ };
1807
+ // Reasoning is not replayable on the Kiro wire. Adding an empty assistant turn merely to mark
1808
+ // that reasoning existed creates REQUEST_BODY_INVALID; only visible text earns a replay turn.
1809
+ if (assistantText.trim()) {
1810
+ retryParsed.context.messages.push({
1811
+ role: "assistant",
1812
+ content: [{ type: "text" as const, text: assistantText }],
1813
+ phase: "commentary",
1814
+ model: retryParsed.modelId,
1815
+ timestamp: Date.now(),
1816
+ });
1817
+ }
1818
+ // The retry starts from the already measured first wire body, adds one JSON-escaped replay
1819
+ // string, and only changes bounded Kiro-owned fields (completion prompt/tool, history wrapper,
1820
+ // and <=256-byte conversation id). 64 KiB is a conservative envelope for those fixed fields.
1821
+ // Reserve that complete upper bound while the first-attempt collectors are still charged so a
1822
+ // near-cap turn fails before build() can materialize the retry payload or serialized body.
1823
+ const retryBodyUpperBound = firstRequestBodyBytes
1824
+ + jsonStringSerializedUtf8Bytes(assistantText)
1825
+ + KIRO_FALLBACK_SERIALIZATION_ENVELOPE_BYTES;
1826
+ const retryBodyReservation = budget.reserveTransient(retryBodyUpperBound, { kind: "request_copies" });
1827
+ let retryBodyBytes = 0;
1828
+ let retryBodyRetained = false;
1829
+ let requestBodyReleased = false;
1830
+ const releaseRequestBody = () => {
1831
+ if (requestBodyReleased) return;
1832
+ requestBodyReleased = true;
1833
+ if (retryBodyRetained) budget.releaseRetained(retryBodyBytes, { kind: "request_copies" });
1834
+ else retryBodyReservation.release();
1835
+ };
1836
+ try {
1837
+ const retry = await build(retryParsed, "text_fallback");
1838
+ retryBodyBytes = Buffer.byteLength(retry.request.body);
1839
+ if (retryBodyBytes > retryBodyUpperBound) {
1840
+ throw new Error("Kiro retry serialization exceeded its pre-admitted upper bound");
1841
+ }
1842
+ retryBodyReservation.commitRetained();
1843
+ retryBodyRetained = true;
1844
+ budget.releaseRetained(retryBodyUpperBound - retryBodyBytes, { kind: "request_copies" });
1845
+ const response = await fetchKiroWithRetry(retry.request, {
1846
+ abortSignal: requestAbortSignal,
1847
+ returnRawErrors: true,
1848
+ stream: true,
1849
+ });
1850
+ return {
1851
+ response,
1852
+ inputTokens: retry.inputTokens,
1853
+ contextInputEstimate: retry.contextInputEstimate,
1854
+ nameMap: retry.nameMap,
1855
+ conversationId: retry.conversationId,
1856
+ releaseRequestBody,
1857
+ };
1858
+ } catch (error) {
1859
+ releaseRequestBody();
1860
+ throw error;
1861
+ }
1862
+ };
1863
+
1864
+ return {
1865
+ name: "kiro",
1866
+ async buildRequest(parsed: OcxParsedRequest, incoming) {
1867
+ const built = await build(parsed);
1868
+ modelId = parsed.modelId;
1869
+ contextWindow = kiroUpstreamContextWindow(parsed.modelId);
1870
+ inputTokens = built.inputTokens;
1871
+ contextInputEstimate = built.contextInputEstimate;
1872
+ toolNameMap = built.nameMap;
1873
+ conversationId = built.conversationId;
1874
+ completionMode = built.completionMode;
1875
+ requestSnapshot = structuredClone(parsed);
1876
+ firstRequestBodyBytes = Buffer.byteLength(built.request.body);
1877
+ requestAbortSignal = incoming?.abortSignal;
1878
+ return built.request;
1879
+ },
1880
+
1881
+ parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
1882
+ return parseKiroStream(
1883
+ response,
1884
+ budget,
1885
+ modelId,
1886
+ inputTokens,
1887
+ contextWindow,
1888
+ toolNameMap,
1889
+ conversationId,
1890
+ completionMode,
1891
+ completionMode === "required" ? fallbackFactory : undefined,
1892
+ contextInputEstimate,
1893
+ );
1894
+ },
1895
+
1896
+ fetchResponse(request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> {
1897
+ // The normal Responses path supplies cancellation at fetch time rather than build time.
1898
+ // Keep it for the adapter-owned bounded continuation so cancelling the client turn aborts
1899
+ // both the first Kiro request and its one allowed completion retry.
1900
+ if (ctx?.abortSignal) requestAbortSignal = ctx.abortSignal;
1901
+ return fetchKiroWithRetry(request, ctx);
1902
+ },
1903
+
1904
+ formatErrorBody(status: number, headers: Headers, payloadText: string): string {
1905
+ return safeKiroHttpErrorMessage(status, headers, payloadText);
1906
+ },
1907
+
1908
+ // Kiro always returns an event stream, including for non-streaming Responses requests. Drain
1909
+ // the decoder into a budget-owned batch so an upstream stream cannot grow this array without
1910
+ // bound while the caller waits for the complete JSON response.
1911
+ async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
1912
+ const events: AdapterEvent[] = [];
1913
+ try {
1914
+ for await (const e of parseKiroStream(
1915
+ response,
1916
+ budget,
1917
+ modelId,
1918
+ inputTokens,
1919
+ contextWindow,
1920
+ toolNameMap,
1921
+ conversationId,
1922
+ completionMode,
1923
+ completionMode === "required" ? fallbackFactory : undefined,
1924
+ contextInputEstimate,
1925
+ )) {
1926
+ retainTranslatedEvent(e, budget, events.at(-1));
1927
+ events.push(e);
1928
+ }
1929
+ return events;
1930
+ } catch (error) {
1931
+ for (const event of events) releaseTranslatedEvent(event, budget);
1932
+ throw error;
1933
+ }
1934
+ },
1935
+ };
1936
+ }