palaryn 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 (607) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +716 -0
  3. package/dist/sdk/typescript/src/client.d.ts +71 -0
  4. package/dist/sdk/typescript/src/client.d.ts.map +1 -0
  5. package/dist/sdk/typescript/src/client.js +176 -0
  6. package/dist/sdk/typescript/src/client.js.map +1 -0
  7. package/dist/sdk/typescript/src/errors.d.ts +50 -0
  8. package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
  9. package/dist/sdk/typescript/src/errors.js +103 -0
  10. package/dist/sdk/typescript/src/errors.js.map +1 -0
  11. package/dist/sdk/typescript/src/index.d.ts +4 -0
  12. package/dist/sdk/typescript/src/index.d.ts.map +1 -0
  13. package/dist/sdk/typescript/src/index.js +15 -0
  14. package/dist/sdk/typescript/src/index.js.map +1 -0
  15. package/dist/sdk/typescript/src/types.d.ts +101 -0
  16. package/dist/sdk/typescript/src/types.d.ts.map +1 -0
  17. package/dist/sdk/typescript/src/types.js +6 -0
  18. package/dist/sdk/typescript/src/types.js.map +1 -0
  19. package/dist/src/admin/index.d.ts +2 -0
  20. package/dist/src/admin/index.d.ts.map +1 -0
  21. package/dist/src/admin/index.js +6 -0
  22. package/dist/src/admin/index.js.map +1 -0
  23. package/dist/src/admin/routes.d.ts +5 -0
  24. package/dist/src/admin/routes.d.ts.map +1 -0
  25. package/dist/src/admin/routes.js +471 -0
  26. package/dist/src/admin/routes.js.map +1 -0
  27. package/dist/src/admin/templates.d.ts +51 -0
  28. package/dist/src/admin/templates.d.ts.map +1 -0
  29. package/dist/src/admin/templates.js +500 -0
  30. package/dist/src/admin/templates.js.map +1 -0
  31. package/dist/src/anomaly/detector.d.ts +141 -0
  32. package/dist/src/anomaly/detector.d.ts.map +1 -0
  33. package/dist/src/anomaly/detector.js +554 -0
  34. package/dist/src/anomaly/detector.js.map +1 -0
  35. package/dist/src/anomaly/index.d.ts +2 -0
  36. package/dist/src/anomaly/index.d.ts.map +1 -0
  37. package/dist/src/anomaly/index.js +7 -0
  38. package/dist/src/anomaly/index.js.map +1 -0
  39. package/dist/src/approval/manager.d.ts +147 -0
  40. package/dist/src/approval/manager.d.ts.map +1 -0
  41. package/dist/src/approval/manager.js +511 -0
  42. package/dist/src/approval/manager.js.map +1 -0
  43. package/dist/src/approval/webhook.d.ts +36 -0
  44. package/dist/src/approval/webhook.d.ts.map +1 -0
  45. package/dist/src/approval/webhook.js +135 -0
  46. package/dist/src/approval/webhook.js.map +1 -0
  47. package/dist/src/audit/logger.d.ts +70 -0
  48. package/dist/src/audit/logger.d.ts.map +1 -0
  49. package/dist/src/audit/logger.js +440 -0
  50. package/dist/src/audit/logger.js.map +1 -0
  51. package/dist/src/auth/index.d.ts +6 -0
  52. package/dist/src/auth/index.d.ts.map +1 -0
  53. package/dist/src/auth/index.js +22 -0
  54. package/dist/src/auth/index.js.map +1 -0
  55. package/dist/src/auth/password.d.ts +3 -0
  56. package/dist/src/auth/password.d.ts.map +1 -0
  57. package/dist/src/auth/password.js +25 -0
  58. package/dist/src/auth/password.js.map +1 -0
  59. package/dist/src/auth/pkce.d.ts +13 -0
  60. package/dist/src/auth/pkce.d.ts.map +1 -0
  61. package/dist/src/auth/pkce.js +58 -0
  62. package/dist/src/auth/pkce.js.map +1 -0
  63. package/dist/src/auth/providers.d.ts +28 -0
  64. package/dist/src/auth/providers.d.ts.map +1 -0
  65. package/dist/src/auth/providers.js +198 -0
  66. package/dist/src/auth/providers.js.map +1 -0
  67. package/dist/src/auth/routes.d.ts +14 -0
  68. package/dist/src/auth/routes.d.ts.map +1 -0
  69. package/dist/src/auth/routes.js +431 -0
  70. package/dist/src/auth/routes.js.map +1 -0
  71. package/dist/src/auth/session.d.ts +24 -0
  72. package/dist/src/auth/session.d.ts.map +1 -0
  73. package/dist/src/auth/session.js +105 -0
  74. package/dist/src/auth/session.js.map +1 -0
  75. package/dist/src/billing/index.d.ts +7 -0
  76. package/dist/src/billing/index.d.ts.map +1 -0
  77. package/dist/src/billing/index.js +14 -0
  78. package/dist/src/billing/index.js.map +1 -0
  79. package/dist/src/billing/plan-enforcer.d.ts +44 -0
  80. package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
  81. package/dist/src/billing/plan-enforcer.js +110 -0
  82. package/dist/src/billing/plan-enforcer.js.map +1 -0
  83. package/dist/src/billing/routes.d.ts +15 -0
  84. package/dist/src/billing/routes.d.ts.map +1 -0
  85. package/dist/src/billing/routes.js +193 -0
  86. package/dist/src/billing/routes.js.map +1 -0
  87. package/dist/src/billing/stripe-client.d.ts +14 -0
  88. package/dist/src/billing/stripe-client.d.ts.map +1 -0
  89. package/dist/src/billing/stripe-client.js +51 -0
  90. package/dist/src/billing/stripe-client.js.map +1 -0
  91. package/dist/src/billing/webhook-handler.d.ts +19 -0
  92. package/dist/src/billing/webhook-handler.d.ts.map +1 -0
  93. package/dist/src/billing/webhook-handler.js +169 -0
  94. package/dist/src/billing/webhook-handler.js.map +1 -0
  95. package/dist/src/billing/webhook-routes.d.ts +5 -0
  96. package/dist/src/billing/webhook-routes.d.ts.map +1 -0
  97. package/dist/src/billing/webhook-routes.js +30 -0
  98. package/dist/src/billing/webhook-routes.js.map +1 -0
  99. package/dist/src/budget/manager.d.ts +95 -0
  100. package/dist/src/budget/manager.d.ts.map +1 -0
  101. package/dist/src/budget/manager.js +547 -0
  102. package/dist/src/budget/manager.js.map +1 -0
  103. package/dist/src/budget/usage-extractor.d.ts +38 -0
  104. package/dist/src/budget/usage-extractor.d.ts.map +1 -0
  105. package/dist/src/budget/usage-extractor.js +165 -0
  106. package/dist/src/budget/usage-extractor.js.map +1 -0
  107. package/dist/src/cli.d.ts +3 -0
  108. package/dist/src/cli.d.ts.map +1 -0
  109. package/dist/src/cli.js +115 -0
  110. package/dist/src/cli.js.map +1 -0
  111. package/dist/src/config/defaults.d.ts +3 -0
  112. package/dist/src/config/defaults.d.ts.map +1 -0
  113. package/dist/src/config/defaults.js +243 -0
  114. package/dist/src/config/defaults.js.map +1 -0
  115. package/dist/src/config/validate.d.ts +15 -0
  116. package/dist/src/config/validate.d.ts.map +1 -0
  117. package/dist/src/config/validate.js +105 -0
  118. package/dist/src/config/validate.js.map +1 -0
  119. package/dist/src/dlp/composite-scanner.d.ts +47 -0
  120. package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
  121. package/dist/src/dlp/composite-scanner.js +186 -0
  122. package/dist/src/dlp/composite-scanner.js.map +1 -0
  123. package/dist/src/dlp/index.d.ts +10 -0
  124. package/dist/src/dlp/index.d.ts.map +1 -0
  125. package/dist/src/dlp/index.js +26 -0
  126. package/dist/src/dlp/index.js.map +1 -0
  127. package/dist/src/dlp/interfaces.d.ts +33 -0
  128. package/dist/src/dlp/interfaces.d.ts.map +1 -0
  129. package/dist/src/dlp/interfaces.js +3 -0
  130. package/dist/src/dlp/interfaces.js.map +1 -0
  131. package/dist/src/dlp/patterns.d.ts +9 -0
  132. package/dist/src/dlp/patterns.d.ts.map +1 -0
  133. package/dist/src/dlp/patterns.js +25 -0
  134. package/dist/src/dlp/patterns.js.map +1 -0
  135. package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
  136. package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
  137. package/dist/src/dlp/prompt-injection-backend.js +148 -0
  138. package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
  139. package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
  140. package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
  141. package/dist/src/dlp/prompt-injection-patterns.js +290 -0
  142. package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
  143. package/dist/src/dlp/regex-backend.d.ts +32 -0
  144. package/dist/src/dlp/regex-backend.d.ts.map +1 -0
  145. package/dist/src/dlp/regex-backend.js +153 -0
  146. package/dist/src/dlp/regex-backend.js.map +1 -0
  147. package/dist/src/dlp/scanner.d.ts +122 -0
  148. package/dist/src/dlp/scanner.d.ts.map +1 -0
  149. package/dist/src/dlp/scanner.js +444 -0
  150. package/dist/src/dlp/scanner.js.map +1 -0
  151. package/dist/src/dlp/text-normalizer.d.ts +41 -0
  152. package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
  153. package/dist/src/dlp/text-normalizer.js +203 -0
  154. package/dist/src/dlp/text-normalizer.js.map +1 -0
  155. package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
  156. package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
  157. package/dist/src/dlp/trufflehog-backend.js +151 -0
  158. package/dist/src/dlp/trufflehog-backend.js.map +1 -0
  159. package/dist/src/executor/http-executor.d.ts +25 -0
  160. package/dist/src/executor/http-executor.d.ts.map +1 -0
  161. package/dist/src/executor/http-executor.js +333 -0
  162. package/dist/src/executor/http-executor.js.map +1 -0
  163. package/dist/src/executor/index.d.ts +6 -0
  164. package/dist/src/executor/index.d.ts.map +1 -0
  165. package/dist/src/executor/index.js +12 -0
  166. package/dist/src/executor/index.js.map +1 -0
  167. package/dist/src/executor/interfaces.d.ts +11 -0
  168. package/dist/src/executor/interfaces.d.ts.map +1 -0
  169. package/dist/src/executor/interfaces.js +3 -0
  170. package/dist/src/executor/interfaces.js.map +1 -0
  171. package/dist/src/executor/noop-executor.d.ts +13 -0
  172. package/dist/src/executor/noop-executor.d.ts.map +1 -0
  173. package/dist/src/executor/noop-executor.js +21 -0
  174. package/dist/src/executor/noop-executor.js.map +1 -0
  175. package/dist/src/executor/registry.d.ts +30 -0
  176. package/dist/src/executor/registry.d.ts.map +1 -0
  177. package/dist/src/executor/registry.js +62 -0
  178. package/dist/src/executor/registry.js.map +1 -0
  179. package/dist/src/executor/slack-executor.d.ts +24 -0
  180. package/dist/src/executor/slack-executor.d.ts.map +1 -0
  181. package/dist/src/executor/slack-executor.js +147 -0
  182. package/dist/src/executor/slack-executor.js.map +1 -0
  183. package/dist/src/index.d.ts +25 -0
  184. package/dist/src/index.d.ts.map +1 -0
  185. package/dist/src/index.js +74 -0
  186. package/dist/src/index.js.map +1 -0
  187. package/dist/src/mcp/auth-verifier.d.ts +23 -0
  188. package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
  189. package/dist/src/mcp/auth-verifier.js +162 -0
  190. package/dist/src/mcp/auth-verifier.js.map +1 -0
  191. package/dist/src/mcp/bridge.d.ts +132 -0
  192. package/dist/src/mcp/bridge.d.ts.map +1 -0
  193. package/dist/src/mcp/bridge.js +734 -0
  194. package/dist/src/mcp/bridge.js.map +1 -0
  195. package/dist/src/mcp/http-transport.d.ts +32 -0
  196. package/dist/src/mcp/http-transport.d.ts.map +1 -0
  197. package/dist/src/mcp/http-transport.js +538 -0
  198. package/dist/src/mcp/http-transport.js.map +1 -0
  199. package/dist/src/mcp/index.d.ts +10 -0
  200. package/dist/src/mcp/index.d.ts.map +1 -0
  201. package/dist/src/mcp/index.js +17 -0
  202. package/dist/src/mcp/index.js.map +1 -0
  203. package/dist/src/mcp/oauth-pages.d.ts +23 -0
  204. package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
  205. package/dist/src/mcp/oauth-pages.js +121 -0
  206. package/dist/src/mcp/oauth-pages.js.map +1 -0
  207. package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
  208. package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
  209. package/dist/src/mcp/oauth-postgres-stores.js +226 -0
  210. package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
  211. package/dist/src/mcp/oauth-provider.d.ts +95 -0
  212. package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
  213. package/dist/src/mcp/oauth-provider.js +360 -0
  214. package/dist/src/mcp/oauth-provider.js.map +1 -0
  215. package/dist/src/mcp/oauth-stores.d.ts +62 -0
  216. package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
  217. package/dist/src/mcp/oauth-stores.js +154 -0
  218. package/dist/src/mcp/oauth-stores.js.map +1 -0
  219. package/dist/src/mcp/server.d.ts +18 -0
  220. package/dist/src/mcp/server.d.ts.map +1 -0
  221. package/dist/src/mcp/server.js +51 -0
  222. package/dist/src/mcp/server.js.map +1 -0
  223. package/dist/src/metrics/collector.d.ts +106 -0
  224. package/dist/src/metrics/collector.d.ts.map +1 -0
  225. package/dist/src/metrics/collector.js +311 -0
  226. package/dist/src/metrics/collector.js.map +1 -0
  227. package/dist/src/metrics/index.d.ts +2 -0
  228. package/dist/src/metrics/index.d.ts.map +1 -0
  229. package/dist/src/metrics/index.js +6 -0
  230. package/dist/src/metrics/index.js.map +1 -0
  231. package/dist/src/middleware/auth.d.ts +77 -0
  232. package/dist/src/middleware/auth.d.ts.map +1 -0
  233. package/dist/src/middleware/auth.js +720 -0
  234. package/dist/src/middleware/auth.js.map +1 -0
  235. package/dist/src/middleware/session.d.ts +18 -0
  236. package/dist/src/middleware/session.d.ts.map +1 -0
  237. package/dist/src/middleware/session.js +67 -0
  238. package/dist/src/middleware/session.js.map +1 -0
  239. package/dist/src/middleware/validate.d.ts +3 -0
  240. package/dist/src/middleware/validate.d.ts.map +1 -0
  241. package/dist/src/middleware/validate.js +85 -0
  242. package/dist/src/middleware/validate.js.map +1 -0
  243. package/dist/src/policy/engine.d.ts +107 -0
  244. package/dist/src/policy/engine.d.ts.map +1 -0
  245. package/dist/src/policy/engine.js +646 -0
  246. package/dist/src/policy/engine.js.map +1 -0
  247. package/dist/src/policy/index.d.ts +3 -0
  248. package/dist/src/policy/index.d.ts.map +1 -0
  249. package/dist/src/policy/index.js +8 -0
  250. package/dist/src/policy/index.js.map +1 -0
  251. package/dist/src/policy/opa-engine.d.ts +176 -0
  252. package/dist/src/policy/opa-engine.d.ts.map +1 -0
  253. package/dist/src/policy/opa-engine.js +790 -0
  254. package/dist/src/policy/opa-engine.js.map +1 -0
  255. package/dist/src/proxy/forward-proxy.d.ts +30 -0
  256. package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
  257. package/dist/src/proxy/forward-proxy.js +580 -0
  258. package/dist/src/proxy/forward-proxy.js.map +1 -0
  259. package/dist/src/proxy/index.d.ts +2 -0
  260. package/dist/src/proxy/index.d.ts.map +1 -0
  261. package/dist/src/proxy/index.js +8 -0
  262. package/dist/src/proxy/index.js.map +1 -0
  263. package/dist/src/ratelimit/limiter.d.ts +45 -0
  264. package/dist/src/ratelimit/limiter.d.ts.map +1 -0
  265. package/dist/src/ratelimit/limiter.js +158 -0
  266. package/dist/src/ratelimit/limiter.js.map +1 -0
  267. package/dist/src/replay/engine.d.ts +40 -0
  268. package/dist/src/replay/engine.d.ts.map +1 -0
  269. package/dist/src/replay/engine.js +106 -0
  270. package/dist/src/replay/engine.js.map +1 -0
  271. package/dist/src/replay/index.d.ts +2 -0
  272. package/dist/src/replay/index.d.ts.map +1 -0
  273. package/dist/src/replay/index.js +6 -0
  274. package/dist/src/replay/index.js.map +1 -0
  275. package/dist/src/saas/index.d.ts +2 -0
  276. package/dist/src/saas/index.d.ts.map +1 -0
  277. package/dist/src/saas/index.js +18 -0
  278. package/dist/src/saas/index.js.map +1 -0
  279. package/dist/src/saas/routes.d.ts +18 -0
  280. package/dist/src/saas/routes.d.ts.map +1 -0
  281. package/dist/src/saas/routes.js +1566 -0
  282. package/dist/src/saas/routes.js.map +1 -0
  283. package/dist/src/server/app.d.ts +44 -0
  284. package/dist/src/server/app.d.ts.map +1 -0
  285. package/dist/src/server/app.js +854 -0
  286. package/dist/src/server/app.js.map +1 -0
  287. package/dist/src/server/errors.d.ts +32 -0
  288. package/dist/src/server/errors.d.ts.map +1 -0
  289. package/dist/src/server/errors.js +39 -0
  290. package/dist/src/server/errors.js.map +1 -0
  291. package/dist/src/server/gateway.d.ts +165 -0
  292. package/dist/src/server/gateway.d.ts.map +1 -0
  293. package/dist/src/server/gateway.js +964 -0
  294. package/dist/src/server/gateway.js.map +1 -0
  295. package/dist/src/server/index.d.ts +2 -0
  296. package/dist/src/server/index.d.ts.map +1 -0
  297. package/dist/src/server/index.js +295 -0
  298. package/dist/src/server/index.js.map +1 -0
  299. package/dist/src/server/logger.d.ts +33 -0
  300. package/dist/src/server/logger.d.ts.map +1 -0
  301. package/dist/src/server/logger.js +230 -0
  302. package/dist/src/server/logger.js.map +1 -0
  303. package/dist/src/server/stream-proxy.d.ts +32 -0
  304. package/dist/src/server/stream-proxy.d.ts.map +1 -0
  305. package/dist/src/server/stream-proxy.js +184 -0
  306. package/dist/src/server/stream-proxy.js.map +1 -0
  307. package/dist/src/storage/file-persistence.d.ts +48 -0
  308. package/dist/src/storage/file-persistence.d.ts.map +1 -0
  309. package/dist/src/storage/file-persistence.js +280 -0
  310. package/dist/src/storage/file-persistence.js.map +1 -0
  311. package/dist/src/storage/index.d.ts +5 -0
  312. package/dist/src/storage/index.d.ts.map +1 -0
  313. package/dist/src/storage/index.js +21 -0
  314. package/dist/src/storage/index.js.map +1 -0
  315. package/dist/src/storage/interfaces.d.ts +237 -0
  316. package/dist/src/storage/interfaces.d.ts.map +1 -0
  317. package/dist/src/storage/interfaces.js +3 -0
  318. package/dist/src/storage/interfaces.js.map +1 -0
  319. package/dist/src/storage/memory.d.ts +162 -0
  320. package/dist/src/storage/memory.d.ts.map +1 -0
  321. package/dist/src/storage/memory.js +603 -0
  322. package/dist/src/storage/memory.js.map +1 -0
  323. package/dist/src/storage/postgres.d.ts +267 -0
  324. package/dist/src/storage/postgres.d.ts.map +1 -0
  325. package/dist/src/storage/postgres.js +1555 -0
  326. package/dist/src/storage/postgres.js.map +1 -0
  327. package/dist/src/storage/redis.d.ts +202 -0
  328. package/dist/src/storage/redis.d.ts.map +1 -0
  329. package/dist/src/storage/redis.js +629 -0
  330. package/dist/src/storage/redis.js.map +1 -0
  331. package/dist/src/tracing/index.d.ts +2 -0
  332. package/dist/src/tracing/index.d.ts.map +1 -0
  333. package/dist/src/tracing/index.js +6 -0
  334. package/dist/src/tracing/index.js.map +1 -0
  335. package/dist/src/tracing/provider.d.ts +43 -0
  336. package/dist/src/tracing/provider.d.ts.map +1 -0
  337. package/dist/src/tracing/provider.js +74 -0
  338. package/dist/src/tracing/provider.js.map +1 -0
  339. package/dist/src/trust/calculator.d.ts +54 -0
  340. package/dist/src/trust/calculator.d.ts.map +1 -0
  341. package/dist/src/trust/calculator.js +102 -0
  342. package/dist/src/trust/calculator.js.map +1 -0
  343. package/dist/src/trust/index.d.ts +2 -0
  344. package/dist/src/trust/index.d.ts.map +1 -0
  345. package/dist/src/trust/index.js +7 -0
  346. package/dist/src/trust/index.js.map +1 -0
  347. package/dist/src/types/budget.d.ts +30 -0
  348. package/dist/src/types/budget.d.ts.map +1 -0
  349. package/dist/src/types/budget.js +3 -0
  350. package/dist/src/types/budget.js.map +1 -0
  351. package/dist/src/types/config.d.ts +176 -0
  352. package/dist/src/types/config.d.ts.map +1 -0
  353. package/dist/src/types/config.js +3 -0
  354. package/dist/src/types/config.js.map +1 -0
  355. package/dist/src/types/events.d.ts +24 -0
  356. package/dist/src/types/events.d.ts.map +1 -0
  357. package/dist/src/types/events.js +3 -0
  358. package/dist/src/types/events.js.map +1 -0
  359. package/dist/src/types/index.d.ts +8 -0
  360. package/dist/src/types/index.d.ts.map +1 -0
  361. package/dist/src/types/index.js +24 -0
  362. package/dist/src/types/index.js.map +1 -0
  363. package/dist/src/types/policy.d.ts +60 -0
  364. package/dist/src/types/policy.d.ts.map +1 -0
  365. package/dist/src/types/policy.js +3 -0
  366. package/dist/src/types/policy.js.map +1 -0
  367. package/dist/src/types/stripe-config.d.ts +12 -0
  368. package/dist/src/types/stripe-config.d.ts.map +1 -0
  369. package/dist/src/types/stripe-config.js +3 -0
  370. package/dist/src/types/stripe-config.js.map +1 -0
  371. package/dist/src/types/subscription.d.ts +24 -0
  372. package/dist/src/types/subscription.d.ts.map +1 -0
  373. package/dist/src/types/subscription.js +38 -0
  374. package/dist/src/types/subscription.js.map +1 -0
  375. package/dist/src/types/tool-call.d.ts +42 -0
  376. package/dist/src/types/tool-call.d.ts.map +1 -0
  377. package/dist/src/types/tool-call.js +3 -0
  378. package/dist/src/types/tool-call.js.map +1 -0
  379. package/dist/src/types/tool-result.d.ts +58 -0
  380. package/dist/src/types/tool-result.d.ts.map +1 -0
  381. package/dist/src/types/tool-result.js +3 -0
  382. package/dist/src/types/tool-result.js.map +1 -0
  383. package/dist/src/types/user.d.ts +101 -0
  384. package/dist/src/types/user.d.ts.map +1 -0
  385. package/dist/src/types/user.js +6 -0
  386. package/dist/src/types/user.js.map +1 -0
  387. package/dist/tests/integration/api.test.d.ts +2 -0
  388. package/dist/tests/integration/api.test.d.ts.map +1 -0
  389. package/dist/tests/integration/api.test.js +1199 -0
  390. package/dist/tests/integration/api.test.js.map +1 -0
  391. package/dist/tests/integration/proxy.test.d.ts +2 -0
  392. package/dist/tests/integration/proxy.test.d.ts.map +1 -0
  393. package/dist/tests/integration/proxy.test.js +251 -0
  394. package/dist/tests/integration/proxy.test.js.map +1 -0
  395. package/dist/tests/integration/storage.test.d.ts +16 -0
  396. package/dist/tests/integration/storage.test.d.ts.map +1 -0
  397. package/dist/tests/integration/storage.test.js +826 -0
  398. package/dist/tests/integration/storage.test.js.map +1 -0
  399. package/dist/tests/unit/admin.test.d.ts +2 -0
  400. package/dist/tests/unit/admin.test.d.ts.map +1 -0
  401. package/dist/tests/unit/admin.test.js +698 -0
  402. package/dist/tests/unit/admin.test.js.map +1 -0
  403. package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
  404. package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
  405. package/dist/tests/unit/anomaly-detector.test.js +903 -0
  406. package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
  407. package/dist/tests/unit/approval-manager.test.d.ts +2 -0
  408. package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
  409. package/dist/tests/unit/approval-manager.test.js +528 -0
  410. package/dist/tests/unit/approval-manager.test.js.map +1 -0
  411. package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
  412. package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
  413. package/dist/tests/unit/approval-webhook.test.js +355 -0
  414. package/dist/tests/unit/approval-webhook.test.js.map +1 -0
  415. package/dist/tests/unit/audit-logger.test.d.ts +2 -0
  416. package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
  417. package/dist/tests/unit/audit-logger.test.js +635 -0
  418. package/dist/tests/unit/audit-logger.test.js.map +1 -0
  419. package/dist/tests/unit/auth-routes.test.d.ts +2 -0
  420. package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
  421. package/dist/tests/unit/auth-routes.test.js +281 -0
  422. package/dist/tests/unit/auth-routes.test.js.map +1 -0
  423. package/dist/tests/unit/auth.test.d.ts +2 -0
  424. package/dist/tests/unit/auth.test.d.ts.map +1 -0
  425. package/dist/tests/unit/auth.test.js +1382 -0
  426. package/dist/tests/unit/auth.test.js.map +1 -0
  427. package/dist/tests/unit/billing.test.d.ts +2 -0
  428. package/dist/tests/unit/billing.test.d.ts.map +1 -0
  429. package/dist/tests/unit/billing.test.js +579 -0
  430. package/dist/tests/unit/billing.test.js.map +1 -0
  431. package/dist/tests/unit/budget-manager.test.d.ts +2 -0
  432. package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
  433. package/dist/tests/unit/budget-manager.test.js +778 -0
  434. package/dist/tests/unit/budget-manager.test.js.map +1 -0
  435. package/dist/tests/unit/budget-race.test.d.ts +2 -0
  436. package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
  437. package/dist/tests/unit/budget-race.test.js +58 -0
  438. package/dist/tests/unit/budget-race.test.js.map +1 -0
  439. package/dist/tests/unit/cli.test.d.ts +2 -0
  440. package/dist/tests/unit/cli.test.d.ts.map +1 -0
  441. package/dist/tests/unit/cli.test.js +93 -0
  442. package/dist/tests/unit/cli.test.js.map +1 -0
  443. package/dist/tests/unit/concurrency.test.d.ts +2 -0
  444. package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
  445. package/dist/tests/unit/concurrency.test.js +1270 -0
  446. package/dist/tests/unit/concurrency.test.js.map +1 -0
  447. package/dist/tests/unit/config-validate.test.d.ts +2 -0
  448. package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
  449. package/dist/tests/unit/config-validate.test.js +230 -0
  450. package/dist/tests/unit/config-validate.test.js.map +1 -0
  451. package/dist/tests/unit/defaults.test.d.ts +2 -0
  452. package/dist/tests/unit/defaults.test.d.ts.map +1 -0
  453. package/dist/tests/unit/defaults.test.js +364 -0
  454. package/dist/tests/unit/defaults.test.js.map +1 -0
  455. package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
  456. package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
  457. package/dist/tests/unit/dlp-backends.test.js +563 -0
  458. package/dist/tests/unit/dlp-backends.test.js.map +1 -0
  459. package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
  460. package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
  461. package/dist/tests/unit/dlp-scanner.test.js +739 -0
  462. package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
  463. package/dist/tests/unit/error-responses.test.d.ts +2 -0
  464. package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
  465. package/dist/tests/unit/error-responses.test.js +101 -0
  466. package/dist/tests/unit/error-responses.test.js.map +1 -0
  467. package/dist/tests/unit/executor-registry.test.d.ts +2 -0
  468. package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
  469. package/dist/tests/unit/executor-registry.test.js +390 -0
  470. package/dist/tests/unit/executor-registry.test.js.map +1 -0
  471. package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
  472. package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
  473. package/dist/tests/unit/forward-proxy.test.js +621 -0
  474. package/dist/tests/unit/forward-proxy.test.js.map +1 -0
  475. package/dist/tests/unit/gateway-features.test.d.ts +2 -0
  476. package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
  477. package/dist/tests/unit/gateway-features.test.js +753 -0
  478. package/dist/tests/unit/gateway-features.test.js.map +1 -0
  479. package/dist/tests/unit/http-executor.test.d.ts +2 -0
  480. package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
  481. package/dist/tests/unit/http-executor.test.js +310 -0
  482. package/dist/tests/unit/http-executor.test.js.map +1 -0
  483. package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
  484. package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
  485. package/dist/tests/unit/mcp-bridge.test.js +1136 -0
  486. package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
  487. package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
  488. package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
  489. package/dist/tests/unit/mcp-http-transport.test.js +899 -0
  490. package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
  491. package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
  492. package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
  493. package/dist/tests/unit/mcp-oauth.test.js +759 -0
  494. package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
  495. package/dist/tests/unit/mcp-server.test.d.ts +15 -0
  496. package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
  497. package/dist/tests/unit/mcp-server.test.js +158 -0
  498. package/dist/tests/unit/mcp-server.test.js.map +1 -0
  499. package/dist/tests/unit/metrics.test.d.ts +2 -0
  500. package/dist/tests/unit/metrics.test.d.ts.map +1 -0
  501. package/dist/tests/unit/metrics.test.js +208 -0
  502. package/dist/tests/unit/metrics.test.js.map +1 -0
  503. package/dist/tests/unit/oauth.test.d.ts +2 -0
  504. package/dist/tests/unit/oauth.test.d.ts.map +1 -0
  505. package/dist/tests/unit/oauth.test.js +281 -0
  506. package/dist/tests/unit/oauth.test.js.map +1 -0
  507. package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
  508. package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
  509. package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
  510. package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
  511. package/dist/tests/unit/opa-engine.test.d.ts +2 -0
  512. package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
  513. package/dist/tests/unit/opa-engine.test.js +1813 -0
  514. package/dist/tests/unit/opa-engine.test.js.map +1 -0
  515. package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
  516. package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
  517. package/dist/tests/unit/pipeline-timing.test.js +528 -0
  518. package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
  519. package/dist/tests/unit/policy-engine.test.d.ts +2 -0
  520. package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
  521. package/dist/tests/unit/policy-engine.test.js +1345 -0
  522. package/dist/tests/unit/policy-engine.test.js.map +1 -0
  523. package/dist/tests/unit/policy-store.test.d.ts +2 -0
  524. package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
  525. package/dist/tests/unit/policy-store.test.js +60 -0
  526. package/dist/tests/unit/policy-store.test.js.map +1 -0
  527. package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
  528. package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
  529. package/dist/tests/unit/postgres-storage.test.js +614 -0
  530. package/dist/tests/unit/postgres-storage.test.js.map +1 -0
  531. package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
  532. package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
  533. package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
  534. package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
  535. package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
  536. package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
  537. package/dist/tests/unit/proxy-hardening.test.js +166 -0
  538. package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
  539. package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
  540. package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
  541. package/dist/tests/unit/rate-limiter.test.js +443 -0
  542. package/dist/tests/unit/rate-limiter.test.js.map +1 -0
  543. package/dist/tests/unit/redis-storage.test.d.ts +2 -0
  544. package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
  545. package/dist/tests/unit/redis-storage.test.js +766 -0
  546. package/dist/tests/unit/redis-storage.test.js.map +1 -0
  547. package/dist/tests/unit/replay-engine.test.d.ts +2 -0
  548. package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
  549. package/dist/tests/unit/replay-engine.test.js +371 -0
  550. package/dist/tests/unit/replay-engine.test.js.map +1 -0
  551. package/dist/tests/unit/saas-routes.test.d.ts +2 -0
  552. package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
  553. package/dist/tests/unit/saas-routes.test.js +1399 -0
  554. package/dist/tests/unit/saas-routes.test.js.map +1 -0
  555. package/dist/tests/unit/session.test.d.ts +2 -0
  556. package/dist/tests/unit/session.test.d.ts.map +1 -0
  557. package/dist/tests/unit/session.test.js +532 -0
  558. package/dist/tests/unit/session.test.js.map +1 -0
  559. package/dist/tests/unit/slack-executor.test.d.ts +2 -0
  560. package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
  561. package/dist/tests/unit/slack-executor.test.js +209 -0
  562. package/dist/tests/unit/slack-executor.test.js.map +1 -0
  563. package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
  564. package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
  565. package/dist/tests/unit/storage-hardening.test.js +165 -0
  566. package/dist/tests/unit/storage-hardening.test.js.map +1 -0
  567. package/dist/tests/unit/storage.test.d.ts +2 -0
  568. package/dist/tests/unit/storage.test.d.ts.map +1 -0
  569. package/dist/tests/unit/storage.test.js +698 -0
  570. package/dist/tests/unit/storage.test.js.map +1 -0
  571. package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
  572. package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
  573. package/dist/tests/unit/text-normalizer.test.js +229 -0
  574. package/dist/tests/unit/text-normalizer.test.js.map +1 -0
  575. package/dist/tests/unit/tracing.test.d.ts +2 -0
  576. package/dist/tests/unit/tracing.test.d.ts.map +1 -0
  577. package/dist/tests/unit/tracing.test.js +611 -0
  578. package/dist/tests/unit/tracing.test.js.map +1 -0
  579. package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
  580. package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
  581. package/dist/tests/unit/trust-calculator.test.js +497 -0
  582. package/dist/tests/unit/trust-calculator.test.js.map +1 -0
  583. package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
  584. package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
  585. package/dist/tests/unit/ts-sdk.test.js +421 -0
  586. package/dist/tests/unit/ts-sdk.test.js.map +1 -0
  587. package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
  588. package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
  589. package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
  590. package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
  591. package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
  592. package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
  593. package/dist/tests/unit/usage-extractor.test.js +271 -0
  594. package/dist/tests/unit/usage-extractor.test.js.map +1 -0
  595. package/dist/tests/unit/user-stores.test.d.ts +2 -0
  596. package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
  597. package/dist/tests/unit/user-stores.test.js +687 -0
  598. package/dist/tests/unit/user-stores.test.js.map +1 -0
  599. package/dist/tests/unit/validate.test.d.ts +2 -0
  600. package/dist/tests/unit/validate.test.d.ts.map +1 -0
  601. package/dist/tests/unit/validate.test.js +545 -0
  602. package/dist/tests/unit/validate.test.js.map +1 -0
  603. package/package.json +86 -0
  604. package/policy-packs/README.md +42 -0
  605. package/policy-packs/default.yaml +46 -0
  606. package/policy-packs/dev_fast.yaml +54 -0
  607. package/policy-packs/prod_strict.yaml +83 -0
