@wardby/cli 0.2.1 → 0.4.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 (555) hide show
  1. package/.env.example +53 -4
  2. package/README.md +52 -13
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +14 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +284 -36
  12. package/dist/coding/base-commit.d.ts +6 -0
  13. package/dist/coding/base-commit.js +12 -0
  14. package/dist/coding/collect-exclude.d.ts +25 -0
  15. package/dist/coding/collect-exclude.js +76 -0
  16. package/dist/coding/profile.d.ts +67 -22
  17. package/dist/coding/profile.js +58 -25
  18. package/dist/coding/protected-path-wording.d.ts +24 -0
  19. package/dist/coding/protected-path-wording.js +55 -0
  20. package/dist/coding/protected-paths.d.ts +31 -0
  21. package/dist/coding/protected-paths.js +48 -0
  22. package/dist/coding/protocol.d.ts +91 -4
  23. package/dist/coding/protocol.js +123 -12
  24. package/dist/coding/provider.d.ts +6 -1
  25. package/dist/coding/provider.js +16 -9
  26. package/dist/coding/registry/adapters.d.ts +2 -0
  27. package/dist/coding/registry/adapters.js +9 -0
  28. package/dist/coding/registry/allowlist.d.ts +9 -0
  29. package/dist/coding/registry/allowlist.js +28 -0
  30. package/dist/coding/registry/json-scan.d.ts +55 -0
  31. package/dist/coding/registry/json-scan.js +191 -0
  32. package/dist/coding/registry/lockfiles.d.ts +6 -0
  33. package/dist/coding/registry/lockfiles.js +89 -0
  34. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  35. package/dist/coding/registry/npm-lockfile.js +73 -0
  36. package/dist/coding/registry/npm-plan.d.ts +29 -0
  37. package/dist/coding/registry/npm-plan.js +289 -0
  38. package/dist/coding/registry/npm.d.ts +7 -0
  39. package/dist/coding/registry/npm.js +252 -0
  40. package/dist/coding/registry/pypi.d.ts +4 -0
  41. package/dist/coding/registry/pypi.js +210 -0
  42. package/dist/coding/registry/report.d.ts +37 -0
  43. package/dist/coding/registry/report.js +31 -0
  44. package/dist/coding/registry/token.d.ts +4 -0
  45. package/dist/coding/registry/token.js +7 -0
  46. package/dist/coding/registry/types.d.ts +286 -0
  47. package/dist/coding/registry/types.js +19 -0
  48. package/dist/coding/registry/worker-config.d.ts +14 -0
  49. package/dist/coding/registry/worker-config.js +31 -0
  50. package/dist/coding/services/builtins.d.ts +22 -0
  51. package/dist/coding/services/builtins.js +85 -0
  52. package/dist/coding/services/catalog.d.ts +443 -0
  53. package/dist/coding/services/catalog.js +159 -0
  54. package/dist/coding/services/declaration.d.ts +11 -0
  55. package/dist/coding/services/declaration.js +114 -0
  56. package/dist/coding/services/note.d.ts +8 -0
  57. package/dist/coding/services/note.js +15 -0
  58. package/dist/coding/services/resolve.d.ts +25 -0
  59. package/dist/coding/services/resolve.js +50 -0
  60. package/dist/coding/services/wording.d.ts +25 -0
  61. package/dist/coding/services/wording.js +70 -0
  62. package/dist/coding-proxy/main.js +24 -4
  63. package/dist/coding-worker/artifact.d.ts +6 -0
  64. package/dist/coding-worker/debug-trace.d.ts +37 -0
  65. package/dist/coding-worker/debug-trace.js +117 -0
  66. package/dist/coding-worker/driver.d.ts +13 -1
  67. package/dist/coding-worker/driver.js +102 -13
  68. package/dist/coding-worker/errors.js +8 -0
  69. package/dist/coding-worker/main.js +10 -2
  70. package/dist/coding-worker/sdk.d.ts +2 -2
  71. package/dist/coding-worker/sdk.js +7 -2
  72. package/dist/coding-worker/types.d.ts +3 -0
  73. package/dist/config/providers.d.ts +66 -0
  74. package/dist/config/providers.js +137 -0
  75. package/dist/core/attribution.d.ts +101 -0
  76. package/dist/core/attribution.js +208 -0
  77. package/dist/core/budget-groups.d.ts +120 -10
  78. package/dist/core/budget-groups.js +133 -24
  79. package/dist/core/budget-wording.d.ts +22 -0
  80. package/dist/core/budget-wording.js +55 -0
  81. package/dist/core/coding-queue.d.ts +3 -0
  82. package/dist/core/coding-queue.js +6 -2
  83. package/dist/core/coding-service-status.d.ts +11 -0
  84. package/dist/core/coding-service-status.js +17 -0
  85. package/dist/core/cost-report.d.ts +88 -0
  86. package/dist/core/cost-report.js +248 -0
  87. package/dist/core/datastores.js +18 -2
  88. package/dist/core/db.d.ts +5 -1
  89. package/dist/core/db.js +8 -2
  90. package/dist/core/dispatch.d.ts +74 -7
  91. package/dist/core/dispatch.js +382 -47
  92. package/dist/core/engine-native.js +44 -14
  93. package/dist/core/glob.d.ts +10 -0
  94. package/dist/core/glob.js +33 -0
  95. package/dist/core/grants.d.ts +86 -0
  96. package/dist/core/grants.js +126 -0
  97. package/dist/core/host-events.d.ts +72 -0
  98. package/dist/core/host-events.js +597 -0
  99. package/dist/core/host-identity-links.d.ts +54 -0
  100. package/dist/core/host-identity-links.js +189 -0
  101. package/dist/core/host-status.d.ts +78 -0
  102. package/dist/core/host-status.js +228 -0
  103. package/dist/core/in-flight-runs.d.ts +8 -0
  104. package/dist/core/in-flight-runs.js +56 -0
  105. package/dist/core/issue-bridge.d.ts +60 -0
  106. package/dist/core/issue-bridge.js +189 -0
  107. package/dist/core/issue-dedupe.d.ts +70 -0
  108. package/dist/core/issue-dedupe.js +255 -0
  109. package/dist/core/issue-events.d.ts +42 -0
  110. package/dist/core/issue-events.js +155 -0
  111. package/dist/core/issue-status.d.ts +29 -0
  112. package/dist/core/issue-status.js +241 -0
  113. package/dist/core/issue-tracker-tools.d.ts +64 -0
  114. package/dist/core/issue-tracker-tools.js +850 -0
  115. package/dist/core/model-usage.d.ts +10 -0
  116. package/dist/core/model-usage.js +24 -0
  117. package/dist/core/provider-wording.d.ts +12 -0
  118. package/dist/core/provider-wording.js +44 -0
  119. package/dist/core/reconciler.d.ts +42 -2
  120. package/dist/core/reconciler.js +97 -2
  121. package/dist/core/repo-access.d.ts +99 -0
  122. package/dist/core/repo-access.js +136 -0
  123. package/dist/core/review-host-checks.d.ts +11 -0
  124. package/dist/core/review-host-checks.js +41 -0
  125. package/dist/core/review-host-tools.d.ts +47 -0
  126. package/dist/core/review-host-tools.js +354 -0
  127. package/dist/core/run-heartbeat.d.ts +27 -0
  128. package/dist/core/run-heartbeat.js +54 -0
  129. package/dist/core/run-pricing.d.ts +61 -0
  130. package/dist/core/run-pricing.js +56 -0
  131. package/dist/core/runner.d.ts +33 -8
  132. package/dist/core/runner.js +410 -62
  133. package/dist/core/scheduler.d.ts +4 -1
  134. package/dist/core/scheduler.js +3 -2
  135. package/dist/core/secrets.d.ts +11 -2
  136. package/dist/core/secrets.js +25 -6
  137. package/dist/core/self-defects.d.ts +80 -0
  138. package/dist/core/self-defects.js +180 -0
  139. package/dist/core/subagent-memory-tools.d.ts +1 -1
  140. package/dist/core/subagent-memory-tools.js +16 -3
  141. package/dist/core/tool-admin.d.ts +81 -0
  142. package/dist/core/tool-admin.js +129 -0
  143. package/dist/core/tool-names.d.ts +42 -0
  144. package/dist/core/tool-names.js +67 -0
  145. package/dist/core/untrusted-content.d.ts +32 -0
  146. package/dist/core/untrusted-content.js +72 -0
  147. package/dist/core/webhooks.d.ts +17 -2
  148. package/dist/core/webhooks.js +42 -3
  149. package/dist/generated/prisma/browser.d.ts +166 -0
  150. package/dist/generated/prisma/client.d.ts +166 -0
  151. package/dist/generated/prisma/commonInputTypes.d.ts +152 -52
  152. package/dist/generated/prisma/enums.d.ts +13 -0
  153. package/dist/generated/prisma/enums.js +12 -1
  154. package/dist/generated/prisma/internal/class.d.ts +242 -0
  155. package/dist/generated/prisma/internal/class.js +4 -4
  156. package/dist/generated/prisma/internal/prismaNamespace.d.ts +2571 -577
  157. package/dist/generated/prisma/internal/prismaNamespace.js +312 -6
  158. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +328 -0
  159. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +312 -6
  160. package/dist/generated/prisma/models/Agent.d.ts +682 -1
  161. package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
  162. package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
  163. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  164. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  165. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  166. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  167. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  168. package/dist/generated/prisma/models/CodingProxySession.d.ts +195 -2
  169. package/dist/generated/prisma/models/CodingRun.d.ts +1405 -95
  170. package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
  171. package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
  172. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  173. package/dist/generated/prisma/models/CodingService.js +1 -0
  174. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  175. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  176. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  177. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  178. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  179. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  180. package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
  181. package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
  182. package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
  183. package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
  184. package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
  185. package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
  186. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  187. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  188. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  189. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  190. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  191. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  192. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  193. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  194. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  195. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  196. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  197. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  198. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  199. package/dist/generated/prisma/models/Run.d.ts +1402 -82
  200. package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
  201. package/dist/generated/prisma/models/RunAttribution.js +1 -0
  202. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  203. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  204. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  205. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  206. package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
  207. package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
  208. package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
  209. package/dist/generated/prisma/models/RunModelUsage.js +1 -0
  210. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  211. package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
  212. package/dist/generated/prisma/models/WorkItem.js +1 -0
  213. package/dist/generated/prisma/models.d.ts +22 -0
  214. package/dist/help/build.d.ts +1 -0
  215. package/dist/help/build.js +9 -0
  216. package/dist/help/catalog.d.ts +24 -0
  217. package/dist/help/catalog.js +160 -0
  218. package/dist/help/cli.d.ts +2 -0
  219. package/dist/help/cli.js +65 -0
  220. package/dist/help/runtime.d.ts +3 -0
  221. package/dist/help/runtime.js +44 -0
  222. package/dist/help/search.d.ts +9 -0
  223. package/dist/help/search.js +104 -0
  224. package/dist/help-index.json +999 -0
  225. package/dist/import/cli-args.js +3 -2
  226. package/dist/import/create.d.ts +5 -0
  227. package/dist/import/create.js +67 -13
  228. package/dist/import/index.js +19 -9
  229. package/dist/import/neutral-schema.d.ts +18 -18
  230. package/dist/knowledge/check.d.ts +13 -0
  231. package/dist/knowledge/check.js +69 -0
  232. package/dist/knowledge/cli.d.ts +14 -0
  233. package/dist/knowledge/cli.js +67 -0
  234. package/dist/knowledge/concept.d.ts +54 -0
  235. package/dist/knowledge/concept.js +78 -0
  236. package/dist/knowledge/note.d.ts +11 -0
  237. package/dist/knowledge/note.js +39 -0
  238. package/dist/knowledge/relevance.d.ts +11 -0
  239. package/dist/knowledge/relevance.js +14 -0
  240. package/dist/knowledge/span-hash.d.ts +3 -0
  241. package/dist/knowledge/span-hash.js +16 -0
  242. package/dist/mcp/auth/access.d.ts +72 -0
  243. package/dist/mcp/auth/access.js +58 -0
  244. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  245. package/dist/mcp/auth/grants-cli.js +518 -0
  246. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  247. package/dist/mcp/auth/host-account-cli.js +47 -0
  248. package/dist/mcp/auth/ownership.d.ts +25 -51
  249. package/dist/mcp/auth/ownership.js +19 -14
  250. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  251. package/dist/mcp/auth/repo-authorization.js +51 -0
  252. package/dist/mcp/auth/resource-server.d.ts +33 -2
  253. package/dist/mcp/auth/resource-server.js +99 -3
  254. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  255. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  256. package/dist/mcp/auth/self-hosted/credentials.d.ts +23 -5
  257. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  258. package/dist/mcp/auth/self-hosted/session.d.ts +7 -6
  259. package/dist/mcp/context.d.ts +30 -1
  260. package/dist/mcp/errors.d.ts +25 -6
  261. package/dist/mcp/errors.js +98 -0
  262. package/dist/mcp/host-events/deliveries.d.ts +9 -0
  263. package/dist/mcp/host-events/deliveries.js +17 -0
  264. package/dist/mcp/host-events/github-ingress.d.ts +38 -0
  265. package/dist/mcp/host-events/github-ingress.js +83 -0
  266. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  267. package/dist/mcp/host-events/github-user-callback.js +50 -0
  268. package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
  269. package/dist/mcp/host-events/jira-ingress.js +92 -0
  270. package/dist/mcp/index.d.ts +10 -1
  271. package/dist/mcp/index.js +187 -19
  272. package/dist/mcp/server.js +32 -11
  273. package/dist/mcp/tools/agents.js +519 -50
  274. package/dist/mcp/tools/budget-groups.js +3 -3
  275. package/dist/mcp/tools/cost-report.d.ts +8 -0
  276. package/dist/mcp/tools/cost-report.js +60 -0
  277. package/dist/mcp/tools/datastore.js +22 -15
  278. package/dist/mcp/tools/grants.d.ts +2 -0
  279. package/dist/mcp/tools/grants.js +239 -0
  280. package/dist/mcp/tools/help.d.ts +5 -0
  281. package/dist/mcp/tools/help.js +67 -0
  282. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  283. package/dist/mcp/tools/host-accounts.js +114 -0
  284. package/dist/mcp/tools/issue-projects.d.ts +2 -0
  285. package/dist/mcp/tools/issue-projects.js +238 -0
  286. package/dist/mcp/tools/memory.d.ts +7 -1
  287. package/dist/mcp/tools/memory.js +5 -5
  288. package/dist/mcp/tools/model-catalog.d.ts +22 -0
  289. package/dist/mcp/tools/model-catalog.js +423 -0
  290. package/dist/mcp/tools/repositories.d.ts +2 -0
  291. package/dist/mcp/tools/repositories.js +182 -0
  292. package/dist/mcp/tools/runs.d.ts +6 -0
  293. package/dist/mcp/tools/runs.js +60 -6
  294. package/dist/mcp/tools/scheduling.js +3 -6
  295. package/dist/mcp/tools/secrets.js +18 -6
  296. package/dist/mcp/tools/services.d.ts +2 -0
  297. package/dist/mcp/tools/services.js +222 -0
  298. package/dist/mcp/tools/subagents.js +52 -16
  299. package/dist/mcp/tools/tools.d.ts +41 -0
  300. package/dist/mcp/tools/tools.js +201 -40
  301. package/dist/mcp/tools/trigger.js +65 -7
  302. package/dist/mcp/tools/webhooks.js +15 -4
  303. package/dist/mcp/transport/streamable-http.d.ts +15 -0
  304. package/dist/mcp/transport/streamable-http.js +55 -3
  305. package/dist/mcp/webhooks/ingress.d.ts +2 -1
  306. package/dist/mcp/webhooks/ingress.js +9 -2
  307. package/dist/providers/auth/delegating.d.ts +19 -0
  308. package/dist/providers/auth/delegating.js +71 -0
  309. package/dist/providers/auth/index.d.ts +2 -0
  310. package/dist/providers/auth/index.js +11 -0
  311. package/dist/providers/auth/self-hosted.d.ts +10 -2
  312. package/dist/providers/auth/self-hosted.js +55 -6
  313. package/dist/providers/auth/types.d.ts +6 -0
  314. package/dist/providers/coding-proxy/memory-ledger.d.ts +4 -1
  315. package/dist/providers/coding-proxy/memory-ledger.js +31 -3
  316. package/dist/providers/coding-proxy/metering.d.ts +6 -1
  317. package/dist/providers/coding-proxy/metering.js +41 -4
  318. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  319. package/dist/providers/coding-proxy/prisma-ledger.js +106 -8
  320. package/dist/providers/coding-proxy/proxy.d.ts +25 -2
  321. package/dist/providers/coding-proxy/proxy.js +561 -38
  322. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  323. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  324. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  325. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  326. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  327. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  328. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  329. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  330. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  331. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  332. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  333. package/dist/providers/coding-proxy/registry/store.js +70 -0
  334. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  335. package/dist/providers/coding-proxy/runtime.js +63 -0
  336. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  337. package/dist/providers/coding-proxy/server.d.ts +11 -0
  338. package/dist/providers/coding-proxy/server.js +163 -0
  339. package/dist/providers/coding-proxy/types.d.ts +33 -1
  340. package/dist/providers/coding-proxy/types.js +12 -1
  341. package/dist/providers/engine/types.d.ts +29 -0
  342. package/dist/providers/executor/build.d.ts +2 -2
  343. package/dist/providers/executor/composition.d.ts +3 -0
  344. package/dist/providers/executor/composition.js +28 -1
  345. package/dist/providers/executor/container.d.ts +134 -4
  346. package/dist/providers/executor/container.js +394 -37
  347. package/dist/providers/executor/dbos.d.ts +4 -3
  348. package/dist/providers/executor/dbos.js +7 -5
  349. package/dist/providers/executor/in-process.d.ts +2 -3
  350. package/dist/providers/executor/routing.d.ts +13 -0
  351. package/dist/providers/executor/routing.js +18 -0
  352. package/dist/providers/executor/types.d.ts +34 -0
  353. package/dist/providers/issue-tracker/adf.d.ts +31 -0
  354. package/dist/providers/issue-tracker/adf.js +181 -0
  355. package/dist/providers/issue-tracker/index.d.ts +5 -0
  356. package/dist/providers/issue-tracker/index.js +12 -0
  357. package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
  358. package/dist/providers/issue-tracker/jira-client.js +151 -0
  359. package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
  360. package/dist/providers/issue-tracker/jira-events.js +98 -0
  361. package/dist/providers/issue-tracker/jira.d.ts +116 -0
  362. package/dist/providers/issue-tracker/jira.js +502 -0
  363. package/dist/providers/issue-tracker/types.d.ts +269 -0
  364. package/dist/providers/issue-tracker/types.js +16 -0
  365. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  366. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  367. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  368. package/dist/providers/jobs/collect-prune.js +27 -0
  369. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  370. package/dist/providers/jobs/docker-isolation.js +213 -22
  371. package/dist/providers/jobs/docker-services.d.ts +35 -0
  372. package/dist/providers/jobs/docker-services.js +191 -0
  373. package/dist/providers/jobs/docker.d.ts +53 -2
  374. package/dist/providers/jobs/docker.js +307 -32
  375. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  376. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  377. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  378. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  379. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  380. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  381. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  382. package/dist/providers/jobs/kubernetes.d.ts +24 -2
  383. package/dist/providers/jobs/kubernetes.js +153 -22
  384. package/dist/providers/jobs/service-state.d.ts +22 -0
  385. package/dist/providers/jobs/service-state.js +17 -0
  386. package/dist/providers/jobs/types.d.ts +18 -0
  387. package/dist/providers/llm/anthropic.d.ts +3 -3
  388. package/dist/providers/llm/anthropic.js +3 -5
  389. package/dist/providers/llm/bedrock.d.ts +3 -3
  390. package/dist/providers/llm/bedrock.js +3 -8
  391. package/dist/providers/llm/catalog-lookup.d.ts +10 -0
  392. package/dist/providers/llm/catalog-lookup.js +15 -0
  393. package/dist/providers/llm/catalog-shipped.d.ts +18 -0
  394. package/dist/providers/llm/catalog-shipped.js +197 -0
  395. package/dist/providers/llm/catalog-store.d.ts +58 -0
  396. package/dist/providers/llm/catalog-store.js +138 -0
  397. package/dist/providers/llm/catalog-types.d.ts +66 -0
  398. package/dist/providers/llm/catalog-types.js +64 -0
  399. package/dist/providers/llm/catalog.d.ts +61 -0
  400. package/dist/providers/llm/catalog.js +147 -0
  401. package/dist/providers/llm/claude-messages.d.ts +5 -1
  402. package/dist/providers/llm/claude-messages.js +1 -0
  403. package/dist/providers/llm/claude-provider.d.ts +10 -12
  404. package/dist/providers/llm/claude-provider.js +15 -6
  405. package/dist/providers/llm/index.d.ts +9 -6
  406. package/dist/providers/llm/index.js +8 -5
  407. package/dist/providers/llm/openai.d.ts +14 -5
  408. package/dist/providers/llm/openai.js +24 -14
  409. package/dist/providers/llm/pricing-core.d.ts +5 -3
  410. package/dist/providers/llm/registration.js +8 -12
  411. package/dist/providers/llm/routing.d.ts +21 -11
  412. package/dist/providers/llm/routing.js +47 -13
  413. package/dist/providers/llm/types.d.ts +12 -0
  414. package/dist/providers/llm/types.js +8 -1
  415. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  416. package/dist/providers/review-host/diff-lines.js +59 -0
  417. package/dist/providers/review-host/github-events.d.ts +6 -0
  418. package/dist/providers/review-host/github-events.js +226 -0
  419. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  420. package/dist/providers/review-host/github-user-auth.js +128 -0
  421. package/dist/providers/review-host/github.d.ts +57 -0
  422. package/dist/providers/review-host/github.js +568 -0
  423. package/dist/providers/review-host/index.d.ts +12 -0
  424. package/dist/providers/review-host/index.js +30 -0
  425. package/dist/providers/review-host/review-format.d.ts +19 -0
  426. package/dist/providers/review-host/review-format.js +59 -0
  427. package/dist/providers/review-host/types.d.ts +309 -0
  428. package/dist/providers/review-host/types.js +24 -0
  429. package/dist/providers/vcs/git.d.ts +16 -6
  430. package/dist/providers/vcs/git.js +78 -34
  431. package/dist/providers/vcs/github.d.ts +94 -3
  432. package/dist/providers/vcs/github.js +223 -17
  433. package/dist/providers/vcs/types.d.ts +48 -4
  434. package/dist/quickstart/index.d.ts +2 -0
  435. package/dist/quickstart/index.js +36 -8
  436. package/dist/sandbox/fetch-policy.d.ts +20 -2
  437. package/dist/sandbox/fetch-policy.js +64 -3
  438. package/dist/sandbox/host-functions.d.ts +9 -1
  439. package/dist/sandbox/host-functions.js +12 -5
  440. package/dist/serve.js +8 -2
  441. package/dist/viewer/api-schema.d.ts +2757 -0
  442. package/dist/viewer/api-schema.js +165 -0
  443. package/dist/viewer/build-schemas.d.ts +2 -0
  444. package/dist/viewer/build-schemas.js +18 -0
  445. package/dist/viewer/event-bus.d.ts +38 -0
  446. package/dist/viewer/event-bus.js +232 -0
  447. package/dist/viewer/graph.d.ts +40 -0
  448. package/dist/viewer/graph.js +243 -0
  449. package/dist/viewer/http.d.ts +30 -0
  450. package/dist/viewer/http.js +133 -0
  451. package/dist/viewer/run-detail.d.ts +4 -0
  452. package/dist/viewer/run-detail.js +61 -0
  453. package/dist/wardby-bin.js +11 -0
  454. package/docs/README.md +40 -0
  455. package/docs/agent-recipes.md +383 -0
  456. package/docs/architecture-runtime.md +90 -0
  457. package/docs/assets/brand/wardby-icon-512.png +0 -0
  458. package/docs/assets/brand/wardby-icon.svg +16 -0
  459. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  460. package/docs/assets/brand/wardby-mascot.png +0 -0
  461. package/docs/assets/brand/wardby-mascot.svg +5 -0
  462. package/docs/assets/wardby-workflow.svg +106 -0
  463. package/docs/code-review-agents.md +508 -0
  464. package/docs/coding-agent-setup.md +175 -0
  465. package/docs/coding-packages.md +455 -0
  466. package/docs/coding-services.md +300 -0
  467. package/docs/coding-worker-byo-images.md +98 -0
  468. package/docs/coding-worker-isolation.md +1068 -0
  469. package/docs/getting-started-gke.md +632 -0
  470. package/docs/getting-started-identity-provider.md +319 -0
  471. package/docs/getting-started.md +147 -0
  472. package/docs/jira-agents.md +649 -0
  473. package/docs/knowledge.md +387 -0
  474. package/docs/models.md +221 -0
  475. package/docs/observability.md +53 -0
  476. package/docs/release-verification.md +66 -0
  477. package/docs/security-deployment.md +667 -0
  478. package/docs/viewer-api.md +142 -0
  479. package/help/admin-viewer.md +39 -0
  480. package/help/agent-recipes.md +173 -0
  481. package/help/architecture-agent.md +189 -0
  482. package/help/builder-agent.md +80 -0
  483. package/help/code-review-agents.md +37 -0
  484. package/help/coding-packages.md +31 -0
  485. package/help/coding-services.md +71 -0
  486. package/help/cost-attribution.md +67 -0
  487. package/help/creating-agents.md +90 -0
  488. package/help/deploy-gke.md +45 -0
  489. package/help/deployment-targets.md +39 -0
  490. package/help/errors/budget-group-exhausted.md +25 -0
  491. package/help/errors/docker-isolation-unsupported.md +26 -0
  492. package/help/errors/model-unavailable.md +63 -0
  493. package/help/errors/protected-path.md +45 -0
  494. package/help/errors/repo-access.md +27 -0
  495. package/help/errors/service-declaration-invalid.md +27 -0
  496. package/help/errors/service-declaration-unavailable.md +25 -0
  497. package/help/errors/service-launcher-unsupported.md +30 -0
  498. package/help/errors/service-not-allowed.md +27 -0
  499. package/help/errors/service-unknown.md +23 -0
  500. package/help/errors/service-unready.md +49 -0
  501. package/help/getting-started.md +32 -0
  502. package/help/github.md +48 -0
  503. package/help/identity-and-access.md +44 -0
  504. package/help/jira.md +135 -0
  505. package/help/knowledge.md +47 -0
  506. package/help/mcp.md +30 -0
  507. package/help/models.md +90 -0
  508. package/help/native-capabilities.md +30 -0
  509. package/help/observability.md +41 -0
  510. package/help/operating-agents.md +36 -0
  511. package/help/security.md +27 -0
  512. package/help/troubleshooting/budgets.md +32 -0
  513. package/help/troubleshooting/coding-workers.md +47 -0
  514. package/help/troubleshooting/repository-access.md +27 -0
  515. package/package.json +14 -3
  516. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  517. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  518. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  519. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  520. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  521. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  522. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  523. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  524. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  525. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  526. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  527. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  528. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  529. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  530. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  531. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  532. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  533. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  534. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  535. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  536. package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
  537. package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
  538. package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
  539. package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
  540. package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
  541. package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
  542. package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
  543. package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
  544. package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
  545. package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
  546. package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
  547. package/prisma/schema.prisma +663 -21
  548. package/dist/mcp/tools/models.d.ts +0 -8
  549. package/dist/mcp/tools/models.js +0 -15
  550. package/dist/providers/llm/pricing-anthropic.d.ts +0 -4
  551. package/dist/providers/llm/pricing-anthropic.js +0 -30
  552. package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -12
  553. package/dist/providers/llm/pricing-bedrock-claude.js +0 -37
  554. package/dist/providers/llm/pricing.d.ts +0 -30
  555. package/dist/providers/llm/pricing.js +0 -74
