@yansigit/opencodex 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,1259 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import type { ResponsesTerminalStatus } from "../bridge";
3
+ import {
4
+ classifyError,
5
+ CYBER_POLICY_ERROR_CODE,
6
+ httpStatusFromTerminalError as httpStatusFromClassifiedTerminalError,
7
+ isClientClosedMessage,
8
+ isCyberPolicyCode,
9
+ } from "../lib/errors";
10
+ import { CODEX_CONFIG_PATH, readRootTomlString } from "../codex/paths";
11
+ import { readCodexCatalogPath } from "../codex/catalog";
12
+ import type { AttemptTierOutcome, OcxUsage } from "../types";
13
+ import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace";
14
+ import type { AdapterRequest } from "../adapters/base";
15
+ import type { AdapterTierMetadata } from "../providers/fastwire";
16
+ import { redactSecretString, sanitizeLogMetadataString } from "../lib/redact";
17
+ import {
18
+ appendUsageEntry,
19
+ isKnownAdmissionKind,
20
+ isKnownInboundProtocol,
21
+ isKnownUsageSurface,
22
+ isCodexUsageAccountLogLabel,
23
+ isValidReasoningWireValue,
24
+ readRecentUsageEntries,
25
+ usageForFinalLog,
26
+ usageStatusForFinalLog,
27
+ usageTotalTokens,
28
+ type AttemptRecoveryKind,
29
+ type PersistedUsageAttempt,
30
+ type PersistedUsageEntry,
31
+ type UsageStatus,
32
+ } from "../usage/log";
33
+ import {
34
+ appendUsageDebug,
35
+ isUsageDebugEnabled,
36
+ truncateForDebug,
37
+ USAGE_DEBUG_BODY_SAMPLE_BYTES,
38
+ type UsageDebugBodyKind,
39
+ } from "../usage/debug";
40
+ import { matchesLogConversationId } from "./request-log-conversation";
41
+ import { enforceAppOwnedMemoryBudget, type RetainedStoreSnapshot } from "../lib/app-owned-memory";
42
+ import { capEstimateAtContextWindow } from "../lib/token-estimate";
43
+ import { inferCursorContextWindow } from "../adapters/cursor/discovery";
44
+ import { KIRO_MODEL_CONTEXT_WINDOWS, normalizeKiroModelId } from "../providers/kiro-models";
45
+ import { modelRecordValue } from "../reasoning-effort";
46
+
47
+ export interface RequestLogContext {
48
+ model: string;
49
+ provider: string;
50
+ /** TTFT: ms from request start to the first non-empty model output delta (WP4, devlog 040). */
51
+ firstOutputMs?: number;
52
+ /** Best-effort chat/session correlation for Logs grouping (#330). Opaque; omit when unknown. */
53
+ conversationId?: string;
54
+ surface?: "claude" | "claude-desktop" | "grok";
55
+ /** The matched configured key's id. Set ONLY for admissionKind "configured" —
56
+ * never a sentinel, so a hand-edited entry whose id happens to be "loopback"
57
+ * cannot absorb unrelated traffic. */
58
+ apiKeyId?: string;
59
+ /** Which kind of admission opened this request. Carries no secret. */
60
+ admissionKind?: "configured" | "environment" | "loopback";
61
+ /** Which inbound wire was used. Orthogonal to `surface`, which names the client
62
+ * product: widening that enum would merge Responses and Chat Completions,
63
+ * since both leave it undefined. */
64
+ inboundProtocol?: "responses" | "chat" | "messages";
65
+ /** Stable non-PII Codex Pool account identity for durable usage attribution. */
66
+ accountLogLabel?: string;
67
+ requestedModel?: string;
68
+ /** Original bare helper model when the opt-in shadow-call route rewrote this request. */
69
+ shadowCallRewrittenFrom?: string;
70
+ /** Internal structural combo identity; omitted from RequestLogEntry/JSONL. */
71
+ comboId?: string;
72
+ requestedEffort?: string;
73
+ effectiveEffort?: string;
74
+ reasoningWireField?: string;
75
+ reasoningWireValue?: string | number | boolean;
76
+ callerServiceTier?: string;
77
+ requestedServiceTier?: string;
78
+ requestedSpeedLabel?: string;
79
+ configuredServiceTier?: string;
80
+ configuredSpeedLabel?: string;
81
+ modelSupportsServiceTier?: boolean;
82
+ responseServiceTier?: string;
83
+ /** Final-attempt tier summary; attempt rows remain the accounting source of truth. */
84
+ tierOutcome?: AttemptTierOutcome;
85
+ resolvedModel?: string;
86
+ /** Internal: client-facing response metadata must not replace the physical routed model. */
87
+ preserveResolvedModelFromRoute?: boolean;
88
+ usage?: OcxUsage;
89
+ usageLogInputTokens?: number;
90
+ attempts?: PersistedUsageAttempt[];
91
+ /** Internal mutable final attempt; omitted from RequestLogEntry/JSONL. */
92
+ activeAttempt?: PersistedUsageAttempt;
93
+ /** Internal wall-clock origin for the committed final attempt; never persisted. */
94
+ activeAttemptStartedAt?: number;
95
+ /** Internal adapter response observer paired with activeAttempt.tierOutcome. */
96
+ activeTierMetadata?: AdapterTierMetadata;
97
+ usageDebugBodyKind?: UsageDebugBodyKind;
98
+ usageDebugBodySample?: string;
99
+ usageDebugContentType?: string;
100
+ /** Route adapter type ("cursor"/"kiro"/"anthropic"/…): drives estimated-usage detection
101
+ * independent of the user-chosen provider NAME (devlog 130 B2). */
102
+ providerAdapter?: string;
103
+ /** Set when the bridge reported raw adapter usage via onUsage: the bridged wire now always
104
+ * carries synthetic zero-default token-detail objects (strict-client normalization, see
105
+ * responsesUsage in src/bridge.ts), so SSE/JSON re-parsing must not overwrite the raw
106
+ * provenance — a synthetic cached_tokens:0 is NOT a measured cache read. */
107
+ usageFromBridge?: boolean;
108
+ /** Secret-redacted upstream error reason (e.g. the granular Cursor "rate limit exceeded…"
109
+ * message) extracted from a `response.failed` SSE payload or non-streaming error body, so the
110
+ * request log / GUI shows the actual upstream failure rather than only the HTTP-mapped code. */
111
+ upstreamError?: string;
112
+ /** HTTP status derived from a terminal `response.failed` SSE payload (429/401/503/etc.). */
113
+ terminalHttpStatus?: number;
114
+ /** Recognized structured terminal code whose exact identity must survive status mapping. */
115
+ terminalErrorCode?: typeof CYBER_POLICY_ERROR_CODE;
116
+ /** Structured reason from `response.incomplete`; internal-only input to log classification. */
117
+ terminalIncompleteReason?: string;
118
+ affinity?: "reused" | "new_bind" | "rebound" | "cleared";
119
+ transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
120
+ terminalSource?: "upstream" | "synthetic";
121
+ /** Bounded route-decision trace (RI-01); never contains secrets. */
122
+ routeDecision?: RouteDecisionTraceV1;
123
+ }
124
+
125
+ export interface RequestLogEntry {
126
+ requestId: string;
127
+ timestamp: number;
128
+ model: string;
129
+ provider: string;
130
+ /** TTFT: ms from request start to the first non-empty model output delta; unset for non-streaming/tool-only. */
131
+ firstOutputMs?: number;
132
+ surface?: "claude" | "claude-desktop" | "grok";
133
+ /** The matched configured key's id. Set ONLY for admissionKind "configured" —
134
+ * never a sentinel, so a hand-edited entry whose id happens to be "loopback"
135
+ * cannot absorb unrelated traffic. */
136
+ apiKeyId?: string;
137
+ /** Which kind of admission opened this request. Carries no secret. */
138
+ admissionKind?: "configured" | "environment" | "loopback";
139
+ /** Which inbound wire was used. Orthogonal to `surface`, which names the client
140
+ * product: widening that enum would merge Responses and Chat Completions,
141
+ * since both leave it undefined. */
142
+ inboundProtocol?: "responses" | "chat" | "messages";
143
+ accountLogLabel?: string;
144
+ /** Best-effort chat/session correlation for Logs grouping (#330). */
145
+ conversationId?: string;
146
+ requestedModel?: string;
147
+ /** Original bare helper model when the opt-in shadow-call route rewrote this request. */
148
+ shadowCallRewrittenFrom?: string;
149
+ requestedEffort?: string;
150
+ effectiveEffort?: string;
151
+ reasoningWireField?: string;
152
+ reasoningWireValue?: string | number | boolean;
153
+ callerServiceTier?: string;
154
+ requestedServiceTier?: string;
155
+ requestedSpeedLabel?: string;
156
+ configuredServiceTier?: string;
157
+ configuredSpeedLabel?: string;
158
+ modelSupportsServiceTier?: boolean;
159
+ responseServiceTier?: string;
160
+ tierOutcome?: AttemptTierOutcome;
161
+ resolvedModel?: string;
162
+ status: number;
163
+ durationMs: number;
164
+ errorCode?: string;
165
+ terminalStatus?: ResponsesTerminalStatus;
166
+ closeReason?: "terminal" | "client_cancel" | "non_stream" | "body_stall" | "body_overflow";
167
+ /** Secret-redacted upstream error reason, surfaced in /api/logs and the GUI detail modal. */
168
+ upstreamError?: string;
169
+ usageStatus: UsageStatus;
170
+ usage?: OcxUsage;
171
+ totalTokens?: number;
172
+ attempts?: PersistedUsageAttempt[];
173
+ /** Codex pool affinity decision for this request (diagnostics for #186). */
174
+ affinity?: "reused" | "new_bind" | "rebound" | "cleared";
175
+ /** Where the upstream terminal/failure was observed. */
176
+ transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
177
+ /** Whether the terminal came from a real upstream SSE event or a proxy synthetic tail. */
178
+ terminalSource?: "upstream" | "synthetic";
179
+ /** Bounded route-decision trace (RI-01); never contains secrets. */
180
+ routeDecision?: RouteDecisionTraceV1;
181
+ }
182
+
183
+ const requestLog: RequestLogEntry[] = [];
184
+ const MAX_LOG_SIZE = 2000;
185
+ const requestLogEntryBytes = new WeakMap<RequestLogEntry, number>();
186
+ let requestLogBytes = 0;
187
+ let requestLogSeq = 0;
188
+ /** True after hydrateRequestLogsFromDisk ran once in this process. */
189
+ let requestLogsHydratedFromDisk = false;
190
+
191
+ function retainedRequestLogBytes(entry: RequestLogEntry): number {
192
+ return Buffer.byteLength(JSON.stringify(entry), "utf8");
193
+ }
194
+
195
+ function removeOldestRequestLogEntry(): number {
196
+ const entry = requestLog.shift();
197
+ if (!entry) return 0;
198
+ const bytes = requestLogEntryBytes.get(entry) ?? retainedRequestLogBytes(entry);
199
+ requestLogEntryBytes.delete(entry);
200
+ requestLogBytes = Math.max(0, requestLogBytes - bytes);
201
+ return bytes;
202
+ }
203
+
204
+ function retainRequestLogEntry(entry: RequestLogEntry): void {
205
+ const bytes = retainedRequestLogBytes(entry);
206
+ requestLog.push(entry);
207
+ requestLogEntryBytes.set(entry, bytes);
208
+ requestLogBytes += bytes;
209
+ while (requestLog.length > MAX_LOG_SIZE) removeOldestRequestLogEntry();
210
+ enforceAppOwnedMemoryBudget();
211
+ }
212
+
213
+ export function requestLogRetainedStoreSnapshot(): RetainedStoreSnapshot {
214
+ return {
215
+ count: requestLog.length,
216
+ bytes: requestLogBytes,
217
+ evictableBytes: requestLogBytes,
218
+ pinnedBytes: 0,
219
+ oldestAt: requestLog[0]?.timestamp ?? null,
220
+ };
221
+ }
222
+
223
+ export function evictOldestRequestLogForBudget(): number {
224
+ return removeOldestRequestLogEntry();
225
+ }
226
+
227
+ function asTerminalStatus(value: string | undefined): ResponsesTerminalStatus | undefined {
228
+ if (value === "completed" || value === "failed" || value === "incomplete") return value;
229
+ return undefined;
230
+ }
231
+
232
+ function asCloseReason(value: string | undefined): RequestLogEntry["closeReason"] | undefined {
233
+ switch (value) {
234
+ case "terminal":
235
+ case "client_cancel":
236
+ case "non_stream":
237
+ case "body_stall":
238
+ case "body_overflow":
239
+ return value;
240
+ default:
241
+ return undefined;
242
+ }
243
+ }
244
+
245
+ /** Project a persisted usage.jsonl row back into the in-memory /api/logs shape. */
246
+ export function requestLogEntryFromPersistedUsage(entry: PersistedUsageEntry): RequestLogEntry {
247
+ const terminalStatus = asTerminalStatus(entry.terminalStatus);
248
+ const closeReason = asCloseReason(entry.closeReason);
249
+ const routeDecision = normalizeRouteDecisionTraceForLog(entry.routeDecision);
250
+ return {
251
+ requestId: entry.requestId,
252
+ timestamp: entry.timestamp,
253
+ model: entry.model,
254
+ provider: entry.provider,
255
+ ...(entry.firstOutputMs !== undefined ? { firstOutputMs: entry.firstOutputMs } : {}),
256
+ ...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
257
+ ...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
258
+ ...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
259
+ ? { accountLogLabel: entry.accountLogLabel }
260
+ : {}),
261
+ ...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
262
+ ...(entry.shadowCallRewrittenFrom
263
+ ? { shadowCallRewrittenFrom: entry.shadowCallRewrittenFrom }
264
+ : {}),
265
+ ...(entry.requestedEffort ? { requestedEffort: entry.requestedEffort } : {}),
266
+ ...(entry.effectiveEffort ? { effectiveEffort: entry.effectiveEffort } : {}),
267
+ ...(entry.reasoningWireField ? { reasoningWireField: entry.reasoningWireField } : {}),
268
+ ...(entry.reasoningWireValue !== undefined ? { reasoningWireValue: entry.reasoningWireValue } : {}),
269
+ ...(entry.callerServiceTier ? { callerServiceTier: entry.callerServiceTier } : {}),
270
+ ...(entry.requestedServiceTier ? { requestedServiceTier: entry.requestedServiceTier } : {}),
271
+ ...(entry.requestedSpeedLabel ? { requestedSpeedLabel: entry.requestedSpeedLabel } : {}),
272
+ ...(entry.configuredServiceTier ? { configuredServiceTier: entry.configuredServiceTier } : {}),
273
+ ...(entry.configuredSpeedLabel ? { configuredSpeedLabel: entry.configuredSpeedLabel } : {}),
274
+ ...(entry.modelSupportsServiceTier !== undefined
275
+ ? { modelSupportsServiceTier: entry.modelSupportsServiceTier }
276
+ : {}),
277
+ ...(entry.responseServiceTier ? { responseServiceTier: entry.responseServiceTier } : {}),
278
+ ...(entry.tierOutcome ? { tierOutcome: entry.tierOutcome } : {}),
279
+ ...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
280
+ status: entry.status,
281
+ durationMs: entry.durationMs,
282
+ ...(entry.errorCode ? { errorCode: entry.errorCode } : {}),
283
+ ...(terminalStatus ? { terminalStatus } : {}),
284
+ ...(closeReason ? { closeReason } : {}),
285
+ ...(entry.upstreamError ? { upstreamError: entry.upstreamError } : {}),
286
+ usageStatus: entry.usageStatus,
287
+ ...(entry.usage ? { usage: entry.usage } : {}),
288
+ ...(entry.totalTokens !== undefined ? { totalTokens: entry.totalTokens } : {}),
289
+ ...(entry.attempts !== undefined ? { attempts: entry.attempts } : {}),
290
+ ...(routeDecision ? { routeDecision } : {}),
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Hydration guard: persisted traces are re-normalized before they enter the
296
+ * in-memory ring buffer so a hand-edited or corrupt row cannot poison the DTO.
297
+ * A row that fails validation is dropped, never forwarded unvalidated.
298
+ */
299
+ function normalizeRouteDecisionTraceForLog(
300
+ entry: RouteDecisionTraceV1 | undefined,
301
+ ): RouteDecisionTraceV1 | null {
302
+ return entry ? normalizeRouteDecisionTrace(entry) : null;
303
+ }
304
+
305
+ /**
306
+ * Seed the in-memory Logs ring buffer from usage.jsonl so GUI /api/logs survives
307
+ * `ocx stop` / `ocx start` (process restart). Idempotent per process; no-ops when
308
+ * the buffer already has live entries. Read failures are non-fatal (same as /api/usage).
309
+ */
310
+ export function hydrateRequestLogsFromDisk(
311
+ reader: () => PersistedUsageEntry[] = () => readRecentUsageEntries(MAX_LOG_SIZE),
312
+ ): number {
313
+ if (requestLogsHydratedFromDisk) return 0;
314
+ if (requestLog.length > 0) {
315
+ requestLogsHydratedFromDisk = true;
316
+ return 0;
317
+ }
318
+ try {
319
+ const persisted = reader();
320
+ requestLogsHydratedFromDisk = true;
321
+ if (persisted.length === 0) return 0;
322
+ const slice = persisted.length > MAX_LOG_SIZE
323
+ ? persisted.slice(persisted.length - MAX_LOG_SIZE)
324
+ : persisted;
325
+ for (const entry of slice) retainRequestLogEntry(requestLogEntryFromPersistedUsage(entry));
326
+ return slice.length;
327
+ } catch (err) {
328
+ requestLogsHydratedFromDisk = true;
329
+ console.warn(
330
+ `[request-log] failed to hydrate from usage.jsonl: ${err instanceof Error ? err.message : String(err)}`,
331
+ );
332
+ return 0;
333
+ }
334
+ }
335
+
336
+ export function addRequestLog(entry: RequestLogEntry) {
337
+ // Sanitize ONCE, at the ingress, and use that one value for both destinations.
338
+ //
339
+ // `addFinalRequestLog` is not the only way in: `addRequestLog` is exported and callable
340
+ // directly, and it retained the caller's entry verbatim in the in-memory ring while only the
341
+ // field-by-field disk projection below saw a sanitized value. That split let `/api/logs`
342
+ // serve a raw upstream-supplied marker — a newline in it can forge a record boundary in a
343
+ // line-oriented viewer — while `usage.jsonl` looked clean, which is the worst shape for a
344
+ // sanitization bug because the safe surface is the one you check.
345
+ const shadowCallRewrittenFrom = sanitizeLogMetadataString(entry.shadowCallRewrittenFrom);
346
+ const retained: RequestLogEntry = shadowCallRewrittenFrom === entry.shadowCallRewrittenFrom
347
+ ? entry
348
+ : { ...entry, ...(shadowCallRewrittenFrom ? { shadowCallRewrittenFrom } : {}) };
349
+ if (!shadowCallRewrittenFrom && retained !== entry) delete retained.shadowCallRewrittenFrom;
350
+ entry = retained;
351
+ retainRequestLogEntry(entry);
352
+ try {
353
+ // Failure diagnostics survive the 200-entry ring buffer by riding the persisted
354
+ // usage entry (devlog/_plan/260716_claudecode_hardening/030). Success rows stay
355
+ // in their existing shape; the >=400 gate deliberately includes 499 client-cancels.
356
+ const failureDiagnostics = entry.status >= 400 || (entry.terminalStatus && entry.terminalStatus !== "completed")
357
+ ? {
358
+ ...(entry.errorCode ? { errorCode: entry.errorCode } : {}),
359
+ ...(entry.terminalStatus ? { terminalStatus: entry.terminalStatus } : {}),
360
+ ...(entry.closeReason ? { closeReason: entry.closeReason } : {}),
361
+ ...(entry.upstreamError ? { upstreamError: entry.upstreamError } : {}),
362
+ }
363
+ : {};
364
+ appendUsageEntry({
365
+ requestId: entry.requestId,
366
+ timestamp: entry.timestamp,
367
+ provider: entry.provider,
368
+ model: entry.model,
369
+ ...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
370
+ // This function REBUILDS the persisted row field by field rather than
371
+ // spreading it, so a field missing here reaches /api/logs and never
372
+ // reaches usage.jsonl — which is where the per-key rollup reads from.
373
+ ...(entry.apiKeyId ? { apiKeyId: entry.apiKeyId } : {}),
374
+ ...(isKnownAdmissionKind(entry.admissionKind) ? { admissionKind: entry.admissionKind } : {}),
375
+ ...(isKnownInboundProtocol(entry.inboundProtocol) ? { inboundProtocol: entry.inboundProtocol } : {}),
376
+ ...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
377
+ ? { accountLogLabel: entry.accountLogLabel }
378
+ : {}),
379
+ ...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
380
+ ...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
381
+ ...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
382
+ ...(entry.shadowCallRewrittenFrom
383
+ ? { shadowCallRewrittenFrom: entry.shadowCallRewrittenFrom }
384
+ : {}),
385
+ ...(entry.requestedEffort ? { requestedEffort: entry.requestedEffort } : {}),
386
+ ...(entry.effectiveEffort ? { effectiveEffort: entry.effectiveEffort } : {}),
387
+ ...(entry.reasoningWireField ? { reasoningWireField: entry.reasoningWireField } : {}),
388
+ ...(entry.reasoningWireValue !== undefined ? { reasoningWireValue: entry.reasoningWireValue } : {}),
389
+ ...(entry.callerServiceTier ? { callerServiceTier: entry.callerServiceTier } : {}),
390
+ ...(entry.requestedServiceTier ? { requestedServiceTier: entry.requestedServiceTier } : {}),
391
+ ...(entry.requestedSpeedLabel ? { requestedSpeedLabel: entry.requestedSpeedLabel } : {}),
392
+ ...(entry.configuredServiceTier ? { configuredServiceTier: entry.configuredServiceTier } : {}),
393
+ ...(entry.configuredSpeedLabel ? { configuredSpeedLabel: entry.configuredSpeedLabel } : {}),
394
+ ...(entry.modelSupportsServiceTier !== undefined
395
+ ? { modelSupportsServiceTier: entry.modelSupportsServiceTier }
396
+ : {}),
397
+ ...(entry.responseServiceTier ? { responseServiceTier: entry.responseServiceTier } : {}),
398
+ ...(entry.tierOutcome ? { tierOutcome: entry.tierOutcome } : {}),
399
+ status: entry.status,
400
+ durationMs: entry.durationMs,
401
+ ...(entry.firstOutputMs !== undefined ? { firstOutputMs: entry.firstOutputMs } : {}),
402
+ usageStatus: entry.usageStatus,
403
+ ...(entry.usage ? { usage: entry.usage } : {}),
404
+ ...(entry.totalTokens !== undefined ? { totalTokens: entry.totalTokens } : {}),
405
+ ...(entry.attempts !== undefined ? { attempts: entry.attempts } : {}),
406
+ ...failureDiagnostics,
407
+ ...(entry.routeDecision ? { routeDecision: entry.routeDecision } : {}),
408
+ });
409
+ } catch {
410
+ /* request logging must never fail a user request */
411
+ }
412
+ }
413
+
414
+ export function nextRequestLogId(timestamp = Date.now()): string {
415
+ requestLogSeq = (requestLogSeq % 1_000_000) + 1;
416
+ return `ocx-${timestamp.toString(36)}-${requestLogSeq.toString(36)}`;
417
+ }
418
+
419
+ /**
420
+ * One-shot TTFT recorder (WP4). Records the first non-empty model output moment
421
+ * relative to the request start, and — when a combo attempt is in flight —
422
+ * relative to that attempt's start as well. Later calls are no-ops, so both the
423
+ * bridge callback and the deferred SSE tap may fire without double-recording.
424
+ */
425
+ export function recordFirstOutput(
426
+ logCtx: RequestLogContext,
427
+ requestStartedAt: number,
428
+ now = Date.now(),
429
+ ): void {
430
+ if (!Number.isFinite(requestStartedAt) || !Number.isFinite(now)) return;
431
+ const requestElapsed = Math.max(0, now - requestStartedAt);
432
+ if (logCtx.firstOutputMs === undefined) logCtx.firstOutputMs = requestElapsed;
433
+ if (logCtx.activeAttempt && logCtx.activeAttempt.firstOutputMs === undefined) {
434
+ const attemptStartedAt = logCtx.activeAttemptStartedAt ?? requestStartedAt;
435
+ logCtx.activeAttempt.firstOutputMs = Math.max(0, now - attemptStartedAt);
436
+ }
437
+ }
438
+
439
+ /** Snapshot target-specific requested effort even for runTurn adapters with no AdapterRequest. */
440
+ export function recordAttemptRequestedEffort(logCtx: RequestLogContext): void {
441
+ const attempt = logCtx.activeAttempt;
442
+ if (!attempt) return;
443
+ delete attempt.requestedEffort;
444
+ try {
445
+ if (typeof logCtx.requestedEffort === "string" && logCtx.requestedEffort) {
446
+ attempt.requestedEffort = redactSecretString(logCtx.requestedEffort).slice(0, 64);
447
+ }
448
+ } catch {
449
+ // Request logging is best-effort and must not affect request delivery.
450
+ }
451
+ }
452
+
453
+ /** Copy the adapter's exact outbound reasoning parameter into the durable request log. */
454
+ export function recordAdapterReasoning(
455
+ logCtx: RequestLogContext,
456
+ request: AdapterRequest,
457
+ ): void {
458
+ delete logCtx.effectiveEffort;
459
+ delete logCtx.reasoningWireField;
460
+ delete logCtx.reasoningWireValue;
461
+ const attempt = logCtx.activeAttempt;
462
+ if (attempt) {
463
+ delete attempt.effectiveEffort;
464
+ delete attempt.reasoningWireField;
465
+ delete attempt.reasoningWireValue;
466
+ }
467
+ recordAttemptRequestedEffort(logCtx);
468
+
469
+ // Diagnostics must never make an otherwise valid upstream request fail. Config files
470
+ // written by older versions (or edited by hand) can contain values that violate the
471
+ // current TypeScript shape, so validate the runtime object before redacting strings.
472
+ try {
473
+ const raw: unknown = request.reasoningLog;
474
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
475
+ const reasoning = raw as Record<string, unknown>;
476
+ if (typeof reasoning.effectiveEffort !== "string" || !reasoning.effectiveEffort
477
+ || (reasoning.wireField !== "reasoning_effort"
478
+ && reasoning.wireField !== "reasoning.enabled"
479
+ && reasoning.wireField !== "reasoning.effort"
480
+ && reasoning.wireField !== "thinking_budget"
481
+ && reasoning.wireField !== "thinking.type")
482
+ || !isValidReasoningWireValue(reasoning.wireField, reasoning.wireValue)) {
483
+ return;
484
+ }
485
+
486
+ const effectiveEffort = redactSecretString(reasoning.effectiveEffort).slice(0, 64);
487
+ const wireValue = typeof reasoning.wireValue === "string"
488
+ ? redactSecretString(reasoning.wireValue).slice(0, 64)
489
+ : reasoning.wireValue;
490
+ logCtx.effectiveEffort = effectiveEffort;
491
+ logCtx.reasoningWireField = reasoning.wireField;
492
+ logCtx.reasoningWireValue = wireValue;
493
+ if (attempt) {
494
+ attempt.effectiveEffort = effectiveEffort;
495
+ attempt.reasoningWireField = reasoning.wireField;
496
+ attempt.reasoningWireValue = wireValue;
497
+ }
498
+ } catch {
499
+ // Request logging is best-effort and must not affect request delivery.
500
+ }
501
+ }
502
+
503
+ /** Attach the serializing adapter's tier observation to the active durable attempt. */
504
+ export function recordAdapterTier(
505
+ logCtx: RequestLogContext,
506
+ request: AdapterRequest,
507
+ ): void {
508
+ recordAdapterTierMetadata(logCtx, request.tierLog);
509
+ }
510
+
511
+ /** Attach adapter-owned metadata for transports that expose no AdapterRequest (runTurn). */
512
+ export function recordAdapterTierMetadata(
513
+ logCtx: RequestLogContext,
514
+ metadata: AdapterTierMetadata | undefined,
515
+ ): void {
516
+ delete logCtx.tierOutcome;
517
+ delete logCtx.activeTierMetadata;
518
+ const attempt = logCtx.activeAttempt;
519
+ if (attempt) delete attempt.tierOutcome;
520
+
521
+ try {
522
+ const outcome = metadata?.outcome;
523
+ if (!metadata || !outcome) return;
524
+ logCtx.tierOutcome = outcome;
525
+ logCtx.activeTierMetadata = metadata;
526
+ if (attempt) attempt.tierOutcome = outcome;
527
+ } catch {
528
+ // Request logging is best-effort and must not affect request delivery.
529
+ }
530
+ }
531
+
532
+ export function requestLogErrorCode(
533
+ status: number,
534
+ upstreamError?: string,
535
+ terminalErrorCode?: string,
536
+ ): string | undefined {
537
+ if (status >= 200 && status < 400) return undefined;
538
+ // A structured terminal code is authoritative even when the provider message is localized,
539
+ // generic, or absent. Only preserve the one narrowly recognized policy code here: broadly
540
+ // forwarding arbitrary upstream codes would change unrelated request-log taxonomy.
541
+ if (isCyberPolicyCode(terminalErrorCode)) return CYBER_POLICY_ERROR_CODE;
542
+ const classifiedCode = upstreamError?.trim()
543
+ ? classifyError(status, "upstream_error", upstreamError).code
544
+ : undefined;
545
+ // Defense in depth: mid-stream web-search aborts used to land as 502 with this message.
546
+ if (status === 499 || classifiedCode === "client_closed_request") {
547
+ return "client_closed_request";
548
+ }
549
+ // Keep the high-confidence message fallback for runtimes/providers that stripped the
550
+ // structured code before emitting response.failed.
551
+ if (classifiedCode === CYBER_POLICY_ERROR_CODE) return CYBER_POLICY_ERROR_CODE;
552
+ if (status === 400 || status === 409) return "invalid_request_error";
553
+ if (status === 401) return "invalid_api_key";
554
+ if (status === 403) {
555
+ // Prefer message-aware codes (e.g. Ollama Cloud subscription gates) over a blunt
556
+ // invalid_api_key — 403 usually means authenticated but not allowed.
557
+ if (upstreamError?.trim()) {
558
+ const code = classifyError(403, "upstream_error", upstreamError).code;
559
+ if (code) return code;
560
+ }
561
+ return "permission_denied";
562
+ }
563
+ if (status === 429) return "rate_limit_exceeded";
564
+ if (status === 503) return "server_is_overloaded";
565
+ if (status >= 500) return "upstream_server_error";
566
+ return `http_${status}`;
567
+ }
568
+
569
+ export function requestLogSpeedLabel(serviceTier: string | undefined): string | undefined {
570
+ const normalized = serviceTier?.trim().toLowerCase();
571
+ if (normalized === "priority" || normalized === "fast") return "fast";
572
+ return undefined;
573
+ }
574
+
575
+ export function readConfiguredCodexServiceTier(): string | undefined {
576
+ try {
577
+ if (!existsSync(CODEX_CONFIG_PATH)) return undefined;
578
+ return readRootTomlString(readFileSync(CODEX_CONFIG_PATH, "utf-8"), "service_tier") ?? undefined;
579
+ } catch {
580
+ return undefined;
581
+ }
582
+ }
583
+
584
+ export function catalogModelSupportsServiceTier(modelId: string, serviceTier: string | undefined): boolean | undefined {
585
+ if (!serviceTier) return undefined;
586
+ const requestTier = serviceTier.trim().toLowerCase() === "fast" ? "priority" : serviceTier.trim();
587
+ try {
588
+ const catalogPath = readCodexCatalogPath();
589
+ if (!existsSync(catalogPath)) return undefined;
590
+ const catalog = JSON.parse(readFileSync(catalogPath, "utf-8")) as { models?: unknown };
591
+ const models = Array.isArray(catalog.models) ? catalog.models : [];
592
+ const entry = models.find(model => {
593
+ if (!model || typeof model !== "object") return false;
594
+ return (model as { slug?: unknown; id?: unknown }).slug === modelId
595
+ || (model as { slug?: unknown; id?: unknown }).id === modelId;
596
+ });
597
+ if (!entry || typeof entry !== "object") return undefined;
598
+ const tiers = (entry as { service_tiers?: unknown }).service_tiers;
599
+ return Array.isArray(tiers) && tiers.some(tier => (
600
+ tier && typeof tier === "object" && (tier as { id?: unknown }).id === requestTier
601
+ ));
602
+ } catch {
603
+ return undefined;
604
+ }
605
+ }
606
+
607
+ export function applyResponseLogMetadata(logCtx: RequestLogContext, payload: unknown): void {
608
+ if (!payload || typeof payload !== "object") return;
609
+ const source = "response" in payload && typeof (payload as { response?: unknown }).response === "object"
610
+ ? (payload as { response?: unknown }).response
611
+ : payload;
612
+ if (!source || typeof source !== "object") return;
613
+ const model = (source as { model?: unknown }).model;
614
+ if (
615
+ !logCtx.preserveResolvedModelFromRoute
616
+ && typeof model === "string"
617
+ && model.trim()
618
+ ) logCtx.resolvedModel = model;
619
+ const serviceTier = (source as { service_tier?: unknown }).service_tier;
620
+ if (typeof serviceTier === "string" && serviceTier.trim()) {
621
+ const sanitized = sanitizeLogMetadataString(serviceTier);
622
+ if (sanitized) logCtx.responseServiceTier = sanitized;
623
+ logCtx.activeTierMetadata?.observeResponseServiceTier(serviceTier);
624
+ } else if (Object.prototype.hasOwnProperty.call(source, "service_tier")) {
625
+ logCtx.activeTierMetadata?.observeResponseServiceTier(serviceTier);
626
+ }
627
+ const usage = usageFromResponsesPayload((source as { usage?: unknown }).usage);
628
+ if (usage && !logCtx.usageFromBridge) {
629
+ logCtx.usage = usage;
630
+ if (logCtx.activeAttempt) logCtx.activeAttempt.usage = usage;
631
+ }
632
+ }
633
+
634
+ export function usageFromResponsesPayload(usage: unknown): OcxUsage | undefined {
635
+ if (!usage || typeof usage !== "object") return undefined;
636
+ const raw = usage as {
637
+ input_tokens?: unknown;
638
+ output_tokens?: unknown;
639
+ input_tokens_details?: { cached_tokens?: unknown; cache_write_tokens?: unknown };
640
+ output_tokens_details?: { reasoning_tokens?: unknown };
641
+ total_tokens?: unknown;
642
+ prompt_tokens?: unknown;
643
+ completion_tokens?: unknown;
644
+ prompt_tokens_details?: { cached_tokens?: unknown; cache_write_tokens?: unknown };
645
+ completion_tokens_details?: { reasoning_tokens?: unknown };
646
+ };
647
+ if (typeof raw.input_tokens === "number" && typeof raw.output_tokens === "number") {
648
+ return {
649
+ inputTokens: raw.input_tokens,
650
+ outputTokens: raw.output_tokens,
651
+ ...(typeof raw.total_tokens === "number" ? { totalTokens: raw.total_tokens } : {}),
652
+ ...(typeof raw.input_tokens_details?.cached_tokens === "number"
653
+ ? {
654
+ cachedInputTokens: raw.input_tokens_details.cached_tokens,
655
+ cacheReadInputTokens: raw.input_tokens_details.cached_tokens,
656
+ }
657
+ : {}),
658
+ ...(typeof raw.input_tokens_details?.cache_write_tokens === "number"
659
+ ? { cacheCreationInputTokens: raw.input_tokens_details.cache_write_tokens }
660
+ : {}),
661
+ ...(typeof raw.output_tokens_details?.reasoning_tokens === "number"
662
+ ? { reasoningOutputTokens: raw.output_tokens_details.reasoning_tokens }
663
+ : {}),
664
+ };
665
+ }
666
+ if (typeof raw.prompt_tokens === "number" && typeof raw.completion_tokens === "number") {
667
+ return {
668
+ inputTokens: raw.prompt_tokens,
669
+ outputTokens: raw.completion_tokens,
670
+ ...(typeof raw.total_tokens === "number" ? { totalTokens: raw.total_tokens } : {}),
671
+ ...(typeof raw.prompt_tokens_details?.cached_tokens === "number"
672
+ ? {
673
+ cachedInputTokens: raw.prompt_tokens_details.cached_tokens,
674
+ cacheReadInputTokens: raw.prompt_tokens_details.cached_tokens,
675
+ }
676
+ : {}),
677
+ ...(typeof raw.prompt_tokens_details?.cache_write_tokens === "number"
678
+ ? { cacheCreationInputTokens: raw.prompt_tokens_details.cache_write_tokens }
679
+ : {}),
680
+ ...(typeof raw.completion_tokens_details?.reasoning_tokens === "number"
681
+ ? { reasoningOutputTokens: raw.completion_tokens_details.reasoning_tokens }
682
+ : {}),
683
+ };
684
+ }
685
+ return undefined;
686
+ }
687
+
688
+ export function inspectResponseLogJson(logCtx: RequestLogContext, text: string): void {
689
+ try {
690
+ applyResponseLogMetadata(logCtx, JSON.parse(text));
691
+ } catch {
692
+ logCtx.activeTierMetadata?.markResponseUnparseable();
693
+ /* body may not be JSON; request log metadata is best-effort only */
694
+ }
695
+ captureUpstreamError(logCtx, text);
696
+ if (isUsageDebugEnabled() && logCtx.usageDebugBodyKind === undefined) {
697
+ logCtx.usageDebugBodyKind = "json";
698
+ logCtx.usageDebugBodySample = truncateForDebug(text);
699
+ }
700
+ }
701
+
702
+ export function inspectResponseLogSsePayload(logCtx: RequestLogContext, payload: string | null): void {
703
+ if (!payload || payload.trim() === "[DONE]") return;
704
+ let parsed: unknown | undefined;
705
+ try {
706
+ parsed = JSON.parse(payload);
707
+ } catch {
708
+ /* SSE block payload may not be JSON; metadata inspection is best-effort */
709
+ }
710
+ inspectResponseLogSsePayloadParsed(logCtx, payload, parsed);
711
+ }
712
+
713
+ /** Inspect an SSE payload using the caller's single best-effort JSON parse. */
714
+ export function inspectResponseLogSsePayloadParsed(
715
+ logCtx: RequestLogContext,
716
+ payload: string | null,
717
+ parsed: unknown | undefined,
718
+ ): void {
719
+ if (!payload || payload.trim() === "[DONE]") return;
720
+ const debugEnabled = isUsageDebugEnabled();
721
+ const sseAlreadyMarked = logCtx.usageDebugBodyKind === "sse";
722
+ if (parsed !== undefined) applyResponseLogMetadata(logCtx, parsed);
723
+ else logCtx.activeTierMetadata?.markResponseUnparseable();
724
+ captureUpstreamErrorParsed(logCtx, payload, parsed);
725
+ if (debugEnabled) {
726
+ if (!sseAlreadyMarked) {
727
+ logCtx.usageDebugBodyKind = "sse";
728
+ logCtx.usageDebugBodySample = truncateForDebug(payload);
729
+ } else if (typeof logCtx.usageDebugBodySample === "string"
730
+ && logCtx.usageDebugBodySample.length < USAGE_DEBUG_BODY_SAMPLE_BYTES) {
731
+ const combined = `${logCtx.usageDebugBodySample}\n${payload}`;
732
+ logCtx.usageDebugBodySample = truncateForDebug(combined);
733
+ }
734
+ }
735
+ }
736
+
737
+ /**
738
+ * Capture the upstream error reason into the request log context. Codex/consumer surfaces only
739
+ * see an HTTP-mapped error code (502 → upstream_server_error); the granular reason lives inside
740
+ * a `response.failed` SSE payload's `error.message` (the adapter's redacted upstream message) or
741
+ * a non-streaming JSON error body. We keep the FIRST non-empty reason (the original failure) and
742
+ * run it through redactSecretString so secrets never reach /api/logs. Pure; safe on any text.
743
+ */
744
+ function captureUpstreamError(logCtx: RequestLogContext, text: string | null): void {
745
+ if (!text) return;
746
+ let parsed: unknown | undefined;
747
+ try {
748
+ parsed = JSON.parse(text);
749
+ } catch {
750
+ /* retain the raw malformed payload for the bounded fallback below */
751
+ }
752
+ captureUpstreamErrorParsed(logCtx, text, parsed);
753
+ }
754
+
755
+ function captureUpstreamErrorParsed(
756
+ logCtx: RequestLogContext,
757
+ text: string,
758
+ parsed: unknown | undefined,
759
+ ): void {
760
+ if (parsed !== undefined && parsed !== null) {
761
+ const json = parsed as {
762
+ type?: unknown;
763
+ error?: { message?: unknown };
764
+ last_error?: { message?: unknown };
765
+ response?: {
766
+ error?: { type?: unknown; code?: unknown; message?: unknown };
767
+ incomplete_details?: { reason?: unknown };
768
+ };
769
+ };
770
+ captureTerminalHttpStatus(logCtx, json);
771
+ const reason = json?.response?.incomplete_details?.reason;
772
+ if (json.type === "response.incomplete"
773
+ && logCtx.terminalIncompleteReason === undefined
774
+ && typeof reason === "string"
775
+ && reason.trim()) {
776
+ logCtx.terminalIncompleteReason = reason.trim();
777
+ }
778
+ if (logCtx.upstreamError) return;
779
+ const message = json?.error?.message
780
+ ?? json?.last_error?.message
781
+ ?? json?.response?.error?.message;
782
+ if (typeof message === "string" && message.trim()) {
783
+ logCtx.upstreamError = redactSecretString(message).slice(0, 500);
784
+ return;
785
+ }
786
+ // No human-readable error message: fall back to the structured incomplete reason emitted by
787
+ // the bridge on a stall-timeout or adapter EOF (response.incomplete). Maps the raw reason to a
788
+ // reader-facing label so a generic 502 in /api/logs explains WHY the turn ended, not just the
789
+ // mapped HTTP code.
790
+ if (typeof reason === "string" && reason.trim()) {
791
+ logCtx.upstreamError = redactSecretString(incompleteReasonLabel(reason.trim())).slice(0, 500);
792
+ }
793
+ return;
794
+ }
795
+ if (logCtx.upstreamError) return;
796
+ const trimmed = text.trim();
797
+ if (trimmed) {
798
+ logCtx.upstreamError = redactSecretString(trimmed).slice(0, 500);
799
+ }
800
+ }
801
+
802
+ /** Map a raw `incomplete_details.reason` (emitted by the bridge) to a reader-facing label. */
803
+ function incompleteReasonLabel(reason: string): string {
804
+ switch (reason) {
805
+ case "max_output_tokens":
806
+ return `Output reached the requested token limit (${reason})`;
807
+ case "upstream_stall_timeout":
808
+ return `Upstream stalled: no data for the stall-timeout window (${reason})`;
809
+ case "adapter_eof":
810
+ return `Upstream stream ended unexpectedly without a terminal event (${reason})`;
811
+ default:
812
+ return `Upstream incomplete: ${reason}`;
813
+ }
814
+ }
815
+
816
+ function captureTerminalHttpStatus(
817
+ logCtx: RequestLogContext,
818
+ json: {
819
+ type?: unknown;
820
+ response?: { error?: { type?: unknown; code?: unknown; message?: unknown } };
821
+ },
822
+ ): void {
823
+ if (logCtx.terminalHttpStatus !== undefined) return;
824
+ if (json.type !== "response.failed") return;
825
+ const error = json.response?.error;
826
+ if (!error || typeof error !== "object") return;
827
+ const terminalCode = error.code === null || typeof error.code === "string"
828
+ ? error.code
829
+ : undefined;
830
+ if (isCyberPolicyCode(terminalCode)) {
831
+ logCtx.terminalErrorCode = CYBER_POLICY_ERROR_CODE;
832
+ } else {
833
+ delete logCtx.terminalErrorCode;
834
+ }
835
+ logCtx.terminalHttpStatus = httpStatusFromTerminalError({
836
+ type: typeof error.type === "string" ? error.type : undefined,
837
+ code: terminalCode,
838
+ message: typeof error.message === "string" ? error.message : undefined,
839
+ });
840
+ }
841
+
842
+ /** Map a terminal Responses error object to the HTTP status we record in /api/logs. */
843
+ export function httpStatusFromTerminalError(error: {
844
+ type?: string;
845
+ code?: string | null;
846
+ message?: string;
847
+ } | undefined): number {
848
+ return httpStatusFromClassifiedTerminalError(error);
849
+ }
850
+
851
+ export function httpStatusForTerminalStatus(status: ResponsesTerminalStatus): number {
852
+ return status === "completed" ? 200 : 502;
853
+ }
854
+
855
+ export function httpStatusForRequestLogTerminal(
856
+ status: ResponsesTerminalStatus,
857
+ logCtx?: RequestLogContext,
858
+ ): number {
859
+ /**
860
+ * [Decision Log]
861
+ * - 목적과 의도: Keep request logs aligned with the successful HTTP/SSE contract.
862
+ * - 기존 구현 및 제약 조건: All incomplete terminals were recorded as 502 even when the
863
+ * client-requested output limit was reached normally.
864
+ * - 검토한 주요 대안: Treat every incomplete as success, or infer the reason from display text.
865
+ * - 선택한 방식: Only structured max_output_tokens incompletes map to 200.
866
+ * - 다른 대안 대신 이 방식을 선택한 이유: Stall, EOF, and unknown incompletes must remain
867
+ * visible failures, and display text is not a stable classification contract.
868
+ * - 장점, 단점 및 영향: Logs stop reporting false upstream errors while retaining the
869
+ * incomplete terminal detail; native callers without a structured reason keep old behavior.
870
+ */
871
+ if (status === "incomplete" && logCtx?.terminalIncompleteReason === "max_output_tokens") {
872
+ return 200;
873
+ }
874
+ if (status === "failed" && logCtx?.terminalHttpStatus !== undefined) {
875
+ return logCtx.terminalHttpStatus;
876
+ }
877
+ return httpStatusForTerminalStatus(status);
878
+ }
879
+
880
+ export function addFinalRequestLog(
881
+ requestId: string,
882
+ start: number,
883
+ logCtx: RequestLogContext,
884
+ status: number,
885
+ meta?: Pick<RequestLogEntry, "terminalStatus" | "closeReason">,
886
+ addLog: (entry: RequestLogEntry) => void = addRequestLog,
887
+ ): void {
888
+ // Mid-stream web-search aborts used to emit response.failed and land as 502/upstream_server_error.
889
+ // Prefer the client-close classification whenever the captured reason says so.
890
+ const effectiveStatus = status >= 500 && logCtx.upstreamError && isClientClosedMessage(logCtx.upstreamError)
891
+ ? 499
892
+ : status;
893
+ const errorCode = requestLogErrorCode(
894
+ effectiveStatus,
895
+ logCtx.upstreamError,
896
+ logCtx.terminalErrorCode,
897
+ );
898
+ // A response.failed whose classified status is 499 is still a client cancel, not an upstream
899
+ // terminal failure — keep /api/logs closeReason aligned with that.
900
+ const closeReason = effectiveStatus === 499
901
+ ? "client_cancel"
902
+ : meta?.closeReason;
903
+ if (logCtx.activeAttempt) {
904
+ finishRequestAttempt(
905
+ logCtx.activeAttempt,
906
+ effectiveStatus,
907
+ Date.now() - (logCtx.activeAttemptStartedAt ?? start),
908
+ logCtx.usage,
909
+ );
910
+ // The final row and its active physical attempt describe the same terminal. Preserve the
911
+ // semantic code on both so detailed attempt telemetry cannot regress to a generic status code.
912
+ if (errorCode) logCtx.activeAttempt.errorCode = errorCode;
913
+ else delete logCtx.activeAttempt.errorCode;
914
+ }
915
+ const existing = finalizedUsage(
916
+ logCtx.providerAdapter ?? logCtx.provider,
917
+ logCtx.usage,
918
+ logCtx.usageLogInputTokens,
919
+ contextWindowForModel(logCtx.providerAdapter ?? logCtx.provider, logCtx.model),
920
+ );
921
+ const attempts = logCtx.attempts?.map(attempt => ({
922
+ ...attempt,
923
+ recoveryKinds: [...attempt.recoveryKinds],
924
+ ...(attempt.usage ? { usage: { ...attempt.usage } } : {}),
925
+ ...(attempt.tierOutcome ? { tierOutcome: { ...attempt.tierOutcome } } : {}),
926
+ }));
927
+ const isCombo = logCtx.comboId !== undefined && (attempts?.length ?? 0) > 0;
928
+ const aggregate = isCombo ? aggregateAttemptUsage(attempts ?? []) : null;
929
+ const loggedUsage = aggregate?.usage ?? existing.usage;
930
+ const usageStatus = aggregate?.status ?? existing.status;
931
+ const totalTokens = aggregate?.totalTokens ?? existing.totalTokens;
932
+ // Sanitize at the logging layer, not only at the one call site that populates this today.
933
+ // The value originates in an upstream-supplied model id, so an unsanitized newline would
934
+ // let a single field forge a record boundary in any line-oriented log viewer. Doing it here
935
+ // means a future caller cannot reintroduce the hole by forgetting to sanitize first, and
936
+ // the in-memory /api/logs row matches what usage.jsonl already stores.
937
+ const shadowCallRewrittenFrom = sanitizeLogMetadataString(logCtx.shadowCallRewrittenFrom);
938
+ addLog({
939
+ requestId,
940
+ timestamp: start,
941
+ model: isCombo ? logCtx.requestedModel! : logCtx.model,
942
+ provider: isCombo ? "combo" : logCtx.provider,
943
+ ...(logCtx.surface ? { surface: logCtx.surface } : {}),
944
+ ...(logCtx.apiKeyId ? { apiKeyId: logCtx.apiKeyId } : {}),
945
+ ...(logCtx.admissionKind ? { admissionKind: logCtx.admissionKind } : {}),
946
+ ...(logCtx.inboundProtocol ? { inboundProtocol: logCtx.inboundProtocol } : {}),
947
+ ...(isCodexUsageAccountLogLabel(logCtx.accountLogLabel)
948
+ ? { accountLogLabel: logCtx.accountLogLabel }
949
+ : {}),
950
+ ...(logCtx.conversationId ? { conversationId: logCtx.conversationId } : {}),
951
+ ...(logCtx.requestedModel ? { requestedModel: logCtx.requestedModel } : {}),
952
+ ...(shadowCallRewrittenFrom ? { shadowCallRewrittenFrom } : {}),
953
+ ...(logCtx.requestedEffort ? { requestedEffort: logCtx.requestedEffort } : {}),
954
+ ...(logCtx.effectiveEffort ? { effectiveEffort: logCtx.effectiveEffort } : {}),
955
+ ...(logCtx.reasoningWireField ? { reasoningWireField: logCtx.reasoningWireField } : {}),
956
+ ...(logCtx.reasoningWireValue !== undefined ? { reasoningWireValue: logCtx.reasoningWireValue } : {}),
957
+ ...(logCtx.callerServiceTier ? { callerServiceTier: logCtx.callerServiceTier } : {}),
958
+ ...(logCtx.requestedServiceTier ? { requestedServiceTier: logCtx.requestedServiceTier } : {}),
959
+ ...(logCtx.requestedSpeedLabel ? { requestedSpeedLabel: logCtx.requestedSpeedLabel } : {}),
960
+ ...(logCtx.configuredServiceTier ? { configuredServiceTier: logCtx.configuredServiceTier } : {}),
961
+ ...(logCtx.configuredSpeedLabel ? { configuredSpeedLabel: logCtx.configuredSpeedLabel } : {}),
962
+ ...(logCtx.modelSupportsServiceTier !== undefined ? { modelSupportsServiceTier: logCtx.modelSupportsServiceTier } : {}),
963
+ ...(logCtx.responseServiceTier ? { responseServiceTier: logCtx.responseServiceTier } : {}),
964
+ ...((attempts?.at(-1)?.tierOutcome ?? logCtx.tierOutcome)
965
+ ? { tierOutcome: attempts?.at(-1)?.tierOutcome ?? { ...logCtx.tierOutcome! } }
966
+ : {}),
967
+ ...(logCtx.resolvedModel ? { resolvedModel: logCtx.resolvedModel } : {}),
968
+ status: effectiveStatus,
969
+ durationMs: Date.now() - start,
970
+ ...(logCtx.firstOutputMs !== undefined ? { firstOutputMs: logCtx.firstOutputMs } : {}),
971
+ ...(errorCode ? { errorCode } : {}),
972
+ ...(meta?.terminalStatus ? { terminalStatus: meta.terminalStatus } : {}),
973
+ ...(closeReason ? { closeReason } : {}),
974
+ ...(logCtx.upstreamError ? { upstreamError: logCtx.upstreamError } : {}),
975
+ usageStatus,
976
+ ...(loggedUsage ? { usage: loggedUsage } : {}),
977
+ ...(totalTokens !== undefined ? { totalTokens } : {}),
978
+ ...(attempts !== undefined ? { attempts } : {}),
979
+ ...(logCtx.affinity ? { affinity: logCtx.affinity } : {}),
980
+ ...(logCtx.transportPhase ? { transportPhase: logCtx.transportPhase } : {}),
981
+ ...(logCtx.terminalSource ? { terminalSource: logCtx.terminalSource } : {}),
982
+ ...(logCtx.routeDecision ? { routeDecision: logCtx.routeDecision } : {}),
983
+ });
984
+ if (isUsageDebugEnabled()) {
985
+ appendUsageDebug({
986
+ ts: Date.now(),
987
+ requestId,
988
+ provider: logCtx.provider,
989
+ model: logCtx.model,
990
+ upstreamContentType: logCtx.usageDebugContentType ?? null,
991
+ upstreamStatus: effectiveStatus,
992
+ bodyKind: logCtx.usageDebugBodyKind ?? "none",
993
+ bodySample: logCtx.usageDebugBodySample ?? "",
994
+ extractedUsage: loggedUsage ?? null,
995
+ });
996
+ }
997
+ }
998
+
999
+ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchParams): RequestLogEntry[] {
1000
+ let filtered = logs;
1001
+ const provider = params.get("provider")?.trim();
1002
+ if (provider) {
1003
+ filtered = filtered.filter(entry => entry.provider === provider
1004
+ || entry.attempts?.some(attempt => attempt.provider === provider));
1005
+ }
1006
+ const conversationId = params.get("conversationId")?.trim() || params.get("conversation")?.trim();
1007
+ if (conversationId) {
1008
+ filtered = filtered.filter(entry => matchesLogConversationId(entry.conversationId, conversationId));
1009
+ }
1010
+ const status = params.get("status")?.trim().toLowerCase();
1011
+ if (status) {
1012
+ filtered = /^[1-5]xx$/.test(status)
1013
+ ? filtered.filter(entry => Math.floor(entry.status / 100) === Number(status[0]))
1014
+ : filtered.filter(entry => String(entry.status) === status);
1015
+ }
1016
+ const tailRaw = params.get("tail")?.trim();
1017
+ if (tailRaw) {
1018
+ const tail = Number.parseInt(tailRaw, 10);
1019
+ if (Number.isFinite(tail) && tail > 0) filtered = filtered.slice(-Math.min(tail, MAX_LOG_SIZE));
1020
+ }
1021
+ const offsetRaw = params.get("offset")?.trim();
1022
+ const limitRaw = params.get("limit")?.trim();
1023
+ if (limitRaw) {
1024
+ const limit = Number.parseInt(limitRaw, 10);
1025
+ const offset = offsetRaw ? Number.parseInt(offsetRaw, 10) : 0;
1026
+ if (Number.isFinite(limit) && limit > 0) {
1027
+ const capped = Math.min(limit, MAX_LOG_SIZE);
1028
+ const startOffset = Number.isFinite(offset) && offset > 0 ? offset : 0;
1029
+ const end = filtered.length - startOffset;
1030
+ if (end <= 0) filtered = [];
1031
+ else {
1032
+ const begin = Math.max(0, end - capped);
1033
+ filtered = filtered.slice(begin, end);
1034
+ }
1035
+ }
1036
+ }
1037
+ return filtered;
1038
+ }
1039
+
1040
+ export function filteredRequestLogCount(logs: RequestLogEntry[], params: URLSearchParams): number {
1041
+ const withoutPagination = new URLSearchParams(params);
1042
+ withoutPagination.delete("limit");
1043
+ withoutPagination.delete("offset");
1044
+ return filterRequestLogs(logs, withoutPagination).length;
1045
+ }
1046
+
1047
+ interface FinalizedUsageResult {
1048
+ usage?: OcxUsage;
1049
+ status: UsageStatus;
1050
+ totalTokens?: number;
1051
+ }
1052
+
1053
+ /**
1054
+ * Context window for the routed model, used to cap the token estimate (codex-router PR #140):
1055
+ * a request the provider answered cannot have exceeded the window, so the estimate must never
1056
+ * claim it did. The family is picked by the route ADAPTER, not the model id alone, because
1057
+ * claude-family ids are shared between Kiro and Cursor with different windows. Kiro "auto" is
1058
+ * a router with no fixed window and is never guessed; unknown adapters/models stay uncapped.
1059
+ */
1060
+ function contextWindowForModel(adapter: string, modelId: string | undefined): number | undefined {
1061
+ if (!modelId) return undefined;
1062
+ if (adapter === "kiro" || adapter.startsWith("kiro-")) {
1063
+ const normalized = normalizeKiroModelId(modelId);
1064
+ if (normalized === "auto") return undefined;
1065
+ return modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, modelId)
1066
+ ?? modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, normalized);
1067
+ }
1068
+ if (adapter === "cursor" || adapter.startsWith("cursor-")) {
1069
+ return inferCursorContextWindow(modelId);
1070
+ }
1071
+ return undefined;
1072
+ }
1073
+
1074
+ function finalizedUsage(
1075
+ adapter: string,
1076
+ usage: OcxUsage | undefined,
1077
+ inputTokenEstimate: number | undefined,
1078
+ contextWindow: number | undefined,
1079
+ ): FinalizedUsageResult {
1080
+ // The ESTIMATE itself is capped at the model's context window (codex-router PR #140). The
1081
+ // combined value below keeps its max(inputTokens, estimate) behavior — a provider-reported
1082
+ // positive count is never reduced by this cap, only the estimate that could substitute it.
1083
+ const estimate = typeof inputTokenEstimate === "number"
1084
+ && Number.isFinite(inputTokenEstimate)
1085
+ && inputTokenEstimate >= 0
1086
+ ? capEstimateAtContextWindow(inputTokenEstimate, contextWindow)
1087
+ : undefined;
1088
+ const finalUsage = usageForFinalLog(adapter, usage);
1089
+ const usageFallback = !finalUsage && estimate !== undefined
1090
+ ? { inputTokens: estimate, outputTokens: 0, estimated: true }
1091
+ : undefined;
1092
+ const combinedInputTokens = finalUsage && estimate !== undefined
1093
+ ? Math.max(finalUsage.inputTokens, estimate)
1094
+ : undefined;
1095
+ const loggedUsage = finalUsage && combinedInputTokens !== undefined
1096
+ ? {
1097
+ ...finalUsage,
1098
+ inputTokens: combinedInputTokens,
1099
+ totalTokens: combinedInputTokens + finalUsage.outputTokens,
1100
+ estimated: true,
1101
+ }
1102
+ : finalUsage
1103
+ // When the adapter alone produced an estimated count and no local estimate
1104
+ // exists, cap it at the context window — an adapter estimate above the window
1105
+ // misleads the usage dashboard. The combined branch (above) already caps the
1106
+ // ESTIMATE via capEstimateAtContextWindow, and Math.max preserves a real
1107
+ // provider-reported count, so it needs no further reduction.
1108
+ ? (finalUsage.estimated && contextWindow !== undefined && finalUsage.inputTokens > contextWindow
1109
+ ? {
1110
+ ...finalUsage,
1111
+ inputTokens: contextWindow,
1112
+ totalTokens: contextWindow + finalUsage.outputTokens,
1113
+ }
1114
+ : finalUsage)
1115
+ : usageFallback;
1116
+ const totalTokens = usageTotalTokens(loggedUsage);
1117
+ return {
1118
+ status: usageStatusForFinalLog(loggedUsage),
1119
+ ...(loggedUsage ? { usage: loggedUsage } : {}),
1120
+ ...(totalTokens !== undefined ? { totalTokens } : {}),
1121
+ };
1122
+ }
1123
+
1124
+ export function beginRequestAttempt(
1125
+ ordinal: number,
1126
+ provider: string,
1127
+ model: string,
1128
+ adapter: string,
1129
+ ): PersistedUsageAttempt {
1130
+ return {
1131
+ ordinal,
1132
+ provider,
1133
+ model,
1134
+ adapter,
1135
+ status: 0,
1136
+ durationMs: 0,
1137
+ sendCount: 0,
1138
+ recoveryKinds: [],
1139
+ usageStatus: "unreported",
1140
+ };
1141
+ }
1142
+
1143
+ export function sealRequestAttemptIdentity(
1144
+ attempt: PersistedUsageAttempt | undefined,
1145
+ provider: string,
1146
+ adapter: string,
1147
+ accountLogLabel?: string,
1148
+ ): void {
1149
+ if (!attempt) return;
1150
+ attempt.provider = provider;
1151
+ attempt.adapter = adapter;
1152
+ if (isCodexUsageAccountLogLabel(accountLogLabel)) attempt.accountLogLabel = accountLogLabel;
1153
+ }
1154
+
1155
+ export function noteAttemptSend(
1156
+ attempt: PersistedUsageAttempt | undefined,
1157
+ inputTokenEstimate: number | undefined,
1158
+ recovery?: AttemptRecoveryKind,
1159
+ ): void {
1160
+ if (!attempt) return;
1161
+ attempt.sendCount += 1;
1162
+ if (typeof inputTokenEstimate === "number"
1163
+ && Number.isFinite(inputTokenEstimate)
1164
+ && inputTokenEstimate >= 0) {
1165
+ // Store the ESTIMATE field already capped at the model's window (codex-router PR #140):
1166
+ // what gets persisted, and later merged into usage, never claims a count above the window.
1167
+ attempt.inputTokenEstimate = capEstimateAtContextWindow(
1168
+ inputTokenEstimate,
1169
+ contextWindowForModel(attempt.adapter, attempt.model),
1170
+ );
1171
+ }
1172
+ if (recovery && !attempt.recoveryKinds.includes(recovery)) {
1173
+ attempt.recoveryKinds.push(recovery);
1174
+ }
1175
+ }
1176
+
1177
+ export function finishRequestAttempt(
1178
+ attempt: PersistedUsageAttempt,
1179
+ status: number,
1180
+ durationMs: number,
1181
+ usage?: OcxUsage,
1182
+ ): PersistedUsageAttempt {
1183
+ const finalized = finalizedUsage(
1184
+ attempt.adapter,
1185
+ usage ?? attempt.usage,
1186
+ attempt.inputTokenEstimate,
1187
+ contextWindowForModel(attempt.adapter, attempt.model),
1188
+ );
1189
+ attempt.status = status;
1190
+ attempt.durationMs = Math.max(0, durationMs);
1191
+ attempt.usageStatus = finalized.status;
1192
+ if (finalized.usage) attempt.usage = finalized.usage;
1193
+ else delete attempt.usage;
1194
+ if (finalized.totalTokens !== undefined) attempt.totalTokens = finalized.totalTokens;
1195
+ else delete attempt.totalTokens;
1196
+ const errorCode = requestLogErrorCode(status);
1197
+ if (errorCode) attempt.errorCode = errorCode;
1198
+ else delete attempt.errorCode;
1199
+ return attempt;
1200
+ }
1201
+
1202
+ export function aggregateAttemptUsage(
1203
+ attempts: readonly PersistedUsageAttempt[],
1204
+ ): FinalizedUsageResult {
1205
+ const status: UsageStatus = attempts.length > 0
1206
+ && attempts.every(attempt => attempt.usageStatus === "unsupported")
1207
+ ? "unsupported"
1208
+ : attempts.some(attempt => (
1209
+ attempt.usageStatus === "unreported" || attempt.usageStatus === "unsupported"
1210
+ ))
1211
+ ? "unreported"
1212
+ : attempts.some(attempt => attempt.usageStatus === "estimated")
1213
+ ? "estimated"
1214
+ : attempts.length > 0
1215
+ ? "reported"
1216
+ : "unreported";
1217
+
1218
+ const usages = attempts.flatMap(attempt => attempt.usage ? [attempt.usage] : []);
1219
+ if (usages.length === 0) return { status };
1220
+
1221
+ const sumOptional = (
1222
+ key: "cachedInputTokens" | "cacheReadInputTokens" | "cacheCreationInputTokens"
1223
+ | "reasoningOutputTokens",
1224
+ ): number | undefined => {
1225
+ const present = usages.flatMap(usage => (
1226
+ typeof usage[key] === "number" ? [usage[key] as number] : []
1227
+ ));
1228
+ return present.length > 0 ? present.reduce((sum, value) => sum + value, 0) : undefined;
1229
+ };
1230
+ const cachedInputTokens = sumOptional("cachedInputTokens");
1231
+ const cacheReadInputTokens = sumOptional("cacheReadInputTokens");
1232
+ const cacheCreationInputTokens = sumOptional("cacheCreationInputTokens");
1233
+ const reasoningOutputTokens = sumOptional("reasoningOutputTokens");
1234
+ const totalTokens = usages.reduce(
1235
+ (sum, usage) => sum + (usageTotalTokens(usage) ?? 0),
1236
+ 0,
1237
+ );
1238
+ const aggregate: OcxUsage = {
1239
+ inputTokens: usages.reduce((sum, usage) => sum + usage.inputTokens, 0),
1240
+ outputTokens: usages.reduce((sum, usage) => sum + usage.outputTokens, 0),
1241
+ totalTokens,
1242
+ ...(cachedInputTokens !== undefined ? { cachedInputTokens } : {}),
1243
+ ...(cacheReadInputTokens !== undefined ? { cacheReadInputTokens } : {}),
1244
+ ...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
1245
+ ...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}),
1246
+ ...(status === "estimated" ? { estimated: true } : {}),
1247
+ };
1248
+ return { usage: aggregate, status, totalTokens };
1249
+ }
1250
+
1251
+ export function getRequestLogEntries(): RequestLogEntry[] { return requestLog; }
1252
+
1253
+ /** Test-only process-state reset for isolated integration harnesses. */
1254
+ export function clearRequestLogsForTests(): void {
1255
+ requestLog.length = 0;
1256
+ requestLogBytes = 0;
1257
+ requestLogSeq = 0;
1258
+ requestLogsHydratedFromDisk = false;
1259
+ }