@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,577 @@
1
+ /**
2
+ * Kiro (AWS CodeWhisperer) OAuth — import-first.
3
+ *
4
+ * Normal login imports the locally installed kiro-cli session. Account-add login deliberately
5
+ * asks kiro-cli to switch identities in its supported browser flow, then imports that fresh session.
6
+ *
7
+ * Ported from jawcode packages/ai/src/providers/kiro.ts (readKiroCliSqlite, refreshKiroDesktopToken).
8
+ * profileArn/region/client registration are persisted per OCX account so switching the account pool
9
+ * never combines one account's access token with another account's local Kiro profile metadata.
10
+ */
11
+ import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
12
+ import {
13
+ discardKiroCliSessionRecovery,
14
+ inferRegionFromProfileArn,
15
+ inspectKiroCliSqliteSources,
16
+ inspectKiroCliSessionSnapshot,
17
+ normalizeKiroRegion,
18
+ persistKiroCliSessionRecovery,
19
+ readImportedKiroCredential,
20
+ readKiroCliSqliteCredential,
21
+ resolveKiroCliExecutable,
22
+ restoreKiroCliSession,
23
+ restoreStaleKiroCliSessionRecovery,
24
+ requireKiroRegion,
25
+ type ImportedKiroCredential,
26
+ type KiroCliSessionSnapshot,
27
+ type KiroImportDiagnostic,
28
+ } from "./kiro-credentials";
29
+ import { homedir } from "node:os";
30
+ import { getAccountSet, saveAccountCredential } from "./store";
31
+
32
+ const DEFAULT_REGION = "us-east-1";
33
+ const REFRESH_URL = "https://prod.{region}.auth.desktop.kiro.dev/refreshToken";
34
+ const OIDC_URL = "https://oidc.{region}.amazonaws.com/token";
35
+ const KIRO_CLI_UNIX_INSTALL_COMMAND = "curl -fsSL https://cli.kiro.dev/install | bash";
36
+ const KIRO_CLI_WINDOWS_INSTALL_COMMAND = "irm 'https://cli.kiro.dev/install.ps1' | iex";
37
+ const KIRO_TERMINAL_REFRESH_ERRORS = new Set([
38
+ "invalid_grant",
39
+ "refresh_token_reused",
40
+ "revoked",
41
+ "revoked_token",
42
+ "refresh_token_revoked",
43
+ "access_denied",
44
+ "expired_token",
45
+ ]);
46
+
47
+ interface ImportedKiroToken {
48
+ access: string;
49
+ refresh: string;
50
+ expires: number;
51
+ }
52
+
53
+ export interface KiroCliCommandResult {
54
+ exitCode: number;
55
+ stdout: string;
56
+ }
57
+
58
+ export class KiroTokenRefreshError extends Error {
59
+ constructor(
60
+ readonly httpStatus: number,
61
+ readonly oauthError?: string,
62
+ ) {
63
+ super(`Kiro token refresh failed: ${httpStatus}${oauthError ? ` (${oauthError})` : ""}`);
64
+ this.name = "KiroTokenRefreshError";
65
+ }
66
+ }
67
+
68
+ export type KiroCliRunner = (args: string[], signal?: AbortSignal) => Promise<KiroCliCommandResult>;
69
+
70
+ export interface KiroLoginOptions {
71
+ forceLogin?: boolean;
72
+ cliRunner?: KiroCliRunner;
73
+ }
74
+
75
+ export function kiroCliInstallGuidance(platform = process.platform): string {
76
+ return platform === "win32"
77
+ ? `install the Kiro CLI in PowerShell (\`${KIRO_CLI_WINDOWS_INSTALL_COMMAND}\`)`
78
+ : `install the Kiro CLI (\`${KIRO_CLI_UNIX_INSTALL_COMMAND}\`)`;
79
+ }
80
+
81
+ const pendingKiroLoginTransactions = new WeakMap<OAuthCredentials, KiroCliSessionSnapshot>();
82
+ /** Forced logins that started with no native CLI DB must logout on persistence failure. */
83
+ const pendingKiroEmptyPriorSessions = new WeakSet<OAuthCredentials>();
84
+
85
+
86
+ function resolveRuntimeKiroCliExecutable(): string {
87
+ return resolveKiroCliExecutable({
88
+ env: process.env,
89
+ platform: process.platform,
90
+ home: process.platform === "win32" ? homedir() : (process.env.HOME || homedir()),
91
+ });
92
+ }
93
+
94
+ function logoutKiroCliBestEffort(): void {
95
+ try {
96
+ Bun.spawnSync([resolveRuntimeKiroCliExecutable(), "logout"], {
97
+ stdin: "ignore",
98
+ stdout: "ignore",
99
+ stderr: "ignore",
100
+ windowsHide: true,
101
+ });
102
+ } catch {
103
+ // Best-effort rollback when the prior CLI state was empty.
104
+ }
105
+ }
106
+
107
+ /** Settle the external CLI side of a forced login after OCX credential persistence resolves. */
108
+ export function settleKiroLoginTransaction(credential: OAuthCredentials, persisted: boolean): void {
109
+ const snapshot = pendingKiroLoginTransactions.get(credential);
110
+ const emptyPrior = pendingKiroEmptyPriorSessions.has(credential);
111
+ pendingKiroLoginTransactions.delete(credential);
112
+ pendingKiroEmptyPriorSessions.delete(credential);
113
+ if (!persisted) {
114
+ if (snapshot) {
115
+ restoreKiroCliSession(snapshot);
116
+ discardKiroCliSessionRecovery(snapshot);
117
+ return;
118
+ }
119
+ if (emptyPrior) logoutKiroCliBestEffort();
120
+ return;
121
+ }
122
+ if (snapshot) discardKiroCliSessionRecovery(snapshot);
123
+ }
124
+
125
+ function restoreKiroLoginOrThrow(snapshot: KiroCliSessionSnapshot | null, emptyPrior: boolean, cause: unknown): never {
126
+ if (snapshot) {
127
+ try {
128
+ restoreKiroCliSession(snapshot);
129
+ discardKiroCliSessionRecovery(snapshot);
130
+ } catch (restoreError) {
131
+ throw new AggregateError(
132
+ [cause, restoreError],
133
+ "Kiro login failed and the previous Kiro CLI session could not be restored.",
134
+ );
135
+ }
136
+ } else if (emptyPrior) {
137
+ logoutKiroCliBestEffort();
138
+ }
139
+ throw cause;
140
+ }
141
+
142
+ function throwIfKiroLoginCancelled(signal?: AbortSignal): void {
143
+ if (signal?.aborted) throw new Error("Kiro login cancelled.");
144
+ }
145
+
146
+ async function defaultKiroCliRunner(args: string[], signal?: AbortSignal): Promise<KiroCliCommandResult> {
147
+ throwIfKiroLoginCancelled(signal);
148
+ let child: ReturnType<typeof Bun.spawn>;
149
+ try {
150
+ child = Bun.spawn([resolveRuntimeKiroCliExecutable(), ...args], {
151
+ stdin: "ignore",
152
+ stdout: "pipe",
153
+ stderr: "ignore",
154
+ windowsHide: true,
155
+ });
156
+ } catch {
157
+ throw new Error("Kiro CLI is not installed or could not be started.");
158
+ }
159
+ const abort = () => child.kill();
160
+ signal?.addEventListener("abort", abort, { once: true });
161
+ // AbortSignal does not replay an abort that lands between the pre-check and listener registration.
162
+ if (signal?.aborted) abort();
163
+ try {
164
+ const [exitCode, stdout] = await Promise.all([
165
+ child.exited,
166
+ child.stdout instanceof ReadableStream ? new Response(child.stdout).text() : Promise.resolve(""),
167
+ ]);
168
+ throwIfKiroLoginCancelled(signal);
169
+ return { exitCode, stdout };
170
+ } finally {
171
+ signal?.removeEventListener("abort", abort);
172
+ }
173
+ }
174
+
175
+ async function readKiroCliIdentity(runner: KiroCliRunner, signal?: AbortSignal): Promise<{ email?: string }> {
176
+ try {
177
+ const result = await runner(["whoami", "--format", "json"], signal);
178
+ if (result.exitCode !== 0) return {};
179
+ const parsed = JSON.parse(result.stdout) as { email?: unknown };
180
+ const email = typeof parsed.email === "string" ? parsed.email.trim().toLowerCase() : "";
181
+ return email && email.length <= 320 ? { email } : {};
182
+ } catch {
183
+ return {};
184
+ }
185
+ }
186
+
187
+ function metadataFromImported(imported: ImportedKiroCredential): KiroOAuthMetadata | undefined {
188
+ const metadata: KiroOAuthMetadata = {
189
+ ...(imported.profileArn ? { profileArn: imported.profileArn } : {}),
190
+ ...(imported.ssoRegion ? { ssoRegion: imported.ssoRegion } : {}),
191
+ ...(imported.apiRegion ? { apiRegion: imported.apiRegion } : {}),
192
+ ...(imported.clientId ? { clientId: imported.clientId } : {}),
193
+ ...(imported.clientSecret ? { clientSecret: imported.clientSecret } : {}),
194
+ };
195
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
196
+ }
197
+
198
+ /** Persistable routing subset from explicit KIRO_* environment overrides (never local CLI state). */
199
+ export function environmentKiroRoutingMetadata(): Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion"> | undefined {
200
+ const profileArn = process.env.KIRO_PROFILE_ARN?.trim() || undefined;
201
+ const apiRegion = process.env.KIRO_API_REGION !== undefined
202
+ ? requireKiroRegion(process.env.KIRO_API_REGION)
203
+ : undefined;
204
+ const ssoRegion = process.env.KIRO_REGION !== undefined
205
+ ? requireKiroRegion(process.env.KIRO_REGION)
206
+ : undefined;
207
+ if (!profileArn && !apiRegion && !ssoRegion) return undefined;
208
+ return {
209
+ ...(profileArn ? { profileArn } : {}),
210
+ ...(apiRegion ? { apiRegion } : {}),
211
+ ...(ssoRegion ? { ssoRegion } : {}),
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Before Add-account switches the external CLI identity, bind any matching legacy identity-less
217
+ * OCX row to the current CLI session. Unmatched identity-less rows are left alone here so a
218
+ * cancelled browser login cannot destroy the only stored credential; after a successful add they
219
+ * remain selectable but cannot borrow the new CLI identity (and reauth refuses identity-less slots).
220
+ */
221
+ async function bindMatchingLegacyIdentitylessKiroAccounts(
222
+ runner: KiroCliRunner,
223
+ signal?: AbortSignal,
224
+ ): Promise<void> {
225
+ const set = getAccountSet("kiro");
226
+ if (!set) return;
227
+ const legacyAccounts = set.accounts.filter(
228
+ account => account.credential.accountId === undefined && account.credential.email === undefined,
229
+ );
230
+ if (legacyAccounts.length === 0) return;
231
+
232
+ let imported: ImportedKiroCredential | null = null;
233
+ try {
234
+ imported = readKiroCliSqliteCredential();
235
+ } catch {
236
+ imported = null;
237
+ }
238
+ if (!imported?.refresh) return;
239
+
240
+ for (const account of legacyAccounts) {
241
+ const refresh = account.credential.refresh;
242
+ if (!refresh || imported.refresh !== refresh) continue;
243
+ const bound = await oauthCredentialFromImported(imported, runner, signal);
244
+ if (!bound.accountId && !bound.email) continue;
245
+ await saveAccountCredential("kiro", account.id, bound);
246
+ }
247
+ }
248
+
249
+ async function oauthCredentialFromImported(
250
+ imported: ImportedKiroCredential,
251
+ runner: KiroCliRunner,
252
+ signal?: AbortSignal,
253
+ ): Promise<OAuthCredentials> {
254
+ const identity = imported.source === "sqlite" ? await readKiroCliIdentity(runner, signal) : {};
255
+ const metadata = metadataFromImported(imported);
256
+ return {
257
+ access: imported.access,
258
+ refresh: imported.refresh,
259
+ expires: imported.expires,
260
+ source: imported.source === "json" ? "credential-file" : "local-cli",
261
+ ...(imported.profileArn ? { accountId: imported.profileArn } : {}),
262
+ ...(identity.email ? { email: identity.email } : {}),
263
+ ...(metadata ? { kiro: metadata } : {}),
264
+ };
265
+ }
266
+
267
+ export type KiroCliImportDiagnosticStatus = KiroImportDiagnostic["status"];
268
+ export type KiroCliImportDiagnostic = KiroImportDiagnostic;
269
+
270
+ export function inspectKiroCliSqlite(): { token: ImportedKiroToken | null; diagnostics: KiroCliImportDiagnostic[] } {
271
+ const { credential, diagnostics } = inspectKiroCliSqliteSources();
272
+ return {
273
+ token: credential ? { access: credential.access, refresh: credential.refresh, expires: credential.expires } : null,
274
+ diagnostics,
275
+ };
276
+ }
277
+
278
+ /** Read the kiro-cli SQLite token store (mac/linux). Returns null if no token found. */
279
+ export function readKiroCliSqlite(): ImportedKiroToken | null {
280
+ const imported = readKiroCliSqliteCredential();
281
+ return imported ? { access: imported.access, refresh: imported.refresh, expires: imported.expires } : null;
282
+ }
283
+
284
+ /**
285
+ * Import-first login: kiro-cli SQLite → KIRO_ACCESS_TOKEN env → manual paste (CLI only).
286
+ * When no local token is available, resolves the login flow via onAuth with instructions
287
+ * so the GUI renders the paste-input field, then blocks on onManualCodeInput for the token.
288
+ * If neither onAuth nor onManualCodeInput is available, throws a clear error.
289
+ */
290
+ export async function loginKiro(ctrl: OAuthController, options: KiroLoginOptions = {}): Promise<OAuthCredentials> {
291
+ const runner = options.cliRunner ?? defaultKiroCliRunner;
292
+ // A prior process may have exited after switching the external CLI account but before
293
+ // settlement. Recover that durable transaction before either importing or switching again.
294
+ restoreStaleKiroCliSessionRecovery();
295
+ if (options.forceLogin) {
296
+ throwIfKiroLoginCancelled(ctrl.signal);
297
+ ctrl.onAuth?.({
298
+ url: "",
299
+ instructions: "Kiro CLI is opening a fresh browser login. This also switches the account used by kiro-cli.",
300
+ });
301
+ ctrl.onProgress?.("Opening a fresh Kiro CLI browser login.");
302
+ // Never log out of a session we could not capture: the recovery contract promises an exact
303
+ // restore, so an uncapturable store (unreadable / schema-mismatched / ambiguous token) must
304
+ // abort here rather than let a later failure destroy it permanently.
305
+ const inspected = inspectKiroCliSessionSnapshot();
306
+ if (inspected.blocked) {
307
+ throw new Error(
308
+ "Kiro CLI session could not be backed up, so OCX will not sign it out. " +
309
+ "Repair or remove the unreadable kiro-cli credential database " +
310
+ "(usually `~/.local/share/kiro-cli/data.sqlite3` or " +
311
+ "`~/Library/Application Support/kiro-cli/data.sqlite3`, or " +
312
+ "`%LOCALAPPDATA%\\Kiro-Cli\\data.sqlite3` on Windows), " +
313
+ "unset KIROCLI_DB_PATH / KIRO_CLI_DB_FILE if set for import-only overrides, then retry.",
314
+ );
315
+ }
316
+ const previousSession = inspected.snapshot;
317
+ await bindMatchingLegacyIdentitylessKiroAccounts(runner, ctrl.signal);
318
+ if (previousSession) persistKiroCliSessionRecovery(previousSession);
319
+ try {
320
+ const logout = await runner(["logout"], ctrl.signal);
321
+ throwIfKiroLoginCancelled(ctrl.signal);
322
+ if (logout.exitCode !== 0) throw new Error("Kiro CLI could not prepare a fresh login.");
323
+ const login = await runner(["login"], ctrl.signal);
324
+ throwIfKiroLoginCancelled(ctrl.signal);
325
+ if (login.exitCode !== 0) throw new Error("Kiro CLI login did not complete successfully.");
326
+ const fresh = readKiroCliSqliteCredential();
327
+ if (!fresh) throw new Error("Kiro CLI login completed but no credential could be imported.");
328
+ const credential = await oauthCredentialFromImported(fresh, runner, ctrl.signal);
329
+ throwIfKiroLoginCancelled(ctrl.signal);
330
+ if (!credential.accountId && !credential.email) {
331
+ throw new Error("Kiro login completed but OCX could not determine a stable account identity.");
332
+ }
333
+ if (previousSession) pendingKiroLoginTransactions.set(credential, previousSession);
334
+ else pendingKiroEmptyPriorSessions.add(credential);
335
+ return credential;
336
+ } catch (error) {
337
+ restoreKiroLoginOrThrow(previousSession, !previousSession, error);
338
+ }
339
+ }
340
+
341
+ const imported = readImportedKiroCredential();
342
+ if (imported) {
343
+ ctrl.onProgress?.(imported.source === "json" ? "Imported token from Kiro credentials file." : "Imported token from installed kiro-cli login.");
344
+ return oauthCredentialFromImported(imported, runner, ctrl.signal);
345
+ }
346
+
347
+ const envToken = process.env.KIRO_ACCESS_TOKEN;
348
+ if (envToken) {
349
+ ctrl.onProgress?.("Using KIRO_ACCESS_TOKEN from environment.");
350
+ const routing = environmentKiroRoutingMetadata();
351
+ return {
352
+ access: envToken,
353
+ refresh: process.env.KIRO_REFRESH_TOKEN ?? "",
354
+ expires: Date.now() + 3600_000,
355
+ source: "environment",
356
+ ...(routing ? { kiro: routing } : {}),
357
+ };
358
+ }
359
+
360
+ if (ctrl.onManualCodeInput) {
361
+ // Resolve the login flow immediately so the GUI receives instructions and
362
+ // shows the paste-input field. Without this, onManualCodeInput blocks
363
+ // forever and the HTTP response never reaches the dashboard.
364
+ ctrl.onAuth?.({
365
+ url: "",
366
+ instructions:
367
+ "No kiro-cli token found. Paste a Kiro access token below (starts with 'aoa'). " +
368
+ `Otherwise ${kiroCliInstallGuidance()}, ` +
369
+ "run `kiro-cli login`, and retry — or set KIRO_ACCESS_TOKEN.",
370
+ });
371
+ ctrl.onProgress?.("No kiro-cli token found. Paste a Kiro access token (starts with 'aoa'), or install the Kiro CLI and run `kiro-cli login` first.");
372
+ const raw = (await ctrl.onManualCodeInput()).trim();
373
+ if (raw) {
374
+ const routing = environmentKiroRoutingMetadata();
375
+ return {
376
+ access: raw,
377
+ refresh: "",
378
+ expires: Date.now() + 3600_000,
379
+ source: "manual",
380
+ ...(routing ? { kiro: routing } : {}),
381
+ };
382
+ }
383
+ }
384
+
385
+ throw new Error(
386
+ `Kiro: no token found. ${kiroCliInstallGuidance()} ` +
387
+ "and run `kiro-cli login` to import its session, or set KIRO_ACCESS_TOKEN. " +
388
+ "Browser login is not supported for Kiro.",
389
+ );
390
+ }
391
+
392
+ /** Account metadata is authoritative; legacy accountless calls use KIRO_REGION → local import → default. */
393
+ export function resolveKiroRegion(account?: KiroOAuthMetadata): string {
394
+ if (account !== undefined) return normalizeKiroRegion(account.ssoRegion) || DEFAULT_REGION;
395
+ if (process.env.KIRO_REGION !== undefined) return requireKiroRegion(process.env.KIRO_REGION);
396
+ return normalizeKiroRegion(readImportedKiroCredential()?.ssoRegion) || DEFAULT_REGION;
397
+ }
398
+
399
+ /** Account metadata is authoritative; legacy accountless calls may use KIRO_API_REGION/local import. */
400
+ export function resolveKiroApiRegion(account?: Pick<KiroOAuthMetadata, "apiRegion" | "profileArn" | "ssoRegion">): string {
401
+ if (account !== undefined) {
402
+ return (
403
+ normalizeKiroRegion(account.apiRegion) ||
404
+ inferRegionFromProfileArn(account.profileArn) ||
405
+ normalizeKiroRegion(account.ssoRegion) ||
406
+ DEFAULT_REGION
407
+ );
408
+ }
409
+ if (process.env.KIRO_API_REGION !== undefined) return requireKiroRegion(process.env.KIRO_API_REGION);
410
+ const imported = readImportedKiroCredential();
411
+ return (
412
+ normalizeKiroRegion(imported?.apiRegion) ||
413
+ inferRegionFromProfileArn(imported?.profileArn) ||
414
+ normalizeKiroRegion(imported?.ssoRegion) ||
415
+ (process.env.KIRO_REGION !== undefined ? requireKiroRegion(process.env.KIRO_REGION) : undefined) ||
416
+ DEFAULT_REGION
417
+ );
418
+ }
419
+
420
+ /**
421
+ * Resolve the CodeWhisperer profileArn for request-time use by the adapter.
422
+ * Account metadata is authoritative. Legacy accountless calls use KIRO_PROFILE_ARN → local import.
423
+ * Returns undefined if absent (the adapter decides whether that is fatal).
424
+ */
425
+ export function resolveKiroProfileArn(account?: Pick<KiroOAuthMetadata, "profileArn">): string | undefined {
426
+ if (account !== undefined) return account.profileArn;
427
+ const env = process.env.KIRO_PROFILE_ARN;
428
+ if (env) return env;
429
+ return readImportedKiroCredential()?.profileArn;
430
+ }
431
+
432
+ async function kiroTokenRefreshError(response: Response): Promise<KiroTokenRefreshError> {
433
+ let oauthError: string | undefined;
434
+ try {
435
+ const payload = await response.json() as { error?: unknown };
436
+ if (typeof payload.error === "string" && KIRO_TERMINAL_REFRESH_ERRORS.has(payload.error)) {
437
+ oauthError = payload.error;
438
+ }
439
+ } catch {
440
+ // Error bodies are untrusted and intentionally excluded from the surfaced message.
441
+ }
442
+ return new KiroTokenRefreshError(response.status, oauthError);
443
+ }
444
+
445
+ async function readTokenResponse(res: Response, oldRefresh: string): Promise<OAuthCredentials> {
446
+ const data = (await res.json()) as { accessToken?: string; refreshToken?: string; expiresIn?: number };
447
+ if (!data.accessToken) throw new Error("Kiro refresh returned no accessToken");
448
+ return {
449
+ access: data.accessToken,
450
+ refresh: data.refreshToken || oldRefresh,
451
+ expires: Date.now() + (data.expiresIn ?? 3600) * 1000,
452
+ };
453
+ }
454
+
455
+ function kiroRefreshSignal(signal?: AbortSignal): AbortSignal {
456
+ const timeout = AbortSignal.timeout(30_000);
457
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
458
+ }
459
+
460
+ async function refreshKiroDesktopToken(refresh: string, signal?: AbortSignal, metadata?: KiroOAuthMetadata): Promise<OAuthCredentials> {
461
+ const region = resolveKiroRegion(metadata);
462
+ const res = await fetch(REFRESH_URL.replace("{region}", region), {
463
+ method: "POST",
464
+ headers: { "Content-Type": "application/json" },
465
+ body: JSON.stringify({ refreshToken: refresh }),
466
+ signal: kiroRefreshSignal(signal),
467
+ });
468
+ if (!res.ok) throw await kiroTokenRefreshError(res);
469
+ return readTokenResponse(res, refresh);
470
+ }
471
+
472
+ async function refreshAwsSsoOidcToken(
473
+ refresh: string,
474
+ signal?: AbortSignal,
475
+ credential?: OAuthCredentials,
476
+ ): Promise<OAuthCredentials> {
477
+ let metadata = credential?.kiro;
478
+ if (!metadata) {
479
+ const local = readImportedKiroCredential();
480
+ // Only a local store holding this exact refresh token describes this account. Anything else
481
+ // belongs to whichever account kiro-cli is currently signed into.
482
+ if (local?.refresh === refresh) metadata = metadataFromImported(local);
483
+ }
484
+ // A stored OCX account with no usable `kiro` metadata must still refresh account-scoped: falling
485
+ // through to `resolveKiroRegion(undefined)` would read KIRO_REGION or the local CLI import and
486
+ // borrow an unrelated account's region after a switch. Environment/manual credentials may still
487
+ // honor explicit KIRO_* routing; other stored accounts pin an empty marker (default region).
488
+ // Only a truly accountless refresh (no stored credential) keeps the legacy env/local fallback.
489
+ if (!metadata && credential) {
490
+ metadata = credential.source === "environment" || credential.source === "manual"
491
+ ? environmentKiroRoutingMetadata() ?? {}
492
+ : {};
493
+ }
494
+ const clientId = metadata?.clientId;
495
+ const clientSecret = metadata?.clientSecret;
496
+ if (!clientId || !clientSecret) return refreshKiroDesktopToken(refresh, signal, metadata);
497
+ const region = resolveKiroRegion(metadata);
498
+ const run = async (refreshToken: string): Promise<Response> => fetch(OIDC_URL.replace("{region}", region), {
499
+ method: "POST",
500
+ headers: { "Content-Type": "application/json" },
501
+ body: JSON.stringify({
502
+ grantType: "refresh_token",
503
+ clientId,
504
+ clientSecret,
505
+ refreshToken,
506
+ }),
507
+ signal: kiroRefreshSignal(signal),
508
+ });
509
+ const res = await run(refresh);
510
+ if (!res.ok) throw await kiroTokenRefreshError(res);
511
+ return readTokenResponse(res, refresh);
512
+ }
513
+
514
+ function matchingRotatedKiroCliCredential(
515
+ refresh: string,
516
+ credential?: OAuthCredentials,
517
+ ): ImportedKiroCredential | undefined {
518
+ const storedIdentities = [credential?.kiro?.profileArn, credential?.accountId]
519
+ .filter((value): value is string => Boolean(value));
520
+ if (storedIdentities.length === 0 || new Set(storedIdentities).size !== 1) return undefined;
521
+ try {
522
+ const local = readKiroCliSqliteCredential();
523
+ if (!local?.profileArn || storedIdentities.some(identity => identity !== local.profileArn)) return undefined;
524
+ if (!local.refresh || local.refresh === refresh) return undefined;
525
+ return local;
526
+ } catch {
527
+ // An unrelated or malformed local store must not block the stored OCX account.
528
+ return undefined;
529
+ }
530
+ }
531
+
532
+ function metadataForRotatedKiroCliCredential(
533
+ credential: OAuthCredentials,
534
+ local: ImportedKiroCredential,
535
+ ): KiroOAuthMetadata {
536
+ const {
537
+ clientId: _storedClientId,
538
+ clientSecret: _storedClientSecret,
539
+ ...storedRouting
540
+ } = credential.kiro ?? {};
541
+ const localMetadata = metadataFromImported(local) ?? {};
542
+ const {
543
+ clientId: _localClientId,
544
+ clientSecret: _localClientSecret,
545
+ ...localRouting
546
+ } = localMetadata;
547
+ return {
548
+ ...storedRouting,
549
+ ...localRouting,
550
+ ...(local.authType === "aws_sso_oidc" && local.clientId && local.clientSecret
551
+ ? { clientId: local.clientId, clientSecret: local.clientSecret }
552
+ : {}),
553
+ };
554
+ }
555
+
556
+ export async function refreshKiroToken(
557
+ refresh: string,
558
+ signal?: AbortSignal,
559
+ credential?: OAuthCredentials,
560
+ ): Promise<OAuthCredentials> {
561
+ if (!refresh) throw new Error("Kiro: no refresh token available (re-run `kiro-cli login`).");
562
+ try {
563
+ return await refreshAwsSsoOidcToken(refresh, signal, credential);
564
+ } catch (error) {
565
+ if (!(error instanceof KiroTokenRefreshError) || error.httpStatus !== 400) throw error;
566
+ if (!credential) throw error;
567
+ const local = matchingRotatedKiroCliCredential(refresh, credential);
568
+ if (!local) throw error;
569
+ const retryMetadata = metadataForRotatedKiroCliCredential(credential, local);
570
+ const fresh = await refreshAwsSsoOidcToken(local.refresh, signal, {
571
+ ...credential,
572
+ refresh: local.refresh,
573
+ kiro: retryMetadata,
574
+ });
575
+ return { ...fresh, kiro: retryMetadata };
576
+ }
577
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Local token auto-detection — reads an existing Grok CLI credential (~/.grok/auth.json).
3
+ * Read-only: never writes to external credential stores.
4
+ * Ported from jawcode packages/ai/src/utils/oauth/local-token-detect.ts (xAI portion).
5
+ */
6
+ import { execSync } from "node:child_process";
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { join } from "node:path";
10
+ import type { OAuthCredentials } from "./types";
11
+
12
+ const XAI_AUTH_KEY_PREFIX = "https://auth.x.ai::";
13
+ const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
14
+
15
+ export function detectGrokCliToken(): OAuthCredentials | null {
16
+ const authPath = join(process.env.HOME ?? homedir(), ".grok", "auth.json");
17
+ if (!existsSync(authPath)) return null;
18
+
19
+ try {
20
+ const raw = JSON.parse(readFileSync(authPath, "utf8")) as Record<string, Record<string, unknown>>;
21
+
22
+ const entry = Object.entries(raw).find(([key]) => key.startsWith(XAI_AUTH_KEY_PREFIX))?.[1];
23
+ if (!entry?.key || !entry?.refresh_token) return null;
24
+
25
+ const accessToken = entry.key as string;
26
+ const refreshToken = entry.refresh_token as string;
27
+ const expiresAt = entry.expires_at ? new Date(entry.expires_at as string).getTime() : 0;
28
+
29
+ return {
30
+ refresh: refreshToken,
31
+ access: accessToken,
32
+ expires: expiresAt,
33
+ accountId: entry.user_id as string | undefined,
34
+ email: entry.email as string | undefined,
35
+ source: "local-cli",
36
+ };
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ export function hasComparableGrokIdentity(stored: OAuthCredentials, disk: OAuthCredentials): boolean {
43
+ return Boolean((stored.accountId && disk.accountId) || (stored.email && disk.email));
44
+ }
45
+
46
+ export function isSameGrokIdentity(stored: OAuthCredentials, disk: OAuthCredentials): boolean {
47
+ if (stored.accountId && disk.accountId) return stored.accountId === disk.accountId;
48
+ if (stored.email && disk.email) return stored.email.toLowerCase() === disk.email.toLowerCase();
49
+ return false;
50
+ }
51
+
52
+ export function shouldAdoptGrokGeneration(
53
+ stored: OAuthCredentials,
54
+ disk: OAuthCredentials,
55
+ now = Date.now(),
56
+ refreshSkewMs = 60_000,
57
+ ): boolean {
58
+ if (disk.expires <= now + refreshSkewMs) return false;
59
+ const bothExpiriesExist = stored.expires > 0 && disk.expires > 0;
60
+ if (bothExpiriesExist) return disk.expires >= stored.expires;
61
+ return true;
62
+ }
63
+
64
+ /** Claude Code config dir: `CLAUDE_CONFIG_DIR` override, else `~/.claude`. */
65
+ function claudeConfigDir(): string {
66
+ const explicit = process.env.CLAUDE_CONFIG_DIR?.trim();
67
+ return explicit ? explicit : join(homedir(), ".claude");
68
+ }
69
+
70
+ /** Read the Claude Code OAuth credential from the macOS Keychain (darwin only). */
71
+ function readClaudeKeychain(): string | null {
72
+ if (process.platform !== "darwin") return null;
73
+ try {
74
+ return execSync(`security find-generic-password -s "${CLAUDE_KEYCHAIN_SERVICE}" -w`, {
75
+ encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
76
+ }).trim();
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Read the Claude Code credential file (`<config-dir>/.credentials.json`).
84
+ * Claude Code writes this on Linux/Windows (and on macOS when the Keychain is
85
+ * unavailable); it carries the same `claudeAiOauth` payload as the Keychain item.
86
+ * Exported for tests.
87
+ */
88
+ export function readClaudeCredentialsFile(): string | null {
89
+ const path = join(claudeConfigDir(), ".credentials.json");
90
+ try {
91
+ if (!existsSync(path)) return null;
92
+ return readFileSync(path, "utf8").trim();
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /** Keychain first on macOS, then the cross-platform credentials file. */
99
+ function readClaudeSecureStorage(): string | null {
100
+ // An explicit config-dir override identifies a separate Claude installation/profile.
101
+ // Do not let the default macOS Keychain entry shadow that requested credential file.
102
+ if (process.env.CLAUDE_CONFIG_DIR?.trim()) return readClaudeCredentialsFile() ?? readClaudeKeychain();
103
+ return readClaudeKeychain() ?? readClaudeCredentialsFile();
104
+ }
105
+
106
+ export function parseClaudeOauthPayload(raw: string): OAuthCredentials | null {
107
+ try {
108
+ const data = JSON.parse(raw) as { claudeAiOauth?: { accessToken?: string; refreshToken?: string; expiresAt?: number } };
109
+ const o = data.claudeAiOauth;
110
+ if (!o?.accessToken || !o?.refreshToken) return null;
111
+ return { access: o.accessToken, refresh: o.refreshToken, expires: o.expiresAt ?? 0, source: "local-cli" };
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ export function detectClaudeCodeToken(): OAuthCredentials | null {
118
+ const raw = readClaudeSecureStorage();
119
+ if (!raw) return null;
120
+ return parseClaudeOauthPayload(raw);
121
+ }