@@ -1,7 +1,8 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
- import { getModelPricing, PRICING_VERSION } from "../llm/pricing.js";
3
- import { getAnthropicPricing } from "../llm/pricing-anthropic.js";
4
- import { AnthropicSseUsageTracker, actualCostUsd, estimateReservationUsd, fingerprintRequest, parseAnthropicAuthoritativeUsage, parseAuthoritativeUsage, pricingSnapshot, terminalUsageFromSseFrame, } from "./metering.js";
2
+ import { shippedCatalog } from "../llm/catalog.js";
3
+ import { SHIPPED_CATALOG_VERSION } from "../llm/catalog-shipped.js";
4
+ import { deriveRegistryToken } from "../../coding/registry/token.js";
5
+ import { AnthropicSseUsageTracker, actualCostUsd, estimateReservationUsd, fingerprintRequest, parseAnthropicAuthoritativeUsage, parseAuthoritativeUsage, pricingSnapshot, safeUpstreamErrorCode, terminalUsageFromSseFrame, } from "./metering.js";
5
6
  import { createPinnedProxyFetch } from "./secure-fetch.js";
6
7
  export const PROXY_MAX_BODY_BYTES = 8 * 1024 * 1024;
7
8
  export const PROXY_MAX_OUTPUT_TOKENS = 1_000_000;
