@wardby/cli 0.2.1 → 0.3.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 (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  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 +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +75 -4
  21. package/dist/coding/protocol.js +107 -7
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -1,7 +1,8 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
  import { getModelPricing, PRICING_VERSION } from "../llm/pricing.js";
3
3
  import { getAnthropicPricing } from "../llm/pricing-anthropic.js";
4
- import { AnthropicSseUsageTracker, actualCostUsd, estimateReservationUsd, fingerprintRequest, parseAnthropicAuthoritativeUsage, parseAuthoritativeUsage, pricingSnapshot, terminalUsageFromSseFrame, } from "./metering.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,20 @@ 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
23
  const APPROVED_ANTHROPIC_BETAS = new Set(CLAUDE_CODE_ANTHROPIC_BETAS);
21
24
  const WARDBY_COMMAND_TOOL = "mcp__wardby_tools__run_command";
22
25
  const STRUCTURED_OUTPUT_TOOL = "StructuredOutput";
23
26
  const MAX_COMMAND_BYTES = 16 * 1024;
24
27
  const MAX_TOOL_TEXT_BYTES = 256 * 1024;
28
+ /**
29
+ * The longest run_command timeout the Claude tool runner offers the model (its MAX_TIMEOUT_MS,
30
+ * src/claude-tool-runner/command.mjs; the proxy tests keep the two equal). A lower limit here let the
31
+ * command run, then refused the next request, which replays that tool call, as unsupported.
32
+ */
33
+ export const MAX_COMMAND_TIMEOUT_MS = 120_000;
25
34
  export class CodingProxyError extends Error {
26
35
  status;
27
36
  code;
@@ -31,9 +40,361 @@ export class CodingProxyError extends Error {
31
40
  this.code = code;
32
41
  }
33
42
  }
34
- function capabilityHash(capability) {
43
+ export function capabilityHash(capability) {
35
44
  return createHash("sha256").update(capability).digest("base64url");
36
45
  }
46
+ // OpenAI Responses allowlist. Everything a request may carry is listed here;
47
+ // anything else is refused before a credential is resolved. It mirrors what the
48
+ // pinned Codex CLI (src/coding-worker/package.json) actually sends, recorded in
49
+ // fixtures/codex-<version>-responses-requests.json. Only client-side tools
50
+ // (function, custom, and namespaces of them) are allowed: a hosted tool (web
51
+ // search, remote MCP, code interpreter, image generation, file search, ...), a
52
+ // remote image or file, a stored prompt, or a previous response would make
53
+ // OpenAI fetch or run something outside the worker's egress policy and outside
54
+ // the token-metered budget. Tool names vary by model and Codex version, so they
55
+ // are checked for shape, not against a fixed list: a client-side tool the model
56
+ // calls is executed by the worker itself, which the container boundary governs.
57
+ const OPENAI_REQUEST_KEYS = new Set([
58
+ "model",
59
+ "instructions",
60
+ "input",
61
+ "tools",
62
+ "tool_choice",
63
+ "parallel_tool_calls",
64
+ "reasoning",
65
+ "store",
66
+ "stream",
67
+ "include",
68
+ "prompt_cache_key",
69
+ "text",
70
+ "client_metadata",
71
+ "max_output_tokens",
72
+ "background",
73
+ "service_tier",
74
+ ]);
75
+ const OPENAI_REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
76
+ const OPENAI_REASONING_SUMMARIES = new Set(["auto", "concise", "detailed", "none"]);
77
+ const OPENAI_REASONING_CONTEXTS = new Set(["all_turns", "current_turn"]);
78
+ const OPENAI_INCLUDES = new Set(["reasoning.encrypted_content"]);
79
+ // Only unset (Codex never sends one) or an explicit "default". "auto" defers to
80
+ // the OpenAI project's own tier setting, which may be priority or scale; those
81
+ // and "flex" are billed at rates the pricing table does not track.
82
+ const OPENAI_SERVICE_TIERS = new Set(["default"]);
83
+ // The client_metadata keys the pinned Codex sends (recorded fixture). Several
84
+ // are x-codex-*/x-openai-* keys whose server-side meaning is undocumented, so a
85
+ // new key fails closed like every other unknown field.
86
+ const OPENAI_CLIENT_METADATA_KEYS = new Set([
87
+ "parent_turn_id",
88
+ "root_turn_id",
89
+ "session_id",
90
+ "thread_id",
91
+ "turn_id",
92
+ "x-codex-installation-id",
93
+ "x-codex-parent-thread-id",
94
+ "x-codex-turn-metadata",
95
+ "x-codex-window-id",
96
+ "x-openai-subagent",
97
+ ]);
98
+ const OPENAI_TOOL_CHOICES = new Set(["auto", "none", "required"]);
99
+ const OPENAI_VERBOSITIES = new Set(["low", "medium", "high"]);
100
+ const OPENAI_IMAGE_DETAILS = new Set(["auto", "low", "high", "original"]);
101
+ const OPENAI_MESSAGE_ROLES = new Set(["user", "developer", "system", "assistant"]);
102
+ const OPENAI_MESSAGE_PHASES = new Set(["commentary", "final_answer"]);
103
+ const OPENAI_GRAMMAR_SYNTAXES = new Set(["lark", "regex"]);
104
+ const OPENAI_INPUT_PARTS = new Set(["input_text", "input_image"]);
105
+ const OPENAI_ASSISTANT_PARTS = new Set(["output_text"]);
106
+ const OPENAI_AGENT_MESSAGE_PARTS = new Set(["input_text", "encrypted_content"]);
107
+ const OPENAI_TOOL_NAME = /^[A-Za-z0-9_-]{1,128}$/;
108
+ const OPENAI_CODE_SUFFIX = /^[A-Za-z0-9_.-]{1,64}$/;
109
+ // Only an inline base64 image: Codex's view_image and code-mode image() send
110
+ // local files this way. A URL (or a file_id) would make OpenAI fetch it.
111
+ const OPENAI_INLINE_IMAGE = /^data:image\/(?:png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;
112
+ const MAX_OPENAI_ITEMS = 10_000;
113
+ const MAX_OPENAI_TOOLS = 512;
114
+ const MAX_OPENAI_ID_LENGTH = 512;
115
+ const MAX_OPENAI_METADATA_VALUE_LENGTH = 16 * 1024;
116
+ function openAiCode(prefix, value) {
117
+ return `${prefix}:${typeof value === "string" && OPENAI_CODE_SUFFIX.test(value) ? value : "other"}`;
118
+ }
119
+ function openAiRecord(value, code = "invalid_openai_request") {
120
+ if (!value || typeof value !== "object" || Array.isArray(value))
121
+ throw new CodingProxyError(400, code);
122
+ return value;
123
+ }
124
+ function openAiOnlyKeys(value, keys, code = "invalid_openai_request") {
125
+ const allowed = new Set(keys);
126
+ if (Object.keys(value).some((key) => !allowed.has(key)))
127
+ throw new CodingProxyError(400, code);
128
+ }
129
+ function openAiString(value, maxLength, code = "invalid_openai_request") {
130
+ if (typeof value !== "string" || value.length > maxLength)
131
+ throw new CodingProxyError(400, code);
132
+ }
133
+ function openAiOptional(value, check) {
134
+ if (value !== undefined)
135
+ check(value);
136
+ }
137
+ function openAiId(value) {
138
+ if (typeof value !== "string" || value.length < 1 || value.length > MAX_OPENAI_ID_LENGTH) {
139
+ throw new CodingProxyError(400, "invalid_openai_request");
140
+ }
141
+ }
142
+ function openAiToolName(value, code = "invalid_openai_request") {
143
+ if (typeof value !== "string" || !OPENAI_TOOL_NAME.test(value))
144
+ throw new CodingProxyError(400, code);
145
+ }
146
+ function validateOpenAiFunctionTool(tool) {
147
+ openAiOnlyKeys(tool, ["type", "name", "description", "strict", "parameters"], "invalid_openai_tool");
148
+ openAiToolName(tool.name, "invalid_openai_tool");
149
+ openAiOptional(tool.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool"));
150
+ if (tool.strict !== undefined && typeof tool.strict !== "boolean") {
151
+ throw new CodingProxyError(400, "invalid_openai_tool");
152
+ }
153
+ openAiOptional(tool.parameters, (value) => openAiRecord(value, "invalid_openai_tool"));
154
+ }
155
+ function validateOpenAiCustomTool(tool) {
156
+ openAiOnlyKeys(tool, ["type", "name", "description", "format"], "invalid_openai_tool");
157
+ openAiToolName(tool.name, "invalid_openai_tool");
158
+ openAiOptional(tool.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool"));
159
+ if (tool.format === undefined)
160
+ return;
161
+ const format = openAiRecord(tool.format, "invalid_openai_tool");
162
+ if (format.type === "text")
163
+ return openAiOnlyKeys(format, ["type"], "invalid_openai_tool");
164
+ openAiOnlyKeys(format, ["type", "syntax", "definition"], "invalid_openai_tool");
165
+ if (format.type !== "grammar" || typeof format.syntax !== "string" || !OPENAI_GRAMMAR_SYNTAXES.has(format.syntax)) {
166
+ throw new CodingProxyError(400, "invalid_openai_tool");
167
+ }
168
+ openAiString(format.definition, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool");
169
+ }
170
+ function validateOpenAiTools(value, count, nested = false) {
171
+ if (!Array.isArray(value))
172
+ throw new CodingProxyError(400, "invalid_openai_tool");
173
+ for (const candidate of value) {
174
+ if (++count.value > MAX_OPENAI_TOOLS)
175
+ throw new CodingProxyError(400, "invalid_openai_tool");
176
+ const tool = openAiRecord(candidate, "invalid_openai_tool");
177
+ if (tool.type === "function") {
178
+ validateOpenAiFunctionTool(tool);
179
+ }
180
+ else if (tool.type === "custom") {
181
+ validateOpenAiCustomTool(tool);
182
+ }
183
+ else if (tool.type === "namespace" && !nested) {
184
+ openAiOnlyKeys(tool, ["type", "name", "description", "tools"], "invalid_openai_tool");
185
+ openAiToolName(tool.name, "invalid_openai_tool");
186
+ openAiOptional(tool.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "invalid_openai_tool"));
187
+ validateOpenAiTools(tool.tools, count, true);
188
+ }
189
+ else {
190
+ throw new CodingProxyError(400, openAiCode("openai_tool_not_allowed", tool.type));
191
+ }
192
+ }
193
+ }
194
+ function validateOpenAiContentPart(value, allowed) {
195
+ const part = openAiRecord(value);
196
+ if (typeof part.type !== "string" || !allowed.has(part.type)) {
197
+ throw new CodingProxyError(400, openAiCode("openai_input_not_allowed", part.type));
198
+ }
199
+ if (part.type === "input_image") {
200
+ if (part.file_id !== undefined || typeof part.image_url !== "string" || !OPENAI_INLINE_IMAGE.test(part.image_url)) {
201
+ throw new CodingProxyError(400, "openai_remote_input_not_allowed");
202
+ }
203
+ openAiOnlyKeys(part, ["type", "image_url", "detail"]);
204
+ if (part.detail !== undefined && (typeof part.detail !== "string" || !OPENAI_IMAGE_DETAILS.has(part.detail))) {
205
+ throw new CodingProxyError(400, "invalid_openai_request");
206
+ }
207
+ }
208
+ else if (part.type === "encrypted_content") {
209
+ openAiOnlyKeys(part, ["type", "encrypted_content"]);
210
+ openAiString(part.encrypted_content, PROXY_MAX_BODY_BYTES);
211
+ }
212
+ else {
213
+ openAiOnlyKeys(part, ["type", "text"]);
214
+ openAiString(part.text, PROXY_MAX_BODY_BYTES);
215
+ }
216
+ }
217
+ function validateOpenAiContent(value, allowed, allowString = true) {
218
+ if (typeof value === "string" && allowString)
219
+ return;
220
+ if (!Array.isArray(value) || value.length > MAX_OPENAI_ITEMS)
221
+ throw new CodingProxyError(400, "invalid_openai_request");
222
+ for (const part of value)
223
+ validateOpenAiContentPart(part, allowed);
224
+ }
225
+ function validateOpenAiTextParts(value, type) {
226
+ if (!Array.isArray(value) || value.length > MAX_OPENAI_ITEMS)
227
+ throw new CodingProxyError(400, "invalid_openai_request");
228
+ for (const candidate of value) {
229
+ const part = openAiRecord(candidate);
230
+ openAiOnlyKeys(part, ["type", "text"]);
231
+ if (part.type !== type)
232
+ throw new CodingProxyError(400, openAiCode("openai_input_not_allowed", part.type));
233
+ openAiString(part.text, PROXY_MAX_BODY_BYTES);
234
+ }
235
+ }
236
+ function validateOpenAiInputItem(value, toolCount) {
237
+ const item = openAiRecord(value);
238
+ switch (item.type) {
239
+ case undefined:
240
+ case "message": {
241
+ openAiOnlyKeys(item, ["type", "id", "role", "content", "phase"]);
242
+ openAiOptional(item.id, openAiId);
243
+ if (typeof item.role !== "string" || !OPENAI_MESSAGE_ROLES.has(item.role)) {
244
+ throw new CodingProxyError(400, "invalid_openai_request");
245
+ }
246
+ if (item.phase !== undefined && (typeof item.phase !== "string" || !OPENAI_MESSAGE_PHASES.has(item.phase))) {
247
+ throw new CodingProxyError(400, "invalid_openai_request");
248
+ }
249
+ validateOpenAiContent(item.content, item.role === "assistant" ? OPENAI_ASSISTANT_PARTS : OPENAI_INPUT_PARTS);
250
+ return;
251
+ }
252
+ case "reasoning":
253
+ openAiOnlyKeys(item, ["type", "id", "summary", "content", "encrypted_content"]);
254
+ openAiOptional(item.id, openAiId);
255
+ validateOpenAiTextParts(item.summary, "summary_text");
256
+ if (item.content !== undefined && item.content !== null)
257
+ validateOpenAiTextParts(item.content, "reasoning_text");
258
+ if (item.encrypted_content !== undefined && item.encrypted_content !== null) {
259
+ openAiString(item.encrypted_content, PROXY_MAX_BODY_BYTES);
260
+ }
261
+ return;
262
+ case "function_call":
263
+ openAiOnlyKeys(item, ["type", "id", "call_id", "name", "namespace", "arguments"]);
264
+ openAiOptional(item.id, openAiId);
265
+ openAiId(item.call_id);
266
+ openAiToolName(item.name);
267
+ openAiOptional(item.namespace, (value) => openAiToolName(value));
268
+ openAiString(item.arguments, PROXY_MAX_BODY_BYTES);
269
+ return;
270
+ case "custom_tool_call":
271
+ openAiOnlyKeys(item, ["type", "id", "status", "call_id", "name", "input"]);
272
+ openAiOptional(item.id, openAiId);
273
+ openAiOptional(item.status, (value) => openAiString(value, 64));
274
+ openAiId(item.call_id);
275
+ openAiToolName(item.name);
276
+ openAiString(item.input, PROXY_MAX_BODY_BYTES);
277
+ return;
278
+ case "function_call_output":
279
+ case "custom_tool_call_output":
280
+ openAiOnlyKeys(item, item.type === "function_call_output"
281
+ ? ["type", "id", "call_id", "output"]
282
+ : ["type", "id", "call_id", "name", "output"]);
283
+ openAiOptional(item.id, openAiId);
284
+ openAiId(item.call_id);
285
+ openAiOptional(item.name, (value) => openAiToolName(value));
286
+ validateOpenAiContent(item.output, OPENAI_INPUT_PARTS);
287
+ return;
288
+ case "agent_message":
289
+ openAiOnlyKeys(item, ["type", "id", "author", "recipient", "content"]);
290
+ openAiOptional(item.id, openAiId);
291
+ openAiOptional(item.author, (value) => openAiString(value, MAX_OPENAI_ID_LENGTH));
292
+ openAiOptional(item.recipient, (value) => openAiString(value, MAX_OPENAI_ID_LENGTH));
293
+ validateOpenAiContent(item.content, OPENAI_AGENT_MESSAGE_PARTS, false);
294
+ return;
295
+ case "additional_tools":
296
+ openAiOnlyKeys(item, ["type", "id", "role", "tools"]);
297
+ openAiOptional(item.id, openAiId);
298
+ if (item.role !== undefined && item.role !== "developer")
299
+ throw new CodingProxyError(400, "invalid_openai_request");
300
+ validateOpenAiTools(item.tools, toolCount);
301
+ return;
302
+ default:
303
+ throw new CodingProxyError(400, openAiCode("openai_input_not_allowed", item.type));
304
+ }
305
+ }
306
+ function validateOpenAiReasoning(value) {
307
+ const reasoning = openAiRecord(value, "openai_reasoning_not_allowed");
308
+ openAiOnlyKeys(reasoning, ["effort", "summary", "context"], "openai_reasoning_not_allowed");
309
+ for (const [key, allowed] of [
310
+ ["effort", OPENAI_REASONING_EFFORTS],
311
+ ["summary", OPENAI_REASONING_SUMMARIES],
312
+ ["context", OPENAI_REASONING_CONTEXTS],
313
+ ]) {
314
+ const setting = reasoning[key];
315
+ if (setting !== undefined && (typeof setting !== "string" || !allowed.has(setting))) {
316
+ throw new CodingProxyError(400, "openai_reasoning_not_allowed");
317
+ }
318
+ }
319
+ }
320
+ function validateOpenAiText(value) {
321
+ const text = openAiRecord(value, "openai_text_not_allowed");
322
+ openAiOnlyKeys(text, ["verbosity", "format"], "openai_text_not_allowed");
323
+ if (text.verbosity !== undefined && (typeof text.verbosity !== "string" || !OPENAI_VERBOSITIES.has(text.verbosity))) {
324
+ throw new CodingProxyError(400, "openai_text_not_allowed");
325
+ }
326
+ if (text.format === undefined)
327
+ return;
328
+ const format = openAiRecord(text.format, "openai_text_not_allowed");
329
+ if (format.type === "text")
330
+ return openAiOnlyKeys(format, ["type"], "openai_text_not_allowed");
331
+ openAiOnlyKeys(format, ["type", "name", "schema", "strict", "description"], "openai_text_not_allowed");
332
+ if (format.type !== "json_schema" || typeof format.name !== "string" || !OPENAI_TOOL_NAME.test(format.name)) {
333
+ throw new CodingProxyError(400, "openai_text_not_allowed");
334
+ }
335
+ openAiRecord(format.schema, "openai_text_not_allowed");
336
+ if (format.strict !== undefined && typeof format.strict !== "boolean") {
337
+ throw new CodingProxyError(400, "openai_text_not_allowed");
338
+ }
339
+ openAiOptional(format.description, (value) => openAiString(value, MAX_TOOL_TEXT_BYTES, "openai_text_not_allowed"));
340
+ }
341
+ function validateOpenAiClientMetadata(value) {
342
+ const metadata = openAiRecord(value);
343
+ for (const [key, entry] of Object.entries(metadata)) {
344
+ if (!OPENAI_CLIENT_METADATA_KEYS.has(key)) {
345
+ throw new CodingProxyError(400, openAiCode("openai_client_metadata_key_not_allowed", key));
346
+ }
347
+ openAiString(entry, MAX_OPENAI_METADATA_VALUE_LENGTH);
348
+ }
349
+ }
350
+ function validateOpenAiBody(body) {
351
+ for (const key of Object.keys(body)) {
352
+ if (!OPENAI_REQUEST_KEYS.has(key))
353
+ throw new CodingProxyError(400, openAiCode("openai_request_key_not_allowed", key));
354
+ }
355
+ if (body.background !== undefined && body.background !== false) {
356
+ throw new CodingProxyError(400, body.background === true ? "background_not_allowed" : "invalid_openai_request");
357
+ }
358
+ if (body.service_tier !== undefined) {
359
+ if (typeof body.service_tier !== "string" || !OPENAI_SERVICE_TIERS.has(body.service_tier)) {
360
+ throw new CodingProxyError(400, "service_tier_not_allowed");
361
+ }
362
+ }
363
+ if (body.store !== undefined && typeof body.store !== "boolean") {
364
+ throw new CodingProxyError(400, "invalid_openai_request");
365
+ }
366
+ openAiOptional(body.instructions, (value) => openAiString(value, PROXY_MAX_BODY_BYTES));
367
+ const toolCount = { value: 0 };
368
+ if (body.tools !== undefined)
369
+ validateOpenAiTools(body.tools, toolCount);
370
+ if (body.tool_choice !== undefined) {
371
+ if (typeof body.tool_choice !== "string" || !OPENAI_TOOL_CHOICES.has(body.tool_choice)) {
372
+ throw new CodingProxyError(400, "openai_tool_choice_not_allowed");
373
+ }
374
+ }
375
+ if (body.parallel_tool_calls !== undefined && typeof body.parallel_tool_calls !== "boolean") {
376
+ throw new CodingProxyError(400, "invalid_openai_request");
377
+ }
378
+ openAiOptional(body.reasoning, validateOpenAiReasoning);
379
+ if (body.include !== undefined) {
380
+ const include = body.include;
381
+ if (!Array.isArray(include) ||
382
+ new Set(include).size !== include.length ||
383
+ include.some((entry) => typeof entry !== "string" || !OPENAI_INCLUDES.has(entry))) {
384
+ throw new CodingProxyError(400, "openai_include_not_allowed");
385
+ }
386
+ }
387
+ openAiOptional(body.prompt_cache_key, (value) => openAiString(value, MAX_OPENAI_ID_LENGTH));
388
+ openAiOptional(body.text, validateOpenAiText);
389
+ openAiOptional(body.client_metadata, validateOpenAiClientMetadata);
390
+ if (typeof body.input === "string")
391
+ return;
392
+ if (!Array.isArray(body.input) || body.input.length > MAX_OPENAI_ITEMS) {
393
+ throw new CodingProxyError(400, "invalid_openai_request");
394
+ }
395
+ for (const item of body.input)
396
+ validateOpenAiInputItem(item, toolCount);
397
+ }
37
398
  function parseOpenAiRequest(rawBody) {
38
399
  if (Buffer.byteLength(rawBody) > PROXY_MAX_BODY_BYTES)
39
400
  throw new CodingProxyError(413, "payload_too_large");
@@ -59,8 +420,7 @@ function parseOpenAiRequest(rawBody) {
59
420
  }
60
421
  if (body.stream !== true && body.stream !== false)
61
422
  throw new CodingProxyError(400, "stream_required");
62
- if (body.background === true)
63
- throw new CodingProxyError(400, "background_not_allowed");
423
+ validateOpenAiBody(body);
64
424
  const normalized = { ...body, max_output_tokens: maxOutputTokens, store: false, background: false };
65
425
  const encoded = JSON.stringify(normalized);
66
426
  if (Buffer.byteLength(encoded) > PROXY_MAX_BODY_BYTES)
@@ -118,7 +478,7 @@ function validateCommandInput(value) {
118
478
  (typeof input.timeout_ms !== "number" ||
119
479
  !Number.isSafeInteger(input.timeout_ms) ||
120
480
  input.timeout_ms < 1_000 ||
121
- input.timeout_ms > 60_000)) {
481
+ input.timeout_ms > MAX_COMMAND_TIMEOUT_MS)) {
122
482
  throw new CodingProxyError(400, "unsupported_anthropic_feature");
123
483
  }
124
484
  }
@@ -227,6 +587,15 @@ function parseAnthropicBeta(value) {
227
587
  const normalized = [...unique].sort();
228
588
  return { header: normalized.join(","), values: new Set(normalized) };
229
589
  }
590
+ /**
591
+ * Models that take manual extended thinking (`{type: "enabled", budget_tokens}`) instead of adaptive
592
+ * thinking, and no effort level: Claude Haiku 4.5, which returns a 400 for adaptive thinking. The
593
+ * pinned Claude Agent SDK sends it `budget_tokens` = max_tokens - 1 and no `output_config`
594
+ * (fixtures/anthropic-sdk-request-haiku-4-5.json). Every other model stays adaptive-only.
595
+ */
596
+ const MANUAL_THINKING_MODELS = new Set(["claude-haiku-4-5"]);
597
+ /** Anthropic's minimum manual thinking budget. */
598
+ const MIN_THINKING_BUDGET_TOKENS = 1024;
230
599
  function requireAnthropicBeta(values, beta) {
231
600
  if (!values.has(beta))
232
601
  throw new CodingProxyError(400, "anthropic_beta_required");
@@ -305,13 +674,27 @@ function parseAnthropicRequest(rawBody, betaHeader) {
305
674
  if (typeof metadata.user_id !== "string")
306
675
  throw new CodingProxyError(400, "invalid_anthropic_request");
307
676
  }
677
+ const manualThinking = MANUAL_THINKING_MODELS.has(body.model);
308
678
  if (body.thinking !== undefined) {
309
679
  requireAnthropicBeta(beta.values, "interleaved-thinking-2025-05-14");
310
680
  requireAnthropicBeta(beta.values, "thinking-token-count-2026-05-13");
311
681
  const thinking = record(body.thinking);
312
- onlyKeys(thinking, ["type"]);
313
- if (thinking.type !== "adaptive")
314
- throw new CodingProxyError(400, "unsupported_anthropic_feature");
682
+ if (manualThinking) {
683
+ onlyKeys(thinking, ["type", "budget_tokens"]);
684
+ const budget = thinking.budget_tokens;
685
+ if (thinking.type !== "enabled" ||
686
+ typeof budget !== "number" ||
687
+ !Number.isSafeInteger(budget) ||
688
+ budget < MIN_THINKING_BUDGET_TOKENS ||
689
+ budget >= body.max_tokens) {
690
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
691
+ }
692
+ }
693
+ else {
694
+ onlyKeys(thinking, ["type"]);
695
+ if (thinking.type !== "adaptive")
696
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
697
+ }
315
698
  }
316
699
  if (body.context_management !== undefined) {
317
700
  requireAnthropicBeta(beta.values, "context-management-2025-06-27");
@@ -327,6 +710,9 @@ function parseAnthropicRequest(rawBody, betaHeader) {
327
710
  }
328
711
  }
329
712
  if (body.output_config !== undefined) {
713
+ // Effort doesn't exist on a manual-thinking model; the SDK never sends it there.
714
+ if (manualThinking)
715
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
330
716
  requireAnthropicBeta(beta.values, "effort-2025-11-24");
331
717
  const output = record(body.output_config);
332
718
  onlyKeys(output, ["effort"]);
@@ -352,9 +738,18 @@ function parseRequest(protocol, rawBody, anthropicBeta) {
352
738
  if (protocol !== "anthropic-messages" && anthropicBeta !== undefined) {
353
739
  throw new CodingProxyError(400, "invalid_anthropic_beta");
354
740
  }
355
- return protocol === "anthropic-messages"
356
- ? parseAnthropicRequest(rawBody, anthropicBeta)
357
- : parseOpenAiRequest(rawBody);
741
+ try {
742
+ return protocol === "anthropic-messages"
743
+ ? parseAnthropicRequest(rawBody, anthropicBeta)
744
+ : parseOpenAiRequest(rawBody);
745
+ }
746
+ catch (error) {
747
+ // JSON nested deeply enough (inside a free-form tool schema, say) overflows
748
+ // the stack when re-encoded. It is the client's request, not a proxy fault.
749
+ if (error instanceof RangeError)
750
+ throw new CodingProxyError(400, "request_nesting_too_deep");
751
+ throw error;
752
+ }
358
753
  }
359
754
  function safeRequestKey(value, fingerprint) {
360
755
  if (value === undefined)
@@ -437,6 +832,7 @@ export class CodingProxy {
437
832
  throw new Error("invalid_proxy_credential_reference");
438
833
  const capability = `rrp_${randomBytes(32).toString("base64url")}`;
439
834
  const id = randomUUID();
835
+ const registryTokenHash = capabilityHash(deriveRegistryToken(capability));
440
836
  await this.ledger.createSession({
441
837
  id,
442
838
  runId: input.runId,
@@ -446,6 +842,7 @@ export class CodingProxy {
446
842
  allowedModels: models,
447
843
  deadlineAt: input.deadlineAt,
448
844
  budgetUsd: input.budgetUsd,
845
+ registryTokenHash,
449
846
  });
450
847
  this.audit({ type: "session.created", runId: input.runId });
451
848
  return {
@@ -463,13 +860,33 @@ export class CodingProxy {
463
860
  for (const controller of this.activeRequests.get(sessionId) ?? [])
464
861
  controller.abort();
465
862
  }
863
+ /** Whether this session's run was refused a model request for budget. */
864
+ budgetExhausted(sessionId) {
865
+ return this.ledger.budgetExhausted(sessionId);
866
+ }
867
+ /** The code of the first upstream failure relayed for this session's run, if any. */
868
+ upstreamFailure(sessionId) {
869
+ return this.ledger.upstreamFailure(sessionId);
870
+ }
466
871
  async execute(input, sink) {
467
872
  const session = await this.authenticate(input.bearer);
468
873
  if (session.protocol !== input.protocol) {
469
874
  this.audit({ type: "request.rejected", runId: session.runId, reason: "protocol_mismatch" });
470
875
  throw new CodingProxyError(403, "protocol_mismatch");
471
876
  }
472
- const parsed = parseRequest(input.protocol, input.rawBody, input.anthropicBeta);
877
+ let parsed;
878
+ try {
879
+ parsed = parseRequest(input.protocol, input.rawBody, input.anthropicBeta);
880
+ }
881
+ catch (error) {
882
+ // The session is already authenticated and no credential has been
883
+ // resolved; record the refusal against the run so a smuggling attempt
884
+ // is attributable, then refuse.
885
+ if (error instanceof CodingProxyError) {
886
+ this.audit({ type: "request.rejected", runId: session.runId, status: error.status, reason: error.code });
887
+ }
888
+ throw error;
889
+ }
473
890
  if (!session.allowedModels.includes(parsed.model)) {
474
891
  this.audit({ type: "request.rejected", runId: session.runId, model: parsed.model, reason: "model_not_allowed" });
475
892
  throw new CodingProxyError(403, "model_not_allowed");
@@ -558,6 +975,8 @@ export class CodingProxy {
558
975
  const headers = {
559
976
  "content-type": "application/json",
560
977
  accept: parsed.stream ? "text/event-stream" : "application/json",
978
+ // The pinned fetch never decompresses, and the response is relayed as-is.
979
+ "accept-encoding": "identity",
561
980
  };
562
981
  if (session.protocol === "anthropic-messages") {
563
982
  headers["x-api-key"] = key;
@@ -583,7 +1002,10 @@ export class CodingProxy {
583
1002
  throw new CodingProxyError(502, controller.signal.aborted ? "upstream_aborted" : "upstream_transport_error");
584
1003
  }
585
1004
  if (!upstream.ok) {
1005
+ // Read while the deadline still bounds the body; the body itself is never relayed.
1006
+ const code = (await rejectedUpstreamErrorCode(upstream)) ?? `http_${upstream.status}`;
586
1007
  cleanupActive();
1008
+ await this.recordUpstreamFailure(session, code);
587
1009
  await this.ledger.release(request.id, upstream.status);
588
1010
  this.audit({
589
1011
  type: "request.released",
@@ -604,7 +1026,7 @@ export class CodingProxy {
604
1026
  await this.forwardJson(session, request, upstream, sink);
605
1027
  }
606
1028
  catch (error) {
607
- await this.markUncertain(session, request, upstream.status, "invalid_or_incomplete_upstream_response");
1029
+ await this.markUncertain(session, request, upstream.status, failureReason(error), responseHeadersOf(upstream));
608
1030
  sink.destroy();
609
1031
  throw error instanceof CodingProxyError ? error : new CodingProxyError(502, "invalid_upstream_response");
610
1032
  }
@@ -681,6 +1103,7 @@ export class CodingProxy {
681
1103
  let buffer = "";
682
1104
  let receivedBytes = 0;
683
1105
  let completed = false;
1106
+ let failedWithoutUsage = false;
684
1107
  const anthropicUsage = session.protocol === "anthropic-messages" ? new AnthropicSseUsageTracker() : undefined;
685
1108
  const body = upstream.body;
686
1109
  for await (const chunk of body) {
@@ -699,23 +1122,61 @@ export class CodingProxy {
699
1122
  await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
700
1123
  continue;
701
1124
  }
702
- if (!terminal.usage)
703
- throw new Error("terminal_usage_missing");
1125
+ if (!terminal.usage) {
1126
+ if (!terminal.failure)
1127
+ throw new Error("terminal_usage_missing");
1128
+ // The upstream failed the response and reported no usage. Pass its failure on so the
1129
+ // client sees the real error instead of a cut connection; the reservation stays held.
1130
+ await this.recordUpstreamFailure(session, terminal.failure);
1131
+ if (!completed) {
1132
+ await this.markUncertain(session, request, upstream.status, `upstream_failed:${terminal.failure}`, responseHeadersOf(upstream));
1133
+ }
1134
+ failedWithoutUsage = true;
1135
+ await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
1136
+ continue;
1137
+ }
704
1138
  await this.complete(session, request, terminal.usage, upstream.status);
705
1139
  completed = true;
706
1140
  await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
707
1141
  }
708
1142
  }
709
1143
  buffer = (buffer + decoder.decode()).replaceAll("\r\n", "\n");
710
- if (!completed)
1144
+ if (!completed && !failedWithoutUsage)
711
1145
  throw new Error("terminal_usage_missing");
712
1146
  if (buffer)
713
1147
  await safeWrite(sink, Buffer.from(buffer), connected);
714
1148
  if (connected.value)
715
1149
  sink.end();
716
1150
  }
717
- async markUncertain(session, request, status, reason) {
1151
+ /** Best effort: the executor reads it to name the failure; it must never break the relay. */
1152
+ async recordUpstreamFailure(session, code) {
1153
+ await this.ledger.recordUpstreamFailure(session.id, code).catch(() => undefined);
1154
+ }
1155
+ async markUncertain(session, request, status, reason, headers = {}) {
718
1156
  await this.ledger.markUncertain(request.id, status);
719
- this.audit({ type: "request.uncertain", runId: session.runId, requestId: request.id, status, reason });
1157
+ this.audit({ type: "request.uncertain", runId: session.runId, requestId: request.id, status, reason, ...headers });
720
1158
  }
721
1159
  }
1160
+ /** The safe error code in a rejected upstream's JSON body, or undefined when it names none or cannot be read. */
1161
+ async function rejectedUpstreamErrorCode(upstream) {
1162
+ try {
1163
+ const bytes = await readBoundedBody(upstream.body, MAX_UPSTREAM_ERROR_BYTES);
1164
+ const value = JSON.parse(Buffer.from(bytes).toString("utf8"));
1165
+ return value && typeof value === "object" ? safeUpstreamErrorCode(value) : undefined;
1166
+ }
1167
+ catch {
1168
+ return undefined;
1169
+ }
1170
+ }
1171
+ /** The fixed code a relay failed with, or a generic one: error messages are not recorded. */
1172
+ function failureReason(error) {
1173
+ const message = error instanceof Error ? error.message : "";
1174
+ return /^[a-z_]{1,64}$/.test(message) ? message : "stream_read_failed";
1175
+ }
1176
+ /** The upstream's content type and encoding, trimmed to a short printable value, for the audit record. */
1177
+ function responseHeadersOf(upstream) {
1178
+ const clean = (value) => value ? value.replace(/[^\x20-\x7e]/g, "").slice(0, 100) || undefined : undefined;
1179
+ const contentType = clean(upstream.headers.get("content-type"));
1180
+ const contentEncoding = clean(upstream.headers.get("content-encoding"));
1181
+ return { ...(contentType ? { contentType } : {}), ...(contentEncoding ? { contentEncoding } : {}) };
1182
+ }