@iislee/opencodex 2.11.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 (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/assets/architecture.png +0 -0
  4. package/assets/banner.png +0 -0
  5. package/assets/claude-code-models.gif +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +451 -0
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-DTpMHS4F.js +67 -0
  10. package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
  11. package/gui/dist/favicon.png +0 -0
  12. package/gui/dist/icons.svg +24 -0
  13. package/gui/dist/index.html +25 -0
  14. package/gui/dist/logo.png +0 -0
  15. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  16. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/claude.svg +1 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/cursor.svg +2 -0
  25. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  26. package/gui/dist/provider-icons/discord.svg +1 -0
  27. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  28. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  29. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok-color.svg +1 -0
  34. package/gui/dist/provider-icons/grok.svg +1 -0
  35. package/gui/dist/provider-icons/groq-color.svg +1 -0
  36. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  37. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  38. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  39. package/gui/dist/provider-icons/kiro.svg +14 -0
  40. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  41. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  42. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  43. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  44. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  45. package/gui/dist/provider-icons/openai.svg +1 -0
  46. package/gui/dist/provider-icons/opencode.svg +1 -0
  47. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  48. package/gui/dist/provider-icons/pi.svg +21 -0
  49. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  50. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  51. package/gui/dist/provider-icons/telegram.svg +1 -0
  52. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  53. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  54. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  55. package/package.json +102 -0
  56. package/src/AGENTS.md +28 -0
  57. package/src/adapters/anthropic-image-guard.ts +251 -0
  58. package/src/adapters/anthropic-image-normalize.ts +518 -0
  59. package/src/adapters/anthropic.ts +1003 -0
  60. package/src/adapters/azure.ts +36 -0
  61. package/src/adapters/base.ts +72 -0
  62. package/src/adapters/client-fingerprint.ts +59 -0
  63. package/src/adapters/cursor/arg-codec.ts +38 -0
  64. package/src/adapters/cursor/arg-normalize.ts +104 -0
  65. package/src/adapters/cursor/cursor-errors.ts +165 -0
  66. package/src/adapters/cursor/discovery.ts +276 -0
  67. package/src/adapters/cursor/effort-map.ts +127 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +211 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/kv-store.ts +52 -0
  72. package/src/adapters/cursor/live-models.ts +153 -0
  73. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  74. package/src/adapters/cursor/live-transport.ts +1214 -0
  75. package/src/adapters/cursor/mcp-config.ts +42 -0
  76. package/src/adapters/cursor/mcp-manager.ts +333 -0
  77. package/src/adapters/cursor/message-mapper.ts +49 -0
  78. package/src/adapters/cursor/native-exec-common.ts +55 -0
  79. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  80. package/src/adapters/cursor/native-exec-fs.ts +329 -0
  81. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  82. package/src/adapters/cursor/native-exec-network.ts +43 -0
  83. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  84. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  85. package/src/adapters/cursor/native-exec.ts +576 -0
  86. package/src/adapters/cursor/protobuf-events.ts +563 -0
  87. package/src/adapters/cursor/protobuf-request.ts +714 -0
  88. package/src/adapters/cursor/request-builder.ts +255 -0
  89. package/src/adapters/cursor/thread-continuity.ts +67 -0
  90. package/src/adapters/cursor/tool-definitions.ts +505 -0
  91. package/src/adapters/cursor/transport-retry.ts +132 -0
  92. package/src/adapters/cursor/transport.ts +57 -0
  93. package/src/adapters/cursor/types.ts +52 -0
  94. package/src/adapters/cursor.ts +196 -0
  95. package/src/adapters/google-antigravity-replay.ts +303 -0
  96. package/src/adapters/google-antigravity-wire.ts +108 -0
  97. package/src/adapters/google-errors.ts +85 -0
  98. package/src/adapters/google-http.ts +100 -0
  99. package/src/adapters/google-tool-schema.ts +173 -0
  100. package/src/adapters/google-truncation.ts +13 -0
  101. package/src/adapters/google-wire-compiler.ts +232 -0
  102. package/src/adapters/google.ts +758 -0
  103. package/src/adapters/identity.ts +44 -0
  104. package/src/adapters/image.ts +23 -0
  105. package/src/adapters/kiro-constants.ts +16 -0
  106. package/src/adapters/kiro-errors.ts +197 -0
  107. package/src/adapters/kiro-events.ts +179 -0
  108. package/src/adapters/kiro-images.ts +129 -0
  109. package/src/adapters/kiro-retry.ts +312 -0
  110. package/src/adapters/kiro-thinking.ts +96 -0
  111. package/src/adapters/kiro-tool-fallback.ts +36 -0
  112. package/src/adapters/kiro-tools.ts +215 -0
  113. package/src/adapters/kiro-truncation.ts +33 -0
  114. package/src/adapters/kiro-wire.ts +129 -0
  115. package/src/adapters/kiro.ts +1898 -0
  116. package/src/adapters/mimo-free.ts +263 -0
  117. package/src/adapters/openai-chat.ts +1005 -0
  118. package/src/adapters/openai-responses.ts +1137 -0
  119. package/src/adapters/run-turn-queue.ts +114 -0
  120. package/src/adapters/tool-catalog-nudge.ts +71 -0
  121. package/src/adapters/upstream-http-error.ts +48 -0
  122. package/src/bridge.ts +1619 -0
  123. package/src/chat/inbound.ts +295 -0
  124. package/src/chat/outbound.ts +765 -0
  125. package/src/claude/agents-inject.ts +243 -0
  126. package/src/claude/alias.ts +149 -0
  127. package/src/claude/auth-detect.ts +229 -0
  128. package/src/claude/auth-mode-migration.ts +32 -0
  129. package/src/claude/auth-mode.ts +62 -0
  130. package/src/claude/context-windows.ts +189 -0
  131. package/src/claude/desktop-3p-guard.ts +35 -0
  132. package/src/claude/desktop-3p-paths.ts +84 -0
  133. package/src/claude/desktop-3p.ts +381 -0
  134. package/src/claude/desktop-health.ts +26 -0
  135. package/src/claude/desktop-profile.ts +263 -0
  136. package/src/claude/gateway-cache.ts +70 -0
  137. package/src/claude/inbound-debug.ts +163 -0
  138. package/src/claude/inbound.ts +509 -0
  139. package/src/claude/model-info.ts +151 -0
  140. package/src/claude/outbound.ts +872 -0
  141. package/src/cli/access.ts +108 -0
  142. package/src/cli/account-api.ts +268 -0
  143. package/src/cli/account-auth.ts +223 -0
  144. package/src/cli/account-extended.ts +350 -0
  145. package/src/cli/account.ts +275 -0
  146. package/src/cli/agent-driven.ts +70 -0
  147. package/src/cli/agent.ts +184 -0
  148. package/src/cli/catalog-prewarm.ts +27 -0
  149. package/src/cli/claude-desktop.ts +188 -0
  150. package/src/cli/claude.ts +286 -0
  151. package/src/cli/codex-shim-autorestore.ts +45 -0
  152. package/src/cli/combo.ts +119 -0
  153. package/src/cli/config-command.ts +145 -0
  154. package/src/cli/debug.ts +228 -0
  155. package/src/cli/doctor.ts +930 -0
  156. package/src/cli/export-command.ts +187 -0
  157. package/src/cli/help.ts +354 -0
  158. package/src/cli/index.ts +1113 -0
  159. package/src/cli/init.ts +224 -0
  160. package/src/cli/integrations.ts +142 -0
  161. package/src/cli/interactive-confirm.ts +133 -0
  162. package/src/cli/internal-dispatch.ts +20 -0
  163. package/src/cli/models-runtime.ts +212 -0
  164. package/src/cli/models.ts +336 -0
  165. package/src/cli/observe.ts +117 -0
  166. package/src/cli/opencode.ts +586 -0
  167. package/src/cli/pi.ts +188 -0
  168. package/src/cli/provider-runtime.ts +162 -0
  169. package/src/cli/provider.ts +463 -0
  170. package/src/cli/runtime-api.ts +325 -0
  171. package/src/cli/star-prompt.ts +155 -0
  172. package/src/cli/status-oauth.ts +78 -0
  173. package/src/cli/status.ts +321 -0
  174. package/src/cli/sync-cloud.ts +283 -0
  175. package/src/cli/system-command.ts +112 -0
  176. package/src/cli/tray-proxy.ts +52 -0
  177. package/src/cli/v2.ts +173 -0
  178. package/src/cli.ts +10 -0
  179. package/src/clients/config-export.ts +377 -0
  180. package/src/clients/effective-status.ts +385 -0
  181. package/src/clients/probes/agy.ts +55 -0
  182. package/src/clients/probes/cc-switch.ts +110 -0
  183. package/src/clients/probes/claude.ts +90 -0
  184. package/src/clients/probes/codex.ts +125 -0
  185. package/src/clients/probes/grok.ts +29 -0
  186. package/src/clients/probes/opencode.ts +109 -0
  187. package/src/clients/probes/paseo.ts +55 -0
  188. package/src/clients/probes/pi.ts +55 -0
  189. package/src/cloud/onedrive-auth.ts +666 -0
  190. package/src/cloud/onedrive-graph.ts +108 -0
  191. package/src/cloud/settings.ts +75 -0
  192. package/src/cloud/sync.ts +212 -0
  193. package/src/cloud/types.ts +56 -0
  194. package/src/cloud/vault.ts +89 -0
  195. package/src/codex/account-id.ts +34 -0
  196. package/src/codex/account-label.ts +34 -0
  197. package/src/codex/account-lifecycle.ts +55 -0
  198. package/src/codex/account-namespace-match.ts +63 -0
  199. package/src/codex/account-namespaces.ts +149 -0
  200. package/src/codex/account-pause.ts +20 -0
  201. package/src/codex/account-runtime-state.ts +31 -0
  202. package/src/codex/account-store.ts +517 -0
  203. package/src/codex/account-usability.ts +20 -0
  204. package/src/codex/app-server-processes.ts +756 -0
  205. package/src/codex/auth-api.ts +1540 -0
  206. package/src/codex/auth-collision.ts +107 -0
  207. package/src/codex/auth-context.ts +352 -0
  208. package/src/codex/autostart-health.ts +149 -0
  209. package/src/codex/catalog/aggregation.ts +378 -0
  210. package/src/codex/catalog/bundled.ts +251 -0
  211. package/src/codex/catalog/effort.ts +355 -0
  212. package/src/codex/catalog/metadata.ts +180 -0
  213. package/src/codex/catalog/parsing.ts +456 -0
  214. package/src/codex/catalog/provider-fetch.ts +902 -0
  215. package/src/codex/catalog/sync.ts +620 -0
  216. package/src/codex/catalog.ts +12 -0
  217. package/src/codex/data/upstream-models.json +830 -0
  218. package/src/codex/exec-invocation.ts +22 -0
  219. package/src/codex/features.ts +969 -0
  220. package/src/codex/history-migration-guardian.ts +102 -0
  221. package/src/codex/history-provider.ts +776 -0
  222. package/src/codex/home.ts +206 -0
  223. package/src/codex/inject.ts +799 -0
  224. package/src/codex/injected-marker.ts +72 -0
  225. package/src/codex/journal.ts +163 -0
  226. package/src/codex/main-account-cache.ts +32 -0
  227. package/src/codex/main-account.ts +40 -0
  228. package/src/codex/model-cache.ts +227 -0
  229. package/src/codex/paths.ts +65 -0
  230. package/src/codex/plugins-doctor.ts +242 -0
  231. package/src/codex/pool-rotation.ts +225 -0
  232. package/src/codex/project-config-warnings.ts +411 -0
  233. package/src/codex/quota.ts +411 -0
  234. package/src/codex/refresh.ts +53 -0
  235. package/src/codex/routing.ts +1477 -0
  236. package/src/codex/runtime.ts +538 -0
  237. package/src/codex/shim.ts +1189 -0
  238. package/src/codex/subagent-defaults.ts +550 -0
  239. package/src/codex/subagent-model-fallback.ts +469 -0
  240. package/src/codex/sync.ts +130 -0
  241. package/src/codex/warmup.ts +192 -0
  242. package/src/codex/websocket-registry.ts +100 -0
  243. package/src/combos/failover.ts +140 -0
  244. package/src/combos/index.ts +41 -0
  245. package/src/combos/request.ts +62 -0
  246. package/src/combos/resolve.ts +232 -0
  247. package/src/combos/types.ts +326 -0
  248. package/src/config.ts +2356 -0
  249. package/src/generated/jawcode-model-metadata.ts +104 -0
  250. package/src/github/star-state.ts +203 -0
  251. package/src/grok/inject.ts +545 -0
  252. package/src/grok/status.ts +121 -0
  253. package/src/grok/sync.ts +103 -0
  254. package/src/grok/usage-hook/report.mjs +348 -0
  255. package/src/grok/usage-hook.ts +278 -0
  256. package/src/images/artifacts.ts +516 -0
  257. package/src/images/fulfill-video.ts +163 -0
  258. package/src/images/fulfill.ts +149 -0
  259. package/src/images/index.ts +4 -0
  260. package/src/images/loop.ts +829 -0
  261. package/src/images/plan.ts +133 -0
  262. package/src/images/synthetic-tool.ts +133 -0
  263. package/src/images/types.ts +41 -0
  264. package/src/images/xai-client.ts +141 -0
  265. package/src/images/xai-video-client.ts +163 -0
  266. package/src/index.ts +22 -0
  267. package/src/lib/abort.ts +146 -0
  268. package/src/lib/admin-secrets.ts +25 -0
  269. package/src/lib/admission.ts +83 -0
  270. package/src/lib/app-owned-memory-stores.ts +173 -0
  271. package/src/lib/app-owned-memory.ts +265 -0
  272. package/src/lib/bounded-body.ts +202 -0
  273. package/src/lib/bun-binary-validator.d.mts +3 -0
  274. package/src/lib/bun-binary-validator.mjs +18 -0
  275. package/src/lib/bun-runtime.ts +71 -0
  276. package/src/lib/bun-stream-caps.ts +126 -0
  277. package/src/lib/config-ownership.ts +360 -0
  278. package/src/lib/crash-guard.ts +344 -0
  279. package/src/lib/debug-log-buffer.ts +83 -0
  280. package/src/lib/debug-settings.ts +108 -0
  281. package/src/lib/debug.ts +31 -0
  282. package/src/lib/destination-policy.ts +316 -0
  283. package/src/lib/errors.ts +364 -0
  284. package/src/lib/eventstream-decoder.ts +253 -0
  285. package/src/lib/gcp-adc.ts +341 -0
  286. package/src/lib/injection-debug-log.ts +58 -0
  287. package/src/lib/open-url.ts +25 -0
  288. package/src/lib/pinned-http.ts +151 -0
  289. package/src/lib/privacy.ts +20 -0
  290. package/src/lib/process-control.ts +165 -0
  291. package/src/lib/provider-outbound.ts +170 -0
  292. package/src/lib/provider-url.ts +14 -0
  293. package/src/lib/proxy-env.ts +18 -0
  294. package/src/lib/redact.ts +105 -0
  295. package/src/lib/retry-after.ts +55 -0
  296. package/src/lib/service-secrets.ts +25 -0
  297. package/src/lib/shadow-call.ts +30 -0
  298. package/src/lib/sidecar-tracker.ts +52 -0
  299. package/src/lib/sse-decoder.ts +323 -0
  300. package/src/lib/state-store-registrations.ts +109 -0
  301. package/src/lib/state-store-sweeper.ts +184 -0
  302. package/src/lib/test-home-guard.ts +90 -0
  303. package/src/lib/token-estimate.ts +69 -0
  304. package/src/lib/translator-budget.ts +356 -0
  305. package/src/lib/upstream-retry.ts +239 -0
  306. package/src/lib/win-exec.ts +115 -0
  307. package/src/lib/win-paths.ts +68 -0
  308. package/src/lib/windows-elevation.ts +705 -0
  309. package/src/lib/windows-secret-acl.ts +514 -0
  310. package/src/lib/winsw.ts +375 -0
  311. package/src/oauth/anthropic-routing.ts +594 -0
  312. package/src/oauth/anthropic.ts +177 -0
  313. package/src/oauth/callback-server.ts +294 -0
  314. package/src/oauth/chatgpt.ts +150 -0
  315. package/src/oauth/cursor.ts +211 -0
  316. package/src/oauth/github-copilot.ts +428 -0
  317. package/src/oauth/google-antigravity.ts +230 -0
  318. package/src/oauth/health.ts +399 -0
  319. package/src/oauth/index.ts +1174 -0
  320. package/src/oauth/key-providers.ts +108 -0
  321. package/src/oauth/kimi.ts +213 -0
  322. package/src/oauth/kiro-credentials.ts +726 -0
  323. package/src/oauth/kiro.ts +577 -0
  324. package/src/oauth/local-token-detect.ts +121 -0
  325. package/src/oauth/log.ts +48 -0
  326. package/src/oauth/login-cli.ts +163 -0
  327. package/src/oauth/pkce.ts +15 -0
  328. package/src/oauth/store.ts +630 -0
  329. package/src/oauth/token-guardian.ts +303 -0
  330. package/src/oauth/types.ts +62 -0
  331. package/src/oauth/xai.ts +241 -0
  332. package/src/pi/extensions.ts +72 -0
  333. package/src/pi/home.ts +42 -0
  334. package/src/pi/index.ts +40 -0
  335. package/src/pi/models.ts +278 -0
  336. package/src/pi/packages.ts +219 -0
  337. package/src/pi/settings.ts +365 -0
  338. package/src/pi/status.ts +68 -0
  339. package/src/pi/sync.ts +75 -0
  340. package/src/providers/alibaba-region-backup.ts +75 -0
  341. package/src/providers/alibaba-region-migration.ts +156 -0
  342. package/src/providers/alibaba-region-startup.ts +36 -0
  343. package/src/providers/antigravity-models.ts +205 -0
  344. package/src/providers/api-keys.ts +140 -0
  345. package/src/providers/base-url-choices.ts +64 -0
  346. package/src/providers/context-cap.ts +65 -0
  347. package/src/providers/derive.ts +339 -0
  348. package/src/providers/free-directory.ts +184 -0
  349. package/src/providers/github-copilot-transport.ts +56 -0
  350. package/src/providers/key-failover.ts +203 -0
  351. package/src/providers/kiro-models.ts +67 -0
  352. package/src/providers/label.ts +19 -0
  353. package/src/providers/model-discovery.ts +356 -0
  354. package/src/providers/openai-sidecar.ts +175 -0
  355. package/src/providers/openai-tier-startup.ts +27 -0
  356. package/src/providers/openai-tiers.ts +301 -0
  357. package/src/providers/openai-virtual-models.ts +82 -0
  358. package/src/providers/openrouter-routing.ts +102 -0
  359. package/src/providers/provider-id-rewrite.ts +150 -0
  360. package/src/providers/quota.ts +1260 -0
  361. package/src/providers/registry.ts +1600 -0
  362. package/src/providers/slug-codec.ts +67 -0
  363. package/src/providers/xai-transport.ts +141 -0
  364. package/src/reasoning-effort.ts +135 -0
  365. package/src/responses/compaction.ts +117 -0
  366. package/src/responses/parser.ts +656 -0
  367. package/src/responses/reasoning-envelope.ts +52 -0
  368. package/src/responses/schema.ts +159 -0
  369. package/src/responses/spill-store.ts +394 -0
  370. package/src/responses/state.ts +895 -0
  371. package/src/responses/tool-groups.ts +19 -0
  372. package/src/router.ts +425 -0
  373. package/src/server/adapter-resolve.ts +80 -0
  374. package/src/server/auth-cors.ts +530 -0
  375. package/src/server/chat-completions.ts +368 -0
  376. package/src/server/claude-messages.ts +914 -0
  377. package/src/server/effort-policy.ts +172 -0
  378. package/src/server/gui-static.ts +123 -0
  379. package/src/server/image-retry.ts +42 -0
  380. package/src/server/images.ts +476 -0
  381. package/src/server/index.ts +1126 -0
  382. package/src/server/lifecycle.ts +227 -0
  383. package/src/server/live.ts +598 -0
  384. package/src/server/management/agent-settings-routes.ts +1169 -0
  385. package/src/server/management/api-access.ts +141 -0
  386. package/src/server/management/api-key-usage.ts +167 -0
  387. package/src/server/management/body.ts +35 -0
  388. package/src/server/management/clients-routes.ts +63 -0
  389. package/src/server/management/cloud-sync-routes.ts +266 -0
  390. package/src/server/management/combo-routes.ts +220 -0
  391. package/src/server/management/config-routes.ts +422 -0
  392. package/src/server/management/context.ts +31 -0
  393. package/src/server/management/logs-usage-routes.ts +707 -0
  394. package/src/server/management/model-routes.ts +525 -0
  395. package/src/server/management/oauth-account-routes.ts +563 -0
  396. package/src/server/management/provider-routes.ts +556 -0
  397. package/src/server/management/shared.ts +277 -0
  398. package/src/server/management/sidebar-routes.ts +90 -0
  399. package/src/server/management/system-restart.ts +179 -0
  400. package/src/server/management/system-routes.ts +117 -0
  401. package/src/server/management/usage-summary-cache.ts +86 -0
  402. package/src/server/management-api.ts +215 -0
  403. package/src/server/management-auth.ts +267 -0
  404. package/src/server/memory-watchdog.ts +156 -0
  405. package/src/server/port-reclaim.ts +307 -0
  406. package/src/server/ports.ts +116 -0
  407. package/src/server/proxy-liveness.ts +201 -0
  408. package/src/server/relay-eager.ts +313 -0
  409. package/src/server/relay.ts +1049 -0
  410. package/src/server/request-decompress.ts +132 -0
  411. package/src/server/request-log-conversation.ts +168 -0
  412. package/src/server/request-log.ts +1046 -0
  413. package/src/server/responses/collaboration.ts +354 -0
  414. package/src/server/responses/compact.ts +384 -0
  415. package/src/server/responses/core.ts +2758 -0
  416. package/src/server/responses/encrypted-payload.ts +308 -0
  417. package/src/server/responses/fetch-helpers.ts +157 -0
  418. package/src/server/responses/passthrough-error.ts +78 -0
  419. package/src/server/responses/terminal-guard.ts +230 -0
  420. package/src/server/responses/upstream-error.ts +48 -0
  421. package/src/server/responses-image-gen-repair.ts +132 -0
  422. package/src/server/responses-item-id-repair.ts +224 -0
  423. package/src/server/responses.ts +9 -0
  424. package/src/server/search.ts +136 -0
  425. package/src/server/sse-payload-rewrite.ts +175 -0
  426. package/src/server/startup-action-control.ts +308 -0
  427. package/src/server/startup-health-cache.ts +113 -0
  428. package/src/server/system-env.ts +413 -0
  429. package/src/server/windows-tcp-drop.ts +184 -0
  430. package/src/server/windows-tray-control.ts +41 -0
  431. package/src/server/ws-bridge.ts +471 -0
  432. package/src/service.ts +2554 -0
  433. package/src/stall-timeout.ts +20 -0
  434. package/src/storage/cleanup-job.ts +57 -0
  435. package/src/storage/cleanup.ts +3085 -0
  436. package/src/storage/policy-job.ts +457 -0
  437. package/src/storage/policy-scheduler.ts +40 -0
  438. package/src/storage/policy-worker.ts +59 -0
  439. package/src/storage/policy.ts +527 -0
  440. package/src/storage/restore-job.ts +299 -0
  441. package/src/storage/restore-worker.ts +58 -0
  442. package/src/storage/scanner.ts +238 -0
  443. package/src/storage/storage-mutation-coordinator.ts +139 -0
  444. package/src/storage/worker-lifecycle.ts +215 -0
  445. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  446. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  447. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  448. package/src/tray/assets/opencodex-tray.png +0 -0
  449. package/src/tray/windows-tray.ps1 +290 -0
  450. package/src/tray/windows.ts +730 -0
  451. package/src/types.ts +1237 -0
  452. package/src/update/badge.ts +72 -0
  453. package/src/update/index.ts +407 -0
  454. package/src/update/job.ts +1520 -0
  455. package/src/update/notify.ts +257 -0
  456. package/src/update/npm-invocation.d.mts +23 -0
  457. package/src/update/npm-invocation.mjs +94 -0
  458. package/src/update/tray-update-plan.d.mts +18 -0
  459. package/src/update/tray-update-plan.mjs +38 -0
  460. package/src/usage/cost.ts +0 -0
  461. package/src/usage/debug.ts +97 -0
  462. package/src/usage/expected-prices.ts +164 -0
  463. package/src/usage/log.ts +658 -0
  464. package/src/usage/summary.ts +585 -0
  465. package/src/usage/totals.ts +14 -0
  466. package/src/vision/anthropic-describe.ts +185 -0
  467. package/src/vision/describe.ts +125 -0
  468. package/src/vision/index.ts +467 -0
  469. package/src/web-search/anthropic-executor.ts +189 -0
  470. package/src/web-search/executor.ts +105 -0
  471. package/src/web-search/format-result.ts +89 -0
  472. package/src/web-search/index.ts +196 -0
  473. package/src/web-search/loop.ts +664 -0
  474. package/src/web-search/parse.ts +220 -0
  475. package/src/web-search/progress-stream.ts +342 -0
  476. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Central identity neutralization.
3
+ *
4
+ * Codex sends the SAME GPT-5 identity line to EVERY model at request time (the per-model catalog
5
+ * `base_instructions` is ignored on the wire). For routed, non-OpenAI providers that line is both
6
+ * wrong (the model isn't GPT-5) and a liability: the previous fix replaced it with text that
7
+ * advertised "...served through / running via the opencodex proxy", which leaked our proxy identity
8
+ * into the upstream payload — a signature no first-party client (Claude Code, Gemini CLI, Kiro) ever
9
+ * sends, and a likely ToS trigger.
10
+ *
11
+ * The neutral replacement keeps ONLY the necessary instruction (don't misreport as GPT-5/OpenAI)
12
+ * and names no proxy. Provider-native identity blocks (e.g. the anthropic OAuth "You are a Claude
13
+ * agent..." prefix) are layered on TOP of this by the individual adapters; this module never claims
14
+ * to be a specific first-party client.
15
+ */
16
+
17
+ /** Historical exact identity line Codex injected for every model. */
18
+ export const CODEX_GPT5_IDENTITY_LINE = "You are Codex, a coding agent based on GPT-5.";
19
+
20
+ /** Codex CLI 0.145.0+ wording (#622) — still GPT-5 identity, slightly different phrasing. */
21
+ export const CODEX_GPT5_IDENTITY_LINE_AGENT = "You are Codex, an agent based on GPT-5.";
22
+
23
+ /**
24
+ * Known Codex GPT-5 identity sentences. Narrow: only "coding agent" / "an agent" + GPT-5(.x)?
25
+ * Avoid a broad `You are Codex.*` rewrite that could touch unrelated content.
26
+ */
27
+ const CODEX_GPT5_IDENTITY_RE =
28
+ /You are Codex, (?:a coding agent|an agent) based on GPT-5(?:\.[0-9]+)*\./g;
29
+
30
+ /** Proxy-neutral replacement: no "opencodex proxy" mention, just the GPT-5/OpenAI disclaimer. */
31
+ export const NEUTRAL_IDENTITY_LINE = "You are a coding agent. Do not claim to be GPT-5 or to be made by OpenAI.";
32
+
33
+ /**
34
+ * Replace Codex's hardcoded GPT-5 identity line with the proxy-neutral line. Safe to call on any
35
+ * system text: when the line is absent (already neutralized, or a provider that never received it)
36
+ * the input is returned unchanged. This is the single chokepoint every adapter routes through, so
37
+ * the leak can't reappear in one adapter while being fixed in another.
38
+ */
39
+ export function neutralizeIdentity(systemText: string): string {
40
+ return systemText.replace(CODEX_GPT5_IDENTITY_RE, NEUTRAL_IDENTITY_LINE);
41
+ }
42
+
43
+ /** The catalog (static, on-disk) replacement for `base_instructions`. Same neutral wording. */
44
+ export const NEUTRAL_IDENTITY_CATALOG = NEUTRAL_IDENTITY_LINE;
@@ -0,0 +1,23 @@
1
+ import type { OcxContentPart } from "../types";
2
+
3
+ /**
4
+ * Parse a `data:<media-type>;base64,<data>` URL into its parts. Codex sends inline images as base64
5
+ * data URLs (`into_data_url()`), which Anthropic/Google need split into media_type + raw base64.
6
+ * Returns null for non-data URLs (e.g. a remote https image), which callers pass through differently.
7
+ */
8
+ export function parseDataUrl(url: string): { mediaType: string; base64: string } | null {
9
+ const m = url.match(/^data:([^;,]+);base64,(.*)$/s);
10
+ if (!m) return null;
11
+ return { mediaType: m[1], base64: m[2] };
12
+ }
13
+
14
+ /**
15
+ * Flatten tool-result content to a string for chat/Gemini tool messages (which are text-only). After
16
+ * the vision sidecar runs, images are already text; this is the fallback for an undescribed image
17
+ * (vision model via view_image): a short marker, never the token-exploding image_url.
18
+ */
19
+ export function contentPartsToText(content: string | OcxContentPart[]): string {
20
+ if (typeof content === "string") return content;
21
+ const text = content.map(p => (p.type === "text" ? p.text : "[image]")).join("");
22
+ return text || "[image]";
23
+ }
@@ -0,0 +1,16 @@
1
+ export const KIRO_COMPLETION_TOOL_NAME = "codex_kiro_final_answer";
2
+ export const KIRO_CONTINUATION_MESSAGE =
3
+ "Continue from the prior conversation. Do not quote or mention this instruction.";
4
+ export const KIRO_COMPLETION_RETRY_MESSAGE =
5
+ `Continue the existing task without quoting this instruction. If the task is complete, call ${KIRO_COMPLETION_TOOL_NAME} now with the complete final answer. Otherwise issue the next real tool call now. Do not ask the user for another task or emit another progress-only message.`;
6
+
7
+ export const KIRO_TOOL_RESULT_CARRIER_MESSAGE = "The requested tool result is attached.";
8
+ export const KIRO_EMPTY_TOOL_RESULT_MESSAGE = "The tool completed without textual output.";
9
+
10
+ export const KIRO_COMPLETION_INSTRUCTIONS =
11
+ `When tools are available, ordinary assistant text is mid-task commentary and does not end the turn. Continue using tools after progress updates. When the task is fully complete and no more tool calls are needed, call ${KIRO_COMPLETION_TOOL_NAME} exactly once with the complete user-facing final answer in \`answer\`. Do not provide the final answer as ordinary assistant text.`;
12
+
13
+ export type KiroCompletionMode = "disabled" | "required" | "text_fallback";
14
+
15
+ /** Bound proxy-authored prompt additions independently of caller-owned instructions/history. */
16
+ export const MAX_KIRO_INJECTED_INSTRUCTION_CHARS = 16_384;
@@ -0,0 +1,197 @@
1
+ import { parseUpstreamJsonPayload, safeUpstreamErrorString, sanitizeUpstreamErrorText } from "./upstream-http-error";
2
+ const DETAIL_KEYS = ["__type", "code", "error", "name", "reason", "message", "Message", "errorMessage"];
3
+
4
+ export interface KiroErrorClassification {
5
+ message: string;
6
+ status: number;
7
+ errorType: string;
8
+ code: string;
9
+ retryable: boolean;
10
+ }
11
+
12
+ function headerValue(headers: Headers | Record<string, unknown>, name: string): string | undefined {
13
+ if (headers instanceof Headers) return name.startsWith(":") ? undefined : safeUpstreamErrorString(headers.get(name));
14
+ return safeUpstreamErrorString(headers[name]) || safeUpstreamErrorString(headers[name.toLowerCase()]);
15
+ }
16
+
17
+ function payloadDetails(payloadText: string): string[] {
18
+ const trimmed = payloadText.trim();
19
+ if (!trimmed) return [];
20
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return [trimmed];
21
+ const parsed = parseUpstreamJsonPayload(trimmed);
22
+ if (parsed === undefined) return [];
23
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
24
+ const obj = parsed as Record<string, unknown>;
25
+ return DETAIL_KEYS.map(key => safeUpstreamErrorString(obj[key])).filter((v): v is string => !!v);
26
+ }
27
+ if (typeof parsed === "string" && parsed.trim()) return [parsed.trim()];
28
+ return [];
29
+ }
30
+
31
+ function classifyKiroText(status: number | undefined, text: string): string {
32
+ const lower = text.toLowerCase();
33
+ const rateQuota = /requests?\s+per\s+(?:min|minute|second)|rpm|tpm/.test(lower);
34
+ const quotaExhausted =
35
+ lower.includes("insufficient_quota") ||
36
+ lower.includes("quota exhausted") ||
37
+ lower.includes("account quota exceeded") ||
38
+ lower.includes("monthly quota exceeded") ||
39
+ lower.includes("daily quota exceeded") ||
40
+ lower.includes("exceeded your current quota");
41
+ if (quotaExhausted && !rateQuota) return "Kiro quota exhausted";
42
+ if (
43
+ status === 429 ||
44
+ lower.includes("throttlingexception") ||
45
+ lower.includes("too many requests") ||
46
+ lower.includes("rate limited") ||
47
+ lower.includes("rate limit")
48
+ ) return "Kiro rate limit exceeded";
49
+ if (
50
+ status === 401 ||
51
+ status === 403 ||
52
+ lower.includes("accessdenied") ||
53
+ lower.includes("access denied") ||
54
+ lower.includes("unauthorized") ||
55
+ lower.includes("unrecognizedclient") ||
56
+ lower.includes("expiredtoken") ||
57
+ lower.includes("expired token") ||
58
+ lower.includes("invalid token") ||
59
+ lower.includes("authentication")
60
+ ) return "Kiro authentication failed";
61
+ if (
62
+ status === 503 ||
63
+ lower.includes("overloaded") ||
64
+ lower.includes("server is busy") ||
65
+ lower.includes("temporarily unavailable")
66
+ ) return "Kiro server overloaded";
67
+ if (
68
+ status === 400 ||
69
+ lower.includes("validationexception") ||
70
+ lower.includes("invalid request") ||
71
+ lower.includes("profile arn") ||
72
+ lower.includes("model unavailable") ||
73
+ lower.includes("model not found") ||
74
+ lower.includes("unsupported model") ||
75
+ lower.includes("region") ||
76
+ lower.includes("schema") ||
77
+ lower.includes("malformed")
78
+ ) return "Kiro invalid request";
79
+ return "Kiro upstream error";
80
+ }
81
+
82
+ function normalizedKiroErrorMessage(headers: Headers | Record<string, unknown>, payloadText: string, status?: number): string {
83
+ const headerType = headerValue(headers, ":exception-type") || headerValue(headers, ":error-type");
84
+ const parts = [headerType, ...payloadDetails(payloadText)].filter((part): part is string => !!part);
85
+ const detail = parts.length > 0 ? sanitizeUpstreamErrorText(parts.join(": ")).slice(0, 500) : status ? `HTTP ${status}` : "";
86
+ const prefix = classifyKiroText(status, [detail, headerType].filter(Boolean).join(" "));
87
+ return detail ? `${prefix}: ${detail}` : prefix;
88
+ }
89
+
90
+ function isContentLengthError(text: string): boolean {
91
+ const lower = text.toLowerCase();
92
+ return lower.includes("content_length_exceeds_threshold") || lower.includes("content length exceeds");
93
+ }
94
+
95
+ function classifyKiroFailure(
96
+ headers: Headers | Record<string, unknown>,
97
+ payloadText: string,
98
+ status?: number,
99
+ ): KiroErrorClassification {
100
+ const message = normalizedKiroErrorMessage(headers, payloadText, status);
101
+ const headerType = headerValue(headers, ":exception-type") || headerValue(headers, ":error-type") || "";
102
+ const evidence = [headerType, ...payloadDetails(payloadText), message].join(" ").toLowerCase();
103
+ if (isContentLengthError(evidence)) {
104
+ return {
105
+ message: "Kiro rejected the request because the conversation exceeds the model's context window. Compact or reduce the history, or start a new session.",
106
+ status: 400,
107
+ errorType: "invalid_request_error",
108
+ code: "context_length_exceeded",
109
+ retryable: false,
110
+ };
111
+ }
112
+ if (
113
+ evidence.includes("insufficient_quota")
114
+ || evidence.includes("quota exhausted")
115
+ || evidence.includes("quota exceeded")
116
+ ) {
117
+ return { message, status: 429, errorType: "insufficient_quota", code: "insufficient_quota", retryable: false };
118
+ }
119
+ if (
120
+ status === 429
121
+ || evidence.includes("throttlingexception")
122
+ || evidence.includes("too many requests")
123
+ || evidence.includes("rate limit")
124
+ ) {
125
+ return { message, status: 429, errorType: "rate_limit_error", code: "rate_limit_exceeded", retryable: true };
126
+ }
127
+ if (
128
+ status === 401
129
+ || status === 403
130
+ || evidence.includes("accessdenied")
131
+ || evidence.includes("unauthorized")
132
+ || evidence.includes("unrecognizedclient")
133
+ || evidence.includes("expiredtoken")
134
+ || evidence.includes("expired token")
135
+ || evidence.includes("invalid token")
136
+ || evidence.includes("authentication")
137
+ ) {
138
+ return { message, status: status === 403 ? 403 : 401, errorType: status === 403 ? "permission_error" : "authentication_error", code: status === 403 ? "permission_denied" : "invalid_api_key", retryable: false };
139
+ }
140
+ if (
141
+ status === 400
142
+ || evidence.includes("validationexception")
143
+ || evidence.includes("invalid request")
144
+ || evidence.includes("model unavailable")
145
+ || evidence.includes("model not found")
146
+ || evidence.includes("unsupported model")
147
+ || evidence.includes("profile arn")
148
+ || evidence.includes("malformed")
149
+ ) {
150
+ return { message, status: 400, errorType: "invalid_request_error", code: "invalid_request_error", retryable: false };
151
+ }
152
+ if (
153
+ status === 503
154
+ || evidence.includes("overloaded")
155
+ || evidence.includes("server is busy")
156
+ || evidence.includes("temporarily unavailable")
157
+ ) {
158
+ return { message, status: 503, errorType: "server_error", code: "server_is_overloaded", retryable: true };
159
+ }
160
+ return {
161
+ message,
162
+ status: status && status >= 500 ? status : 502,
163
+ errorType: "server_error",
164
+ code: "upstream_server_error",
165
+ retryable: true,
166
+ };
167
+ }
168
+
169
+ export function safeKiroErrorMessage(headers: Record<string, unknown>, payloadText: string): string {
170
+ return normalizedKiroErrorMessage(headers, payloadText);
171
+ }
172
+
173
+ export function classifyKiroStreamError(
174
+ headers: Record<string, unknown>,
175
+ payloadText: string,
176
+ ): KiroErrorClassification {
177
+ return classifyKiroFailure(headers, payloadText);
178
+ }
179
+
180
+ export function classifyKiroHttpError(
181
+ status: number,
182
+ headers: Headers | Record<string, unknown>,
183
+ payloadText: string,
184
+ ): KiroErrorClassification {
185
+ return classifyKiroFailure(headers, payloadText, status);
186
+ }
187
+
188
+ export function classifyKiroEventError(reason: string | undefined, message: string | undefined): KiroErrorClassification {
189
+ const safeReason = reason ? sanitizeUpstreamErrorText(reason).slice(0, 160) : "";
190
+ const safeMessage = message ? sanitizeUpstreamErrorText(message).slice(0, 500) : "Kiro request failed";
191
+ const payload = JSON.stringify({ reason: safeReason, message: safeMessage });
192
+ return classifyKiroFailure({}, payload);
193
+ }
194
+
195
+ export function safeKiroHttpErrorMessage(status: number, headers: Headers | Record<string, unknown>, payloadText: string): string {
196
+ return classifyKiroFailure(headers, payloadText, status).message;
197
+ }
@@ -0,0 +1,179 @@
1
+ import type { OcxUsage } from "../types";
2
+ import { kiroTruncationReason } from "./kiro-truncation";
3
+
4
+ export type ParsedKiroEvent =
5
+ | { type: "content"; data?: string; modelId?: string }
6
+ | { type: "reasoning"; data?: string }
7
+ | { type: "tool"; name?: string; toolUseId?: string; input?: string; stop?: boolean }
8
+ | { type: "truncation"; data: string }
9
+ | { type: "metadata"; usage?: OcxUsage; contextUsagePercentage?: number; stopReason?: string }
10
+ | { type: "message_metadata"; conversationId?: string }
11
+ | { type: "invalid_state"; message?: string }
12
+ | { type: "error"; reason?: string; message?: string };
13
+
14
+ const KNOWN_EVENT_TYPES = new Set([
15
+ "assistantResponseEvent",
16
+ "reasoningContentEvent",
17
+ "toolUseEvent",
18
+ "messageMetadataEvent",
19
+ "metadataEvent",
20
+ "invalidStateEvent",
21
+ "error",
22
+ ]);
23
+
24
+ function malformed(eventType: string, detail: string): never {
25
+ throw new Error(`invalid Kiro ${eventType} payload: ${detail}`);
26
+ }
27
+
28
+ function parseObject(eventType: string, payload: Uint8Array): Record<string, unknown> {
29
+ let value: unknown;
30
+ try {
31
+ value = JSON.parse(new TextDecoder().decode(payload));
32
+ } catch {
33
+ return malformed(eventType, "expected valid JSON");
34
+ }
35
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
36
+ return malformed(eventType, "expected an object");
37
+ }
38
+ return value as Record<string, unknown>;
39
+ }
40
+
41
+ function optionalString(eventType: string, obj: Record<string, unknown>, key: string): string | undefined {
42
+ const value = obj[key];
43
+ if (value === undefined || value === null) return undefined;
44
+ if (typeof value !== "string") return malformed(eventType, `${key} must be a string`);
45
+ return value;
46
+ }
47
+
48
+ function optionalBoolean(eventType: string, obj: Record<string, unknown>, key: string): boolean | undefined {
49
+ const value = obj[key];
50
+ if (value === undefined || value === null) return undefined;
51
+ if (typeof value !== "boolean") return malformed(eventType, `${key} must be a boolean`);
52
+ return value;
53
+ }
54
+
55
+ function tokenCount(eventType: string, obj: Record<string, unknown>, key: string, required: boolean): number {
56
+ const value = obj[key];
57
+ if (value === undefined && !required) return 0;
58
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
59
+ return malformed(eventType, `${key} must be a non-negative safe integer`);
60
+ }
61
+ return value;
62
+ }
63
+
64
+ function parseTokenUsage(eventType: string, value: unknown): OcxUsage | undefined {
65
+ if (value === undefined || value === null) return undefined;
66
+ if (typeof value !== "object" || Array.isArray(value)) {
67
+ return malformed(eventType, "tokenUsage must be an object");
68
+ }
69
+ const usage = value as Record<string, unknown>;
70
+ const uncached = tokenCount(eventType, usage, "uncachedInputTokens", true);
71
+ const cacheRead = tokenCount(eventType, usage, "cacheReadInputTokens", false);
72
+ const cacheWrite = tokenCount(eventType, usage, "cacheWriteInputTokens", false);
73
+ const outputTokens = tokenCount(eventType, usage, "outputTokens", true);
74
+ const totalTokens = tokenCount(eventType, usage, "totalTokens", true);
75
+ const inputTokens = uncached + cacheRead + cacheWrite;
76
+ if (!Number.isSafeInteger(inputTokens)) return malformed(eventType, "input token usage overflowed");
77
+ return {
78
+ inputTokens,
79
+ outputTokens,
80
+ totalTokens,
81
+ cachedInputTokens: cacheRead,
82
+ cacheReadInputTokens: cacheRead,
83
+ cacheCreationInputTokens: cacheWrite,
84
+ };
85
+ }
86
+
87
+ /** Decode a known Kiro event using its Smithy `:event-type` header. */
88
+ export function parseKiroEvent(eventType: string, payload: Uint8Array): ParsedKiroEvent | null {
89
+ // Unknown event types are intentionally ignored without parsing or logging their payload.
90
+ if (!KNOWN_EVENT_TYPES.has(eventType)) return null;
91
+ const parsed = parseObject(eventType, payload);
92
+ // A metadataEvent's `stopReason` is Kiro's own terminal verdict and must reach the parser
93
+ // intact. The generic truncation sniffer matches substrings ("max_tokens", "length",
94
+ // "context_length") across several keys, so it would swallow MAX_TOKENS before the switch
95
+ // below ever runs. Gate on position rather than on the value: a value allowlist would keep
96
+ // eating future reasons such as LENGTH_LIMIT, which matches the pattern today.
97
+ const nativeStopReason = eventType === "metadataEvent"
98
+ ? optionalString(eventType, parsed, "stopReason")
99
+ : undefined;
100
+ if (nativeStopReason === undefined) {
101
+ const truncationReason = kiroTruncationReason(parsed);
102
+ if (truncationReason) return { type: "truncation", data: truncationReason };
103
+ }
104
+
105
+ switch (eventType) {
106
+ case "assistantResponseEvent":
107
+ return {
108
+ type: "content",
109
+ ...(optionalString(eventType, parsed, "content") !== undefined
110
+ ? { data: optionalString(eventType, parsed, "content") }
111
+ : {}),
112
+ ...(optionalString(eventType, parsed, "modelId") !== undefined
113
+ ? { modelId: optionalString(eventType, parsed, "modelId") }
114
+ : {}),
115
+ };
116
+ case "reasoningContentEvent":
117
+ return {
118
+ type: "reasoning",
119
+ ...(optionalString(eventType, parsed, "text") !== undefined
120
+ ? { data: optionalString(eventType, parsed, "text") }
121
+ : {}),
122
+ };
123
+ case "toolUseEvent":
124
+ return {
125
+ type: "tool",
126
+ ...(optionalString(eventType, parsed, "name") !== undefined
127
+ ? { name: optionalString(eventType, parsed, "name") }
128
+ : {}),
129
+ ...(optionalString(eventType, parsed, "toolUseId") !== undefined
130
+ ? { toolUseId: optionalString(eventType, parsed, "toolUseId") }
131
+ : {}),
132
+ ...(optionalString(eventType, parsed, "input") !== undefined
133
+ ? { input: optionalString(eventType, parsed, "input") }
134
+ : {}),
135
+ ...(optionalBoolean(eventType, parsed, "stop") !== undefined
136
+ ? { stop: optionalBoolean(eventType, parsed, "stop") }
137
+ : {}),
138
+ };
139
+ case "messageMetadataEvent":
140
+ return {
141
+ type: "message_metadata",
142
+ conversationId:
143
+ optionalString(eventType, parsed, "conversationId")
144
+ ?? optionalString(eventType, parsed, "utteranceId"),
145
+ };
146
+ case "metadataEvent": {
147
+ const contextUsagePercentage = parsed.contextUsagePercentage;
148
+ if (
149
+ contextUsagePercentage !== undefined
150
+ && (typeof contextUsagePercentage !== "number" || !Number.isFinite(contextUsagePercentage))
151
+ ) {
152
+ return malformed(eventType, "contextUsagePercentage must be a finite number");
153
+ }
154
+ const stopReason = optionalString(eventType, parsed, "stopReason");
155
+ return {
156
+ type: "metadata",
157
+ ...(parseTokenUsage(eventType, parsed.tokenUsage) !== undefined
158
+ ? { usage: parseTokenUsage(eventType, parsed.tokenUsage) }
159
+ : {}),
160
+ ...(typeof contextUsagePercentage === "number" ? { contextUsagePercentage } : {}),
161
+ ...(stopReason !== undefined ? { stopReason } : {}),
162
+ };
163
+ }
164
+ case "invalidStateEvent":
165
+ return { type: "invalid_state", message: optionalString(eventType, parsed, "message") };
166
+ case "error":
167
+ return {
168
+ type: "error",
169
+ reason:
170
+ optionalString(eventType, parsed, "reason")
171
+ ?? optionalString(eventType, parsed, "type")
172
+ ?? optionalString(eventType, parsed, "__type"),
173
+ message:
174
+ optionalString(eventType, parsed, "message")
175
+ ?? optionalString(eventType, parsed, "Message"),
176
+ };
177
+ }
178
+ return null;
179
+ }
@@ -0,0 +1,129 @@
1
+ import type { OcxContentPart } from "../types";
2
+ import { normalizeImageTargets, type NormalizeOptions, type NormalizeTarget } from "./anthropic-image-normalize";
3
+
4
+ // CodeWhisperer native image part (matches Kiro IDE wire format): the base64 bytes live directly in
5
+ // userInputMessage.images, NOT in userInputMessageContext. Verified against kiro-gateway.
6
+ export interface KiroImage {
7
+ format: string; // "jpeg" | "png" | "webp" | "gif" — derived from the media subtype
8
+ source: { bytes: string }; // pure base64, no "data:...;base64," prefix
9
+ }
10
+
11
+ // Codex sends each image as a `data:` URL (base64) or a remote https URL. Only data URLs can be
12
+ // inlined as bytes here; remote URLs are not fetchable at request-build time.
13
+ function parseDataUrlImage(imageUrl: string): KiroImage | undefined {
14
+ if (!imageUrl.startsWith("data:")) return undefined;
15
+ const comma = imageUrl.indexOf(",");
16
+ if (comma === -1) return undefined;
17
+ const header = imageUrl.slice(5, comma);
18
+ const bytes = imageUrl.slice(comma + 1);
19
+ if (!bytes) return undefined;
20
+ const mediaType = header.split(";")[0] || "image/jpeg";
21
+ const subtype = (mediaType.includes("/") ? mediaType.split("/")[1] : mediaType) || "jpeg";
22
+ // CodeWhisperer/Bedrock expects "jpeg", not the "jpg" alias.
23
+ const format = subtype.toLowerCase() === "jpg" ? "jpeg" : subtype.toLowerCase();
24
+ return { format, source: { bytes } };
25
+ }
26
+
27
+ export function extractKiroImages(content: string | OcxContentPart[]): KiroImage[] {
28
+ if (typeof content === "string") return [];
29
+ const out: KiroImage[] = [];
30
+ for (const p of content) {
31
+ if (p.type !== "image") continue;
32
+ const img = parseDataUrlImage(p.imageUrl);
33
+ if (img) out.push(img);
34
+ }
35
+ return out;
36
+ }
37
+
38
+ /**
39
+ * Conservative POLICY caps for the CodeWhisperer GenerateAssistantResponse payload,
40
+ * whose limits are undocumented. Derived from adjacent AWS surfaces
41
+ * (devlog/260714_image_normalization_pipeline/050): Bedrock `Message` allows 20 images
42
+ * per message (Converse), and `InvokeModel` caps requests at 25,000,000 bytes — 18MiB
43
+ * bounds the IMAGE share of the body with headroom for text/tools.
44
+ */
45
+ export const KIRO_IMAGE_BASE64_BUDGET = 18 * 1024 * 1024;
46
+ export const KIRO_MAX_IMAGES_PER_MESSAGE = 20;
47
+
48
+ const COUNT_CAP_NOTE = "[image omitted: exceeded the 20-image per-message cap; oldest images in this message were dropped]";
49
+
50
+ /** A kiro wire message that can carry images (history userInputMessage or currentMessage). */
51
+ interface KiroImageCarrier {
52
+ content?: string;
53
+ images?: KiroImage[];
54
+ }
55
+
56
+ function isCarrier(v: unknown): v is KiroImageCarrier {
57
+ return typeof v === "object" && v !== null;
58
+ }
59
+
60
+ /** Collect image-bearing userInputMessages in wire order (history oldest-first, then current). */
61
+ function collectKiroImageCarriers(payload: unknown): KiroImageCarrier[] {
62
+ const state = (payload as { conversationState?: { history?: unknown[]; currentMessage?: { userInputMessage?: unknown } } })?.conversationState;
63
+ if (!state) return [];
64
+ const carriers: KiroImageCarrier[] = [];
65
+ for (const entry of state.history ?? []) {
66
+ const uim = (entry as { userInputMessage?: unknown })?.userInputMessage;
67
+ if (isCarrier(uim)) carriers.push(uim);
68
+ }
69
+ const current = state.currentMessage?.userInputMessage;
70
+ if (isCarrier(current)) carriers.push(current);
71
+ return carriers;
72
+ }
73
+
74
+ function appendNote(carrier: KiroImageCarrier, note: string): void {
75
+ carrier.content = carrier.content ? `${carrier.content}\n${note}` : note;
76
+ }
77
+
78
+ /**
79
+ * Apply the generous image pipeline to a built CodeWhisperer payload (mutates in
80
+ * place): per-message 20-image cap first (oldest dropped), then the shared tier
81
+ * machinery with the kiro budget and terminal-overflow DROP (kiro has no downstream
82
+ * guard). Test seams (encode/validate) forward into the core.
83
+ */
84
+ export async function normalizeKiroImages(
85
+ payload: unknown,
86
+ opts?: Pick<NormalizeOptions, "encode" | "validate">,
87
+ ): Promise<void> {
88
+ const carriers = collectKiroImageCarriers(payload);
89
+ if (carriers.length === 0) return;
90
+
91
+ // Pre-pass: per-message count cap (drop oldest within the message).
92
+ for (const carrier of carriers) {
93
+ const images = carrier.images;
94
+ if (!images || images.length <= KIRO_MAX_IMAGES_PER_MESSAGE) continue;
95
+ images.splice(0, images.length - KIRO_MAX_IMAGES_PER_MESSAGE);
96
+ appendNote(carrier, COUNT_CAP_NOTE);
97
+ }
98
+
99
+ // Targets over the survivors, oldest→newest across carriers. Drops resolve the image
100
+ // by OBJECT IDENTITY at execution time (indices go stale after earlier splices) and
101
+ // delete an emptied images field per the builder's omission contract.
102
+ const targets: NormalizeTarget[] = [];
103
+ for (const carrier of carriers) {
104
+ for (const img of carrier.images ?? []) {
105
+ targets.push({
106
+ base64: typeof img.source?.bytes === "string" && img.source.bytes.length > 0 ? img.source.bytes : null,
107
+ mediaType: `image/${(img.format || "jpeg").toLowerCase()}`,
108
+ replace: (data: string, mediaType: string) => {
109
+ img.source.bytes = data;
110
+ img.format = (mediaType.split("/")[1] ?? "jpeg").toLowerCase();
111
+ },
112
+ drop: (note: string) => {
113
+ const arr = carrier.images;
114
+ if (arr) {
115
+ const idx = arr.indexOf(img);
116
+ if (idx !== -1) arr.splice(idx, 1);
117
+ if (arr.length === 0) delete carrier.images;
118
+ }
119
+ appendNote(carrier, note);
120
+ },
121
+ });
122
+ }
123
+ }
124
+ await normalizeImageTargets(targets, {
125
+ budget: KIRO_IMAGE_BASE64_BUDGET,
126
+ overflowAction: "drop",
127
+ ...(opts ?? {}),
128
+ });
129
+ }