@@ -16,12 +17,26 @@ export const CLAUDE_CODE_ANTHROPIC_BETAS = [
16
17
  "thinking-token-count-2026-05-13",
17
18
  ];
18
19
  const MAX_UPSTREAM_JSON_BYTES = 16 * 1024 * 1024;
20
+ /** A rejected upstream's error body is read only this far, for its error code. */
21
+ const MAX_UPSTREAM_ERROR_BYTES = 64 * 1024;
19
22
  const REQUEST_KEY_PATTERN = /^[\x21-\x7e]{1,200}$/;
20
- const APPROVED_ANTHROPIC_BETAS = new Set(CLAUDE_CODE_ANTHROPIC_BETAS);
23
+ /**
24
+ * Betas Claude Code sends only for some models. per-turn-control is sent for Claude Opus 5.5; it is
25
+ * required only by an effort-only system message (per-turn effort), and every body field is still
26
+ * checked against the reviewed shape, so the header alone unlocks nothing.
27
+ */
28
+ export const OPTIONAL_ANTHROPIC_BETAS = ["per-turn-control-2026-07-01"];
29
+ const APPROVED_ANTHROPIC_BETAS = new Set([...CLAUDE_CODE_ANTHROPIC_BETAS, ...OPTIONAL_ANTHROPIC_BETAS]);
21
30
  const WARDBY_COMMAND_TOOL = "mcp__wardby_tools__run_command";