@@ -0,0 +1,778 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const manager_1 = require("../../src/budget/manager");
4
+ // ---------------------------------------------------------------------------
5
+ // Helper: create a ToolCall with sensible defaults and optional overrides
6
+ // ---------------------------------------------------------------------------
7
+ function createTestToolCall(overrides) {
8
+ return {
9
+ tool_call_id: 'tc-001',
10
+ task_id: 'task-001',
11
+ workspace_id: 'ws-001',
12
+ actor: { type: 'agent', id: 'agent-001' },
13
+ source: { platform: 'test' },
14
+ tool: { name: 'http.request', capability: 'read' },
15
+ args: { method: 'GET', url: 'https://example.com' },
16
+ timestamp: new Date().toISOString(),
17
+ ...overrides,
18
+ };
19
+ }
20
+ // ---------------------------------------------------------------------------
21
+ // Default test config (generous limits so individual tests can tighten them)
22
+ // ---------------------------------------------------------------------------
23
+ function defaultConfig(overrides) {
24
+ return {
25
+ task_budget_usd: 2.0,
26
+ max_steps_per_task: 50,
27
+ max_retries_per_call: 3,
28
+ max_wall_clock_ms: 300000,
29
+ ...overrides,
30
+ };
31
+ }
32
+ // ===========================================================================
33
+ // Tests
34
+ // ===========================================================================
35
+ describe('BudgetManager', () => {
36
+ // -----------------------------------------------------------------------
37
+ // Cost Estimation
38
+ // -----------------------------------------------------------------------
39
+ describe('estimateCost', () => {
40
+ let manager;
41
+ beforeEach(() => {
42
+ manager = new manager_1.BudgetManager(defaultConfig());
43
+ });
44
+ it('should return cost based on tool name from cost table', () => {
45
+ const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'read' } });
46
+ const estimate = manager.estimateCost(toolCall);
47
+ expect(estimate.tool_name).toBe('http.request');
48
+ expect(estimate.capability).toBe('read');
49
+ expect(estimate.estimated_cost_usd).toBe(0.001);
50
+ });
51
+ it('should return higher cost for known write tool entry', () => {
52
+ // http.request with write capability should match http.request.write (0.002)
53
+ const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'write' } });
54
+ const estimate = manager.estimateCost(toolCall);
55
+ expect(estimate.estimated_cost_usd).toBe(0.002);
56
+ });
57
+ it('should return correct cost for slack.post', () => {
58
+ const toolCall = createTestToolCall({ tool: { name: 'slack.post', capability: 'read' } });
59
+ const estimate = manager.estimateCost(toolCall);
60
+ expect(estimate.estimated_cost_usd).toBe(0.003);
61
+ });
62
+ it('should return correct cost for git.operation', () => {
63
+ const toolCall = createTestToolCall({ tool: { name: 'git.operation', capability: 'read' } });
64
+ const estimate = manager.estimateCost(toolCall);
65
+ expect(estimate.estimated_cost_usd).toBe(0.002);
66
+ });
67
+ it('should return correct cost for db.query', () => {
68
+ const toolCall = createTestToolCall({ tool: { name: 'db.query', capability: 'read' } });
69
+ const estimate = manager.estimateCost(toolCall);
70
+ expect(estimate.estimated_cost_usd).toBe(0.005);
71
+ });
72
+ it('should return correct cost for browser.navigate', () => {
73
+ const toolCall = createTestToolCall({ tool: { name: 'browser.navigate', capability: 'read' } });
74
+ const estimate = manager.estimateCost(toolCall);
75
+ expect(estimate.estimated_cost_usd).toBe(0.01);
76
+ });
77
+ it('should return default cost for unknown tools', () => {
78
+ const toolCall = createTestToolCall({ tool: { name: 'custom.unknown_tool', capability: 'read' } });
79
+ const estimate = manager.estimateCost(toolCall);
80
+ expect(estimate.tool_name).toBe('custom.unknown_tool');
81
+ expect(estimate.estimated_cost_usd).toBe(0.001); // default
82
+ });
83
+ it('should apply 1.5x multiplier for delete capability', () => {
84
+ // db.query base = 0.005, delete => 0.005 * 1.5 = 0.0075
85
+ const toolCall = createTestToolCall({ tool: { name: 'db.query', capability: 'delete' } });
86
+ const estimate = manager.estimateCost(toolCall);
87
+ expect(estimate.estimated_cost_usd).toBeCloseTo(0.0075, 6);
88
+ });
89
+ it('should apply 2.0x multiplier for admin capability', () => {
90
+ // db.query base = 0.005, admin => 0.005 * 2.0 = 0.01
91
+ const toolCall = createTestToolCall({ tool: { name: 'db.query', capability: 'admin' } });
92
+ const estimate = manager.estimateCost(toolCall);
93
+ expect(estimate.estimated_cost_usd).toBeCloseTo(0.01, 6);
94
+ });
95
+ it('should respect per-call max_cost_usd constraint as upper bound', () => {
96
+ // browser.navigate base = 0.01, but constraint limits to 0.005
97
+ const toolCall = createTestToolCall({
98
+ tool: { name: 'browser.navigate', capability: 'read' },
99
+ constraints: { max_cost_usd: 0.005 },
100
+ });
101
+ const estimate = manager.estimateCost(toolCall);
102
+ expect(estimate.estimated_cost_usd).toBe(0.005);
103
+ });
104
+ it('should not reduce cost when max_cost_usd is higher than estimated', () => {
105
+ // http.request base = 0.001, constraint = 1.0 (way above)
106
+ const toolCall = createTestToolCall({
107
+ tool: { name: 'http.request', capability: 'read' },
108
+ constraints: { max_cost_usd: 1.0 },
109
+ });
110
+ const estimate = manager.estimateCost(toolCall);
111
+ expect(estimate.estimated_cost_usd).toBe(0.001);
112
+ });
113
+ it('should not apply constraint when max_cost_usd is 0', () => {
114
+ // max_cost_usd: 0 should be ignored (the condition checks > 0)
115
+ const toolCall = createTestToolCall({
116
+ tool: { name: 'http.request', capability: 'read' },
117
+ constraints: { max_cost_usd: 0 },
118
+ });
119
+ const estimate = manager.estimateCost(toolCall);
120
+ expect(estimate.estimated_cost_usd).toBe(0.001);
121
+ });
122
+ it('should not apply constraint when constraints is undefined', () => {
123
+ const toolCall = createTestToolCall({ constraints: undefined });
124
+ const estimate = manager.estimateCost(toolCall);
125
+ expect(estimate.estimated_cost_usd).toBe(0.001);
126
+ });
127
+ it('should look up capability-specific key for write on http.request', () => {
128
+ // http.request.write exists in COST_TABLE with value 0.002
129
+ const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'write' } });
130
+ const estimate = manager.estimateCost(toolCall);
131
+ expect(estimate.estimated_cost_usd).toBe(0.002);
132
+ });
133
+ it('should fall back to base tool cost for write when no .write key exists', () => {
134
+ // slack.post with write capability: no slack.post.write entry, so falls back to slack.post (0.003)
135
+ const toolCall = createTestToolCall({ tool: { name: 'slack.post', capability: 'write' } });
136
+ const estimate = manager.estimateCost(toolCall);
137
+ expect(estimate.estimated_cost_usd).toBe(0.003);
138
+ });
139
+ });
140
+ // -----------------------------------------------------------------------
141
+ // Budget Check
142
+ // -----------------------------------------------------------------------
143
+ describe('check', () => {
144
+ it('should allow call when within all limits', () => {
145
+ const manager = new manager_1.BudgetManager(defaultConfig());
146
+ const toolCall = createTestToolCall();
147
+ const result = manager.check(toolCall);
148
+ expect(result.allowed).toBe(true);
149
+ expect(result.reason).toBeUndefined();
150
+ expect(result.report).toBeDefined();
151
+ expect(result.report.estimated_cost_usd).toBe(0.001);
152
+ expect(result.report.spent_cost_usd_task).toBe(0);
153
+ expect(result.report.remaining_cost_usd_task).toBe(2.0);
154
+ });
155
+ it('should deny when task budget would be exceeded', () => {
156
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.005 }));
157
+ const toolCall = createTestToolCall();
158
+ // Record enough spend to nearly exhaust budget
159
+ manager.record(toolCall, 0.005);
160
+ const result = manager.check(toolCall);
161
+ expect(result.allowed).toBe(false);
162
+ expect(result.reason).toContain('Task budget exceeded');
163
+ });
164
+ it('should deny when step limit exceeded', () => {
165
+ const manager = new manager_1.BudgetManager(defaultConfig({ max_steps_per_task: 2 }));
166
+ const toolCall = createTestToolCall();
167
+ // Record 2 steps
168
+ manager.record(toolCall, 0.0001);
169
+ manager.record(toolCall, 0.0001);
170
+ const result = manager.check(toolCall);
171
+ expect(result.allowed).toBe(false);
172
+ expect(result.reason).toContain('Step limit exceeded');
173
+ });
174
+ it('should deny when wall clock time exceeded', () => {
175
+ const manager = new manager_1.BudgetManager(defaultConfig({ max_wall_clock_ms: 100 }));
176
+ // Create a tool call with a timestamp far in the past
177
+ const pastTimestamp = new Date(Date.now() - 200).toISOString();
178
+ const toolCall = createTestToolCall({ timestamp: pastTimestamp });
179
+ // Force creation of task state with the old timestamp
180
+ // by issuing a check (which calls getTaskState internally)
181
+ // The first check initialises state with toolCall.timestamp
182
+ const result = manager.check(toolCall);
183
+ expect(result.allowed).toBe(false);
184
+ expect(result.reason).toContain('Wall clock time exceeded');
185
+ });
186
+ it('should deny when user daily budget exceeded', () => {
187
+ const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 0.003 }));
188
+ const toolCall = createTestToolCall();
189
+ // Record enough to hit user daily limit
190
+ manager.record(toolCall, 0.003);
191
+ const result = manager.check(toolCall);
192
+ expect(result.allowed).toBe(false);
193
+ expect(result.reason).toContain('User daily budget exceeded');
194
+ });
195
+ it('should deny when user monthly budget exceeded', () => {
196
+ const manager = new manager_1.BudgetManager(defaultConfig({ user_monthly_budget_usd: 0.003 }));
197
+ const toolCall = createTestToolCall();
198
+ manager.record(toolCall, 0.003);
199
+ const result = manager.check(toolCall);
200
+ expect(result.allowed).toBe(false);
201
+ expect(result.reason).toContain('User monthly budget exceeded');
202
+ });
203
+ it('should deny when workspace daily budget exceeded', () => {
204
+ const manager = new manager_1.BudgetManager(defaultConfig({ workspace_daily_budget_usd: 0.003 }));
205
+ const toolCall = createTestToolCall();
206
+ manager.record(toolCall, 0.003);
207
+ const result = manager.check(toolCall);
208
+ expect(result.allowed).toBe(false);
209
+ expect(result.reason).toContain('Workspace daily budget exceeded');
210
+ });
211
+ it('should deny when workspace monthly budget exceeded', () => {
212
+ const manager = new manager_1.BudgetManager(defaultConfig({ workspace_monthly_budget_usd: 0.003 }));
213
+ const toolCall = createTestToolCall();
214
+ manager.record(toolCall, 0.003);
215
+ const result = manager.check(toolCall);
216
+ expect(result.allowed).toBe(false);
217
+ expect(result.reason).toContain('Workspace monthly budget exceeded');
218
+ });
219
+ it('should still allow when exactly at task budget minus estimated cost', () => {
220
+ // task_budget = 0.010, spent = 0.009, estimated = 0.001 => 0.009 + 0.001 = 0.010 => equal, not exceeded
221
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.010 }));
222
+ const toolCall = createTestToolCall();
223
+ manager.record(toolCall, 0.009);
224
+ const result = manager.check(toolCall);
225
+ expect(result.allowed).toBe(true);
226
+ });
227
+ it('should deny when just over task budget', () => {
228
+ // task_budget = 0.010, spent = 0.0091, estimated = 0.001 => 0.0101 > 0.010
229
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.010 }));
230
+ const toolCall = createTestToolCall();
231
+ manager.record(toolCall, 0.0091);
232
+ const result = manager.check(toolCall);
233
+ expect(result.allowed).toBe(false);
234
+ expect(result.reason).toContain('Task budget exceeded');
235
+ });
236
+ it('should still allow when at exactly max_steps minus one', () => {
237
+ const manager = new manager_1.BudgetManager(defaultConfig({ max_steps_per_task: 3 }));
238
+ const toolCall = createTestToolCall();
239
+ // Record 2 steps, so next would be step 3 (exactly max)
240
+ manager.record(toolCall, 0.0001);
241
+ manager.record(toolCall, 0.0001);
242
+ const result = manager.check(toolCall);
243
+ // steps = 2, steps + 1 = 3, max_steps = 3 => 3 > 3 is false => allowed
244
+ expect(result.allowed).toBe(true);
245
+ });
246
+ });
247
+ // -----------------------------------------------------------------------
248
+ // Recording Costs
249
+ // -----------------------------------------------------------------------
250
+ describe('record', () => {
251
+ let manager;
252
+ beforeEach(() => {
253
+ manager = new manager_1.BudgetManager(defaultConfig());
254
+ });
255
+ it('should update task spent_usd after recording', () => {
256
+ const toolCall = createTestToolCall();
257
+ manager.record(toolCall, 0.005);
258
+ const report = manager.getReport(toolCall, 0);
259
+ expect(report.spent_cost_usd_task).toBe(0.005);
260
+ });
261
+ it('should update task steps after recording', () => {
262
+ const toolCall = createTestToolCall();
263
+ manager.record(toolCall, 0.001);
264
+ manager.record(toolCall, 0.001);
265
+ // After 2 records, spent should be 0.002 and remaining = 2.0 - 0.002 = 1.998
266
+ const report = manager.getReport(toolCall, 0);
267
+ expect(report.spent_cost_usd_task).toBeCloseTo(0.002, 6);
268
+ expect(report.remaining_cost_usd_task).toBeCloseTo(1.998, 6);
269
+ });
270
+ it('should update user daily spend', () => {
271
+ const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 1.0 }));
272
+ const toolCall = createTestToolCall();
273
+ manager.record(toolCall, 0.5);
274
+ // After recording 0.5, a subsequent check should show 0.5 spent for user daily
275
+ // We can verify indirectly: record more, then check if the total triggers denial
276
+ manager.record(toolCall, 0.5);
277
+ const result = manager.check(toolCall);
278
+ // user daily spend now 1.0 + estimated 0.001 > 1.0 => denied
279
+ expect(result.allowed).toBe(false);
280
+ expect(result.reason).toContain('User daily budget exceeded');
281
+ });
282
+ it('should update workspace daily spend', () => {
283
+ const manager = new manager_1.BudgetManager(defaultConfig({ workspace_daily_budget_usd: 0.01 }));
284
+ const toolCall = createTestToolCall();
285
+ manager.record(toolCall, 0.01);
286
+ const result = manager.check(toolCall);
287
+ expect(result.allowed).toBe(false);
288
+ expect(result.reason).toContain('Workspace daily budget exceeded');
289
+ });
290
+ it('should update user monthly spend', () => {
291
+ const manager = new manager_1.BudgetManager(defaultConfig({ user_monthly_budget_usd: 0.01 }));
292
+ const toolCall = createTestToolCall();
293
+ manager.record(toolCall, 0.01);
294
+ const result = manager.check(toolCall);
295
+ expect(result.allowed).toBe(false);
296
+ expect(result.reason).toContain('User monthly budget exceeded');
297
+ });
298
+ it('should update workspace monthly spend', () => {
299
+ const manager = new manager_1.BudgetManager(defaultConfig({ workspace_monthly_budget_usd: 0.01 }));
300
+ const toolCall = createTestToolCall();
301
+ manager.record(toolCall, 0.01);
302
+ const result = manager.check(toolCall);
303
+ expect(result.allowed).toBe(false);
304
+ expect(result.reason).toContain('Workspace monthly budget exceeded');
305
+ });
306
+ it('should accumulate costs across multiple records', () => {
307
+ const toolCall = createTestToolCall();
308
+ manager.record(toolCall, 0.001);
309
+ manager.record(toolCall, 0.002);
310
+ manager.record(toolCall, 0.003);
311
+ const report = manager.getReport(toolCall, 0);
312
+ expect(report.spent_cost_usd_task).toBeCloseTo(0.006, 6);
313
+ });
314
+ });
315
+ // -----------------------------------------------------------------------
316
+ // Retry Tracking
317
+ // -----------------------------------------------------------------------
318
+ describe('recordRetry', () => {
319
+ it('should allow retries within limit', () => {
320
+ const manager = new manager_1.BudgetManager(defaultConfig({ max_retries_per_call: 3 }));
321
+ const r1 = manager.recordRetry('tc-001');
322
+ expect(r1.allowed).toBe(true);
323
+ expect(r1.count).toBe(1);
324
+ const r2 = manager.recordRetry('tc-001');
325
+ expect(r2.allowed).toBe(true);
326
+ expect(r2.count).toBe(2);
327
+ const r3 = manager.recordRetry('tc-001');
328
+ expect(r3.allowed).toBe(true);
329
+ expect(r3.count).toBe(3);
330
+ });
331
+ it('should deny retries when limit exceeded', () => {
332
+ const manager = new manager_1.BudgetManager(defaultConfig({ max_retries_per_call: 2 }));
333
+ manager.recordRetry('tc-001');
334
+ manager.recordRetry('tc-001');
335
+ const r3 = manager.recordRetry('tc-001');
336
+ expect(r3.allowed).toBe(false);
337
+ expect(r3.count).toBe(3);
338
+ });
339
+ it('should track retries independently per tool call', () => {
340
+ const manager = new manager_1.BudgetManager(defaultConfig({ max_retries_per_call: 2 }));
341
+ manager.recordRetry('tc-001');
342
+ manager.recordRetry('tc-001');
343
+ // tc-002 has its own counter
344
+ const r1 = manager.recordRetry('tc-002');
345
+ expect(r1.allowed).toBe(true);
346
+ expect(r1.count).toBe(1);
347
+ // tc-001 is at its limit
348
+ const r3 = manager.recordRetry('tc-001');
349
+ expect(r3.allowed).toBe(false);
350
+ expect(r3.count).toBe(3);
351
+ });
352
+ });
353
+ // -----------------------------------------------------------------------
354
+ // Budget Report
355
+ // -----------------------------------------------------------------------
356
+ describe('getReport', () => {
357
+ it('should calculate correct report for fresh task', () => {
358
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 5.0 }));
359
+ const toolCall = createTestToolCall();
360
+ const report = manager.getReport(toolCall, 0.01);
361
+ expect(report.estimated_cost_usd).toBe(0.01);
362
+ expect(report.spent_cost_usd_task).toBe(0);
363
+ expect(report.remaining_cost_usd_task).toBe(5.0);
364
+ });
365
+ it('should calculate correct remaining after spend', () => {
366
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 1.0 }));
367
+ const toolCall = createTestToolCall();
368
+ manager.record(toolCall, 0.3);
369
+ const report = manager.getReport(toolCall, 0.1);
370
+ expect(report.estimated_cost_usd).toBe(0.1);
371
+ expect(report.spent_cost_usd_task).toBeCloseTo(0.3, 6);
372
+ expect(report.remaining_cost_usd_task).toBeCloseTo(0.7, 6);
373
+ });
374
+ it('should clamp remaining to zero when overspent', () => {
375
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.001 }));
376
+ const toolCall = createTestToolCall();
377
+ manager.record(toolCall, 0.005);
378
+ const report = manager.getReport(toolCall, 0);
379
+ // remaining should be Math.max(0, 0.001 - 0.005) = 0
380
+ expect(report.remaining_cost_usd_task).toBe(0);
381
+ });
382
+ });
383
+ // -----------------------------------------------------------------------
384
+ // Reset
385
+ // -----------------------------------------------------------------------
386
+ describe('reset', () => {
387
+ it('should clear all internal state', () => {
388
+ const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 1.0 }));
389
+ const toolCall = createTestToolCall();
390
+ // Record some state
391
+ manager.record(toolCall, 0.5);
392
+ manager.recordRetry('tc-001');
393
+ // After reset, everything should be fresh
394
+ manager.reset();
395
+ const report = manager.getReport(toolCall, 0.001);
396
+ expect(report.spent_cost_usd_task).toBe(0);
397
+ expect(report.remaining_cost_usd_task).toBe(2.0);
398
+ // Retries should also be cleared
399
+ const retry = manager.recordRetry('tc-001');
400
+ expect(retry.count).toBe(1);
401
+ expect(retry.allowed).toBe(true);
402
+ // Budget check should pass again
403
+ const result = manager.check(toolCall);
404
+ expect(result.allowed).toBe(true);
405
+ });
406
+ });
407
+ // -----------------------------------------------------------------------
408
+ // Multiple Tasks Tracked Independently
409
+ // -----------------------------------------------------------------------
410
+ describe('multiple tasks', () => {
411
+ it('should track tasks independently', () => {
412
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.01 }));
413
+ const task1 = createTestToolCall({ task_id: 'task-A' });
414
+ const task2 = createTestToolCall({ task_id: 'task-B' });
415
+ manager.record(task1, 0.008);
416
+ manager.record(task2, 0.001);
417
+ // task-A is nearly exhausted
418
+ const report1 = manager.getReport(task1, 0);
419
+ expect(report1.spent_cost_usd_task).toBeCloseTo(0.008, 6);
420
+ expect(report1.remaining_cost_usd_task).toBeCloseTo(0.002, 6);
421
+ // task-B has plenty remaining
422
+ const report2 = manager.getReport(task2, 0);
423
+ expect(report2.spent_cost_usd_task).toBeCloseTo(0.001, 6);
424
+ expect(report2.remaining_cost_usd_task).toBeCloseTo(0.009, 6);
425
+ // task-A next call should be denied (0.008 + 0.001 estimated > 0.01? No, 0.009 <= 0.01)
426
+ const check1 = manager.check(task1);
427
+ expect(check1.allowed).toBe(true);
428
+ // Record more on task-A to push it over
429
+ manager.record(task1, 0.002);
430
+ const check1b = manager.check(task1);
431
+ expect(check1b.allowed).toBe(false);
432
+ expect(check1b.reason).toContain('Task budget exceeded');
433
+ // task-B should still be fine
434
+ const check2 = manager.check(task2);
435
+ expect(check2.allowed).toBe(true);
436
+ });
437
+ it('should share user spend across tasks', () => {
438
+ const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 0.005, task_budget_usd: 10 }));
439
+ const task1 = createTestToolCall({ task_id: 'task-A', actor: { type: 'agent', id: 'agent-001' } });
440
+ const task2 = createTestToolCall({ task_id: 'task-B', actor: { type: 'agent', id: 'agent-001' } });
441
+ // Spend across two tasks under the same user
442
+ manager.record(task1, 0.003);
443
+ manager.record(task2, 0.002);
444
+ // Total user daily spend = 0.005, any further call should be denied
445
+ const check = manager.check(task2);
446
+ expect(check.allowed).toBe(false);
447
+ expect(check.reason).toContain('User daily budget exceeded');
448
+ });
449
+ it('should share workspace spend across different actors', () => {
450
+ const manager = new manager_1.BudgetManager(defaultConfig({ workspace_daily_budget_usd: 0.005, task_budget_usd: 10 }));
451
+ const tc1 = createTestToolCall({
452
+ task_id: 'task-A',
453
+ workspace_id: 'ws-001',
454
+ actor: { type: 'agent', id: 'agent-001' },
455
+ });
456
+ const tc2 = createTestToolCall({
457
+ task_id: 'task-B',
458
+ workspace_id: 'ws-001',
459
+ actor: { type: 'agent', id: 'agent-002' },
460
+ });
461
+ manager.record(tc1, 0.003);
462
+ manager.record(tc2, 0.002);
463
+ // Both share workspace spend; total = 0.005
464
+ const check = manager.check(tc2);
465
+ expect(check.allowed).toBe(false);
466
+ expect(check.reason).toContain('Workspace daily budget exceeded');
467
+ });
468
+ });
469
+ // -----------------------------------------------------------------------
470
+ // Default Config Values
471
+ // -----------------------------------------------------------------------
472
+ describe('default config values', () => {
473
+ it('should apply default task_budget_usd when not provided', () => {
474
+ const manager = new manager_1.BudgetManager({});
475
+ const config = manager.getConfig();
476
+ expect(config.task_budget_usd).toBe(2.0);
477
+ });
478
+ it('should apply default max_steps_per_task when not provided', () => {
479
+ const manager = new manager_1.BudgetManager({});
480
+ const config = manager.getConfig();
481
+ expect(config.max_steps_per_task).toBe(50);
482
+ });
483
+ it('should apply default max_retries_per_call when not provided', () => {
484
+ const manager = new manager_1.BudgetManager({});
485
+ const config = manager.getConfig();
486
+ expect(config.max_retries_per_call).toBe(3);
487
+ });
488
+ it('should apply default max_wall_clock_ms when not provided', () => {
489
+ const manager = new manager_1.BudgetManager({});
490
+ const config = manager.getConfig();
491
+ expect(config.max_wall_clock_ms).toBe(300000);
492
+ });
493
+ it('should leave optional budgets undefined when not provided', () => {
494
+ const manager = new manager_1.BudgetManager({});
495
+ const config = manager.getConfig();
496
+ expect(config.user_daily_budget_usd).toBeUndefined();
497
+ expect(config.user_monthly_budget_usd).toBeUndefined();
498
+ expect(config.workspace_daily_budget_usd).toBeUndefined();
499
+ expect(config.workspace_monthly_budget_usd).toBeUndefined();
500
+ });
501
+ it('should override defaults when explicit values provided', () => {
502
+ const manager = new manager_1.BudgetManager({
503
+ task_budget_usd: 10,
504
+ max_steps_per_task: 100,
505
+ max_retries_per_call: 5,
506
+ max_wall_clock_ms: 600000,
507
+ });
508
+ const config = manager.getConfig();
509
+ expect(config.task_budget_usd).toBe(10);
510
+ expect(config.max_steps_per_task).toBe(100);
511
+ expect(config.max_retries_per_call).toBe(5);
512
+ expect(config.max_wall_clock_ms).toBe(600000);
513
+ });
514
+ it('should return a copy of config (not the original)', () => {
515
+ const manager = new manager_1.BudgetManager({});
516
+ const config1 = manager.getConfig();
517
+ const config2 = manager.getConfig();
518
+ expect(config1).toEqual(config2);
519
+ expect(config1).not.toBe(config2);
520
+ });
521
+ });
522
+ // -----------------------------------------------------------------------
523
+ // Budget check report structure
524
+ // -----------------------------------------------------------------------
525
+ describe('check report structure', () => {
526
+ it('should include all required fields in report when allowed', () => {
527
+ const manager = new manager_1.BudgetManager(defaultConfig());
528
+ const toolCall = createTestToolCall();
529
+ const result = manager.check(toolCall);
530
+ expect(result.report).toHaveProperty('estimated_cost_usd');
531
+ expect(result.report).toHaveProperty('spent_cost_usd_task');
532
+ expect(result.report).toHaveProperty('remaining_cost_usd_task');
533
+ expect(typeof result.report.estimated_cost_usd).toBe('number');
534
+ expect(typeof result.report.spent_cost_usd_task).toBe('number');
535
+ expect(typeof result.report.remaining_cost_usd_task).toBe('number');
536
+ });
537
+ it('should include report even when denied', () => {
538
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.0001 }));
539
+ const toolCall = createTestToolCall();
540
+ manager.record(toolCall, 0.001);
541
+ const result = manager.check(toolCall);
542
+ expect(result.allowed).toBe(false);
543
+ expect(result.report).toBeDefined();
544
+ expect(result.report.estimated_cost_usd).toBeGreaterThan(0);
545
+ });
546
+ });
547
+ // -----------------------------------------------------------------------
548
+ // Configurable Cost Table
549
+ // -----------------------------------------------------------------------
550
+ describe('configurable cost table', () => {
551
+ it('should use default cost table when no override provided', () => {
552
+ const manager = new manager_1.BudgetManager(defaultConfig());
553
+ const costTable = manager.getCostTable();
554
+ expect(costTable['http.request']).toBe(0.001);
555
+ expect(costTable['default']).toBe(0.001);
556
+ });
557
+ it('should merge custom cost_table over defaults', () => {
558
+ const manager = new manager_1.BudgetManager(defaultConfig({
559
+ cost_table: {
560
+ 'http.request': 0.01,
561
+ 'llm.completion': 0.05,
562
+ },
563
+ }));
564
+ const costTable = manager.getCostTable();
565
+ // Custom overrides applied
566
+ expect(costTable['http.request']).toBe(0.01);
567
+ expect(costTable['llm.completion']).toBe(0.05);
568
+ // Defaults preserved
569
+ expect(costTable['slack.post']).toBe(0.003);
570
+ expect(costTable['default']).toBe(0.001);
571
+ });
572
+ it('should use custom cost_table values for estimateCost', () => {
573
+ const manager = new manager_1.BudgetManager(defaultConfig({
574
+ cost_table: { 'http.request': 0.05 },
575
+ }));
576
+ const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'read' } });
577
+ const estimate = manager.estimateCost(toolCall);
578
+ expect(estimate.estimated_cost_usd).toBe(0.05);
579
+ });
580
+ it('should allow overriding the default cost', () => {
581
+ const manager = new manager_1.BudgetManager(defaultConfig({
582
+ cost_table: { 'default': 0.01 },
583
+ }));
584
+ const toolCall = createTestToolCall({ tool: { name: 'unknown.tool', capability: 'read' } });
585
+ const estimate = manager.estimateCost(toolCall);
586
+ expect(estimate.estimated_cost_usd).toBe(0.01);
587
+ });
588
+ });
589
+ // -----------------------------------------------------------------------
590
+ // CostRecord recording
591
+ // -----------------------------------------------------------------------
592
+ describe('record with CostRecord', () => {
593
+ it('should accept a CostRecord object', () => {
594
+ const manager = new manager_1.BudgetManager(defaultConfig());
595
+ const toolCall = createTestToolCall();
596
+ manager.record(toolCall, {
597
+ estimated_cost_usd: 0.001,
598
+ actual_cost_usd: 0.015,
599
+ usage: { input_tokens: 500, output_tokens: 200, total_tokens: 700, source: 'headers' },
600
+ });
601
+ const report = manager.getReport(toolCall, 0);
602
+ // Should have used actual_cost_usd (0.015) for spend tracking
603
+ expect(report.spent_cost_usd_task).toBe(0.015);
604
+ });
605
+ it('should fall back to estimated_cost_usd when actual is not provided', () => {
606
+ const manager = new manager_1.BudgetManager(defaultConfig());
607
+ const toolCall = createTestToolCall();
608
+ manager.record(toolCall, {
609
+ estimated_cost_usd: 0.005,
610
+ });
611
+ const report = manager.getReport(toolCall, 0);
612
+ expect(report.spent_cost_usd_task).toBe(0.005);
613
+ });
614
+ it('should store CostRecord for retrieval', () => {
615
+ const manager = new manager_1.BudgetManager(defaultConfig());
616
+ const toolCall = createTestToolCall();
617
+ const costRecord = {
618
+ estimated_cost_usd: 0.001,
619
+ actual_cost_usd: 0.015,
620
+ usage: { input_tokens: 500, output_tokens: 200 },
621
+ };
622
+ manager.record(toolCall, costRecord);
623
+ const retrieved = manager.getCostRecord(toolCall.tool_call_id);
624
+ expect(retrieved).toEqual(costRecord);
625
+ });
626
+ it('should return undefined for unknown tool_call_id', () => {
627
+ const manager = new manager_1.BudgetManager(defaultConfig());
628
+ expect(manager.getCostRecord('nonexistent')).toBeUndefined();
629
+ });
630
+ it('should still accept plain number for backward compatibility', () => {
631
+ const manager = new manager_1.BudgetManager(defaultConfig());
632
+ const toolCall = createTestToolCall();
633
+ manager.record(toolCall, 0.003);
634
+ const report = manager.getReport(toolCall, 0);
635
+ expect(report.spent_cost_usd_task).toBe(0.003);
636
+ // No CostRecord stored when using plain number
637
+ expect(manager.getCostRecord(toolCall.tool_call_id)).toBeUndefined();
638
+ });
639
+ });
640
+ // -----------------------------------------------------------------------
641
+ // getReportWithActual
642
+ // -----------------------------------------------------------------------
643
+ describe('getReportWithActual', () => {
644
+ it('should include actual_cost_usd and usage in report', () => {
645
+ const manager = new manager_1.BudgetManager(defaultConfig());
646
+ const toolCall = createTestToolCall();
647
+ const usage = { input_tokens: 500, output_tokens: 200, total_tokens: 700, source: 'headers' };
648
+ const report = manager.getReportWithActual(toolCall, 0.001, 0.015, usage);
649
+ expect(report.estimated_cost_usd).toBe(0.001);
650
+ expect(report.actual_cost_usd).toBe(0.015);
651
+ expect(report.usage).toEqual(usage);
652
+ expect(report.spent_cost_usd_task).toBe(0);
653
+ expect(report.remaining_cost_usd_task).toBe(2.0);
654
+ });
655
+ it('should work without actual cost or usage', () => {
656
+ const manager = new manager_1.BudgetManager(defaultConfig());
657
+ const toolCall = createTestToolCall();
658
+ const report = manager.getReportWithActual(toolCall, 0.001);
659
+ expect(report.estimated_cost_usd).toBe(0.001);
660
+ expect(report.actual_cost_usd).toBeUndefined();
661
+ expect(report.usage).toBeUndefined();
662
+ });
663
+ });
664
+ // -----------------------------------------------------------------------
665
+ // Token pricing config
666
+ // -----------------------------------------------------------------------
667
+ describe('token pricing config', () => {
668
+ it('should store token_pricing in config', () => {
669
+ const manager = new manager_1.BudgetManager(defaultConfig({
670
+ token_pricing: {
671
+ 'gpt-4': { input_per_token: 0.00003, output_per_token: 0.00006 },
672
+ },
673
+ }));
674
+ const config = manager.getConfig();
675
+ expect(config.token_pricing).toBeDefined();
676
+ expect(config.token_pricing['gpt-4']).toEqual({
677
+ input_per_token: 0.00003,
678
+ output_per_token: 0.00006,
679
+ });
680
+ });
681
+ });
682
+ // -----------------------------------------------------------------------
683
+ // Budget Reservation (S5: Atomic check + reserve)
684
+ // -----------------------------------------------------------------------
685
+ describe('reserveAndCheck', () => {
686
+ it('should atomically check and reserve budget', async () => {
687
+ const manager = new manager_1.BudgetManager(defaultConfig());
688
+ const toolCall = createTestToolCall();
689
+ const result = await manager.reserveAndCheck(toolCall);
690
+ expect(result.allowed).toBe(true);
691
+ expect(result.reservationKey).toBeDefined();
692
+ expect(result.reservationKey).toContain('reservation:');
693
+ // The reserved amount should be reflected in the spent budget
694
+ const report = manager.getReport(toolCall, 0);
695
+ expect(report.spent_cost_usd_task).toBe(0.001); // estimated cost reserved
696
+ });
697
+ it('should deny when reservation would exceed task budget', async () => {
698
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.001 }));
699
+ const toolCall = createTestToolCall();
700
+ // First reservation takes the whole budget
701
+ const first = await manager.reserveAndCheck(toolCall);
702
+ expect(first.allowed).toBe(true);
703
+ // Second reservation should be denied (budget exhausted)
704
+ const second = await manager.reserveAndCheck(createTestToolCall({ tool_call_id: 'tc-002' }));
705
+ expect(second.allowed).toBe(false);
706
+ expect(second.reason).toContain('Task budget exceeded');
707
+ });
708
+ it('should prevent concurrent requests from both passing budget check', async () => {
709
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.0015 }));
710
+ const tc1 = createTestToolCall({ tool_call_id: 'tc-001' });
711
+ const tc2 = createTestToolCall({ tool_call_id: 'tc-002' });
712
+ // First reservation passes (0.001 estimated)
713
+ const r1 = await manager.reserveAndCheck(tc1);
714
+ expect(r1.allowed).toBe(true);
715
+ // Second reservation should fail (0.001 reserved + 0.001 estimated > 0.0015)
716
+ const r2 = await manager.reserveAndCheck(tc2);
717
+ expect(r2.allowed).toBe(false);
718
+ });
719
+ });
720
+ describe('commitReservation', () => {
721
+ it('should adjust budget when actual cost differs from estimate', async () => {
722
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 1.0 }));
723
+ const toolCall = createTestToolCall();
724
+ const result = await manager.reserveAndCheck(toolCall);
725
+ expect(result.allowed).toBe(true);
726
+ // Reserved 0.001 (estimated), but actual cost is 0.0005
727
+ manager.commitReservation(result.reservationKey, 0.0005);
728
+ // Budget should reflect actual cost (0.0005), not estimated (0.001)
729
+ const report = manager.getReport(toolCall, 0);
730
+ expect(report.spent_cost_usd_task).toBeCloseTo(0.0005, 6);
731
+ expect(report.remaining_cost_usd_task).toBeCloseTo(0.9995, 6);
732
+ });
733
+ it('should be a no-op for unknown reservation key', () => {
734
+ const manager = new manager_1.BudgetManager(defaultConfig());
735
+ // Should not throw
736
+ manager.commitReservation('reservation:unknown', 0.001);
737
+ });
738
+ });
739
+ describe('releaseReservation', () => {
740
+ it('should fully release reserved budget', async () => {
741
+ const manager = new manager_1.BudgetManager(defaultConfig());
742
+ const toolCall = createTestToolCall();
743
+ const result = await manager.reserveAndCheck(toolCall);
744
+ expect(result.allowed).toBe(true);
745
+ // Verify budget was reserved
746
+ const beforeRelease = manager.getReport(toolCall, 0);
747
+ expect(beforeRelease.spent_cost_usd_task).toBe(0.001);
748
+ // Release the reservation
749
+ manager.releaseReservation(result.reservationKey);
750
+ // Budget should be fully restored
751
+ const afterRelease = manager.getReport(toolCall, 0);
752
+ expect(afterRelease.spent_cost_usd_task).toBe(0);
753
+ expect(afterRelease.remaining_cost_usd_task).toBe(2.0);
754
+ });
755
+ it('should be a no-op for unknown reservation key', () => {
756
+ const manager = new manager_1.BudgetManager(defaultConfig());
757
+ // Should not throw
758
+ manager.releaseReservation('reservation:unknown');
759
+ });
760
+ it('should restore budget for subsequent reservations after release', async () => {
761
+ const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.0015 }));
762
+ const tc1 = createTestToolCall({ tool_call_id: 'tc-001' });
763
+ const tc2 = createTestToolCall({ tool_call_id: 'tc-002' });
764
+ // First reservation
765
+ const r1 = await manager.reserveAndCheck(tc1);
766
+ expect(r1.allowed).toBe(true);
767
+ // Second would fail due to budget
768
+ const r2Fail = await manager.reserveAndCheck(tc2);
769
+ expect(r2Fail.allowed).toBe(false);
770
+ // Release first reservation
771
+ manager.releaseReservation(r1.reservationKey);
772
+ // Now second should succeed
773
+ const r2Pass = await manager.reserveAndCheck(tc2);
774
+ expect(r2Pass.allowed).toBe(true);
775
+ });
776
+ });
777
+ });
778
+ //# sourceMappingURL=budget-manager.test.js.map