@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,108 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { OcxContentPart, OcxParsedRequest } from "../types";
3
+ import { antigravityUserAgent } from "./client-fingerprint";
4
+
5
+ /**
6
+ * Antigravity request User-Agent. Mirrors the real Antigravity CLI UA
7
+ * (`antigravity/cli/{ver} (aidev_client; os_type=darwin; arch=arm64)`) so the request fingerprint
8
+ * matches the OAuth credential — the prior literal `"antigravity"` was a giveaway no real client
9
+ * sends. A `GOOGLE_ANTIGRAVITY_USER_AGENT` override still wins.
10
+ */
11
+ export const ANTIGRAVITY_REQUEST_UA = process.env.GOOGLE_ANTIGRAVITY_USER_AGENT || antigravityUserAgent();
12
+
13
+ /**
14
+ * Whether a stored `OcxToolCall.thoughtSignature` is a REAL upstream Gemini signature versus a
15
+ * foreign id that must not be forwarded to Gemini/Antigravity.
16
+ *
17
+ * Foreign ids that have 400'd Antigravity (`TYPE_BYTES` / Base64 decoding failed) include:
18
+ * - Responses/bridge item ids: `fc_...`, `ctc_...` (custom_tool_call), `tsc_...` (tool_search_call),
19
+ * `call_...`, `rs_...`, …
20
+ * - Anthropic tool-use ids: `toolu_...`
21
+ *
22
+ * Only real signatures may be forwarded — sending a foreign id as `thoughtSignature` breaks
23
+ * multi-turn reasoning continuity. Real signatures are opaque base64-ish blobs with no
24
+ * Responses/Anthropic id prefix.
25
+ *
26
+ * Note: do NOT reject a bare `sig_` / `sig-` prefix — existing Gemini replay fixtures and some
27
+ * upstream blobs use that shape; a deny-list entry for `sig` would drop valid continuity tokens.
28
+ */
29
+ export function isLikelyRealThoughtSignature(sig: string | undefined): boolean {
30
+ if (typeof sig !== "string" || sig.length < 16) return false;
31
+ // Reject synthetic Responses/tool-call ids and Anthropic tool-use ids (`_` or `-` separators).
32
+ if (/^(fc|ctc|tsc|call|msg|rs|resp|reasoning|item|ws|toolu|tool|func|function)[-_]/i.test(sig)) return false;
33
+ // Real Gemini thought signatures are opaque base64/base64url blobs: only [A-Za-z0-9+/_=-].
34
+ // Anything containing other characters (or whitespace) is not a real signature.
35
+ return /^[A-Za-z0-9+/_=-]+$/.test(sig);
36
+ }
37
+
38
+ function firstUserText(parsed: OcxParsedRequest): string | undefined {
39
+ for (const msg of parsed.context.messages) {
40
+ if (msg.role !== "user") continue;
41
+ if (typeof msg.content === "string") return msg.content;
42
+ const first = (msg.content as OcxContentPart[]).find(p => p.type === "text" && typeof p.text === "string");
43
+ if (first && first.type === "text") return first.text;
44
+ }
45
+ return undefined;
46
+ }
47
+
48
+ /**
49
+ * Deterministic Cloud Code Assist session id from the first user message text. Mirrors
50
+ * CLIProxyAPI `generateStableSessionID`: sha256(firstUserText) → BigEndian uint64 masked with
51
+ * 0x7FFFFFFFFFFFFFFF, prefixed with "-". Falls back to a random "-<digits>" id when there is no text.
52
+ */
53
+ export function antigravitySessionId(parsed: OcxParsedRequest): string {
54
+ // Anchored on the first user message text: this is the one value that stays STABLE across every
55
+ // turn of a conversation, which the replay cache requires (it observes signatures on turn N's
56
+ // response and re-injects them on turn N+1's request, so both turns must map to the same id).
57
+ // Cross-conversation collisions (two threads opening with identical text) are made harmless by
58
+ // the replay cache keying signatures on functionCall identity (name+args), not on this id alone.
59
+ const text = firstUserText(parsed);
60
+ if (!text) return `-${Math.floor(Math.random() * 9e18).toString()}`;
61
+ const digest = createHash("sha256").update(text, "utf8").digest();
62
+ const masked = digest.readBigUInt64BE(0) & 0x7fffffffffffffffn;
63
+ return `-${masked.toString()}`;
64
+ }
65
+
66
+ /** A Gemini content part as it appears in an Antigravity request body. */
67
+ interface GeminiPart {
68
+ thought?: boolean;
69
+ thoughtSignature?: string;
70
+ thought_signature?: string;
71
+ text?: string;
72
+ [key: string]: unknown;
73
+ }
74
+ interface GeminiContent {
75
+ role?: string;
76
+ parts?: GeminiPart[];
77
+ [key: string]: unknown;
78
+ }
79
+
80
+ function hasSignature(part: GeminiPart): boolean {
81
+ return typeof part.thoughtSignature === "string" && part.thoughtSignature.length > 0
82
+ || typeof part.thought_signature === "string" && part.thought_signature.length > 0;
83
+ }
84
+
85
+ /**
86
+ * Claude-on-Antigravity signature sanitization (the no-cache path). Mirrors CLIProxyAPI's
87
+ * `StripEmptySignatureThinkingBlocks` + non-model signature stripping: drop thinking parts that
88
+ * carry no valid signature (they would 400 upstream), and strip signature fields from non-model
89
+ * (user) content. Mutates and returns `contents`.
90
+ */
91
+ export function sanitizeAntigravityClaudeSignatures(contents: unknown[]): unknown[] {
92
+ if (!Array.isArray(contents)) return contents;
93
+ for (const raw of contents as GeminiContent[]) {
94
+ if (!raw || typeof raw !== "object" || !Array.isArray(raw.parts)) continue;
95
+ const isModel = raw.role === "model";
96
+ if (!isModel) {
97
+ // Non-model parts must not carry thought signatures.
98
+ for (const part of raw.parts) {
99
+ delete part.thoughtSignature;
100
+ delete part.thought_signature;
101
+ }
102
+ continue;
103
+ }
104
+ // Model turn: drop thinking blocks lacking a valid signature.
105
+ raw.parts = raw.parts.filter(part => !(part.thought === true && !hasSignature(part)));
106
+ }
107
+ return contents;
108
+ }
@@ -0,0 +1,85 @@
1
+ import { parseUpstreamJsonPayload, safeUpstreamErrorString, sanitizeUpstreamErrorText } from "./upstream-http-error";
2
+
3
+ /** Pull the human detail out of the Google API error envelope `{error:{message,status,code}}`. */
4
+ function googleErrorDetail(payloadText: string): { message?: string; status?: string } {
5
+ const trimmed = payloadText.trim();
6
+ if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {
7
+ return { message: trimmed || undefined };
8
+ }
9
+ const parsed = parseUpstreamJsonPayload(trimmed);
10
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
11
+ const err = (parsed as { error?: { message?: unknown; status?: unknown } }).error;
12
+ return {
13
+ message: safeUpstreamErrorString(err?.message),
14
+ status: safeUpstreamErrorString(err?.status),
15
+ };
16
+ }
17
+
18
+ function classifyGoogle(label: string, status: number | undefined, enumStatus: string | undefined, text: string): string {
19
+ const lower = `${enumStatus ?? ""} ${text}`.toLowerCase();
20
+ const quotaExhausted =
21
+ lower.includes("quotafailure") ||
22
+ lower.includes("quota exceeded") ||
23
+ lower.includes("exceeded your current quota") ||
24
+ lower.includes("billing");
25
+ if (enumStatus === "RESOURCE_EXHAUSTED" && quotaExhausted) return `${label} quota exhausted`;
26
+ if (status === 429 || enumStatus === "RESOURCE_EXHAUSTED" || lower.includes("rate limit")) {
27
+ return `${label} rate limit exceeded`;
28
+ }
29
+ if (status === 401 || enumStatus === "UNAUTHENTICATED" || lower.includes("unauthenticated") || lower.includes("invalid authentication") || lower.includes("expired")) {
30
+ return `${label} authentication failed`;
31
+ }
32
+ if (status === 403 || enumStatus === "PERMISSION_DENIED" || lower.includes("permission denied") || lower.includes("access denied")) {
33
+ return `${label} access denied`;
34
+ }
35
+ if (status === 503 || enumStatus === "UNAVAILABLE" || lower.includes("overloaded") || lower.includes("unavailable")) {
36
+ return `${label} server overloaded`;
37
+ }
38
+ if (status === 400 || status === 404 || enumStatus === "INVALID_ARGUMENT" || enumStatus === "NOT_FOUND" || lower.includes("invalid") || lower.includes("not found") || lower.includes("malformed")) {
39
+ return `${label} invalid request`;
40
+ }
41
+ return `${label} upstream error`;
42
+ }
43
+
44
+ /**
45
+ * Normalize a Google/Vertex/Antigravity HTTP error body into a short, classified, secret-redacted
46
+ * message. Mirrors `kiro-errors.ts`. `label` is the provider-facing prefix ("Vertex AI",
47
+ * "Antigravity").
48
+ */
49
+ export function safeGoogleHttpErrorMessage(label: string, status: number, payloadText: string): string {
50
+ const { message, status: enumStatus } = googleErrorDetail(payloadText);
51
+ const prefix = classifyGoogle(label, status, enumStatus, [message, enumStatus].filter(Boolean).join(" "));
52
+ const detail = message ? sanitizeUpstreamErrorText(message).slice(0, 500) : `HTTP ${status}`;
53
+ return `${prefix}: ${detail}`;
54
+ }
55
+
56
+ /** Vertex AI HTTP error message (label = "Vertex AI"). */
57
+ export function safeVertexHttpErrorMessage(status: number, payloadText: string): string {
58
+ return safeGoogleHttpErrorMessage("Vertex AI", status, payloadText);
59
+ }
60
+
61
+ /** Antigravity (Cloud Code Assist) HTTP error message (label = "Antigravity"). */
62
+ export function safeAntigravityHttpErrorMessage(status: number, payloadText: string): string {
63
+ return safeGoogleHttpErrorMessage("Antigravity", status, payloadText);
64
+ }
65
+
66
+ /** Google-family retryable HTTP set (mirrors Kiro). Quota-exhausted is classified above and not retried. */
67
+ export function retryableGoogleStatus(status: number): boolean {
68
+ return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
69
+ }
70
+
71
+ /**
72
+ * True when a 429 body indicates hard quota exhaustion (not a transient rate limit). Quota
73
+ * exhaustion is generally not expected to recover for hours (AIP-194), so it must NOT be retried —
74
+ * unlike a plain rate limit. The HTTP status alone can't distinguish the two, so the retry layer
75
+ * inspects the body with this.
76
+ */
77
+ export function isQuotaExhaustedBody(payloadText: string): boolean {
78
+ const { message, status } = googleErrorDetail(payloadText);
79
+ if (status !== "RESOURCE_EXHAUSTED") return false;
80
+ const lower = (message ?? "").toLowerCase();
81
+ return lower.includes("quotafailure")
82
+ || lower.includes("quota exceeded")
83
+ || lower.includes("exceeded your current quota")
84
+ || lower.includes("billing");
85
+ }
@@ -0,0 +1,100 @@
1
+ import type { AdapterFetchContext, AdapterRequest } from "./base";
2
+ import { isQuotaExhaustedBody, retryableGoogleStatus, safeGoogleHttpErrorMessage } from "./google-errors";
3
+ import { repairGoogleInvalidRequestBody } from "./google-wire-compiler";
4
+ import { normalizeUpstreamHttpErrorResponse, readDisplaySafeErrorPayloadText } from "./upstream-http-error";
5
+ import {
6
+ abortError,
7
+ cancelResponseBodyBestEffort,
8
+ fetchWithAttemptDeadline,
9
+ retryBackoffDelayMs,
10
+ sleepWithAbort,
11
+ } from "../lib/upstream-retry";
12
+
13
+ const GOOGLE_RETRY_ATTEMPTS = 3;
14
+ const GOOGLE_RETRY_BASE_MS = 250;
15
+ const GOOGLE_RETRY_MAX_MS = 2_000;
16
+
17
+ async function normalizeFinalGoogleError(label: string, res: Response, signal?: AbortSignal): Promise<Response> {
18
+ return normalizeUpstreamHttpErrorResponse(res, {
19
+ signal,
20
+ formatMessage: payloadText => safeGoogleHttpErrorMessage(label, res.status, payloadText),
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Fetch a Google-family upstream (Vertex / Antigravity) with Kiro-style hardening: per-attempt
26
+ * timeout (`AbortSignal.any([parent, timeout])`), bounded retry on transient status / network
27
+ * errors, `Retry-After` honoring, jittered exponential backoff, and a classified + redacted final
28
+ * error body. `label` is the provider-facing prefix used in error messages.
29
+ */
30
+ export async function fetchGoogleWithRetry(label: string, request: AdapterRequest, ctx: AdapterFetchContext = {}): Promise<Response> {
31
+ const timeoutMs = ctx.timeoutMs ?? 200_000;
32
+ let lastError: unknown;
33
+ let activeRequest = request;
34
+ let compatibilityReplayUsed = false;
35
+ for (let attempt = 0; attempt < GOOGLE_RETRY_ATTEMPTS; attempt++) {
36
+ if (ctx.abortSignal?.aborted) throw abortError(ctx.abortSignal);
37
+ try {
38
+ const res = await fetchWithAttemptDeadline(activeRequest.url, {
39
+ method: activeRequest.method,
40
+ headers: activeRequest.headers,
41
+ body: activeRequest.body,
42
+ }, timeoutMs, ctx.abortSignal, ctx.stream);
43
+ if (res.status === 400 && !compatibilityReplayUsed) {
44
+ let payloadText = "";
45
+ try {
46
+ payloadText = await readDisplaySafeErrorPayloadText(res.clone(), ctx.abortSignal);
47
+ } catch (error) {
48
+ if (ctx.abortSignal?.aborted) throw error;
49
+ }
50
+ const repairedBody = repairGoogleInvalidRequestBody(activeRequest.body, payloadText);
51
+ if (repairedBody !== undefined) {
52
+ compatibilityReplayUsed = true;
53
+ activeRequest = { ...activeRequest, body: repairedBody };
54
+ cancelResponseBodyBestEffort(res);
55
+ attempt--; // The changed-request replay is separate from transient retry accounting.
56
+ continue;
57
+ }
58
+ }
59
+ if (!retryableGoogleStatus(res.status) || attempt === GOOGLE_RETRY_ATTEMPTS - 1) {
60
+ return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
61
+ }
62
+ // A 429 may be a transient rate limit (retry) or hard quota exhaustion (do NOT retry —
63
+ // it won't recover for hours and burns retries). Peek the body to tell them apart.
64
+ if (res.status === 429 && !ctx.returnRawErrors) {
65
+ const peek = await readDisplaySafeErrorPayloadText(res, ctx.abortSignal);
66
+ if (isQuotaExhaustedBody(peek)) {
67
+ return normalizeUpstreamHttpErrorResponse(res, {
68
+ signal: ctx.abortSignal,
69
+ formatMessage: payloadText => safeGoogleHttpErrorMessage(label, res.status, payloadText || peek),
70
+ });
71
+ }
72
+ }
73
+ cancelResponseBodyBestEffort(res);
74
+ await sleepWithAbort(retryBackoffDelayMs(attempt, {
75
+ baseDelayMs: GOOGLE_RETRY_BASE_MS,
76
+ maxDelayMs: GOOGLE_RETRY_MAX_MS,
77
+ headers: res.headers,
78
+ }), ctx.abortSignal);
79
+ } catch (err) {
80
+ if (ctx.abortSignal?.aborted) throw err;
81
+ lastError = err;
82
+ if (attempt === GOOGLE_RETRY_ATTEMPTS - 1) throw err;
83
+ await sleepWithAbort(retryBackoffDelayMs(attempt, {
84
+ baseDelayMs: GOOGLE_RETRY_BASE_MS,
85
+ maxDelayMs: GOOGLE_RETRY_MAX_MS,
86
+ }), ctx.abortSignal);
87
+ }
88
+ }
89
+ throw lastError ?? new Error(`${label} fetch failed`);
90
+ }
91
+
92
+ /** Vertex AI retry wrapper. */
93
+ export function fetchVertexWithRetry(request: AdapterRequest, ctx: AdapterFetchContext = {}): Promise<Response> {
94
+ return fetchGoogleWithRetry("Vertex AI", request, ctx);
95
+ }
96
+
97
+ /** Antigravity (Cloud Code Assist) retry wrapper. */
98
+ export function fetchAntigravityWithRetry(request: AdapterRequest, ctx: AdapterFetchContext = {}): Promise<Response> {
99
+ return fetchGoogleWithRetry("Antigravity", request, ctx);
100
+ }
@@ -0,0 +1,173 @@
1
+ type Schema = Record<string, unknown>;
2
+
3
+ // Google documents this function-schema subset: type, nullable, required, format, description,
4
+ // properties, items, enum, anyOf, $ref, and $defs. We inline local refs and normalize anyOf, so
5
+ // only the eight scalar/container keywords below are ever emitted. Building from an allowlist
6
+ // prevents new MCP/JSON-Schema annotations from turning into provider-wide 400 responses.
7
+ const ALLOWED_TYPES = new Set(["string", "integer", "number", "boolean", "array", "object"]);
8
+ const MAX_SCHEMA_DEPTH = 24; // Google's documented nesting limit is 32; leave headroom for CCA.
9
+ const MAX_DEREF_DEPTH = 16;
10
+
11
+ function isRecord(value: unknown): value is Schema {
12
+ return !!value && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+
15
+ function resolveRef(ref: string, defs: Map<string, unknown>): unknown {
16
+ // Only local pointers into the schema's own $defs/definitions are safe to inline.
17
+ const match = /^#\/(?:\$defs|definitions)\/(.+)$/.exec(ref);
18
+ if (!match) return undefined;
19
+ try {
20
+ return defs.get(decodeURIComponent(match[1].replace(/~1/g, "/").replace(/~0/g, "~")));
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
25
+
26
+ function collectDefs(root: unknown, defs: Map<string, unknown>): void {
27
+ if (!isRecord(root)) return;
28
+ for (const bag of ["$defs", "definitions"] as const) {
29
+ const group = root[bag];
30
+ if (!isRecord(group)) continue;
31
+ for (const [name, value] of Object.entries(group)) {
32
+ if (!defs.has(name)) defs.set(name, value);
33
+ }
34
+ }
35
+ }
36
+
37
+ function normalizeType(value: unknown, out: Schema, preserveNullType: boolean): void {
38
+ const candidates = Array.isArray(value) ? value : [value];
39
+ let sawNull = false;
40
+
41
+ for (const candidate of candidates) {
42
+ if (typeof candidate !== "string") continue;
43
+ const type = candidate.toLowerCase();
44
+ if (type === "null") {
45
+ sawNull = true;
46
+ } else if (out.type === undefined && ALLOWED_TYPES.has(type)) {
47
+ out.type = type;
48
+ }
49
+ }
50
+
51
+ if (!sawNull) return;
52
+ if (out.type !== undefined) out.nullable = true;
53
+ else if (preserveNullType) out.type = "null";
54
+ else out.nullable = true;
55
+ }
56
+
57
+ function sanitizeEnum(value: unknown): string[] | undefined {
58
+ if (!Array.isArray(value)) return undefined;
59
+ const values = [...new Set(value.filter((item): item is string => typeof item === "string"))];
60
+ return values.length > 0 ? values : undefined;
61
+ }
62
+
63
+ function normalizeAnyOf(
64
+ value: unknown,
65
+ defs: Map<string, unknown>,
66
+ depth: number,
67
+ refDepth: number,
68
+ ): Schema {
69
+ if (!Array.isArray(value) || value.length === 0) return {};
70
+ const schemas = value.map(item => sanitizeSchema(item, defs, depth + 1, refDepth, true));
71
+
72
+ const nonNullSchemas = schemas.filter(schema => schema.type !== "null");
73
+ const nullSchemas = schemas.filter(schema => schema.type === "null");
74
+ if (
75
+ nonNullSchemas.length === 1
76
+ && nullSchemas.length > 0
77
+ && nullSchemas.every(schema => Object.keys(schema).every(key => key === "type"))
78
+ ) {
79
+ return { ...nonNullSchemas[0], nullable: true };
80
+ }
81
+
82
+ const type = schemas[0]?.type;
83
+ const sameType = schemas.length > 0 && schemas.every(schema => schema.type === type);
84
+ const enumOnly = schemas.every(schema => {
85
+ const allowedKeys = type === undefined ? new Set(["enum"]) : new Set(["type", "enum"]);
86
+ return Array.isArray(schema.enum) && Object.keys(schema).every(key => allowedKeys.has(key));
87
+ });
88
+ if (sameType && enumOnly && type !== "null") {
89
+ const values = sanitizeEnum(schemas.flatMap(schema => schema.enum as unknown[]));
90
+ if (values) return { ...(typeof type === "string" ? { type } : {}), enum: values };
91
+ }
92
+
93
+ // CCA's Claude bridge turns typed anyOf branches into an invalid input_schema. Widen only this
94
+ // node when a union cannot be collapsed losslessly; parent annotations and structure survive.
95
+ return {};
96
+ }
97
+
98
+ function sanitizeProperties(
99
+ value: unknown,
100
+ defs: Map<string, unknown>,
101
+ depth: number,
102
+ refDepth: number,
103
+ ): Record<string, Schema> | undefined {
104
+ if (!isRecord(value)) return undefined;
105
+ const properties: Record<string, Schema> = Object.create(null) as Record<string, Schema>;
106
+ for (const [name, schema] of Object.entries(value)) {
107
+ // Property names form a name bag and must never be interpreted as schema keywords.
108
+ properties[name] = sanitizeSchema(schema, defs, depth + 1, refDepth, false);
109
+ }
110
+ return properties;
111
+ }
112
+
113
+ function sanitizeSchema(
114
+ node: unknown,
115
+ defs: Map<string, unknown>,
116
+ depth: number,
117
+ refDepth: number,
118
+ preserveNullType: boolean,
119
+ ): Schema {
120
+ if (depth >= MAX_SCHEMA_DEPTH || !isRecord(node)) return {};
121
+
122
+ if (typeof node.$ref === "string" && refDepth < MAX_DEREF_DEPTH) {
123
+ const target = resolveRef(node.$ref, defs);
124
+ if (isRecord(target)) {
125
+ const merged: Schema = { ...target };
126
+ for (const [key, value] of Object.entries(node)) {
127
+ if (key !== "$ref") merged[key] = value;
128
+ }
129
+ return sanitizeSchema(merged, defs, depth, refDepth + 1, preserveNullType);
130
+ }
131
+ }
132
+
133
+ const out: Schema = {};
134
+ normalizeType(node.type, out, preserveNullType);
135
+
136
+ if (typeof node.nullable === "boolean") out.nullable = node.nullable;
137
+ if (typeof node.description === "string") out.description = node.description;
138
+ if (typeof node.format === "string") out.format = node.format;
139
+
140
+ const enumValues = sanitizeEnum(node.enum ?? (typeof node.const === "string" ? [node.const] : undefined));
141
+ if (enumValues) out.enum = enumValues;
142
+
143
+ const properties = sanitizeProperties(node.properties, defs, depth, refDepth);
144
+ if (properties) out.properties = properties;
145
+
146
+ if (isRecord(node.items)) {
147
+ out.items = sanitizeSchema(node.items, defs, depth + 1, refDepth, false);
148
+ }
149
+
150
+ if (Array.isArray(node.required)) {
151
+ out.required = [...new Set(node.required.filter((item): item is string => typeof item === "string"))];
152
+ }
153
+
154
+ if (node.anyOf !== undefined) Object.assign(out, normalizeAnyOf(node.anyOf, defs, depth, refDepth));
155
+ return out;
156
+ }
157
+
158
+ export function sanitizeGeminiToolParameters(parameters: unknown): Record<string, unknown> {
159
+ try {
160
+ const defs = new Map<string, unknown>();
161
+ collectDefs(parameters, defs);
162
+ const root = sanitizeSchema(parameters, defs, 0, 0, false);
163
+
164
+ // Function arguments are always an object. Claude additionally rejects root composition and a
165
+ // missing root type even when those forms are valid general-purpose JSON Schema.
166
+ root.type = "object";
167
+ if (!isRecord(root.properties)) root.properties = {};
168
+ return root;
169
+ } catch {
170
+ // Last-resort containment: no third-party schema may break every tool in the request.
171
+ return { type: "object", properties: {} };
172
+ }
173
+ }
@@ -0,0 +1,13 @@
1
+ import { redactSecretString } from "../lib/redact";
2
+
3
+ /** Gemini/Vertex finishReason values that mean the turn was cut off, not cleanly stopped. */
4
+ const TRUNCATION_REASONS = new Set(["MAX_TOKENS", "MALFORMED_FUNCTION_CALL"]);
5
+
6
+ export function isVertexTruncationReason(finishReason: string | undefined): boolean {
7
+ return finishReason !== undefined && TRUNCATION_REASONS.has(finishReason);
8
+ }
9
+
10
+ export function vertexTruncationErrorMessage(reason?: string): string {
11
+ const suffix = reason ? ` (${redactSecretString(reason).slice(0, 160)})` : "";
12
+ return `Vertex AI response truncated upstream before the turn completed${suffix}`;
13
+ }