22
31
  const STRUCTURED_OUTPUT_TOOL = "StructuredOutput";
23
32
  const MAX_COMMAND_BYTES = 16 * 1024;
24
33
  const MAX_TOOL_TEXT_BYTES = 256 * 1024;
34
+ /**
35
+ * The longest run_command timeout the Claude tool runner offers the model (its MAX_TIMEOUT_MS,
36
+ * src/claude-tool-runner/command.mjs; the proxy tests keep the two equal). A lower limit here let the
37
+ * command run, then refused the next request, which replays that tool call, as unsupported.
38
+ */
39
+ export const MAX_COMMAND_TIMEOUT_MS = 120_000;
25
40
  export class CodingProxyError extends Error {
26
41
  status;
27
42
  code;
@@ -31,9 +46,361 @@ export class CodingProxyError extends Error {
31
46
  this.code = code;
32
47
  }
33
48
  }
34
- function capabilityHash(capability) {
49
+ export function capabilityHash(capability) {
35
50
  return createHash("sha256").update(capability).digest("base64url");
36
51
  }
52
+ // OpenAI Responses allowlist. Everything a request may carry is listed here;
53
+ // anything else is refused before a credential is resolved. It mirrors what the
54
+ // pinned Codex CLI (src/coding-worker/package.json) actually sends, recorded in
55
+ // fixtures/codex-<version>-responses-requests.json. Only client-side tools
56
+ // (function, custom, and namespaces of them) are allowed: a hosted tool (web
57
+ // search, remote MCP, code interpreter, image generation, file search, ...), a
58
+ // remote image or file, a stored prompt, or a previous response would make
59
+ // OpenAI fetch or run something outside the worker's egress policy and outside
60
+ // the token-metered budget. Tool names vary by model and Codex version, so they
61
+ // are checked for shape, not against a fixed list: a client-side tool the model
62
+ // calls is executed by the worker itself, which the container boundary governs.
63
+ const OPENAI_REQUEST_KEYS = new Set([
64
+ "model",
65
+ "instructions",
66
+ "input",
67
+ "tools",
68
+ "tool_choice",
69
+ "parallel_tool_calls",
70
+ "reasoning",
71
+ "store",
72
+ "stream",
73
+ "include",
74
+ "prompt_cache_key",
75
+ "text",
76
+ "client_metadata",
77
+ "max_output_tokens",
78
+ "background",
79
+ "service_tier",
80
+ ]);
81
+ const OPENAI_REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
82
+ const OPENAI_REASONING_SUMMARIES = new Set(["auto", "concise", "detailed", "none"]);
83
+ const OPENAI_REASONING_CONTEXTS = new Set(["all_turns", "current_turn"]);
84
+ const OPENAI_INCLUDES = new Set(["reasoning.encrypted_content"]);
85
+ // Only unset (Codex never sends one) or an explicit "default". "auto" defers to
86
+ // the OpenAI project's own tier setting, which may be priority or scale; those
87
+ // and "flex" are billed at rates the pricing table does not track.
88
+ const OPENAI_SERVICE_TIERS = new Set(["default"]);
89
+ // The client_metadata keys the pinned Codex sends (recorded fixture). Several
90
+ // are x-codex-*/x-openai-* keys whose server-side meaning is undocumented, so a
91
+ // new key fails closed like every other unknown field.
92
+ const OPENAI_CLIENT_METADATA_KEYS = new Set([
93
+ "parent_turn_id",
94
+ "root_turn_id",
95
+ "session_id",
96
+ "thread_id",
97
+ "turn_id",
98
+ "x-codex-installation-id",
99
+ "x-codex-parent-thread-id",
100
+ "x-codex-turn-metadata",
101
+ "x-codex-window-id",
102
+ "x-openai-subagent",
103
+ ]);
104
+ const OPENAI_TOOL_CHOICES = new Set(["auto", "none", "required"]);
105
+ const OPENAI_VERBOSITIES = new Set(["low", "medium", "high"]);
106
+ const OPENAI_IMAGE_DETAILS = new Set(["auto", "low", "high", "original"]);
107
+ const OPENAI_MESSAGE_ROLES = new Set(["user", "developer", "system", "assistant"]);
108
+ const OPENAI_MESSAGE_PHASES = new Set(["commentary", "final_answer"]);
109
+ const OPENAI_GRAMMAR_SYNTAXES = new Set(["lark", "regex"]);
110
+ const OPENAI_INPUT_PARTS = new Set(["input_text", "input_image"]);
111
+ const OPENAI_ASSISTANT_PARTS = new Set(["output_text"]);
112
+ const OPENAI_AGENT_MESSAGE_PARTS = new Set(["input_text", "encrypted_content"]);
113
+ const OPENAI_TOOL_NAME = /^[A-Za-z0-9_-]{1,128}$/;
114
+ const OPENAI_CODE_SUFFIX = /^[A-Za-z0-9_.-]{1,64}$/;
115
+ // Only an inline base64 image: Codex's view_image and code-mode image() send
116
+ // local files this way. A URL (or a file_id) would make OpenAI fetch it.
117
+ const OPENAI_INLINE_IMAGE = /^data:image\/(?:png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;
118
+ const MAX_OPENAI_ITEMS = 10_000;
119
+ const MAX_OPENAI_TOOLS = 512;
120
+ const MAX_OPENAI_ID_LENGTH = 512;
121
+ const MAX_OPENAI_METADATA_VALUE_LENGTH = 16 * 1024;
122
+ function openAiCode(prefix, value) {
123
+ return `${prefix}:${typeof value === "string" && OPENAI_CODE_SUFFIX.test(value) ? value : "other"}`;
124
+ }
125
+ function openAiRecord(value, code = "invalid_openai_request") {
126
+ if (!value || typeof value !== "object" || Array.isArray(value))
127
+ throw new CodingProxyError(400, code);
128
+ return value;
129
+ }
130
+ function openAiOnlyKeys(value, keys, code = "invalid_openai_request") {
131
+ const allowed = new Set(keys);
132
+ if (Object.keys(value).some((key) => !allowed.has(key)))
133
+ throw new CodingProxyError(400, code);
134
+ }
135
+ function openAiString(value, maxLength, code = "invalid_openai_request") {
136
+ if (typeof value !== "string" || value.length > maxLength)
137
+ throw new CodingProxyError(400, code);
138
+ }
139
+ function openAiOptional(value, check) {
140
+ if (value !== undefined)
141
+ check(value);
142
+ }
143
+ function openAiId(value) {
144
+ if (typeof value !== "string" || value.length < 1 || value.length > MAX_OPENAI_ID_LENGTH) {
145
+ throw new CodingProxyError(400, "invalid_openai_request");
146
+ }
147
+ }
148
+ function openAiToolName(value, code = "invalid_openai_request") {
149
+ if (typeof value !== "string" || !OPENAI_TOOL_NAME.test(value))
150
+ throw new CodingProxyError(400, code);
151
+ }
152
+ function validateOpenAiFunctionTool(tool) {
153
+ openAiOnlyKeys(tool, ["type", "name", "description", "strict", "parameters"], "invalid_openai_tool");
154
+ openAiToolName(tool.name, "invalid_openai_tool");
155
+ openAiOptional(tool.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool"));
156
+ if (tool.strict !== undefined && typeof tool.strict !== "boolean") {
157
+ throw new CodingProxyError(400, "invalid_openai_tool");
158
+ }
159
+ openAiOptional(tool.parameters, (value) => openAiRecord(value, "invalid_openai_tool"));
160
+ }
161
+ function validateOpenAiCustomTool(tool) {
162
+ openAiOnlyKeys(tool, ["type", "name", "description", "format"], "invalid_openai_tool");
163
+ openAiToolName(tool.name, "invalid_openai_tool");
164
+ openAiOptional(tool.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool"));
165
+ if (tool.format === undefined)
166
+ return;
167
+ const format = openAiRecord(tool.format, "invalid_openai_tool");
168
+ if (format.type === "text")
169
+ return openAiOnlyKeys(format, ["type"], "invalid_openai_tool");
170
+ openAiOnlyKeys(format, ["type", "syntax", "definition"], "invalid_openai_tool");
171
+ if (format.type !== "grammar" || typeof format.syntax !== "string" || !OPENAI_GRAMMAR_SYNTAXES.has(format.syntax)) {
172
+ throw new CodingProxyError(400, "invalid_openai_tool");
173
+ }
174
+ openAiString(format.definition, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool");
175
+ }
176
+ function validateOpenAiTools(value, count, nested = false) {
177
+ if (!Array.isArray(value))
178
+ throw new CodingProxyError(400, "invalid_openai_tool");
179
+ for (const candidate of value) {
180
+ if (++count.value > MAX_OPENAI_TOOLS)
181
+ throw new CodingProxyError(400, "invalid_openai_tool");
182
+ const tool = openAiRecord(candidate, "invalid_openai_tool");
183
+ if (tool.type === "function") {
184
+ validateOpenAiFunctionTool(tool);
185
+ }
186
+ else if (tool.type === "custom") {
187
+ validateOpenAiCustomTool(tool);
188
+ }
189
+ else if (tool.type === "namespace" && !nested) {
190
+ openAiOnlyKeys(tool, ["type", "name", "description", "tools"], "invalid_openai_tool");
191
+ openAiToolName(tool.name, "invalid_openai_tool");
192
+ openAiOptional(tool.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool"));
193
+ validateOpenAiTools(tool.tools, count, true);
194
+ }
195
+ else {
196
+ throw new CodingProxyError(400, openAiCode("openai_tool_not_allowed", tool.type));
197
+ }
198
+ }
199
+ }
200
+ function validateOpenAiContentPart(value, allowed) {
201
+ const part = openAiRecord(value);
202
+ if (typeof part.type !== "string" || !allowed.has(part.type)) {
203
+ throw new CodingProxyError(400, openAiCode("openai_input_not_allowed", part.type));
204
+ }
205
+ if (part.type === "input_image") {
206
+ if (part.file_id !== undefined || typeof part.image_url !== "string" || !OPENAI_INLINE_IMAGE.test(part.image_url)) {
207
+ throw new CodingProxyError(400, "openai_remote_input_not_allowed");
208
+ }
209
+ openAiOnlyKeys(part, ["type", "image_url", "detail"]);
210
+ if (part.detail !== undefined && (typeof part.detail !== "string" || !OPENAI_IMAGE_DETAILS.has(part.detail))) {
211
+ throw new CodingProxyError(400, "invalid_openai_request");
212
+ }
213
+ }
214
+ else if (part.type === "encrypted_content") {
215
+ openAiOnlyKeys(part, ["type", "encrypted_content"]);
216
+ openAiString(part.encrypted_content, PROXY_MAX_BODY_BYTES);
217
+ }
218
+ else {
219
+ openAiOnlyKeys(part, ["type", "text"]);
220
+ openAiString(part.text, PROXY_MAX_BODY_BYTES);
221
+ }
222
+ }
223
+ function validateOpenAiContent(value, allowed, allowString = true) {
224
+ if (typeof value === "string" && allowString)
225
+ return;
226
+ if (!Array.isArray(value) || value.length > MAX_OPENAI_ITEMS)
227
+ throw new CodingProxyError(400, "invalid_openai_request");
228
+ for (const part of value)
229
+ validateOpenAiContentPart(part, allowed);
230
+ }
231
+ function validateOpenAiTextParts(value, type) {
232
+ if (!Array.isArray(value) || value.length > MAX_OPENAI_ITEMS)
233
+ throw new CodingProxyError(400, "invalid_openai_request");
234
+ for (const candidate of value) {
235
+ const part = openAiRecord(candidate);
236
+ openAiOnlyKeys(part, ["type", "text"]);
237
+ if (part.type !== type)
238
+ throw new CodingProxyError(400, openAiCode("openai_input_not_allowed", part.type));
239
+ openAiString(part.text, PROXY_MAX_BODY_BYTES);
240
+ }
241
+ }
242
+ function validateOpenAiInputItem(value, toolCount) {
243
+ const item = openAiRecord(value);
244
+ switch (item.type) {
245
+ case undefined:
246
+ case "message": {
247
+ openAiOnlyKeys(item, ["type", "id", "role", "content", "phase"]);
248
+ openAiOptional(item.id, openAiId);
249
+ if (typeof item.role !== "string" || !OPENAI_MESSAGE_ROLES.has(item.role)) {
250
+ throw new CodingProxyError(400, "invalid_openai_request");
251
+ }
252
+ if (item.phase !== undefined && (typeof item.phase !== "string" || !OPENAI_MESSAGE_PHASES.has(item.phase))) {
253
+ throw new CodingProxyError(400, "invalid_openai_request");
254
+ }
255
+ validateOpenAiContent(item.content, item.role === "assistant" ? OPENAI_ASSISTANT_PARTS : OPENAI_INPUT_PARTS);
256
+ return;
257
+ }
258
+ case "reasoning":
259
+ openAiOnlyKeys(item, ["type", "id", "summary", "content", "encrypted_content"]);
260
+ openAiOptional(item.id, openAiId);
261
+ validateOpenAiTextParts(item.summary, "summary_text");
262
+ if (item.content !== undefined && item.content !== null)
263
+ validateOpenAiTextParts(item.content, "reasoning_text");
264
+ if (item.encrypted_content !== undefined && item.encrypted_content !== null) {
265
+ openAiString(item.encrypted_content, PROXY_MAX_BODY_BYTES);
266
+ }
267
+ return;
268
+ case "function_call":
269
+ openAiOnlyKeys(item, ["type", "id", "call_id", "name", "namespace", "arguments"]);
270
+ openAiOptional(item.id, openAiId);
271
+ openAiId(item.call_id);
272
+ openAiToolName(item.name);
273
+ openAiOptional(item.namespace, (value) => openAiToolName(value));
274
+ openAiString(item.arguments, PROXY_MAX_BODY_BYTES);
275
+ return;
276
+ case "custom_tool_call":
277
+ openAiOnlyKeys(item, ["type", "id", "status", "call_id", "name", "input"]);
278
+ openAiOptional(item.id, openAiId);
279
+ openAiOptional(item.status, (value) => openAiString(value, 64));
280
+ openAiId(item.call_id);
281
+ openAiToolName(item.name);
282
+ openAiString(item.input, PROXY_MAX_BODY_BYTES);
283
+ return;
284
+ case "function_call_output":
285
+ case "custom_tool_call_output":
286
+ openAiOnlyKeys(item, item.type === "function_call_output"
287
+ ? ["type", "id", "call_id", "output"]
288
+ : ["type", "id", "call_id", "name", "output"]);
289
+ openAiOptional(item.id, openAiId);
290
+ openAiId(item.call_id);
291
+ openAiOptional(item.name, (value) => openAiToolName(value));
292
+ validateOpenAiContent(item.output, OPENAI_INPUT_PARTS);
293
+ return;
294
+ case "agent_message":
295
+ openAiOnlyKeys(item, ["type", "id", "author", "recipient", "content"]);
296
+ openAiOptional(item.id, openAiId);
297
+ openAiOptional(item.author, (value) => openAiString(value, MAX_OPENAI_ID_LENGTH));
298
+ openAiOptional(item.recipient, (value) => openAiString(value, MAX_OPENAI_ID_LENGTH));
299
+ validateOpenAiContent(item.content, OPENAI_AGENT_MESSAGE_PARTS, false);
300
+ return;
301
+ case "additional_tools":
302
+ openAiOnlyKeys(item, ["type", "id", "role", "tools"]);
303
+ openAiOptional(item.id, openAiId);
304
+ if (item.role !== undefined && item.role !== "developer")
305
+ throw new CodingProxyError(400, "invalid_openai_request");
306
+ validateOpenAiTools(item.tools, toolCount);
307
+ return;
308
+ default:
309
+ throw new CodingProxyError(400, openAiCode("openai_input_not_allowed", item.type));
310
+ }
311
+ }
312
+ function validateOpenAiReasoning(value) {
313
+ const reasoning = openAiRecord(value, "openai_reasoning_not_allowed");
314
+ openAiOnlyKeys(reasoning, ["effort", "summary", "context"], "openai_reasoning_not_allowed");
315
+ for (const [key, allowed] of [
316
+ ["effort", OPENAI_REASONING_EFFORTS],
317
+ ["summary", OPENAI_REASONING_SUMMARIES],
318
+ ["context", OPENAI_REASONING_CONTEXTS],
319
+ ]) {
320
+ const setting = reasoning[key];
321
+ if (setting !== undefined && (typeof setting !== "string" || !allowed.has(setting))) {
322
+ throw new CodingProxyError(400, "openai_reasoning_not_allowed");
323
+ }
324
+ }
325
+ }
326
+ function validateOpenAiText(value) {
327
+ const text = openAiRecord(value, "openai_text_not_allowed");
328
+ openAiOnlyKeys(text, ["verbosity", "format"], "openai_text_not_allowed");
329
+ if (text.verbosity !== undefined && (typeof text.verbosity !== "string" || !OPENAI_VERBOSITIES.has(text.verbosity))) {
330
+ throw new CodingProxyError(400, "openai_text_not_allowed");
331
+ }
332
+ if (text.format === undefined)
333
+ return;
334
+ const format = openAiRecord(text.format, "openai_text_not_allowed");
335
+ if (format.type === "text")
336
+ return openAiOnlyKeys(format, ["type"], "openai_text_not_allowed");
337
+ openAiOnlyKeys(format, ["type", "name", "schema", "strict", "description"], "openai_text_not_allowed");
338
+ if (format.type !== "json_schema" || typeof format.name !== "string" || !OPENAI_TOOL_NAME.test(format.name)) {
339
+ throw new CodingProxyError(400, "openai_text_not_allowed");
340
+ }
341
+ openAiRecord(format.schema, "openai_text_not_allowed");
342
+ if (format.strict !== undefined && typeof format.strict !== "boolean") {
343
+ throw new CodingProxyError(400, "openai_text_not_allowed");
344
+ }
345
+ openAiOptional(format.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "openai_text_not_allowed"));
346
+ }
347
+ function validateOpenAiClientMetadata(value) {
348
+ const metadata = openAiRecord(value);
349
+ for (const [key, entry] of Object.entries(metadata)) {
350
+ if (!OPENAI_CLIENT_METADATA_KEYS.has(key)) {
351
+ throw new CodingProxyError(400, openAiCode("openai_client_metadata_key_not_allowed", key));
352
+ }
353
+ openAiString(entry, MAX_OPENAI_METADATA_VALUE_LENGTH);
354
+ }
355
+ }
356
+ function validateOpenAiBody(body) {
357
+ for (const key of Object.keys(body)) {
358
+ if (!OPENAI_REQUEST_KEYS.has(key))
359
+ throw new CodingProxyError(400, openAiCode("openai_request_key_not_allowed", key));
360
+ }
361
+ if (body.background !== undefined && body.background !== false) {
362
+ throw new CodingProxyError(400, body.background === true ? "background_not_allowed" : "invalid_openai_request");
363
+ }
364
+ if (body.service_tier !== undefined) {
365
+ if (typeof body.service_tier !== "string" || !OPENAI_SERVICE_TIERS.has(body.service_tier)) {
366
+ throw new CodingProxyError(400, "service_tier_not_allowed");
367
+ }
368
+ }
369
+ if (body.store !== undefined && typeof body.store !== "boolean") {
370
+ throw new CodingProxyError(400, "invalid_openai_request");
371
+ }
372
+ openAiOptional(body.instructions, (value) => openAiString(value, PROXY_MAX_BODY_BYTES));
373
+ const toolCount = { value: 0 };
374
+ if (body.tools !== undefined)
375
+ validateOpenAiTools(body.tools, toolCount);
376
+ if (body.tool_choice !== undefined) {
377
+ if (typeof body.tool_choice !== "string" || !OPENAI_TOOL_CHOICES.has(body.tool_choice)) {
378
+ throw new CodingProxyError(400, "openai_tool_choice_not_allowed");
379
+ }
380
+ }
381
+ if (body.parallel_tool_calls !== undefined && typeof body.parallel_tool_calls !== "boolean") {
382
+ throw new CodingProxyError(400, "invalid_openai_request");
383
+ }
384
+ openAiOptional(body.reasoning, validateOpenAiReasoning);
385
+ if (body.include !== undefined) {
386
+ const include = body.include;
387
+ if (!Array.isArray(include) ||
388
+ new Set(include).size !== include.length ||
389
+ include.some((entry) => typeof entry !== "string" || !OPENAI_INCLUDES.has(entry))) {
390
+ throw new CodingProxyError(400, "openai_include_not_allowed");
391
+ }
392
+ }
393
+ openAiOptional(body.prompt_cache_key, (value) => openAiString(value, MAX_OPENAI_ID_LENGTH));
394
+ openAiOptional(body.text, validateOpenAiText);
395
+ openAiOptional(body.client_metadata, validateOpenAiClientMetadata);
396
+ if (typeof body.input === "string")
397
+ return;
398
+ if (!Array.isArray(body.input) || body.input.length > MAX_OPENAI_ITEMS) {
399
+ throw new CodingProxyError(400, "invalid_openai_request");
400
+ }
401
+ for (const item of body.input)
402
+ validateOpenAiInputItem(item, toolCount);
403
+ }
37
404
  function parseOpenAiRequest(rawBody) {
38
405
  if (Buffer.byteLength(rawBody) > PROXY_MAX_BODY_BYTES)
39
406
  throw new CodingProxyError(413, "payload_too_large");
@@ -59,8 +426,7 @@ function parseOpenAiRequest(rawBody) {
59
426
  }
60
427
  if (body.stream !== true && body.stream !== false)
61
428
  throw new CodingProxyError(400, "stream_required");
62
- if (body.background === true)
63
- throw new CodingProxyError(400, "background_not_allowed");
429
+ validateOpenAiBody(body);
64
430
  const normalized = { ...body, max_output_tokens: maxOutputTokens, store: false, background: false };
65
431
  const encoded = JSON.stringify(normalized);
66
432
  if (Buffer.byteLength(encoded) > PROXY_MAX_BODY_BYTES)
@@ -118,7 +484,7 @@ function validateCommandInput(value) {
118
484
  (typeof input.timeout_ms !== "number" ||
119
485
  !Number.isSafeInteger(input.timeout_ms) ||
120
486
  input.timeout_ms < 1_000 ||
121
- input.timeout_ms > 60_000)) {
487
+ input.timeout_ms > MAX_COMMAND_TIMEOUT_MS)) {
122
488
  throw new CodingProxyError(400, "unsupported_anthropic_feature");
123
489
  }
124
490
  }
@@ -227,11 +593,36 @@ function parseAnthropicBeta(value) {
227
593
  const normalized = [...unique].sort();
228
594
  return { header: normalized.join(","), values: new Set(normalized) };
229
595
  }
596
+ /**
597
+ * Models whose catalog entry says `thinkingMode: "manual"` take manual extended thinking
598
+ * (`{type: "enabled", budget_tokens}`) instead of adaptive thinking, and no effort level (Claude
599
+ * Haiku 4.5 in the shipped catalog, which returns a 400 for adaptive thinking). The pinned Claude
600
+ * Agent SDK sends it `budget_tokens` = max_tokens - 1 and no `output_config`
601
+ * (fixtures/anthropic-sdk-request-haiku-4-5.json). Every other model stays adaptive-only.
602
+ */
603
+ /** Anthropic's minimum manual thinking budget. */
604
+ const MIN_THINKING_BUDGET_TOKENS = 1024;
230
605
  function requireAnthropicBeta(values, beta) {
231
606
  if (!values.has(beta))
232
607
  throw new CodingProxyError(400, "anthropic_beta_required");
233
608
  }
234
- function parseAnthropicRequest(rawBody, betaHeader) {
609
+ /**
610
+ * An `output_config` (top-level, or on an effort-only system message): only an effort level, and
611
+ * only one the run's catalog entry lists. Budget is still reserved from max_tokens per request, so
612
+ * effort only changes how much of it the model uses. Effort doesn't exist on a manual-thinking
613
+ * model; the SDK never sends it there.
614
+ */
615
+ function validateEffortConfig(value, { thinkingMode, efforts }, betas) {
616
+ if (thinkingMode === "manual")
617
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
618
+ requireAnthropicBeta(betas, "effort-2025-11-24");
619
+ const output = record(value);
620
+ onlyKeys(output, ["effort"]);
621
+ if (typeof output.effort !== "string" || !efforts.includes(output.effort)) {
622
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
623
+ }
624
+ }
625
+ function parseAnthropicRequest(rawBody, betaHeader, shape) {
235
626
  const beta = parseAnthropicBeta(betaHeader);
236
627
  if (Buffer.byteLength(rawBody) > PROXY_MAX_BODY_BYTES)
237
628
  throw new CodingProxyError(413, "payload_too_large");
@@ -272,12 +663,22 @@ function parseAnthropicRequest(rawBody, betaHeader) {
272
663
  validateTextBlocks(body.system);
273
664
  for (const value of body.messages) {
274
665
  const message = record(value);
275
- onlyKeys(message, ["role", "content"]);
666
+ onlyKeys(message, message.role === "system" ? ["role", "content", "output_config"] : ["role", "content"]);
276
667
  if (message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
277
668
  throw new CodingProxyError(400, "unsupported_anthropic_feature");
278
669
  }
279
670
  if (message.role === "system") {
280
671
  requireAnthropicBeta(beta.values, "mid-conversation-system-2026-04-07");
672
+ if (message.output_config !== undefined) {
673
+ // An effort-only system message (Claude Opus 5.5's per-turn effort): no content at all, and
674
+ // the same effort rule as the top-level output_config.
675
+ requireAnthropicBeta(beta.values, "per-turn-control-2026-07-01");
676
+ if (!Array.isArray(message.content) || message.content.length !== 0) {
677
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
678
+ }
679
+ validateEffortConfig(message.output_config, shape, beta.values);
680
+ continue;
681
+ }
281
682
  if (typeof message.content === "string") {
282
683
  if (Buffer.byteLength(message.content) > MAX_TOOL_TEXT_BYTES) {
283
684
  throw new CodingProxyError(400, "unsupported_anthropic_feature");
@@ -305,13 +706,27 @@ function parseAnthropicRequest(rawBody, betaHeader) {
305
706
  if (typeof metadata.user_id !== "string")
306
707
  throw new CodingProxyError(400, "invalid_anthropic_request");
307
708
  }
709
+ const manualThinking = shape.thinkingMode === "manual";
308
710
  if (body.thinking !== undefined) {
309
711
  requireAnthropicBeta(beta.values, "interleaved-thinking-2025-05-14");
310
712
  requireAnthropicBeta(beta.values, "thinking-token-count-2026-05-13");
311
713
  const thinking = record(body.thinking);
312
- onlyKeys(thinking, ["type"]);
313
- if (thinking.type !== "adaptive")
314
- throw new CodingProxyError(400, "unsupported_anthropic_feature");
714
+ if (manualThinking) {
715
+ onlyKeys(thinking, ["type", "budget_tokens"]);
716
+ const budget = thinking.budget_tokens;
717
+ if (thinking.type !== "enabled" ||
718
+ typeof budget !== "number" ||
719
+ !Number.isSafeInteger(budget) ||
720
+ budget < MIN_THINKING_BUDGET_TOKENS ||
721
+ budget >= body.max_tokens) {
722
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
723
+ }
724
+ }
725
+ else {
726
+ onlyKeys(thinking, ["type"]);
727
+ if (thinking.type !== "adaptive")
728
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
729
+ }
315
730
  }
316
731
  if (body.context_management !== undefined) {
317
732
  requireAnthropicBeta(beta.values, "context-management-2025-06-27");
@@ -326,13 +741,8 @@ function parseAnthropicRequest(rawBody, betaHeader) {
326
741
  throw new CodingProxyError(400, "unsupported_anthropic_feature");
327
742
  }
328
743
  }
329
- if (body.output_config !== undefined) {
330
- requireAnthropicBeta(beta.values, "effort-2025-11-24");
331
- const output = record(body.output_config);
332
- onlyKeys(output, ["effort"]);
333
- if (output.effort !== "high")
334
- throw new CodingProxyError(400, "unsupported_anthropic_feature");
335
- }
744
+ if (body.output_config !== undefined)
745
+ validateEffortConfig(body.output_config, shape, beta.values);
336
746
  const normalized = { ...body };
337
747
  delete normalized.metadata;
338
748
  const encoded = JSON.stringify(normalized);
@@ -348,13 +758,22 @@ function parseAnthropicRequest(rawBody, betaHeader) {
348
758
  anthropicBeta: beta.header,
349
759
  };
350
760
  }
351
- function parseRequest(protocol, rawBody, anthropicBeta) {
761
+ function parseRequest(protocol, rawBody, anthropicBeta, shape) {
352
762
  if (protocol !== "anthropic-messages" && anthropicBeta !== undefined) {
353
763
  throw new CodingProxyError(400, "invalid_anthropic_beta");
354
764
  }
355
- return protocol === "anthropic-messages"
356
- ? parseAnthropicRequest(rawBody, anthropicBeta)
357
- : parseOpenAiRequest(rawBody);
765
+ try {
766
+ return protocol === "anthropic-messages"
767
+ ? parseAnthropicRequest(rawBody, anthropicBeta, shape)
768
+ : parseOpenAiRequest(rawBody);
769
+ }
770
+ catch (error) {
771
+ // JSON nested deeply enough (inside a free-form tool schema, say) overflows
772
+ // the stack when re-encoded. It is the client's request, not a proxy fault.
773
+ if (error instanceof RangeError)
774
+ throw new CodingProxyError(400, "request_nesting_too_deep");
775
+ throw error;
776
+ }
358
777
  }
359
778
  function safeRequestKey(value, fingerprint) {
360
779
  if (value === undefined)
@@ -414,10 +833,18 @@ export class CodingProxy {
414
833
  });
415
834
  this.now = options.now ?? (() => new Date());
416
835
  this.audit = options.audit ?? (() => undefined);
836
+ // The fallback for sessions created before the catalog (no stored terms): the
837
+ // proxy has no catalog store, only the shipped catalog compiled into it.
417
838
  this.getPricing =
418
839
  options.pricing ??
419
- ((model, protocol) => (protocol === "anthropic-messages" ? getAnthropicPricing(model) : getModelPricing(model)));
420
- this.priceVersion = options.pricingVersion ?? PRICING_VERSION;
840
+ ((model, protocol) => {
841
+ const entry = shippedCatalog().require(model);
842
+ const expected = protocol === "anthropic-messages" ? "anthropic" : "openai";
843
+ if (entry.provider !== expected)
844
+ throw new Error("unknown_model");
845
+ return entry;
846
+ });
847
+ this.priceVersion = options.pricingVersion ?? `shipped:${SHIPPED_CATALOG_VERSION}`;
421
848
  }
422
849
  async createSession(input) {
423
850
  if (input.protocol !== "openai-responses" && input.protocol !== "anthropic-messages") {
@@ -427,8 +854,16 @@ export class CodingProxy {
427
854
  if (models.length < 1 || models.length > 8 || models.some((model) => !model || model.length > 100)) {
428
855
  throw new Error("invalid_proxy_model_allowlist");
429
856
  }
430
- for (const model of models)
431
- this.getPricing(model, input.protocol);
857
+ if (input.terms) {
858
+ const expected = input.protocol === "anthropic-messages" ? "anthropic" : "openai";
859
+ if (models.length !== 1 || input.terms.entry.modelId !== models[0] || input.terms.entry.provider !== expected) {
860
+ throw new Error("invalid_proxy_model_terms");
861
+ }
862
+ }
863
+ else {
864
+ for (const model of models)
865
+ this.getPricing(model, input.protocol);
866
+ }
432
867
  if (!Number.isFinite(input.budgetUsd) || input.budgetUsd <= 0)
433
868
  throw new Error("invalid_proxy_budget");
434
869
  if (input.deadlineAt.getTime() <= this.now().getTime())
@@ -437,6 +872,7 @@ export class CodingProxy {
437
872
  throw new Error("invalid_proxy_credential_reference");
438
873
  const capability = `rrp_${randomBytes(32).toString("base64url")}`;
439
874
  const id = randomUUID();
875
+ const registryTokenHash = capabilityHash(deriveRegistryToken(capability));
440
876
  await this.ledger.createSession({
441
877
  id,
442
878
  runId: input.runId,
@@ -446,6 +882,8 @@ export class CodingProxy {
446
882
  allowedModels: models,
447
883
  deadlineAt: input.deadlineAt,
448
884
  budgetUsd: input.budgetUsd,
885
+ registryTokenHash,
886
+ terms: input.terms,
449
887
  });
450
888
  this.audit({ type: "session.created", runId: input.runId });
451
889
  return {
@@ -463,27 +901,53 @@ export class CodingProxy {
463
901
  for (const controller of this.activeRequests.get(sessionId) ?? [])
464
902
  controller.abort();
465
903
  }
904
+ /** Whether this session's run was refused a model request for budget. */
905
+ budgetExhausted(sessionId) {
906
+ return this.ledger.budgetExhausted(sessionId);
907
+ }
908
+ /** The code of the first upstream failure relayed for this session's run, if any. */
909
+ upstreamFailure(sessionId) {
910
+ return this.ledger.upstreamFailure(sessionId);
911
+ }
466
912
  async execute(input, sink) {
467
913
  const session = await this.authenticate(input.bearer);
468
914
  if (session.protocol !== input.protocol) {
469
915
  this.audit({ type: "request.rejected", runId: session.runId, reason: "protocol_mismatch" });
470
916
  throw new CodingProxyError(403, "protocol_mismatch");
471
917
  }
472
- const parsed = parseRequest(input.protocol, input.rawBody, input.anthropicBeta);
918
+ // A session from before the model catalog has no stored entry: take the shape from the shipped one.
919
+ const entry = session.terms?.entry ?? shippedCatalog().get(session.allowedModels[0]);
920
+ const shape = {
921
+ thinkingMode: entry?.thinkingMode ?? "adaptive",
922
+ efforts: entry?.efforts ?? [],
923
+ };
924
+ let parsed;
925
+ try {
926
+ parsed = parseRequest(input.protocol, input.rawBody, input.anthropicBeta, shape);
927
+ }
928
+ catch (error) {
929
+ // The session is already authenticated and no credential has been
930
+ // resolved; record the refusal against the run so a smuggling attempt
931
+ // is attributable, then refuse.
932
+ if (error instanceof CodingProxyError) {
933
+ this.audit({ type: "request.rejected", runId: session.runId, status: error.status, reason: error.code });
934
+ }
935
+ throw error;
936
+ }
473
937
  if (!session.allowedModels.includes(parsed.model)) {
474
938
  this.audit({ type: "request.rejected", runId: session.runId, model: parsed.model, reason: "model_not_allowed" });
475
939
  throw new CodingProxyError(403, "model_not_allowed");
476
940
  }
477
- let pricing;
941
+ let terms;
478
942
  try {
479
- pricing = this.getPricing(parsed.model, session.protocol);
943
+ terms = this.termsFor(session, parsed.model);
480
944
  }
481
945
  catch {
482
946
  this.audit({ type: "request.rejected", runId: session.runId, model: parsed.model, reason: "unknown_model" });
483
947
  throw new CodingProxyError(400, "unknown_model");
484
948
  }
485
949
  const requestKey = safeRequestKey(input.requestKey, parsed.fingerprint);
486
- const snapshot = pricingSnapshot(this.priceVersion, pricing);
950
+ const snapshot = pricingSnapshot(terms.version, terms.pricing);
487
951
  const reservationUsd = estimateReservationUsd(Buffer.byteLength(parsed.encoded), parsed.maxOutputTokens, snapshot);
488
952
  const reservation = await this.ledger.reserve({
489
953
  id: randomUUID(),
@@ -558,6 +1022,8 @@ export class CodingProxy {
558
1022
  const headers = {
559
1023
  "content-type": "application/json",
560
1024
  accept: parsed.stream ? "text/event-stream" : "application/json",
1025
+ // The pinned fetch never decompresses, and the response is relayed as-is.
1026
+ "accept-encoding": "identity",
561
1027
  };
562
1028
  if (session.protocol === "anthropic-messages") {
563
1029
  headers["x-api-key"] = key;
@@ -583,7 +1049,10 @@ export class CodingProxy {
583
1049
  throw new CodingProxyError(502, controller.signal.aborted ? "upstream_aborted" : "upstream_transport_error");
584
1050
  }
585
1051
  if (!upstream.ok) {
1052
+ // Read while the deadline still bounds the body; the body itself is never relayed.
1053
+ const code = (await rejectedUpstreamErrorCode(upstream)) ?? `http_${upstream.status}`;
586
1054
  cleanupActive();
1055
+ await this.recordUpstreamFailure(session, code);
587
1056
  await this.ledger.release(request.id, upstream.status);
588
1057
  this.audit({
589
1058
  type: "request.released",
@@ -604,7 +1073,7 @@ export class CodingProxy {
604
1073
  await this.forwardJson(session, request, upstream, sink);
605
1074
  }
606
1075
  catch (error) {
607
- await this.markUncertain(session, request, upstream.status, "invalid_or_incomplete_upstream_response");
1076
+ await this.markUncertain(session, request, upstream.status, failureReason(error), responseHeadersOf(upstream));
608
1077
  sink.destroy();
609
1078
  throw error instanceof CodingProxyError ? error : new CodingProxyError(502, "invalid_upstream_response");
610
1079
  }
@@ -612,6 +1081,21 @@ export class CodingProxy {
612
1081
  cleanupActive();
613
1082
  }
614
1083
  }
1084
+ /** A session's model terms: its stored catalog entry, or the fallback for sessions from before the catalog. */
1085
+ termsFor(session, model) {
1086
+ if (session.terms) {
1087
+ return {
1088
+ pricing: session.terms.entry,
1089
+ version: session.terms.version,
1090
+ thinkingMode: session.terms.entry.thinkingMode,
1091
+ };
1092
+ }
1093
+ return {
1094
+ pricing: this.getPricing(model, session.protocol),
1095
+ version: this.priceVersion,
1096
+ thinkingMode: shippedCatalog().get(model)?.thinkingMode ?? "adaptive",
1097
+ };
1098
+ }
615
1099
  async authenticate(bearer) {
616
1100
  if (!bearer || bearer.length > 100)
617
1101
  throw new CodingProxyError(401, "invalid_capability");
@@ -681,6 +1165,7 @@ export class CodingProxy {
681
1165
  let buffer = "";
682
1166
  let receivedBytes = 0;
683
1167
  let completed = false;
1168
+ let failedWithoutUsage = false;
684
1169
  const anthropicUsage = session.protocol === "anthropic-messages" ? new AnthropicSseUsageTracker() : undefined;
685
1170
  const body = upstream.body;
686
1171
  for await (const chunk of body) {
@@ -699,23 +1184,61 @@ export class CodingProxy {
699
1184
  await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
700
1185
  continue;
701
1186
  }
702
- if (!terminal.usage)
703
- throw new Error("terminal_usage_missing");
1187
+ if (!terminal.usage) {
1188
+ if (!terminal.failure)
1189
+ throw new Error("terminal_usage_missing");
1190
+ // The upstream failed the response and reported no usage. Pass its failure on so the
1191
+ // client sees the real error instead of a cut connection; the reservation stays held.
1192
+ await this.recordUpstreamFailure(session, terminal.failure);
1193
+ if (!completed) {
1194
+ await this.markUncertain(session, request, upstream.status, `upstream_failed:${terminal.failure}`, responseHeadersOf(upstream));
1195
+ }
1196
+ failedWithoutUsage = true;
1197
+ await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
1198
+ continue;
1199
+ }
704
1200
  await this.complete(session, request, terminal.usage, upstream.status);
705
1201
  completed = true;
706
1202
  await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
707
1203
  }
708
1204
  }
709
1205
  buffer = (buffer + decoder.decode()).replaceAll("\r\n", "\n");
710
- if (!completed)
1206
+ if (!completed && !failedWithoutUsage)
711
1207
  throw new Error("terminal_usage_missing");
712
1208
  if (buffer)
713
1209
  await safeWrite(sink, Buffer.from(buffer), connected);
714
1210
  if (connected.value)
715
1211
  sink.end();
716
1212
  }
717
- async markUncertain(session, request, status, reason) {
1213
+ /** Best effort: the executor reads it to name the failure; it must never break the relay. */
1214
+ async recordUpstreamFailure(session, code) {
1215
+ await this.ledger.recordUpstreamFailure(session.id, code).catch(() => undefined);
1216
+ }
1217
+ async markUncertain(session, request, status, reason, headers = {}) {
718
1218
  await this.ledger.markUncertain(request.id, status);
719
- this.audit({ type: "request.uncertain", runId: session.runId, requestId: request.id, status, reason });
1219
+ this.audit({ type: "request.uncertain", runId: session.runId, requestId: request.id, status, reason, ...headers });
720
1220
  }
721
1221
  }
1222
+ /** The safe error code in a rejected upstream's JSON body, or undefined when it names none or cannot be read. */
1223
+ async function rejectedUpstreamErrorCode(upstream) {
1224
+ try {
1225
+ const bytes = await readBoundedBody(upstream.body, MAX_UPSTREAM_ERROR_BYTES);
1226
+ const value = JSON.parse(Buffer.from(bytes).toString("utf8"));
1227
+ return value && typeof value === "object" ? safeUpstreamErrorCode(value) : undefined;
1228
+ }
1229
+ catch {
1230
+ return undefined;
1231
+ }
1232
+ }
1233
+ /** The fixed code a relay failed with, or a generic one: error messages are not recorded. */
1234
+ function failureReason(error) {
1235
+ const message = error instanceof Error ? error.message : "";
1236
+ return /^[a-z_]{1,64}$/.test(message) ? message : "stream_read_failed";
1237
+ }
1238
+ /** The upstream's content type and encoding, trimmed to a short printable value, for the audit record. */
1239
+ function responseHeadersOf(upstream) {
1240
+ const clean = (value) => value ? value.replace(/[^\x20-\x7e]/g, "").slice(0, 100) || undefined : undefined;
1241
+ const contentType = clean(upstream.headers.get("content-type"));
1242
+ const contentEncoding = clean(upstream.headers.get("content-encoding"));
1243
+ return { ...(contentType ? { contentType } : {}), ...(contentEncoding ? { contentEncoding } : {}) };
1244
+ }