@wardby/cli 0.1.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 (412) hide show
  1. package/.env.example +131 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +2 -0
  4. package/README.md +282 -0
  5. package/bin/wardby.js +3 -0
  6. package/deploy/local/docker-compose.yml +19 -0
  7. package/dist/claude-coding-worker/driver.d.ts +70 -0
  8. package/dist/claude-coding-worker/driver.js +128 -0
  9. package/dist/claude-coding-worker/main.d.ts +2 -0
  10. package/dist/claude-coding-worker/main.js +41 -0
  11. package/dist/claude-coding-worker/sdk.d.ts +4 -0
  12. package/dist/claude-coding-worker/sdk.js +43 -0
  13. package/dist/claude-coding-worker/tool-relay.d.ts +1 -0
  14. package/dist/claude-coding-worker/tool-relay.js +9 -0
  15. package/dist/cli-help.d.ts +1 -0
  16. package/dist/cli-help.js +20 -0
  17. package/dist/cli.d.ts +18 -0
  18. package/dist/cli.js +629 -0
  19. package/dist/coding/observability.d.ts +59 -0
  20. package/dist/coding/observability.js +77 -0
  21. package/dist/coding/profile.d.ts +88 -0
  22. package/dist/coding/profile.js +137 -0
  23. package/dist/coding/protocol.d.ts +345 -0
  24. package/dist/coding/protocol.js +445 -0
  25. package/dist/coding/provider.d.ts +4 -0
  26. package/dist/coding/provider.js +18 -0
  27. package/dist/coding-proxy/main.d.ts +1 -0
  28. package/dist/coding-proxy/main.js +51 -0
  29. package/dist/coding-worker/artifact.d.ts +16 -0
  30. package/dist/coding-worker/artifact.js +37 -0
  31. package/dist/coding-worker/driver.d.ts +46 -0
  32. package/dist/coding-worker/driver.js +158 -0
  33. package/dist/coding-worker/errors.d.ts +1 -0
  34. package/dist/coding-worker/errors.js +23 -0
  35. package/dist/coding-worker/isolation-probe.d.ts +1 -0
  36. package/dist/coding-worker/isolation-probe.js +146 -0
  37. package/dist/coding-worker/keeper.d.ts +4 -0
  38. package/dist/coding-worker/keeper.js +29 -0
  39. package/dist/coding-worker/main.d.ts +2 -0
  40. package/dist/coding-worker/main.js +43 -0
  41. package/dist/coding-worker/sdk.d.ts +26 -0
  42. package/dist/coding-worker/sdk.js +28 -0
  43. package/dist/coding-worker/types.d.ts +64 -0
  44. package/dist/coding-worker/types.js +1 -0
  45. package/dist/config/providers.d.ts +158 -0
  46. package/dist/config/providers.js +162 -0
  47. package/dist/core/budget-groups.d.ts +72 -0
  48. package/dist/core/budget-groups.js +149 -0
  49. package/dist/core/budget.d.ts +63 -0
  50. package/dist/core/budget.js +69 -0
  51. package/dist/core/coding-queue.d.ts +32 -0
  52. package/dist/core/coding-queue.js +54 -0
  53. package/dist/core/cron.d.ts +31 -0
  54. package/dist/core/cron.js +43 -0
  55. package/dist/core/datastores.d.ts +41 -0
  56. package/dist/core/datastores.js +105 -0
  57. package/dist/core/db.d.ts +2 -0
  58. package/dist/core/db.js +2 -0
  59. package/dist/core/dispatch.d.ts +81 -0
  60. package/dist/core/dispatch.js +184 -0
  61. package/dist/core/engine-native.d.ts +29 -0
  62. package/dist/core/engine-native.js +259 -0
  63. package/dist/core/http-runtime.d.ts +7 -0
  64. package/dist/core/http-runtime.js +101 -0
  65. package/dist/core/lease.d.ts +13 -0
  66. package/dist/core/lease.js +22 -0
  67. package/dist/core/logger.d.ts +13 -0
  68. package/dist/core/logger.js +45 -0
  69. package/dist/core/memory-tools.d.ts +11 -0
  70. package/dist/core/memory-tools.js +122 -0
  71. package/dist/core/reconciler.d.ts +45 -0
  72. package/dist/core/reconciler.js +152 -0
  73. package/dist/core/runner.d.ts +87 -0
  74. package/dist/core/runner.js +529 -0
  75. package/dist/core/scheduler.d.ts +47 -0
  76. package/dist/core/scheduler.js +121 -0
  77. package/dist/core/secrets.d.ts +46 -0
  78. package/dist/core/secrets.js +100 -0
  79. package/dist/core/subagent-memory-tools.d.ts +18 -0
  80. package/dist/core/subagent-memory-tools.js +121 -0
  81. package/dist/core/timing.d.ts +7 -0
  82. package/dist/core/timing.js +7 -0
  83. package/dist/core/webhooks.d.ts +23 -0
  84. package/dist/core/webhooks.js +113 -0
  85. package/dist/env.d.ts +11 -0
  86. package/dist/env.js +13 -0
  87. package/dist/import/budgets.d.ts +16 -0
  88. package/dist/import/budgets.js +21 -0
  89. package/dist/import/bundle.d.ts +16 -0
  90. package/dist/import/bundle.js +61 -0
  91. package/dist/import/cli-args.d.ts +2 -0
  92. package/dist/import/cli-args.js +44 -0
  93. package/dist/import/create.d.ts +34 -0
  94. package/dist/import/create.js +363 -0
  95. package/dist/import/index.d.ts +25 -0
  96. package/dist/import/index.js +130 -0
  97. package/dist/import/model-gate.d.ts +1 -0
  98. package/dist/import/model-gate.js +3 -0
  99. package/dist/import/neutral-schema.d.ts +369 -0
  100. package/dist/import/neutral-schema.js +109 -0
  101. package/dist/import/preflight.d.ts +41 -0
  102. package/dist/import/preflight.js +71 -0
  103. package/dist/import/report.d.ts +5 -0
  104. package/dist/import/report.js +98 -0
  105. package/dist/import/tool-scan.d.ts +8 -0
  106. package/dist/import/tool-scan.js +20 -0
  107. package/dist/mcp/auth/ownership.d.ts +113 -0
  108. package/dist/mcp/auth/ownership.js +87 -0
  109. package/dist/mcp/auth/principal.d.ts +9 -0
  110. package/dist/mcp/auth/principal.js +9 -0
  111. package/dist/mcp/auth/resource-server.d.ts +47 -0
  112. package/dist/mcp/auth/resource-server.js +82 -0
  113. package/dist/mcp/auth/self-hosted/browser.d.ts +3 -0
  114. package/dist/mcp/auth/self-hosted/browser.js +273 -0
  115. package/dist/mcp/auth/self-hosted/cli.d.ts +2 -0
  116. package/dist/mcp/auth/self-hosted/cli.js +39 -0
  117. package/dist/mcp/auth/self-hosted/credentials.d.ts +58 -0
  118. package/dist/mcp/auth/self-hosted/credentials.js +106 -0
  119. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +13 -0
  120. package/dist/mcp/auth/self-hosted/rate-limit.js +20 -0
  121. package/dist/mcp/auth/self-hosted/session.d.ts +36 -0
  122. package/dist/mcp/auth/self-hosted/session.js +110 -0
  123. package/dist/mcp/capabilities.d.ts +11 -0
  124. package/dist/mcp/capabilities.js +10 -0
  125. package/dist/mcp/context.d.ts +46 -0
  126. package/dist/mcp/context.js +1 -0
  127. package/dist/mcp/errors.d.ts +13 -0
  128. package/dist/mcp/errors.js +22 -0
  129. package/dist/mcp/index.d.ts +32 -0
  130. package/dist/mcp/index.js +187 -0
  131. package/dist/mcp/jsonrpc.d.ts +8 -0
  132. package/dist/mcp/jsonrpc.js +1 -0
  133. package/dist/mcp/server.d.ts +107 -0
  134. package/dist/mcp/server.js +203 -0
  135. package/dist/mcp/tasks/manager.d.ts +63 -0
  136. package/dist/mcp/tasks/manager.js +103 -0
  137. package/dist/mcp/tools/agents.d.ts +2 -0
  138. package/dist/mcp/tools/agents.js +375 -0
  139. package/dist/mcp/tools/budget-groups.d.ts +2 -0
  140. package/dist/mcp/tools/budget-groups.js +136 -0
  141. package/dist/mcp/tools/datastore.d.ts +2 -0
  142. package/dist/mcp/tools/datastore.js +217 -0
  143. package/dist/mcp/tools/memory.d.ts +3 -0
  144. package/dist/mcp/tools/memory.js +60 -0
  145. package/dist/mcp/tools/models.d.ts +8 -0
  146. package/dist/mcp/tools/models.js +15 -0
  147. package/dist/mcp/tools/runs.d.ts +2 -0
  148. package/dist/mcp/tools/runs.js +61 -0
  149. package/dist/mcp/tools/scheduling.d.ts +2 -0
  150. package/dist/mcp/tools/scheduling.js +48 -0
  151. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -0
  152. package/dist/mcp/tools/secret-elicitation-form.js +93 -0
  153. package/dist/mcp/tools/secret-elicitation-server.d.ts +5 -0
  154. package/dist/mcp/tools/secret-elicitation-server.js +57 -0
  155. package/dist/mcp/tools/secret-elicitation.d.ts +38 -0
  156. package/dist/mcp/tools/secret-elicitation.js +52 -0
  157. package/dist/mcp/tools/secrets.d.ts +13 -0
  158. package/dist/mcp/tools/secrets.js +124 -0
  159. package/dist/mcp/tools/subagents.d.ts +2 -0
  160. package/dist/mcp/tools/subagents.js +133 -0
  161. package/dist/mcp/tools/text-result.d.ts +6 -0
  162. package/dist/mcp/tools/text-result.js +3 -0
  163. package/dist/mcp/tools/tools.d.ts +2 -0
  164. package/dist/mcp/tools/tools.js +211 -0
  165. package/dist/mcp/tools/trigger.d.ts +2 -0
  166. package/dist/mcp/tools/trigger.js +120 -0
  167. package/dist/mcp/tools/webhooks.d.ts +2 -0
  168. package/dist/mcp/tools/webhooks.js +34 -0
  169. package/dist/mcp/transport/http-limits.d.ts +17 -0
  170. package/dist/mcp/transport/http-limits.js +92 -0
  171. package/dist/mcp/transport/stdio.d.ts +23 -0
  172. package/dist/mcp/transport/stdio.js +6 -0
  173. package/dist/mcp/transport/streamable-http.d.ts +34 -0
  174. package/dist/mcp/transport/streamable-http.js +158 -0
  175. package/dist/mcp/unattended-schedules.d.ts +15 -0
  176. package/dist/mcp/unattended-schedules.js +21 -0
  177. package/dist/mcp/webhooks/ingress.d.ts +17 -0
  178. package/dist/mcp/webhooks/ingress.js +33 -0
  179. package/dist/observability/config.d.ts +6 -0
  180. package/dist/observability/config.js +20 -0
  181. package/dist/observability/metrics-server.d.ts +11 -0
  182. package/dist/observability/metrics-server.js +47 -0
  183. package/dist/observability/metrics.d.ts +36 -0
  184. package/dist/observability/metrics.js +158 -0
  185. package/dist/providers/auth/authorization-server.d.ts +41 -0
  186. package/dist/providers/auth/authorization-server.js +1 -0
  187. package/dist/providers/auth/delegating.d.ts +44 -0
  188. package/dist/providers/auth/delegating.js +105 -0
  189. package/dist/providers/auth/index.d.ts +11 -0
  190. package/dist/providers/auth/index.js +20 -0
  191. package/dist/providers/auth/release-gate.d.ts +1 -0
  192. package/dist/providers/auth/release-gate.js +4 -0
  193. package/dist/providers/auth/self-hosted.d.ts +78 -0
  194. package/dist/providers/auth/self-hosted.js +377 -0
  195. package/dist/providers/auth/subject.d.ts +1 -0
  196. package/dist/providers/auth/subject.js +6 -0
  197. package/dist/providers/auth/types.d.ts +63 -0
  198. package/dist/providers/auth/types.js +22 -0
  199. package/dist/providers/coding-proxy/deny-port.d.ts +5 -0
  200. package/dist/providers/coding-proxy/deny-port.js +45 -0
  201. package/dist/providers/coding-proxy/environment-credentials.d.ts +7 -0
  202. package/dist/providers/coding-proxy/environment-credentials.js +16 -0
  203. package/dist/providers/coding-proxy/index.d.ts +7 -0
  204. package/dist/providers/coding-proxy/index.js +7 -0
  205. package/dist/providers/coding-proxy/memory-ledger.d.ts +17 -0
  206. package/dist/providers/coding-proxy/memory-ledger.js +103 -0
  207. package/dist/providers/coding-proxy/metering.d.ts +20 -0
  208. package/dist/providers/coding-proxy/metering.js +161 -0
  209. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -0
  210. package/dist/providers/coding-proxy/prisma-ledger.js +213 -0
  211. package/dist/providers/coding-proxy/proxy.d.ts +75 -0
  212. package/dist/providers/coding-proxy/proxy.js +721 -0
  213. package/dist/providers/coding-proxy/runtime.d.ts +18 -0
  214. package/dist/providers/coding-proxy/runtime.js +39 -0
  215. package/dist/providers/coding-proxy/secure-fetch.d.ts +9 -0
  216. package/dist/providers/coding-proxy/secure-fetch.js +72 -0
  217. package/dist/providers/coding-proxy/server.d.ts +17 -0
  218. package/dist/providers/coding-proxy/server.js +139 -0
  219. package/dist/providers/coding-proxy/types.d.ts +99 -0
  220. package/dist/providers/coding-proxy/types.js +1 -0
  221. package/dist/providers/datastore/index.d.ts +2 -0
  222. package/dist/providers/datastore/index.js +2 -0
  223. package/dist/providers/datastore/postgres.d.ts +20 -0
  224. package/dist/providers/datastore/postgres.js +130 -0
  225. package/dist/providers/datastore/scoped.d.ts +10 -0
  226. package/dist/providers/datastore/scoped.js +46 -0
  227. package/dist/providers/datastore/types.d.ts +31 -0
  228. package/dist/providers/datastore/types.js +7 -0
  229. package/dist/providers/email/types.d.ts +46 -0
  230. package/dist/providers/email/types.js +8 -0
  231. package/dist/providers/engine/index.d.ts +1 -0
  232. package/dist/providers/engine/index.js +1 -0
  233. package/dist/providers/engine/types.d.ts +67 -0
  234. package/dist/providers/engine/types.js +16 -0
  235. package/dist/providers/executor/build.d.ts +6 -0
  236. package/dist/providers/executor/build.js +15 -0
  237. package/dist/providers/executor/composition.d.ts +14 -0
  238. package/dist/providers/executor/composition.js +123 -0
  239. package/dist/providers/executor/container.d.ts +184 -0
  240. package/dist/providers/executor/container.js +936 -0
  241. package/dist/providers/executor/dbos-status.d.ts +50 -0
  242. package/dist/providers/executor/dbos-status.js +64 -0
  243. package/dist/providers/executor/dbos.d.ts +53 -0
  244. package/dist/providers/executor/dbos.js +269 -0
  245. package/dist/providers/executor/in-process.d.ts +18 -0
  246. package/dist/providers/executor/in-process.js +38 -0
  247. package/dist/providers/executor/index.d.ts +8 -0
  248. package/dist/providers/executor/index.js +8 -0
  249. package/dist/providers/executor/routing.d.ts +48 -0
  250. package/dist/providers/executor/routing.js +70 -0
  251. package/dist/providers/executor/types.d.ts +55 -0
  252. package/dist/providers/executor/types.js +1 -0
  253. package/dist/providers/index.d.ts +43 -0
  254. package/dist/providers/index.js +18 -0
  255. package/dist/providers/jobs/docker-isolation.d.ts +166 -0
  256. package/dist/providers/jobs/docker-isolation.js +724 -0
  257. package/dist/providers/jobs/docker.d.ts +127 -0
  258. package/dist/providers/jobs/docker.js +1027 -0
  259. package/dist/providers/jobs/fake-kubernetes-api.d.ts +98 -0
  260. package/dist/providers/jobs/fake-kubernetes-api.js +142 -0
  261. package/dist/providers/jobs/fake.d.ts +23 -0
  262. package/dist/providers/jobs/fake.js +132 -0
  263. package/dist/providers/jobs/kubernetes-api.d.ts +60 -0
  264. package/dist/providers/jobs/kubernetes-api.js +20 -0
  265. package/dist/providers/jobs/kubernetes-client.d.ts +36 -0
  266. package/dist/providers/jobs/kubernetes-client.js +171 -0
  267. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +49 -0
  268. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +141 -0
  269. package/dist/providers/jobs/kubernetes-isolation.d.ts +147 -0
  270. package/dist/providers/jobs/kubernetes-isolation.js +607 -0
  271. package/dist/providers/jobs/kubernetes-platform.d.ts +177 -0
  272. package/dist/providers/jobs/kubernetes-platform.js +280 -0
  273. package/dist/providers/jobs/kubernetes-preflight.d.ts +59 -0
  274. package/dist/providers/jobs/kubernetes-preflight.js +339 -0
  275. package/dist/providers/jobs/kubernetes-witness.d.ts +10 -0
  276. package/dist/providers/jobs/kubernetes-witness.js +193 -0
  277. package/dist/providers/jobs/kubernetes.d.ts +127 -0
  278. package/dist/providers/jobs/kubernetes.js +874 -0
  279. package/dist/providers/jobs/safe-extract.d.ts +9 -0
  280. package/dist/providers/jobs/safe-extract.js +325 -0
  281. package/dist/providers/jobs/types.d.ts +78 -0
  282. package/dist/providers/jobs/types.js +6 -0
  283. package/dist/providers/jobs/workspace-swap.d.ts +2 -0
  284. package/dist/providers/jobs/workspace-swap.js +47 -0
  285. package/dist/providers/llm/anthropic.d.ts +13 -0
  286. package/dist/providers/llm/anthropic.js +21 -0
  287. package/dist/providers/llm/bedrock.d.ts +19 -0
  288. package/dist/providers/llm/bedrock.js +33 -0
  289. package/dist/providers/llm/claude-messages.d.ts +106 -0
  290. package/dist/providers/llm/claude-messages.js +157 -0
  291. package/dist/providers/llm/claude-provider.d.ts +40 -0
  292. package/dist/providers/llm/claude-provider.js +31 -0
  293. package/dist/providers/llm/index.d.ts +10 -0
  294. package/dist/providers/llm/index.js +10 -0
  295. package/dist/providers/llm/openai.d.ts +23 -0
  296. package/dist/providers/llm/openai.js +156 -0
  297. package/dist/providers/llm/pricing-anthropic.d.ts +4 -0
  298. package/dist/providers/llm/pricing-anthropic.js +30 -0
  299. package/dist/providers/llm/pricing-bedrock-claude.d.ts +12 -0
  300. package/dist/providers/llm/pricing-bedrock-claude.js +37 -0
  301. package/dist/providers/llm/pricing-core.d.ts +26 -0
  302. package/dist/providers/llm/pricing-core.js +17 -0
  303. package/dist/providers/llm/pricing.d.ts +30 -0
  304. package/dist/providers/llm/pricing.js +74 -0
  305. package/dist/providers/llm/registration.d.ts +8 -0
  306. package/dist/providers/llm/registration.js +39 -0
  307. package/dist/providers/llm/routing.d.ts +25 -0
  308. package/dist/providers/llm/routing.js +32 -0
  309. package/dist/providers/llm/types.d.ts +85 -0
  310. package/dist/providers/llm/types.js +12 -0
  311. package/dist/providers/memory/index.d.ts +2 -0
  312. package/dist/providers/memory/index.js +2 -0
  313. package/dist/providers/memory/postgres.d.ts +20 -0
  314. package/dist/providers/memory/postgres.js +72 -0
  315. package/dist/providers/memory/types.d.ts +32 -0
  316. package/dist/providers/memory/types.js +17 -0
  317. package/dist/providers/secrets/app-key.d.ts +8 -0
  318. package/dist/providers/secrets/app-key.js +50 -0
  319. package/dist/providers/secrets/index.d.ts +10 -0
  320. package/dist/providers/secrets/index.js +13 -0
  321. package/dist/providers/secrets/transfer-envelope.d.ts +12 -0
  322. package/dist/providers/secrets/transfer-envelope.js +35 -0
  323. package/dist/providers/secrets/types.d.ts +18 -0
  324. package/dist/providers/secrets/types.js +12 -0
  325. package/dist/providers/storage/types.d.ts +18 -0
  326. package/dist/providers/storage/types.js +7 -0
  327. package/dist/providers/vcs/git.d.ts +105 -0
  328. package/dist/providers/vcs/git.js +710 -0
  329. package/dist/providers/vcs/github.d.ts +105 -0
  330. package/dist/providers/vcs/github.js +394 -0
  331. package/dist/providers/vcs/index.d.ts +6 -0
  332. package/dist/providers/vcs/index.js +26 -0
  333. package/dist/providers/vcs/types.d.ts +118 -0
  334. package/dist/providers/vcs/types.js +1 -0
  335. package/dist/sandbox/bounded-json.d.ts +2 -0
  336. package/dist/sandbox/bounded-json.js +36 -0
  337. package/dist/sandbox/bridge.d.ts +15 -0
  338. package/dist/sandbox/bridge.js +78 -0
  339. package/dist/sandbox/eval-core.d.ts +37 -0
  340. package/dist/sandbox/eval-core.js +159 -0
  341. package/dist/sandbox/fetch-policy.d.ts +24 -0
  342. package/dist/sandbox/fetch-policy.js +96 -0
  343. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +5 -0
  344. package/dist/sandbox/generated/zod.bundle.js +1 -0
  345. package/dist/sandbox/host-functions.d.ts +29 -0
  346. package/dist/sandbox/host-functions.js +168 -0
  347. package/dist/sandbox/limits.d.ts +39 -0
  348. package/dist/sandbox/limits.js +39 -0
  349. package/dist/sandbox/parser-worker/__fixtures__/crash.d.ts +1 -0
  350. package/dist/sandbox/parser-worker/__fixtures__/crash.js +6 -0
  351. package/dist/sandbox/parser-worker/__fixtures__/echo.d.ts +1 -0
  352. package/dist/sandbox/parser-worker/__fixtures__/echo.js +5 -0
  353. package/dist/sandbox/parser-worker/__fixtures__/reject.d.ts +1 -0
  354. package/dist/sandbox/parser-worker/__fixtures__/reject.js +5 -0
  355. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.d.ts +1 -0
  356. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.js +11 -0
  357. package/dist/sandbox/parser-worker/pool.d.ts +13 -0
  358. package/dist/sandbox/parser-worker/pool.js +111 -0
  359. package/dist/sandbox/parser-worker/worker.d.ts +14 -0
  360. package/dist/sandbox/parser-worker/worker.js +63 -0
  361. package/dist/sandbox/pii-redaction.d.ts +1 -0
  362. package/dist/sandbox/pii-redaction.js +29 -0
  363. package/dist/sandbox/prelude.d.ts +26 -0
  364. package/dist/sandbox/prelude.js +408 -0
  365. package/dist/sandbox/quickjs-module.d.ts +13 -0
  366. package/dist/sandbox/quickjs-module.js +19 -0
  367. package/dist/sandbox/run-in-sandbox.d.ts +34 -0
  368. package/dist/sandbox/run-in-sandbox.js +53 -0
  369. package/dist/sandbox/safe-fetch.d.ts +26 -0
  370. package/dist/sandbox/safe-fetch.js +140 -0
  371. package/dist/sandbox/tool-capabilities.d.ts +66 -0
  372. package/dist/sandbox/tool-capabilities.js +103 -0
  373. package/dist/sandbox/zod-params.d.ts +22 -0
  374. package/dist/sandbox/zod-params.js +60 -0
  375. package/dist/serve.d.ts +14 -0
  376. package/dist/serve.js +54 -0
  377. package/dist/wardby-bin.d.ts +2 -0
  378. package/dist/wardby-bin.js +20 -0
  379. package/package.json +123 -0
  380. package/prisma/migrations/20260905000000_init/migration.sql +34 -0
  381. package/prisma/migrations/20260905010000_scheduler_durability/migration.sql +31 -0
  382. package/prisma/migrations/20260905020000_tools_multiturn/migration.sql +47 -0
  383. package/prisma/migrations/20260905030000_cache_tool_json_schema/migration.sql +13 -0
  384. package/prisma/migrations/20260906000000_mcp_phase4/migration.sql +115 -0
  385. package/prisma/migrations/20260906010000_run_final_text/migration.sql +7 -0
  386. package/prisma/migrations/20260906020000_run_turns/migration.sql +6 -0
  387. package/prisma/migrations/20260906030000_task_principal_id/migration.sql +5 -0
  388. package/prisma/migrations/20260906040000_secure_self_hosted_oauth/migration.sql +67 -0
  389. package/prisma/migrations/20260906050000_phase5_coding_agents/migration.sql +52 -0
  390. package/prisma/migrations/20260906060000_tool_capability_scoping/migration.sql +22 -0
  391. package/prisma/migrations/20260906070000_datastore_pii_flag/migration.sql +6 -0
  392. package/prisma/migrations/20260906080000_secret_elicitation_outcomes/migration.sql +13 -0
  393. package/prisma/migrations/20260907010000_coding_proxy_ledger/migration.sql +57 -0
  394. package/prisma/migrations/20260907020000_budget_groups/migration.sql +24 -0
  395. package/prisma/migrations/20260907030000_coding_webhook_task_override/migration.sql +4 -0
  396. package/prisma/migrations/20260908010000_run_execution_backend/migration.sql +2 -0
  397. package/prisma/migrations/20260908020000_coding_run_audit_metadata/migration.sql +7 -0
  398. package/prisma/migrations/20260910010000_agent_secret_bound_name/migration.sql +16 -0
  399. package/prisma/migrations/20260911010000_coding_worker_toolchains/migration.sql +5 -0
  400. package/prisma/migrations/20260912010000_coding_provider_contract/migration.sql +24 -0
  401. package/prisma/migrations/20260912020000_coding_proxy_protocol/migration.sql +10 -0
  402. package/prisma/migrations/20260912030000_agent_memory/migration.sql +18 -0
  403. package/prisma/migrations/20260912040000_named_shared_datastores/migration.sql +68 -0
  404. package/prisma/migrations/20260913000000_run_trigger_webhook/migration.sql +2 -0
  405. package/prisma/migrations/20260913010000_agent_sub_agent/migration.sql +32 -0
  406. package/prisma/migrations/20260913020000_run_task_override/migration.sql +5 -0
  407. package/prisma/migrations/20260913030000_coding_run_root/migration.sql +11 -0
  408. package/prisma/migrations/20260922010000_coding_run_queued_at/migration.sql +8 -0
  409. package/prisma/migrations/20260922020000_coding_workspace_disk/migration.sql +8 -0
  410. package/prisma/migrations/migration_lock.toml +3 -0
  411. package/prisma/schema.prisma +723 -0
  412. package/scripts/git-askpass.sh +11 -0
@@ -0,0 +1,721 @@
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";
5
+ import { createPinnedProxyFetch } from "./secure-fetch.js";
6
+ export const PROXY_MAX_BODY_BYTES = 8 * 1024 * 1024;
7
+ export const PROXY_MAX_OUTPUT_TOKENS = 1_000_000;
8
+ export const PROXY_DEFAULT_MAX_OUTPUT_TOKENS = 4_096;
9
+ export const CLAUDE_CODE_ANTHROPIC_BETAS = [
10
+ "claude-code-20250219",
11
+ "context-management-2025-06-27",
12
+ "effort-2025-11-24",
13
+ "interleaved-thinking-2025-05-14",
14
+ "mid-conversation-system-2026-04-07",
15
+ "prompt-caching-scope-2026-01-05",
16
+ "thinking-token-count-2026-05-13",
17
+ ];
18
+ const MAX_UPSTREAM_JSON_BYTES = 16 * 1024 * 1024;
19
+ const REQUEST_KEY_PATTERN = /^[\x21-\x7e]{1,200}$/;
20
+ const APPROVED_ANTHROPIC_BETAS = new Set(CLAUDE_CODE_ANTHROPIC_BETAS);
21
+ const WARDBY_COMMAND_TOOL = "mcp__wardby_tools__run_command";
22
+ const STRUCTURED_OUTPUT_TOOL = "StructuredOutput";
23
+ const MAX_COMMAND_BYTES = 16 * 1024;
24
+ const MAX_TOOL_TEXT_BYTES = 256 * 1024;
25
+ export class CodingProxyError extends Error {
26
+ status;
27
+ code;
28
+ constructor(status, code) {
29
+ super(code);
30
+ this.status = status;
31
+ this.code = code;
32
+ }
33
+ }
34
+ function capabilityHash(capability) {
35
+ return createHash("sha256").update(capability).digest("base64url");
36
+ }
37
+ function parseOpenAiRequest(rawBody) {
38
+ if (Buffer.byteLength(rawBody) > PROXY_MAX_BODY_BYTES)
39
+ throw new CodingProxyError(413, "payload_too_large");
40
+ let value;
41
+ try {
42
+ value = JSON.parse(rawBody);
43
+ }
44
+ catch {
45
+ throw new CodingProxyError(400, "invalid_json");
46
+ }
47
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
48
+ throw new CodingProxyError(400, "expected_json_object");
49
+ }
50
+ const body = value;
51
+ if (typeof body.model !== "string" || !body.model)
52
+ throw new CodingProxyError(400, "model_required");
53
+ const maxOutputTokens = body.max_output_tokens ?? PROXY_DEFAULT_MAX_OUTPUT_TOKENS;
54
+ if (typeof maxOutputTokens !== "number" ||
55
+ !Number.isSafeInteger(maxOutputTokens) ||
56
+ maxOutputTokens < 1 ||
57
+ maxOutputTokens > PROXY_MAX_OUTPUT_TOKENS) {
58
+ throw new CodingProxyError(400, "invalid_max_output_tokens");
59
+ }
60
+ if (body.stream !== true && body.stream !== false)
61
+ throw new CodingProxyError(400, "stream_required");
62
+ if (body.background === true)
63
+ throw new CodingProxyError(400, "background_not_allowed");
64
+ const normalized = { ...body, max_output_tokens: maxOutputTokens, store: false, background: false };
65
+ const encoded = JSON.stringify(normalized);
66
+ if (Buffer.byteLength(encoded) > PROXY_MAX_BODY_BYTES)
67
+ throw new CodingProxyError(413, "payload_too_large");
68
+ return {
69
+ body: normalized,
70
+ encoded,
71
+ model: body.model,
72
+ maxOutputTokens,
73
+ stream: body.stream,
74
+ fingerprint: fingerprintRequest(encoded),
75
+ };
76
+ }
77
+ function record(value, code = "invalid_anthropic_request") {
78
+ if (!value || typeof value !== "object" || Array.isArray(value))
79
+ throw new CodingProxyError(400, code);
80
+ return value;
81
+ }
82
+ function onlyKeys(value, keys) {
83
+ const allowed = new Set(keys);
84
+ if (Object.keys(value).some((key) => !allowed.has(key))) {
85
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
86
+ }
87
+ }
88
+ function validateCacheControl(value) {
89
+ const cache = record(value);
90
+ onlyKeys(cache, ["type"]);
91
+ if (cache.type !== "ephemeral")
92
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
93
+ }
94
+ function validateTextBlock(value) {
95
+ const block = record(value);
96
+ onlyKeys(block, ["type", "text", "cache_control"]);
97
+ if (block.type !== "text" || typeof block.text !== "string") {
98
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
99
+ }
100
+ if (block.cache_control !== undefined)
101
+ validateCacheControl(block.cache_control);
102
+ }
103
+ function validateTextBlocks(value) {
104
+ if (!Array.isArray(value) || value.length > 10_000)
105
+ throw new CodingProxyError(400, "invalid_anthropic_request");
106
+ for (const block of value)
107
+ validateTextBlock(block);
108
+ }
109
+ function validateCommandInput(value) {
110
+ const input = record(value, "unsupported_anthropic_feature");
111
+ onlyKeys(input, ["command", "timeout_ms"]);
112
+ if (typeof input.command !== "string" ||
113
+ input.command.length < 1 ||
114
+ Buffer.byteLength(input.command) > MAX_COMMAND_BYTES) {
115
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
116
+ }
117
+ if (input.timeout_ms !== undefined &&
118
+ (typeof input.timeout_ms !== "number" ||
119
+ !Number.isSafeInteger(input.timeout_ms) ||
120
+ input.timeout_ms < 1_000 ||
121
+ input.timeout_ms > 60_000)) {
122
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
123
+ }
124
+ }
125
+ function validateCommandTool(value) {
126
+ const tool = record(value, "tools_not_allowed");
127
+ onlyKeys(tool, ["name", "description", "input_schema", "cache_control", "type"]);
128
+ if (tool.name !== WARDBY_COMMAND_TOOL ||
129
+ typeof tool.description !== "string" ||
130
+ tool.description.length > MAX_TOOL_TEXT_BYTES ||
131
+ (tool.type !== undefined && tool.type !== "custom")) {
132
+ throw new CodingProxyError(400, "tools_not_allowed");
133
+ }
134
+ record(tool.input_schema, "tools_not_allowed");
135
+ if (tool.cache_control !== undefined)
136
+ validateCacheControl(tool.cache_control);
137
+ }
138
+ function validateStructuredOutputTool(value) {
139
+ const tool = record(value, "tools_not_allowed");
140
+ onlyKeys(tool, ["name", "description", "input_schema", "cache_control", "type"]);
141
+ if (tool.name !== STRUCTURED_OUTPUT_TOOL ||
142
+ typeof tool.description !== "string" ||
143
+ tool.description.length > MAX_TOOL_TEXT_BYTES ||
144
+ (tool.type !== undefined && tool.type !== "custom")) {
145
+ throw new CodingProxyError(400, "tools_not_allowed");
146
+ }
147
+ record(tool.input_schema, "tools_not_allowed");
148
+ if (tool.cache_control !== undefined)
149
+ validateCacheControl(tool.cache_control);
150
+ }
151
+ function validateApprovedTool(value) {
152
+ const tool = record(value, "tools_not_allowed");
153
+ if (tool.name === WARDBY_COMMAND_TOOL)
154
+ return validateCommandTool(tool);
155
+ if (tool.name === STRUCTURED_OUTPUT_TOOL)
156
+ return validateStructuredOutputTool(tool);
157
+ throw new CodingProxyError(400, "tools_not_allowed");
158
+ }
159
+ function validateToolUseBlock(value) {
160
+ onlyKeys(value, ["type", "id", "name", "input"]);
161
+ if (typeof value.id !== "string" || value.id.length < 1 || value.id.length > 512) {
162
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
163
+ }
164
+ if (value.name === WARDBY_COMMAND_TOOL)
165
+ return validateCommandInput(value.input);
166
+ if (value.name === STRUCTURED_OUTPUT_TOOL)
167
+ return void record(value.input, "unsupported_anthropic_feature");
168
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
169
+ }
170
+ function validateToolResultBlock(value) {
171
+ onlyKeys(value, ["type", "tool_use_id", "content", "is_error", "cache_control"]);
172
+ if (typeof value.tool_use_id !== "string" || value.tool_use_id.length < 1 || value.tool_use_id.length > 512) {
173
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
174
+ }
175
+ if (typeof value.content === "string") {
176
+ if (value.content.length > MAX_TOOL_TEXT_BYTES)
177
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
178
+ }
179
+ else {
180
+ validateTextBlocks(value.content);
181
+ }
182
+ if (value.is_error !== undefined && typeof value.is_error !== "boolean") {
183
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
184
+ }
185
+ if (value.cache_control !== undefined)
186
+ validateCacheControl(value.cache_control);
187
+ }
188
+ function validateThinkingBlock(value) {
189
+ onlyKeys(value, ["type", "thinking", "signature"]);
190
+ if (typeof value.thinking !== "string" || typeof value.signature !== "string") {
191
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
192
+ }
193
+ }
194
+ function validateMessageBlocks(value, role) {
195
+ if (!Array.isArray(value) || value.length > 10_000)
196
+ throw new CodingProxyError(400, "invalid_anthropic_request");
197
+ for (const candidate of value) {
198
+ const block = record(candidate);
199
+ if (block.type === "text") {
200
+ validateTextBlock(block);
201
+ }
202
+ else if (block.type === "tool_use" && role === "assistant") {
203
+ validateToolUseBlock(block);
204
+ }
205
+ else if (block.type === "tool_result" && role === "user") {
206
+ validateToolResultBlock(block);
207
+ }
208
+ else if (block.type === "thinking" && role === "assistant") {
209
+ validateThinkingBlock(block);
210
+ }
211
+ else {
212
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
213
+ }
214
+ }
215
+ }
216
+ function parseAnthropicBeta(value) {
217
+ if (value === undefined)
218
+ return { values: new Set() };
219
+ if (value.length < 1 || value.length > 2_048)
220
+ throw new CodingProxyError(400, "invalid_anthropic_beta");
221
+ const values = value.split(",").map((candidate) => candidate.trim());
222
+ const unique = new Set(values);
223
+ if (values.some((candidate) => !candidate || !APPROVED_ANTHROPIC_BETAS.has(candidate)) ||
224
+ unique.size !== values.length) {
225
+ throw new CodingProxyError(400, "invalid_anthropic_beta");
226
+ }
227
+ const normalized = [...unique].sort();
228
+ return { header: normalized.join(","), values: new Set(normalized) };
229
+ }
230
+ function requireAnthropicBeta(values, beta) {
231
+ if (!values.has(beta))
232
+ throw new CodingProxyError(400, "anthropic_beta_required");
233
+ }
234
+ function parseAnthropicRequest(rawBody, betaHeader) {
235
+ const beta = parseAnthropicBeta(betaHeader);
236
+ if (Buffer.byteLength(rawBody) > PROXY_MAX_BODY_BYTES)
237
+ throw new CodingProxyError(413, "payload_too_large");
238
+ let value;
239
+ try {
240
+ value = JSON.parse(rawBody);
241
+ }
242
+ catch {
243
+ throw new CodingProxyError(400, "invalid_json");
244
+ }
245
+ const body = record(value, "expected_json_object");
246
+ onlyKeys(body, [
247
+ "model",
248
+ "messages",
249
+ "system",
250
+ "tools",
251
+ "metadata",
252
+ "max_tokens",
253
+ "thinking",
254
+ "context_management",
255
+ "output_config",
256
+ "stream",
257
+ ]);
258
+ if (typeof body.model !== "string" || !body.model)
259
+ throw new CodingProxyError(400, "model_required");
260
+ if (typeof body.max_tokens !== "number" ||
261
+ !Number.isSafeInteger(body.max_tokens) ||
262
+ body.max_tokens < 1 ||
263
+ body.max_tokens > PROXY_MAX_OUTPUT_TOKENS) {
264
+ throw new CodingProxyError(400, "invalid_max_output_tokens");
265
+ }
266
+ if (body.stream !== true && body.stream !== false)
267
+ throw new CodingProxyError(400, "stream_required");
268
+ if (!Array.isArray(body.messages) || body.messages.length < 1 || body.messages.length > 10_000) {
269
+ throw new CodingProxyError(400, "invalid_anthropic_request");
270
+ }
271
+ if (body.system !== undefined)
272
+ validateTextBlocks(body.system);
273
+ for (const value of body.messages) {
274
+ const message = record(value);
275
+ onlyKeys(message, ["role", "content"]);
276
+ if (message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
277
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
278
+ }
279
+ if (message.role === "system") {
280
+ requireAnthropicBeta(beta.values, "mid-conversation-system-2026-04-07");
281
+ if (typeof message.content === "string") {
282
+ if (Buffer.byteLength(message.content) > MAX_TOOL_TEXT_BYTES) {
283
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
284
+ }
285
+ continue;
286
+ }
287
+ }
288
+ validateMessageBlocks(message.content, message.role);
289
+ }
290
+ if (body.tools !== undefined) {
291
+ if (!Array.isArray(body.tools) || body.tools.length > 2)
292
+ throw new CodingProxyError(400, "tools_not_allowed");
293
+ const names = new Set();
294
+ for (const tool of body.tools) {
295
+ const definition = record(tool, "tools_not_allowed");
296
+ if (typeof definition.name !== "string" || names.has(definition.name))
297
+ throw new CodingProxyError(400, "tools_not_allowed");
298
+ names.add(definition.name);
299
+ validateApprovedTool(definition);
300
+ }
301
+ }
302
+ if (body.metadata !== undefined) {
303
+ const metadata = record(body.metadata);
304
+ onlyKeys(metadata, ["user_id"]);
305
+ if (typeof metadata.user_id !== "string")
306
+ throw new CodingProxyError(400, "invalid_anthropic_request");
307
+ }
308
+ if (body.thinking !== undefined) {
309
+ requireAnthropicBeta(beta.values, "interleaved-thinking-2025-05-14");
310
+ requireAnthropicBeta(beta.values, "thinking-token-count-2026-05-13");
311
+ const thinking = record(body.thinking);
312
+ onlyKeys(thinking, ["type"]);
313
+ if (thinking.type !== "adaptive")
314
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
315
+ }
316
+ if (body.context_management !== undefined) {
317
+ requireAnthropicBeta(beta.values, "context-management-2025-06-27");
318
+ const context = record(body.context_management);
319
+ onlyKeys(context, ["edits"]);
320
+ const edits = context.edits;
321
+ if (!Array.isArray(edits) || edits.length !== 1)
322
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
323
+ const edit = record(edits[0]);
324
+ onlyKeys(edit, ["type", "keep"]);
325
+ if (edit.type !== "clear_thinking_20251015" || edit.keep !== "all") {
326
+ throw new CodingProxyError(400, "unsupported_anthropic_feature");
327
+ }
328
+ }
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
+ }
336
+ const normalized = { ...body };
337
+ delete normalized.metadata;
338
+ const encoded = JSON.stringify(normalized);
339
+ if (Buffer.byteLength(encoded) > PROXY_MAX_BODY_BYTES)
340
+ throw new CodingProxyError(413, "payload_too_large");
341
+ return {
342
+ body: normalized,
343
+ encoded,
344
+ model: body.model,
345
+ maxOutputTokens: body.max_tokens,
346
+ stream: body.stream,
347
+ fingerprint: fingerprintRequest(`${beta.header ?? ""}\n${encoded}`),
348
+ anthropicBeta: beta.header,
349
+ };
350
+ }
351
+ function parseRequest(protocol, rawBody, anthropicBeta) {
352
+ if (protocol !== "anthropic-messages" && anthropicBeta !== undefined) {
353
+ throw new CodingProxyError(400, "invalid_anthropic_beta");
354
+ }
355
+ return protocol === "anthropic-messages"
356
+ ? parseAnthropicRequest(rawBody, anthropicBeta)
357
+ : parseOpenAiRequest(rawBody);
358
+ }
359
+ function safeRequestKey(value, fingerprint) {
360
+ if (value === undefined)
361
+ return `body:${fingerprint}`;
362
+ if (!REQUEST_KEY_PATTERN.test(value))
363
+ throw new CodingProxyError(400, "invalid_idempotency_key");
364
+ return value;
365
+ }
366
+ function errorBody(code) {
367
+ return Buffer.from(JSON.stringify({ error: { type: code, message: code } }));
368
+ }
369
+ async function safeWrite(sink, chunk, connected) {
370
+ if (!connected.value)
371
+ return;
372
+ try {
373
+ await sink.write(chunk);
374
+ }
375
+ catch {
376
+ connected.value = false;
377
+ }
378
+ }
379
+ async function readBoundedBody(body, maxBytes) {
380
+ if (!body)
381
+ throw new Error("missing_upstream_body");
382
+ const chunks = [];
383
+ let size = 0;
384
+ for await (const chunk of body) {
385
+ size += chunk.byteLength;
386
+ if (size > maxBytes)
387
+ throw new Error("upstream_response_too_large");
388
+ chunks.push(chunk);
389
+ }
390
+ return Buffer.concat(chunks, size);
391
+ }
392
+ export class CodingProxy {
393
+ ledger;
394
+ credentials;
395
+ upstreamUrls;
396
+ fetchImpl;
397
+ now;
398
+ audit;
399
+ getPricing;
400
+ priceVersion;
401
+ activeRequests = new Map();
402
+ constructor(options) {
403
+ this.ledger = options.ledger;
404
+ this.credentials = options.credentials;
405
+ this.upstreamUrls = {
406
+ "openai-responses": options.upstreamUrl ?? "https://api.openai.com/v1/responses",
407
+ "anthropic-messages": options.anthropicUpstreamUrl ?? "https://api.anthropic.com/v1/messages?beta=true",
408
+ };
409
+ const upstreamHosts = Object.values(this.upstreamUrls).map((url) => new URL(url).hostname);
410
+ this.fetchImpl =
411
+ options.fetch ??
412
+ createPinnedProxyFetch({
413
+ allowedHosts: options.upstreamAllowedHosts ?? upstreamHosts,
414
+ });
415
+ this.now = options.now ?? (() => new Date());
416
+ this.audit = options.audit ?? (() => undefined);
417
+ this.getPricing =
418
+ options.pricing ??
419
+ ((model, protocol) => (protocol === "anthropic-messages" ? getAnthropicPricing(model) : getModelPricing(model)));
420
+ this.priceVersion = options.pricingVersion ?? PRICING_VERSION;
421
+ }
422
+ async createSession(input) {
423
+ if (input.protocol !== "openai-responses" && input.protocol !== "anthropic-messages") {
424
+ throw new Error("invalid_proxy_protocol");
425
+ }
426
+ const models = [...new Set(input.allowedModels)];
427
+ if (models.length < 1 || models.length > 8 || models.some((model) => !model || model.length > 100)) {
428
+ throw new Error("invalid_proxy_model_allowlist");
429
+ }
430
+ for (const model of models)
431
+ this.getPricing(model, input.protocol);
432
+ if (!Number.isFinite(input.budgetUsd) || input.budgetUsd <= 0)
433
+ throw new Error("invalid_proxy_budget");
434
+ if (input.deadlineAt.getTime() <= this.now().getTime())
435
+ throw new Error("invalid_proxy_deadline");
436
+ if (!input.credentialRef || input.credentialRef.length > 200)
437
+ throw new Error("invalid_proxy_credential_reference");
438
+ const capability = `rrp_${randomBytes(32).toString("base64url")}`;
439
+ const id = randomUUID();
440
+ await this.ledger.createSession({
441
+ id,
442
+ runId: input.runId,
443
+ capabilityHash: capabilityHash(capability),
444
+ credentialRef: input.credentialRef,
445
+ protocol: input.protocol,
446
+ allowedModels: models,
447
+ deadlineAt: input.deadlineAt,
448
+ budgetUsd: input.budgetUsd,
449
+ });
450
+ this.audit({ type: "session.created", runId: input.runId });
451
+ return {
452
+ id,
453
+ runId: input.runId,
454
+ capability,
455
+ protocol: input.protocol,
456
+ allowedModels: models,
457
+ deadlineAt: input.deadlineAt,
458
+ budgetUsd: input.budgetUsd,
459
+ };
460
+ }
461
+ async cancelSession(sessionId) {
462
+ await this.ledger.cancelSession(sessionId);
463
+ for (const controller of this.activeRequests.get(sessionId) ?? [])
464
+ controller.abort();
465
+ }
466
+ async execute(input, sink) {
467
+ const session = await this.authenticate(input.bearer);
468
+ if (session.protocol !== input.protocol) {
469
+ this.audit({ type: "request.rejected", runId: session.runId, reason: "protocol_mismatch" });
470
+ throw new CodingProxyError(403, "protocol_mismatch");
471
+ }
472
+ const parsed = parseRequest(input.protocol, input.rawBody, input.anthropicBeta);
473
+ if (!session.allowedModels.includes(parsed.model)) {
474
+ this.audit({ type: "request.rejected", runId: session.runId, model: parsed.model, reason: "model_not_allowed" });
475
+ throw new CodingProxyError(403, "model_not_allowed");
476
+ }
477
+ let pricing;
478
+ try {
479
+ pricing = this.getPricing(parsed.model, session.protocol);
480
+ }
481
+ catch {
482
+ this.audit({ type: "request.rejected", runId: session.runId, model: parsed.model, reason: "unknown_model" });
483
+ throw new CodingProxyError(400, "unknown_model");
484
+ }
485
+ const requestKey = safeRequestKey(input.requestKey, parsed.fingerprint);
486
+ const snapshot = pricingSnapshot(this.priceVersion, pricing);
487
+ const reservationUsd = estimateReservationUsd(Buffer.byteLength(parsed.encoded), parsed.maxOutputTokens, snapshot);
488
+ const reservation = await this.ledger.reserve({
489
+ id: randomUUID(),
490
+ sessionId: session.id,
491
+ requestKey,
492
+ requestFingerprint: parsed.fingerprint,
493
+ model: parsed.model,
494
+ reservationUsd,
495
+ pricing: snapshot,
496
+ now: this.now(),
497
+ });
498
+ if (reservation.outcome === "inactive") {
499
+ this.audit({ type: "request.rejected", runId: session.runId, reason: reservation.reason });
500
+ throw new CodingProxyError(403, `session_${reservation.reason}`);
501
+ }
502
+ if (reservation.outcome === "budget_exhausted") {
503
+ this.audit({
504
+ type: "request.rejected",
505
+ runId: session.runId,
506
+ model: parsed.model,
507
+ reason: "budget_exhausted",
508
+ reservationUsd,
509
+ });
510
+ throw new CodingProxyError(429, "wardby_budget_exhausted");
511
+ }
512
+ if (reservation.outcome === "duplicate") {
513
+ this.handleDuplicate(session, reservation.request, parsed.fingerprint);
514
+ }
515
+ const request = reservation.request;
516
+ this.audit({
517
+ type: "request.reserved",
518
+ runId: session.runId,
519
+ requestId: request.id,
520
+ model: request.model,
521
+ reservationUsd,
522
+ });
523
+ let key;
524
+ try {
525
+ key = await this.credentials.resolve(session.credentialRef);
526
+ if (!key)
527
+ throw new Error();
528
+ }
529
+ catch {
530
+ await this.ledger.release(request.id, 503);
531
+ this.audit({
532
+ type: "request.released",
533
+ runId: session.runId,
534
+ requestId: request.id,
535
+ status: 503,
536
+ reason: "credential_unavailable",
537
+ });
538
+ throw new CodingProxyError(503, "upstream_unavailable");
539
+ }
540
+ const remainingMs = session.deadlineAt.getTime() - this.now().getTime();
541
+ if (remainingMs <= 0) {
542
+ await this.ledger.release(request.id, 403);
543
+ throw new CodingProxyError(403, "session_expired");
544
+ }
545
+ const controller = new AbortController();
546
+ const deadlineTimer = setTimeout(() => controller.abort(), remainingMs);
547
+ deadlineTimer.unref();
548
+ this.trackActive(session.id, controller);
549
+ const cleanupActive = () => {
550
+ clearTimeout(deadlineTimer);
551
+ const active = this.activeRequests.get(session.id);
552
+ active?.delete(controller);
553
+ if (active?.size === 0)
554
+ this.activeRequests.delete(session.id);
555
+ };
556
+ let upstream;
557
+ try {
558
+ const headers = {
559
+ "content-type": "application/json",
560
+ accept: parsed.stream ? "text/event-stream" : "application/json",
561
+ };
562
+ if (session.protocol === "anthropic-messages") {
563
+ headers["x-api-key"] = key;
564
+ headers["anthropic-version"] = "2023-06-01";
565
+ if (parsed.anthropicBeta)
566
+ headers["anthropic-beta"] = parsed.anthropicBeta;
567
+ }
568
+ else {
569
+ headers.authorization = `Bearer ${key}`;
570
+ headers["idempotency-key"] = `${session.id}:${request.id}`;
571
+ }
572
+ upstream = await this.fetchImpl(this.upstreamUrls[session.protocol], {
573
+ method: "POST",
574
+ headers,
575
+ body: parsed.encoded,
576
+ redirect: "error",
577
+ signal: controller.signal,
578
+ });
579
+ }
580
+ catch {
581
+ cleanupActive();
582
+ await this.markUncertain(session, request, undefined, controller.signal.aborted ? "upstream_aborted" : "upstream_transport");
583
+ throw new CodingProxyError(502, controller.signal.aborted ? "upstream_aborted" : "upstream_transport_error");
584
+ }
585
+ if (!upstream.ok) {
586
+ cleanupActive();
587
+ await this.ledger.release(request.id, upstream.status);
588
+ this.audit({
589
+ type: "request.released",
590
+ runId: session.runId,
591
+ requestId: request.id,
592
+ status: upstream.status,
593
+ reason: "upstream_rejected",
594
+ });
595
+ sink.start(upstream.status, { "content-type": "application/json", "cache-control": "no-store" });
596
+ await safeWrite(sink, errorBody("upstream_rejected"), { value: true });
597
+ sink.end();
598
+ return;
599
+ }
600
+ try {
601
+ if (parsed.stream)
602
+ await this.forwardStream(session, request, upstream, sink);
603
+ else
604
+ await this.forwardJson(session, request, upstream, sink);
605
+ }
606
+ catch (error) {
607
+ await this.markUncertain(session, request, upstream.status, "invalid_or_incomplete_upstream_response");
608
+ sink.destroy();
609
+ throw error instanceof CodingProxyError ? error : new CodingProxyError(502, "invalid_upstream_response");
610
+ }
611
+ finally {
612
+ cleanupActive();
613
+ }
614
+ }
615
+ async authenticate(bearer) {
616
+ if (!bearer || bearer.length > 100)
617
+ throw new CodingProxyError(401, "invalid_capability");
618
+ const session = await this.ledger.findSessionByCapabilityHash(capabilityHash(bearer));
619
+ if (!session)
620
+ throw new CodingProxyError(401, "invalid_capability");
621
+ return session;
622
+ }
623
+ trackActive(sessionId, controller) {
624
+ const active = this.activeRequests.get(sessionId) ?? new Set();
625
+ active.add(controller);
626
+ this.activeRequests.set(sessionId, active);
627
+ }
628
+ handleDuplicate(session, request, fingerprint) {
629
+ const reason = request.requestFingerprint === fingerprint ? `duplicate_${request.status}` : "idempotency_conflict";
630
+ this.audit({ type: "request.rejected", runId: session.runId, requestId: request.id, reason });
631
+ throw new CodingProxyError(409, reason);
632
+ }
633
+ async complete(session, request, usage, upstreamStatus) {
634
+ const costUsd = actualCostUsd(usage, request.pricing);
635
+ await this.ledger.complete(request.id, usage, costUsd, upstreamStatus);
636
+ this.audit({
637
+ type: "response.completed",
638
+ runId: session.runId,
639
+ requestId: request.id,
640
+ model: request.model,
641
+ status: upstreamStatus,
642
+ costUsd,
643
+ inputTokens: usage.inputTokens,
644
+ outputTokens: usage.outputTokens,
645
+ cachedInputTokens: usage.cachedInputTokens,
646
+ cacheWriteTokens: usage.cacheWriteTokens,
647
+ reasoningTokens: usage.reasoningTokens,
648
+ });
649
+ if (costUsd > request.reservationUsd + Number.EPSILON) {
650
+ await this.ledger.cancelSession(session.id);
651
+ throw new CodingProxyError(502, "reservation_invariant_violated");
652
+ }
653
+ }
654
+ async forwardJson(session, request, upstream, sink) {
655
+ const declaredLength = Number(upstream.headers.get("content-length"));
656
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_UPSTREAM_JSON_BYTES) {
657
+ throw new Error("upstream_response_too_large");
658
+ }
659
+ const bytes = await readBoundedBody(upstream.body, MAX_UPSTREAM_JSON_BYTES);
660
+ let value;
661
+ try {
662
+ value = JSON.parse(Buffer.from(bytes).toString("utf8"));
663
+ }
664
+ catch {
665
+ throw new Error("invalid_upstream_json");
666
+ }
667
+ const usage = session.protocol === "anthropic-messages"
668
+ ? parseAnthropicAuthoritativeUsage(value?.usage)
669
+ : parseAuthoritativeUsage(value?.usage);
670
+ await this.complete(session, request, usage, upstream.status);
671
+ sink.start(upstream.status, { "content-type": "application/json", "cache-control": "no-store" });
672
+ await safeWrite(sink, bytes, { value: true });
673
+ sink.end();
674
+ }
675
+ async forwardStream(session, request, upstream, sink) {
676
+ if (!upstream.body)
677
+ throw new Error("missing_upstream_body");
678
+ sink.start(upstream.status, { "content-type": "text/event-stream", "cache-control": "no-store" });
679
+ const connected = { value: true };
680
+ const decoder = new TextDecoder();
681
+ let buffer = "";
682
+ let receivedBytes = 0;
683
+ let completed = false;
684
+ const anthropicUsage = session.protocol === "anthropic-messages" ? new AnthropicSseUsageTracker() : undefined;
685
+ const body = upstream.body;
686
+ for await (const chunk of body) {
687
+ receivedBytes += chunk.byteLength;
688
+ if (receivedBytes > MAX_UPSTREAM_JSON_BYTES)
689
+ throw new Error("upstream_response_too_large");
690
+ buffer = (buffer + decoder.decode(chunk, { stream: true })).replaceAll("\r\n", "\n");
691
+ for (;;) {
692
+ const boundary = buffer.indexOf("\n\n");
693
+ if (boundary < 0)
694
+ break;
695
+ const frame = buffer.slice(0, boundary);
696
+ buffer = buffer.slice(boundary + 2);
697
+ const terminal = anthropicUsage ? anthropicUsage.consume(frame) : terminalUsageFromSseFrame(frame);
698
+ if (!terminal.terminal) {
699
+ await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
700
+ continue;
701
+ }
702
+ if (!terminal.usage)
703
+ throw new Error("terminal_usage_missing");
704
+ await this.complete(session, request, terminal.usage, upstream.status);
705
+ completed = true;
706
+ await safeWrite(sink, Buffer.from(`${frame}\n\n`), connected);
707
+ }
708
+ }
709
+ buffer = (buffer + decoder.decode()).replaceAll("\r\n", "\n");
710
+ if (!completed)
711
+ throw new Error("terminal_usage_missing");
712
+ if (buffer)
713
+ await safeWrite(sink, Buffer.from(buffer), connected);
714
+ if (connected.value)
715
+ sink.end();
716
+ }
717
+ async markUncertain(session, request, status, reason) {
718
+ await this.ledger.markUncertain(request.id, status);
719
+ this.audit({ type: "request.uncertain", runId: session.runId, requestId: request.id, status, reason });
720
+ }
721
+ }