@homeflare/alchemy 0.42.0 → 0.44.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 (648) hide show
  1. package/README.md +10 -1
  2. package/dist/caddy/index.js +35 -1
  3. package/dist/caddy/index.js.map +3 -3
  4. package/dist/launchd/index.js +35 -1
  5. package/dist/launchd/index.js.map +3 -3
  6. package/dist/linux/index.js +35 -1
  7. package/dist/linux/index.js.map +3 -3
  8. package/dist/litellm/access-group-form.d.ts +26 -0
  9. package/dist/litellm/access-group-form.d.ts.map +1 -0
  10. package/dist/litellm/access-group-operations.d.ts +37 -0
  11. package/dist/litellm/access-group-operations.d.ts.map +1 -0
  12. package/dist/litellm/access-group-types.d.ts +49 -0
  13. package/dist/litellm/access-group-types.d.ts.map +1 -0
  14. package/dist/litellm/access-group.d.ts +55 -0
  15. package/dist/litellm/access-group.d.ts.map +1 -0
  16. package/dist/litellm/budget-errors.d.ts +36 -0
  17. package/dist/litellm/budget-errors.d.ts.map +1 -0
  18. package/dist/litellm/budget-form.d.ts +50 -0
  19. package/dist/litellm/budget-form.d.ts.map +1 -0
  20. package/dist/litellm/budget-operations.d.ts +20 -0
  21. package/dist/litellm/budget-operations.d.ts.map +1 -0
  22. package/dist/litellm/budget.d.ts +61 -0
  23. package/dist/litellm/budget.d.ts.map +1 -0
  24. package/dist/litellm/credential-errors.d.ts +131 -0
  25. package/dist/litellm/credential-errors.d.ts.map +1 -0
  26. package/dist/litellm/credential-form.d.ts +79 -0
  27. package/dist/litellm/credential-form.d.ts.map +1 -0
  28. package/dist/litellm/credential-operations.d.ts +84 -0
  29. package/dist/litellm/credential-operations.d.ts.map +1 -0
  30. package/dist/litellm/credential-rewrite.d.ts +6 -0
  31. package/dist/litellm/credential-rewrite.d.ts.map +1 -0
  32. package/dist/litellm/credential-types.d.ts +87 -0
  33. package/dist/litellm/credential-types.d.ts.map +1 -0
  34. package/dist/litellm/credential-values.d.ts +53 -0
  35. package/dist/litellm/credential-values.d.ts.map +1 -0
  36. package/dist/litellm/credential.d.ts +83 -0
  37. package/dist/litellm/credential.d.ts.map +1 -0
  38. package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
  39. package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
  40. package/dist/litellm/fake-credential-litellm.d.ts +54 -0
  41. package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
  42. package/dist/litellm/fake-keys.d.ts +53 -0
  43. package/dist/litellm/fake-keys.d.ts.map +1 -0
  44. package/dist/litellm/fake-litellm.d.ts +9 -1
  45. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  46. package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
  47. package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
  48. package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
  49. package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
  50. package/dist/litellm/fake-model-litellm.d.ts +48 -0
  51. package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
  52. package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
  53. package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
  54. package/dist/litellm/fake-policy-litellm.d.ts +32 -0
  55. package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
  56. package/dist/litellm/fake-registry-base.d.ts +42 -0
  57. package/dist/litellm/fake-registry-base.d.ts.map +1 -0
  58. package/dist/litellm/fake-stack.d.ts +8 -1
  59. package/dist/litellm/fake-stack.d.ts.map +1 -1
  60. package/dist/litellm/fake-team-litellm.d.ts +47 -0
  61. package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
  62. package/dist/litellm/fake-tool-litellm.d.ts +28 -0
  63. package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
  64. package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
  65. package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
  66. package/dist/litellm/index.d.ts +20 -0
  67. package/dist/litellm/index.d.ts.map +1 -1
  68. package/dist/litellm/index.js +3201 -2
  69. package/dist/litellm/index.js.map +70 -5
  70. package/dist/litellm/key-errors.d.ts +169 -0
  71. package/dist/litellm/key-errors.d.ts.map +1 -0
  72. package/dist/litellm/key-form.d.ts +119 -0
  73. package/dist/litellm/key-form.d.ts.map +1 -0
  74. package/dist/litellm/key-harness.d.ts +41 -0
  75. package/dist/litellm/key-harness.d.ts.map +1 -0
  76. package/dist/litellm/key-metadata-errors.d.ts +30 -0
  77. package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
  78. package/dist/litellm/key-metadata.d.ts +22 -0
  79. package/dist/litellm/key-metadata.d.ts.map +1 -0
  80. package/dist/litellm/key-operations.d.ts +63 -0
  81. package/dist/litellm/key-operations.d.ts.map +1 -0
  82. package/dist/litellm/key-secret.d.ts +33 -0
  83. package/dist/litellm/key-secret.d.ts.map +1 -0
  84. package/dist/litellm/key.d.ts +78 -0
  85. package/dist/litellm/key.d.ts.map +1 -0
  86. package/dist/litellm/mcp-server-credential.d.ts +50 -0
  87. package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
  88. package/dist/litellm/mcp-server-errors.d.ts +101 -0
  89. package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
  90. package/dist/litellm/mcp-server-form.d.ts +56 -0
  91. package/dist/litellm/mcp-server-form.d.ts.map +1 -0
  92. package/dist/litellm/mcp-server-locate.d.ts +26 -0
  93. package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
  94. package/dist/litellm/mcp-server-operations.d.ts +63 -0
  95. package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
  96. package/dist/litellm/mcp-server-shadow.d.ts +24 -0
  97. package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
  98. package/dist/litellm/mcp-server-types.d.ts +118 -0
  99. package/dist/litellm/mcp-server-types.d.ts.map +1 -0
  100. package/dist/litellm/mcp-server-url.d.ts +11 -0
  101. package/dist/litellm/mcp-server-url.d.ts.map +1 -0
  102. package/dist/litellm/mcp-server.d.ts +95 -0
  103. package/dist/litellm/mcp-server.d.ts.map +1 -0
  104. package/dist/litellm/model-claim.d.ts +24 -0
  105. package/dist/litellm/model-claim.d.ts.map +1 -0
  106. package/dist/litellm/model-credential.d.ts +12 -0
  107. package/dist/litellm/model-credential.d.ts.map +1 -0
  108. package/dist/litellm/model-errors.d.ts +119 -0
  109. package/dist/litellm/model-errors.d.ts.map +1 -0
  110. package/dist/litellm/model-form.d.ts +78 -0
  111. package/dist/litellm/model-form.d.ts.map +1 -0
  112. package/dist/litellm/model-locate.d.ts +13 -0
  113. package/dist/litellm/model-locate.d.ts.map +1 -0
  114. package/dist/litellm/model-operations.d.ts +59 -0
  115. package/dist/litellm/model-operations.d.ts.map +1 -0
  116. package/dist/litellm/model-types.d.ts +91 -0
  117. package/dist/litellm/model-types.d.ts.map +1 -0
  118. package/dist/litellm/model.d.ts +88 -0
  119. package/dist/litellm/model.d.ts.map +1 -0
  120. package/dist/litellm/operations.d.ts +22 -0
  121. package/dist/litellm/operations.d.ts.map +1 -1
  122. package/dist/litellm/policy-attachment-form.d.ts +19 -0
  123. package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
  124. package/dist/litellm/policy-attachment-operations.d.ts +31 -0
  125. package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
  126. package/dist/litellm/policy-attachment-types.d.ts +48 -0
  127. package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
  128. package/dist/litellm/policy-attachment.d.ts +61 -0
  129. package/dist/litellm/policy-attachment.d.ts.map +1 -0
  130. package/dist/litellm/policy-form.d.ts +22 -0
  131. package/dist/litellm/policy-form.d.ts.map +1 -0
  132. package/dist/litellm/policy-operations.d.ts +42 -0
  133. package/dist/litellm/policy-operations.d.ts.map +1 -0
  134. package/dist/litellm/policy-types.d.ts +57 -0
  135. package/dist/litellm/policy-types.d.ts.map +1 -0
  136. package/dist/litellm/policy.d.ts +65 -0
  137. package/dist/litellm/policy.d.ts.map +1 -0
  138. package/dist/litellm/providers.d.ts +1 -1
  139. package/dist/litellm/providers.d.ts.map +1 -1
  140. package/dist/litellm/registry-errors.d.ts +62 -0
  141. package/dist/litellm/registry-errors.d.ts.map +1 -0
  142. package/dist/litellm/registry-support.d.ts +42 -0
  143. package/dist/litellm/registry-support.d.ts.map +1 -0
  144. package/dist/litellm/team-form.d.ts +26 -0
  145. package/dist/litellm/team-form.d.ts.map +1 -0
  146. package/dist/litellm/team-members.d.ts +10 -0
  147. package/dist/litellm/team-members.d.ts.map +1 -0
  148. package/dist/litellm/team-operations.d.ts +38 -0
  149. package/dist/litellm/team-operations.d.ts.map +1 -0
  150. package/dist/litellm/team-permission.d.ts +35 -0
  151. package/dist/litellm/team-permission.d.ts.map +1 -0
  152. package/dist/litellm/team-types.d.ts +101 -0
  153. package/dist/litellm/team-types.d.ts.map +1 -0
  154. package/dist/litellm/team.d.ts +67 -0
  155. package/dist/litellm/team.d.ts.map +1 -0
  156. package/dist/litellm/tool-policy-form.d.ts +21 -0
  157. package/dist/litellm/tool-policy-form.d.ts.map +1 -0
  158. package/dist/litellm/tool-policy-operations.d.ts +30 -0
  159. package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
  160. package/dist/litellm/tool-policy-types.d.ts +46 -0
  161. package/dist/litellm/tool-policy-types.d.ts.map +1 -0
  162. package/dist/litellm/tool-policy.d.ts +62 -0
  163. package/dist/litellm/tool-policy.d.ts.map +1 -0
  164. package/dist/litellm/toolset-form.d.ts +20 -0
  165. package/dist/litellm/toolset-form.d.ts.map +1 -0
  166. package/dist/litellm/toolset-operations.d.ts +39 -0
  167. package/dist/litellm/toolset-operations.d.ts.map +1 -0
  168. package/dist/litellm/toolset-types.d.ts +46 -0
  169. package/dist/litellm/toolset-types.d.ts.map +1 -0
  170. package/dist/litellm/toolset.d.ts +55 -0
  171. package/dist/litellm/toolset.d.ts.map +1 -0
  172. package/dist/openbao/auth-role-form.d.ts +9 -0
  173. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  174. package/dist/openbao/auth-role-wire.d.ts.map +1 -1
  175. package/dist/openbao/fake-engines-roles.d.ts.map +1 -1
  176. package/dist/openbao/index.js +43 -6
  177. package/dist/openbao/index.js.map +5 -5
  178. package/dist/ownership/rows.d.ts +11 -0
  179. package/dist/ownership/rows.d.ts.map +1 -1
  180. package/dist/postgres/connection.d.ts +32 -2
  181. package/dist/postgres/connection.d.ts.map +1 -1
  182. package/dist/postgres/database-sql.d.ts +28 -9
  183. package/dist/postgres/database-sql.d.ts.map +1 -1
  184. package/dist/postgres/database.d.ts +1 -1
  185. package/dist/postgres/database.d.ts.map +1 -1
  186. package/dist/postgres/fake-grants-parse.d.ts +37 -0
  187. package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
  188. package/dist/postgres/fake-grants-read.d.ts +61 -0
  189. package/dist/postgres/fake-grants-read.d.ts.map +1 -0
  190. package/dist/postgres/fake-grants-sql.d.ts +46 -0
  191. package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
  192. package/dist/postgres/fake-grants-write.d.ts +29 -0
  193. package/dist/postgres/fake-grants-write.d.ts.map +1 -0
  194. package/dist/postgres/fake-role-sql.d.ts +40 -0
  195. package/dist/postgres/fake-role-sql.d.ts.map +1 -0
  196. package/dist/postgres/fake-schema-sql.d.ts +42 -0
  197. package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
  198. package/dist/postgres/fake-sql-parse.d.ts +23 -0
  199. package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
  200. package/dist/postgres/fake-sql-quote.d.ts +12 -0
  201. package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
  202. package/dist/postgres/fake-sql.d.ts +42 -0
  203. package/dist/postgres/fake-sql.d.ts.map +1 -1
  204. package/dist/postgres/grants-attrs.d.ts +115 -0
  205. package/dist/postgres/grants-attrs.d.ts.map +1 -0
  206. package/dist/postgres/grants-declare.d.ts +88 -0
  207. package/dist/postgres/grants-declare.d.ts.map +1 -0
  208. package/dist/postgres/grants-diff.d.ts +25 -0
  209. package/dist/postgres/grants-diff.d.ts.map +1 -0
  210. package/dist/postgres/grants-errors.d.ts +137 -0
  211. package/dist/postgres/grants-errors.d.ts.map +1 -0
  212. package/dist/postgres/grants-existence.d.ts +14 -0
  213. package/dist/postgres/grants-existence.d.ts.map +1 -0
  214. package/dist/postgres/grants-ops.d.ts +65 -0
  215. package/dist/postgres/grants-ops.d.ts.map +1 -0
  216. package/dist/postgres/grants-ownership.d.ts +18 -0
  217. package/dist/postgres/grants-ownership.d.ts.map +1 -0
  218. package/dist/postgres/grants-plan-restore.d.ts +22 -0
  219. package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
  220. package/dist/postgres/grants-plan.d.ts +65 -0
  221. package/dist/postgres/grants-plan.d.ts.map +1 -0
  222. package/dist/postgres/grants-read-defaults.d.ts +17 -0
  223. package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
  224. package/dist/postgres/grants-read.d.ts +77 -0
  225. package/dist/postgres/grants-read.d.ts.map +1 -0
  226. package/dist/postgres/grants-refuse.d.ts +25 -0
  227. package/dist/postgres/grants-refuse.d.ts.map +1 -0
  228. package/dist/postgres/grants-sql.d.ts +42 -0
  229. package/dist/postgres/grants-sql.d.ts.map +1 -0
  230. package/dist/postgres/grants-words.d.ts +29 -0
  231. package/dist/postgres/grants-words.d.ts.map +1 -0
  232. package/dist/postgres/grants.d.ts +37 -0
  233. package/dist/postgres/grants.d.ts.map +1 -0
  234. package/dist/postgres/index.d.ts +35 -8
  235. package/dist/postgres/index.d.ts.map +1 -1
  236. package/dist/postgres/index.js +2063 -39
  237. package/dist/postgres/index.js.map +47 -7
  238. package/dist/postgres/installed-classifier.d.ts +13 -0
  239. package/dist/postgres/installed-classifier.d.ts.map +1 -0
  240. package/dist/postgres/providers.d.ts +17 -7
  241. package/dist/postgres/providers.d.ts.map +1 -1
  242. package/dist/postgres/psql-executor.d.ts +25 -0
  243. package/dist/postgres/psql-executor.d.ts.map +1 -0
  244. package/dist/postgres/role-attrs.d.ts +109 -0
  245. package/dist/postgres/role-attrs.d.ts.map +1 -0
  246. package/dist/postgres/role-diff.d.ts +22 -0
  247. package/dist/postgres/role-diff.d.ts.map +1 -0
  248. package/dist/postgres/role-errors.d.ts +126 -0
  249. package/dist/postgres/role-errors.d.ts.map +1 -0
  250. package/dist/postgres/role-membership-sql.d.ts +75 -0
  251. package/dist/postgres/role-membership-sql.d.ts.map +1 -0
  252. package/dist/postgres/role-provider.d.ts +11 -0
  253. package/dist/postgres/role-provider.d.ts.map +1 -0
  254. package/dist/postgres/role-scram.d.ts +6 -0
  255. package/dist/postgres/role-scram.d.ts.map +1 -0
  256. package/dist/postgres/role-secrets.d.ts +30 -0
  257. package/dist/postgres/role-secrets.d.ts.map +1 -0
  258. package/dist/postgres/role-sql.d.ts +68 -0
  259. package/dist/postgres/role-sql.d.ts.map +1 -0
  260. package/dist/postgres/role.d.ts +57 -0
  261. package/dist/postgres/role.d.ts.map +1 -0
  262. package/dist/postgres/schema-assert.d.ts +50 -0
  263. package/dist/postgres/schema-assert.d.ts.map +1 -0
  264. package/dist/postgres/schema-attrs.d.ts +51 -0
  265. package/dist/postgres/schema-attrs.d.ts.map +1 -0
  266. package/dist/postgres/schema-delete.d.ts +28 -0
  267. package/dist/postgres/schema-delete.d.ts.map +1 -0
  268. package/dist/postgres/schema-diff.d.ts +14 -0
  269. package/dist/postgres/schema-diff.d.ts.map +1 -0
  270. package/dist/postgres/schema-errors.d.ts +139 -0
  271. package/dist/postgres/schema-errors.d.ts.map +1 -0
  272. package/dist/postgres/schema-identity.d.ts +8 -0
  273. package/dist/postgres/schema-identity.d.ts.map +1 -0
  274. package/dist/postgres/schema-reconcile.d.ts +12 -0
  275. package/dist/postgres/schema-reconcile.d.ts.map +1 -0
  276. package/dist/postgres/schema-sql.d.ts +45 -0
  277. package/dist/postgres/schema-sql.d.ts.map +1 -0
  278. package/dist/postgres/schema-test-kit.d.ts +67 -0
  279. package/dist/postgres/schema-test-kit.d.ts.map +1 -0
  280. package/dist/postgres/schema.d.ts +54 -0
  281. package/dist/postgres/schema.d.ts.map +1 -0
  282. package/dist/proxmox/ceph-flag-types.d.ts +57 -0
  283. package/dist/proxmox/ceph-flag-types.d.ts.map +1 -0
  284. package/dist/proxmox/ceph-flag-wire.d.ts +77 -0
  285. package/dist/proxmox/ceph-flag-wire.d.ts.map +1 -0
  286. package/dist/proxmox/ceph-flag.d.ts +15 -50
  287. package/dist/proxmox/ceph-flag.d.ts.map +1 -1
  288. package/dist/proxmox/ceph-osd-write.d.ts +10 -13
  289. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  290. package/dist/proxmox/index.js +806 -740
  291. package/dist/proxmox/index.js.map +9 -8
  292. package/dist/release/fake-engine.d.ts +10 -0
  293. package/dist/release/fake-engine.d.ts.map +1 -1
  294. package/dist/release/index.js +35 -1
  295. package/dist/release/index.js.map +3 -3
  296. package/dist/valkey/acl-attrs.d.ts +94 -0
  297. package/dist/valkey/acl-attrs.d.ts.map +1 -0
  298. package/dist/valkey/acl-compare.d.ts +28 -0
  299. package/dist/valkey/acl-compare.d.ts.map +1 -0
  300. package/dist/valkey/acl-connection.d.ts +6 -0
  301. package/dist/valkey/acl-connection.d.ts.map +1 -0
  302. package/dist/valkey/acl-form.d.ts +62 -0
  303. package/dist/valkey/acl-form.d.ts.map +1 -0
  304. package/dist/valkey/acl-managed.d.ts +4 -0
  305. package/dist/valkey/acl-managed.d.ts.map +1 -0
  306. package/dist/valkey/acl-ops.d.ts +37 -0
  307. package/dist/valkey/acl-ops.d.ts.map +1 -0
  308. package/dist/valkey/acl-profiles.d.ts +53 -0
  309. package/dist/valkey/acl-profiles.d.ts.map +1 -0
  310. package/dist/valkey/acl-reconcile.d.ts +26 -0
  311. package/dist/valkey/acl-reconcile.d.ts.map +1 -0
  312. package/dist/valkey/acl.d.ts +24 -0
  313. package/dist/valkey/acl.d.ts.map +1 -0
  314. package/dist/valkey/connection-service.d.ts +22 -0
  315. package/dist/valkey/connection-service.d.ts.map +1 -0
  316. package/dist/valkey/connection.d.ts +59 -0
  317. package/dist/valkey/connection.d.ts.map +1 -0
  318. package/dist/valkey/errors.d.ts +165 -0
  319. package/dist/valkey/errors.d.ts.map +1 -0
  320. package/dist/valkey/fake-acl.d.ts +29 -0
  321. package/dist/valkey/fake-acl.d.ts.map +1 -0
  322. package/dist/valkey/fake-valkey.d.ts +35 -0
  323. package/dist/valkey/fake-valkey.d.ts.map +1 -0
  324. package/dist/valkey/handler-test-fixture.d.ts +20 -0
  325. package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
  326. package/dist/valkey/index.d.ts +18 -0
  327. package/dist/valkey/index.d.ts.map +1 -0
  328. package/dist/valkey/index.js +964 -0
  329. package/dist/valkey/index.js.map +27 -0
  330. package/dist/valkey/instance-attrs.d.ts +46 -0
  331. package/dist/valkey/instance-attrs.d.ts.map +1 -0
  332. package/dist/valkey/instance-form.d.ts +22 -0
  333. package/dist/valkey/instance-form.d.ts.map +1 -0
  334. package/dist/valkey/instance.d.ts +48 -0
  335. package/dist/valkey/instance.d.ts.map +1 -0
  336. package/dist/valkey/provider-socket-fixture.d.ts +8 -0
  337. package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
  338. package/dist/valkey/providers.d.ts +16 -0
  339. package/dist/valkey/providers.d.ts.map +1 -0
  340. package/dist/valkey/resp.d.ts +38 -0
  341. package/dist/valkey/resp.d.ts.map +1 -0
  342. package/dist/valkey/transport-error.d.ts +36 -0
  343. package/dist/valkey/transport-error.d.ts.map +1 -0
  344. package/dist/valkey/transport.d.ts +50 -0
  345. package/dist/valkey/transport.d.ts.map +1 -0
  346. package/docs/litellm-access-group.md +105 -0
  347. package/docs/litellm-budget.md +55 -0
  348. package/docs/litellm-credential.md +60 -0
  349. package/docs/litellm-key-metadata.md +56 -0
  350. package/docs/litellm-key.md +194 -0
  351. package/docs/litellm-mcp.md +187 -0
  352. package/docs/litellm-model.md +111 -0
  353. package/docs/litellm-policy-attachment.md +86 -0
  354. package/docs/litellm-policy.md +103 -0
  355. package/docs/litellm-team.md +117 -0
  356. package/docs/litellm-tool-policy.md +90 -0
  357. package/docs/litellm-toolset.md +92 -0
  358. package/docs/litellm.md +16 -8
  359. package/docs/peers.md +15 -6
  360. package/docs/postgres-grants-example.md +55 -0
  361. package/docs/postgres-grants.md +157 -0
  362. package/docs/postgres-role.md +114 -0
  363. package/docs/postgres-schema.md +139 -0
  364. package/docs/postgres.md +39 -0
  365. package/docs/proxmox-sdk-migration.md +45 -0
  366. package/docs/valkey-transport.md +29 -0
  367. package/docs/valkey.md +187 -0
  368. package/package.json +7 -3
  369. package/src/litellm/access-group-form.ts +105 -0
  370. package/src/litellm/access-group-operations.ts +92 -0
  371. package/src/litellm/access-group-types.ts +49 -0
  372. package/src/litellm/access-group.test.ts +197 -0
  373. package/src/litellm/access-group.ts +151 -0
  374. package/src/litellm/budget-errors.ts +38 -0
  375. package/src/litellm/budget-form.ts +94 -0
  376. package/src/litellm/budget-operations.ts +51 -0
  377. package/src/litellm/budget.test.ts +105 -0
  378. package/src/litellm/budget.ts +127 -0
  379. package/src/litellm/credential-errors.ts +183 -0
  380. package/src/litellm/credential-form.test.ts +151 -0
  381. package/src/litellm/credential-form.ts +211 -0
  382. package/src/litellm/credential-guards.test.ts +241 -0
  383. package/src/litellm/credential-operations.ts +208 -0
  384. package/src/litellm/credential-recovery.test.ts +93 -0
  385. package/src/litellm/credential-rewrite.test.ts +164 -0
  386. package/src/litellm/credential-rewrite.ts +50 -0
  387. package/src/litellm/credential-types.ts +117 -0
  388. package/src/litellm/credential-values.ts +94 -0
  389. package/src/litellm/credential.test.ts +218 -0
  390. package/src/litellm/credential.ts +227 -0
  391. package/src/litellm/fake-access-group-litellm.ts +127 -0
  392. package/src/litellm/fake-credential-litellm.ts +216 -0
  393. package/src/litellm/fake-keys.ts +190 -0
  394. package/src/litellm/fake-litellm.ts +66 -7
  395. package/src/litellm/fake-mcp-litellm.ts +214 -0
  396. package/src/litellm/fake-model-litellm-post-update.ts +103 -0
  397. package/src/litellm/fake-model-litellm.ts +233 -0
  398. package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
  399. package/src/litellm/fake-policy-litellm.ts +186 -0
  400. package/src/litellm/fake-registry-base.ts +69 -0
  401. package/src/litellm/fake-stack.ts +17 -1
  402. package/src/litellm/fake-team-litellm.ts +229 -0
  403. package/src/litellm/fake-tool-litellm.ts +88 -0
  404. package/src/litellm/fake-toolset-litellm.ts +110 -0
  405. package/src/litellm/index.ts +153 -0
  406. package/src/litellm/key-delete.test.ts +212 -0
  407. package/src/litellm/key-errors.ts +241 -0
  408. package/src/litellm/key-form.ts +200 -0
  409. package/src/litellm/key-harness.ts +121 -0
  410. package/src/litellm/key-metadata-errors.ts +49 -0
  411. package/src/litellm/key-metadata.test.ts +156 -0
  412. package/src/litellm/key-metadata.ts +74 -0
  413. package/src/litellm/key-operations.ts +184 -0
  414. package/src/litellm/key-rotation.test.ts +110 -0
  415. package/src/litellm/key-scope.test.ts +135 -0
  416. package/src/litellm/key-secret.test.ts +248 -0
  417. package/src/litellm/key-secret.ts +137 -0
  418. package/src/litellm/key-transport.test.ts +166 -0
  419. package/src/litellm/key.test.ts +231 -0
  420. package/src/litellm/key.ts +213 -0
  421. package/src/litellm/mcp-server-adopt.test.ts +86 -0
  422. package/src/litellm/mcp-server-alias.test.ts +184 -0
  423. package/src/litellm/mcp-server-credential.ts +85 -0
  424. package/src/litellm/mcp-server-errors.ts +141 -0
  425. package/src/litellm/mcp-server-exports.test.ts +15 -0
  426. package/src/litellm/mcp-server-form.test.ts +241 -0
  427. package/src/litellm/mcp-server-form.ts +233 -0
  428. package/src/litellm/mcp-server-handlers.test.ts +150 -0
  429. package/src/litellm/mcp-server-locate.ts +66 -0
  430. package/src/litellm/mcp-server-operations.ts +111 -0
  431. package/src/litellm/mcp-server-registry.test.ts +146 -0
  432. package/src/litellm/mcp-server-retype.test.ts +114 -0
  433. package/src/litellm/mcp-server-shadow.test.ts +91 -0
  434. package/src/litellm/mcp-server-shadow.ts +45 -0
  435. package/src/litellm/mcp-server-tools.test.ts +85 -0
  436. package/src/litellm/mcp-server-types.ts +143 -0
  437. package/src/litellm/mcp-server-url.ts +66 -0
  438. package/src/litellm/mcp-server.test.ts +213 -0
  439. package/src/litellm/mcp-server.ts +225 -0
  440. package/src/litellm/model-claim.ts +69 -0
  441. package/src/litellm/model-credential.ts +45 -0
  442. package/src/litellm/model-errors.ts +168 -0
  443. package/src/litellm/model-foreign-row.test.ts +173 -0
  444. package/src/litellm/model-form.test.ts +228 -0
  445. package/src/litellm/model-form.ts +223 -0
  446. package/src/litellm/model-locate.test.ts +128 -0
  447. package/src/litellm/model-locate.ts +67 -0
  448. package/src/litellm/model-operations.test.ts +93 -0
  449. package/src/litellm/model-operations.ts +146 -0
  450. package/src/litellm/model-params-preserve.test.ts +119 -0
  451. package/src/litellm/model-recovery.test.ts +57 -0
  452. package/src/litellm/model-types.ts +112 -0
  453. package/src/litellm/model.test.ts +222 -0
  454. package/src/litellm/model.ts +237 -0
  455. package/src/litellm/operations.ts +1 -1
  456. package/src/litellm/policy-attachment-form.ts +103 -0
  457. package/src/litellm/policy-attachment-operations.ts +74 -0
  458. package/src/litellm/policy-attachment-types.ts +48 -0
  459. package/src/litellm/policy-attachment.test.ts +198 -0
  460. package/src/litellm/policy-attachment.ts +178 -0
  461. package/src/litellm/policy-form.ts +122 -0
  462. package/src/litellm/policy-operations.ts +112 -0
  463. package/src/litellm/policy-types.ts +58 -0
  464. package/src/litellm/policy.test.ts +214 -0
  465. package/src/litellm/policy.ts +146 -0
  466. package/src/litellm/providers.ts +25 -1
  467. package/src/litellm/registry-errors.ts +88 -0
  468. package/src/litellm/registry-exports.test.ts +66 -0
  469. package/src/litellm/registry-support.ts +109 -0
  470. package/src/litellm/team-form.ts +140 -0
  471. package/src/litellm/team-members.test.ts +150 -0
  472. package/src/litellm/team-members.ts +58 -0
  473. package/src/litellm/team-operations.ts +90 -0
  474. package/src/litellm/team-permission.ts +159 -0
  475. package/src/litellm/team-read.test.ts +89 -0
  476. package/src/litellm/team-types.ts +106 -0
  477. package/src/litellm/team.test.ts +206 -0
  478. package/src/litellm/team.ts +159 -0
  479. package/src/litellm/tool-policy-form.ts +94 -0
  480. package/src/litellm/tool-policy-operations.ts +58 -0
  481. package/src/litellm/tool-policy-types.ts +48 -0
  482. package/src/litellm/tool-policy.test.ts +172 -0
  483. package/src/litellm/tool-policy.ts +125 -0
  484. package/src/litellm/toolset-form.ts +104 -0
  485. package/src/litellm/toolset-operations.ts +90 -0
  486. package/src/litellm/toolset-types.ts +47 -0
  487. package/src/litellm/toolset.test.ts +198 -0
  488. package/src/litellm/toolset.ts +151 -0
  489. package/src/openbao/auth-role-form.test.ts +37 -0
  490. package/src/openbao/auth-role-form.ts +12 -1
  491. package/src/openbao/auth-role-wire.test.ts +1 -0
  492. package/src/openbao/auth-role-wire.ts +13 -1
  493. package/src/openbao/distilled-retry.test.ts +1 -0
  494. package/src/openbao/fake-engines-roles.ts +7 -1
  495. package/src/ownership/ownership.test.ts +33 -1
  496. package/src/ownership/rows.ts +53 -0
  497. package/src/postgres/connection.ts +72 -3
  498. package/src/postgres/database-bigint-serialization.test.ts +1 -0
  499. package/src/postgres/database-refusals.test.ts +2 -2
  500. package/src/postgres/database-sql.ts +40 -12
  501. package/src/postgres/database.ts +3 -2
  502. package/src/postgres/fake-grants-parse.ts +189 -0
  503. package/src/postgres/fake-grants-read.ts +250 -0
  504. package/src/postgres/fake-grants-sql.ts +207 -0
  505. package/src/postgres/fake-grants-write.ts +90 -0
  506. package/src/postgres/fake-role-sql.ts +220 -0
  507. package/src/postgres/fake-schema-semantics.test.ts +36 -0
  508. package/src/postgres/fake-schema-sql.ts +146 -0
  509. package/src/postgres/fake-sql-parse.ts +86 -0
  510. package/src/postgres/fake-sql-quote.ts +36 -0
  511. package/src/postgres/fake-sql.ts +161 -46
  512. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
  513. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
  514. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
  515. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
  516. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
  517. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
  518. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
  519. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
  520. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
  521. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
  522. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
  523. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
  524. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
  525. package/src/postgres/grants-acl.test.ts +61 -0
  526. package/src/postgres/grants-attrs.ts +185 -0
  527. package/src/postgres/grants-database.test.ts +158 -0
  528. package/src/postgres/grants-declare.ts +172 -0
  529. package/src/postgres/grants-delete-proof.test.ts +154 -0
  530. package/src/postgres/grants-diff.ts +101 -0
  531. package/src/postgres/grants-errors.ts +215 -0
  532. package/src/postgres/grants-existence-guards.test.ts +113 -0
  533. package/src/postgres/grants-existence.ts +101 -0
  534. package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
  535. package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
  536. package/src/postgres/grants-lifecycle.test.ts +181 -0
  537. package/src/postgres/grants-ops.ts +229 -0
  538. package/src/postgres/grants-owner-revoke.test.ts +121 -0
  539. package/src/postgres/grants-ownership.ts +49 -0
  540. package/src/postgres/grants-plan-collateral.test.ts +202 -0
  541. package/src/postgres/grants-plan-diff.test.ts +204 -0
  542. package/src/postgres/grants-plan-restore.ts +43 -0
  543. package/src/postgres/grants-plan-revocations.test.ts +69 -0
  544. package/src/postgres/grants-plan.test.ts +148 -0
  545. package/src/postgres/grants-plan.ts +245 -0
  546. package/src/postgres/grants-provenance.test.ts +134 -0
  547. package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
  548. package/src/postgres/grants-read-absent.test.ts +91 -0
  549. package/src/postgres/grants-read-defaults.ts +51 -0
  550. package/src/postgres/grants-read.ts +250 -0
  551. package/src/postgres/grants-refusals.test.ts +136 -0
  552. package/src/postgres/grants-refuse.ts +119 -0
  553. package/src/postgres/grants-relkind-wire.test.ts +111 -0
  554. package/src/postgres/grants-repair-plan.test.ts +201 -0
  555. package/src/postgres/grants-sql.test.ts +128 -0
  556. package/src/postgres/grants-sql.ts +144 -0
  557. package/src/postgres/grants-words.ts +34 -0
  558. package/src/postgres/grants.ts +110 -0
  559. package/src/postgres/index.ts +137 -7
  560. package/src/postgres/installed-classifier.ts +36 -0
  561. package/src/postgres/providers.ts +44 -7
  562. package/src/postgres/psql-executor-escaping.test.ts +67 -0
  563. package/src/postgres/psql-executor.test.ts +167 -0
  564. package/src/postgres/psql-executor.ts +170 -0
  565. package/src/postgres/role-adopt.test.ts +98 -0
  566. package/src/postgres/role-attrs.ts +134 -0
  567. package/src/postgres/role-diff.ts +89 -0
  568. package/src/postgres/role-errors.ts +198 -0
  569. package/src/postgres/role-guard.test.ts +60 -0
  570. package/src/postgres/role-identity.test.ts +185 -0
  571. package/src/postgres/role-lifecycle.test.ts +228 -0
  572. package/src/postgres/role-membership-sql.ts +198 -0
  573. package/src/postgres/role-password.test.ts +158 -0
  574. package/src/postgres/role-privilege.test.ts +80 -0
  575. package/src/postgres/role-provenance.test.ts +200 -0
  576. package/src/postgres/role-provider.ts +104 -0
  577. package/src/postgres/role-refusals.test.ts +250 -0
  578. package/src/postgres/role-scram.test.ts +38 -0
  579. package/src/postgres/role-scram.ts +38 -0
  580. package/src/postgres/role-secrets.ts +46 -0
  581. package/src/postgres/role-sql.ts +211 -0
  582. package/src/postgres/role-valid-until.test.ts +68 -0
  583. package/src/postgres/role.ts +218 -0
  584. package/src/postgres/schema-assert.ts +139 -0
  585. package/src/postgres/schema-attrs.ts +71 -0
  586. package/src/postgres/schema-delete-proofs.test.ts +192 -0
  587. package/src/postgres/schema-delete.ts +96 -0
  588. package/src/postgres/schema-diff.ts +44 -0
  589. package/src/postgres/schema-drop-2bp01.test.ts +146 -0
  590. package/src/postgres/schema-errors.ts +209 -0
  591. package/src/postgres/schema-handlers.test.ts +193 -0
  592. package/src/postgres/schema-identity.ts +39 -0
  593. package/src/postgres/schema-lifecycle.test.ts +246 -0
  594. package/src/postgres/schema-race.test.ts +70 -0
  595. package/src/postgres/schema-reconcile.ts +100 -0
  596. package/src/postgres/schema-refusals.test.ts +144 -0
  597. package/src/postgres/schema-socket.test.ts +161 -0
  598. package/src/postgres/schema-sql.ts +130 -0
  599. package/src/postgres/schema-takeover.test.ts +149 -0
  600. package/src/postgres/schema-test-kit.ts +85 -0
  601. package/src/postgres/schema.ts +130 -0
  602. package/src/proxmox/ceph-flag-types.ts +70 -0
  603. package/src/proxmox/ceph-flag-wire.test.ts +72 -0
  604. package/src/proxmox/ceph-flag-wire.ts +123 -0
  605. package/src/proxmox/ceph-flag.ts +77 -130
  606. package/src/proxmox/ceph-osd-write.ts +10 -13
  607. package/src/release/binary-alias.test.ts +3 -3
  608. package/src/release/binary-claims.test.ts +3 -3
  609. package/src/release/binary-directory-umask.test.ts +65 -0
  610. package/src/release/fake-engine.ts +15 -0
  611. package/src/valkey/acl-attrs.ts +98 -0
  612. package/src/valkey/acl-compare.ts +57 -0
  613. package/src/valkey/acl-connection.ts +13 -0
  614. package/src/valkey/acl-delete.test.ts +127 -0
  615. package/src/valkey/acl-form.ts +239 -0
  616. package/src/valkey/acl-managed.ts +13 -0
  617. package/src/valkey/acl-ops.ts +122 -0
  618. package/src/valkey/acl-ownership.test.ts +118 -0
  619. package/src/valkey/acl-prefix.test.ts +38 -0
  620. package/src/valkey/acl-profiles.ts +81 -0
  621. package/src/valkey/acl-reconcile.ts +180 -0
  622. package/src/valkey/acl-reserved.test.ts +69 -0
  623. package/src/valkey/acl-revocation.test.ts +94 -0
  624. package/src/valkey/acl-seat-prefix.test.ts +111 -0
  625. package/src/valkey/acl-validation.test.ts +54 -0
  626. package/src/valkey/acl.test.ts +215 -0
  627. package/src/valkey/acl.ts +169 -0
  628. package/src/valkey/connection-service.ts +46 -0
  629. package/src/valkey/connection.ts +180 -0
  630. package/src/valkey/errors.ts +239 -0
  631. package/src/valkey/fake-acl.ts +128 -0
  632. package/src/valkey/fake-valkey.ts +224 -0
  633. package/src/valkey/handler-test-fixture.ts +43 -0
  634. package/src/valkey/handlers.test.ts +205 -0
  635. package/src/valkey/index.ts +54 -0
  636. package/src/valkey/instance-attrs.ts +48 -0
  637. package/src/valkey/instance-form.ts +104 -0
  638. package/src/valkey/instance.test.ts +108 -0
  639. package/src/valkey/instance.ts +160 -0
  640. package/src/valkey/provider-socket-fixture.ts +71 -0
  641. package/src/valkey/providers.test.ts +129 -0
  642. package/src/valkey/providers.ts +21 -0
  643. package/src/valkey/resp.ts +212 -0
  644. package/src/valkey/transport-bounds.test.ts +150 -0
  645. package/src/valkey/transport-error.ts +37 -0
  646. package/src/valkey/transport.test.ts +158 -0
  647. package/src/valkey/transport.ts +109 -0
  648. package/src/valkey/upstream-bounds.test.ts +14 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The four LiteLLM `/v1/unified_access_group*` calls `LiteLLM.AccessGroup` uses, through the SDK's
3
+ * typed operations in `@distilled.cloud/litellm/access_groups` (LiteLLM 1.103.0):
4
+ *
5
+ * list `listAccessGroupsV1UnifiedAccessGroupGet` GET /v1/unified_access_group
6
+ * create `createAccessGroupV1UnifiedAccessGroupPost` POST /v1/unified_access_group
7
+ * update `updateAccessGroupV1UnifiedAccessGroupAccessGroupIdPut` PUT /v1/unified_access_group/{id}
8
+ * delete `deleteAccessGroupV1UnifiedAccessGroupAccessGroupIdDelete` DELETE /v1/unified_access_group/{id}
9
+ *
10
+ * `/v1/unified_access_group` is registered as an alias of `/v1/access_group` on the same handlers
11
+ * (`access_group_endpoints.py`, the "Alias routes" block), so the two answer identically.
12
+ * The by-id `GET` is not used: a missing id is a 404 this tag does not declare, and the list decides
13
+ * absence without depending on it.
14
+ *
15
+ * ⛔ WRITES NEED PROXY_ADMIN (`_require_proxy_admin`, 403 otherwise) and READS need admin or admin-view
16
+ * (`_require_admin_view`). A key that can only read sees every group and can change none.
17
+ * ⛔ NO MESSAGE SNIFFING. Nothing here reads a status or message. `create` of a name that exists is a
18
+ * 409 LiteLLM raises itself; the SDK does not declare it for this tag, so it reaches the caller as
19
+ * the SDK's own error, unchanged. `create` runs only after a list showed the name absent, so a 409
20
+ * means a concurrent create, which is a failure to report, not to paper over.
21
+ * ★ DELETE IS IDEMPOTENT BY A REAL READ (the budget and pass-through rule): a failed DELETE is
22
+ * swallowed only when the list no longer has the id. A 403 for lack of rights leaves the row
23
+ * live, so it re-raises the ORIGINAL error.
24
+ * ⚠️ DELETING A GROUP DETACHES IT FROM EVERY TEAM AND KEY (`delete_access_group` rewrites their
25
+ * `access_group_ids`), which is why the resource defaults to `retain`.
26
+ */
27
+ import * as ag from '@distilled.cloud/litellm/access_groups';
28
+ import * as Effect from 'effect/Effect';
29
+ import { type LitellmOpContext, throughFetch } from './operations.ts';
30
+ import { LitellmRegistryUnreadableError } from './registry-errors.ts';
31
+ import { bodyOf } from './registry-support.ts';
32
+ import { type AccessGroupAttributes } from './access-group-types.ts';
33
+ import { toAttributes } from './access-group-form.ts';
34
+
35
+ const RESOURCE = 'LiteLLM.AccessGroup';
36
+
37
+ /** Every group. The list is the table (`AccessGroupRepository(...).table.find_many`), not a registry. */
38
+ export const listAccessGroups = (): Effect.Effect<
39
+ readonly AccessGroupAttributes[],
40
+ ag.ListAccessGroupsV1UnifiedAccessGroupGetError | LitellmRegistryUnreadableError,
41
+ LitellmOpContext
42
+ > =>
43
+ throughFetch(ag.listAccessGroupsV1UnifiedAccessGroupGet({})).pipe(
44
+ Effect.flatMap((response) => {
45
+ const rows: unknown = bodyOf(response);
46
+ if (!Array.isArray(rows)) {
47
+ return Effect.fail(
48
+ new LitellmRegistryUnreadableError({ reason: 'not an array', resource: RESOURCE }),
49
+ );
50
+ }
51
+ const parsed = rows.map(toAttributes);
52
+ return parsed.every((row) => row !== undefined)
53
+ ? Effect.succeed(parsed as readonly AccessGroupAttributes[])
54
+ : Effect.fail(
55
+ new LitellmRegistryUnreadableError({
56
+ reason: 'a row has no id or name',
57
+ resource: RESOURCE,
58
+ }),
59
+ );
60
+ }),
61
+ );
62
+
63
+ export const createAccessGroup = (body: ag.CreateAccessGroupV1UnifiedAccessGroupPostRequest) =>
64
+ throughFetch(ag.createAccessGroupV1UnifiedAccessGroupPost(body)).pipe(Effect.asVoid);
65
+
66
+ export const updateAccessGroup = (
67
+ accessGroupId: string,
68
+ body: Omit<ag.UpdateAccessGroupV1UnifiedAccessGroupAccessGroupIdPutRequest, 'access_group_id'>,
69
+ ) =>
70
+ throughFetch(
71
+ ag.updateAccessGroupV1UnifiedAccessGroupAccessGroupIdPut({
72
+ ...body,
73
+ access_group_id: accessGroupId,
74
+ }),
75
+ ).pipe(Effect.asVoid);
76
+
77
+ export const deleteAccessGroup = (accessGroupId: string) =>
78
+ throughFetch(
79
+ ag.deleteAccessGroupV1UnifiedAccessGroupAccessGroupIdDelete({ access_group_id: accessGroupId }),
80
+ ).pipe(
81
+ Effect.asVoid,
82
+ Effect.catch((original) =>
83
+ listAccessGroups().pipe(
84
+ Effect.catch(() => Effect.fail(original)),
85
+ Effect.flatMap((rows) =>
86
+ rows.some((row) => row.accessGroupId === accessGroupId)
87
+ ? Effect.fail(original)
88
+ : Effect.void,
89
+ ),
90
+ ),
91
+ ),
92
+ );
@@ -0,0 +1,49 @@
1
+ /**
2
+ * What a `LiteLLM.AccessGroup` declares (props) and what is remembered about it (attributes).
3
+ *
4
+ * ★ THIS IS THE UNIFIED ACCESS GROUP, `LiteLLM_AccessGroupTable` (`/v1/unified_access_group`), not
5
+ * the two other things LiteLLM calls an "access group": a MODEL access group (`access_groups` in a
6
+ * model's `model_info`) and an MCP access group (a bare name on an MCP server row, referenced by
7
+ * `object_permission.mcp_access_groups`). A unified group bundles models, MCP servers and agents
8
+ * under one name and attaches to teams and keys (docs.litellm.ai/docs/proxy/access_groups).
9
+ * ⚠️ TOOLSETS ARE NOT IN IT. `AccessGroupCreateRequest` (`litellm/types/access_group.py`, 1.103.0)
10
+ * has `access_model_names`, `access_mcp_server_ids`, `access_agent_ids`, `assigned_team_ids` and
11
+ * `assigned_key_ids` and no toolset field. A toolset is granted through `object_permission.mcp_toolsets`
12
+ * on a team or key (`LiteLLM.Team`), not through a group.
13
+ * ★ MODELS ARE MODEL NAMES, MCP SERVERS ARE SERVER IDS. `access_model_names` holds the `model_name`
14
+ * a caller sends (`cf-code`), and `access_mcp_server_ids` holds `server_id`s. Declare a server's id
15
+ * with `server.serverId` (a `LiteLLM.MCPServer`) so the dependency edge exists.
16
+ * ⚠️ NOT MODELLED, on purpose: `assigned_team_ids`, `assigned_key_ids` and `access_agent_ids`. A
17
+ * team's group membership is declared on the TEAM (`accessGroupIds`), and LiteLLM mirrors it into
18
+ * `assigned_team_ids` in the same transaction; two resources writing one edge would fight. Agents
19
+ * are not managed here. None of the three is ever sent, and the update is a partial one, so an
20
+ * adopted group keeps whatever it has there.
21
+ */
22
+ export interface AccessGroupProps {
23
+ /**
24
+ * The group's name, and the way an existing row is ADOPTED: `access_group_name` is UNIQUE in the
25
+ * table (`access_group_endpoints.py` finds it with `find_unique`), so the match is exact. Changing
26
+ * it is an update of the same row, never a replace: the id does not change.
27
+ */
28
+ readonly accessGroupName: string;
29
+ /** Compared only when declared: adopting a row never clears text a person wrote. Blank is refused. */
30
+ readonly description?: string;
31
+ /**
32
+ * `model_name`s the group grants, compared as a SET and ALWAYS compared (default `[]`): it is an
33
+ * access grant, so an adopted group that grants more than declared is corrected, never left open.
34
+ * ⚠️ LiteLLM prunes a name itself when its last deployment is deleted
35
+ * (`management_helpers/access_group_model_sync.py`), so a plan can show an update for a model that
36
+ * was removed elsewhere.
37
+ */
38
+ readonly modelNames?: readonly string[];
39
+ /** MCP `server_id`s the group grants, compared as a set and always compared (default `[]`). */
40
+ readonly mcpServerIds?: readonly string[];
41
+ }
42
+
43
+ export interface AccessGroupAttributes {
44
+ readonly accessGroupId: string;
45
+ readonly accessGroupName: string;
46
+ readonly description: string | null;
47
+ readonly modelNames: readonly string[];
48
+ readonly mcpServerIds: readonly string[];
49
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * `LiteLLM.AccessGroup` through Alchemy's real Plan and Apply over the fake proxy
3
+ * (`fake-access-group-litellm.ts`): create, no-op, adopt, update, rename, drift and delete.
4
+ *
5
+ * ★ EVERY VALUE IS `FAKE-*`.
6
+ * ⚠️ WHAT THE FAKE MODELS IS READ FROM THE 1.103.0 SOURCE, NOT MEASURED ON A LIVE PROXY (its header).
7
+ */
8
+ import { expect, test } from 'bun:test';
9
+ import { credentials } from '@distilled.cloud/litellm/Credentials';
10
+ import * as Effect from 'effect/Effect';
11
+ import * as Layer from 'effect/Layer';
12
+ import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
13
+ import { LiteLLMAccessGroup } from './access-group.ts';
14
+ import { deleteAccessGroup } from './access-group-operations.ts';
15
+ import type { AccessGroupProps } from './access-group-types.ts';
16
+ import { FAKE_BASE } from './fake-litellm.ts';
17
+ import {
18
+ type FakeAccessGroupLitellm,
19
+ groupRow,
20
+ startFakeAccessGroupLitellm,
21
+ } from './fake-access-group-litellm.ts';
22
+ import { FAKE_KEY } from './fake-registry-base.ts';
23
+ import { fakeStack, writesOf } from './fake-stack.ts';
24
+
25
+ const stack = (fake: FakeAccessGroupLitellm) =>
26
+ fakeStack({ apiKey: FAKE_KEY, baseUrl: FAKE_BASE }, fake.fetch);
27
+ const declare = (props: AccessGroupProps, name = 'Estate') =>
28
+ Effect.gen(function* () {
29
+ yield* LiteLLMAccessGroup(name, props);
30
+ });
31
+
32
+ const estate: AccessGroupProps = {
33
+ accessGroupName: 'FAKE-estate',
34
+ description: 'FAKE estate group',
35
+ mcpServerIds: ['FAKE-server-a'],
36
+ modelNames: ['fake-code', 'fake-flash'],
37
+ };
38
+
39
+ test('creates a group with both grant lists, then a second deploy writes nothing', async () => {
40
+ const fake = startFakeAccessGroupLitellm();
41
+ const same = stack(fake);
42
+ expect(await same.deploy(declare(estate))).toEqual({ Estate: 'create' });
43
+ expect(fake.groups()[0]).toMatchObject({
44
+ access_group_name: 'FAKE-estate',
45
+ access_mcp_server_ids: ['FAKE-server-a'],
46
+ access_model_names: ['fake-code', 'fake-flash'],
47
+ description: 'FAKE estate group',
48
+ });
49
+ // ★ the id is the proxy's, and it is what the state remembers
50
+ expect(String(fake.groups()[0]?.['access_group_id'])).toStartWith('FAKE-group-');
51
+ const before = fake.requests().length;
52
+ expect(await same.deploy(declare(estate))).toEqual({ Estate: 'noop' });
53
+ expect(writesOf(fake.requests().slice(before))).toEqual([]);
54
+ });
55
+
56
+ test('an undeclared grant list means "grants nothing": it is sent as an empty list on create', async () => {
57
+ const fake = startFakeAccessGroupLitellm();
58
+ await stack(fake).deploy(declare({ accessGroupName: 'FAKE-empty' }));
59
+ expect(fake.bodies()[0]).toMatchObject({ access_mcp_server_ids: [], access_model_names: [] });
60
+ expect(fake.bodies()[0]).not.toHaveProperty('description');
61
+ });
62
+
63
+ test('a changed model set is an update that sends ONLY the field that changed', async () => {
64
+ const fake = startFakeAccessGroupLitellm();
65
+ const same = stack(fake);
66
+ await same.deploy(declare(estate));
67
+ const planned = await same.deploy(declare({ ...estate, modelNames: ['fake-code'] }));
68
+ expect(planned).toEqual({ Estate: 'update' });
69
+ expect(fake.bodies().at(-1)).toEqual({ access_model_names: ['fake-code'] });
70
+ expect(fake.groups()[0]?.['access_mcp_server_ids']).toEqual(['FAKE-server-a']);
71
+ });
72
+
73
+ test('a rename is an update of the SAME row: the id does not change', async () => {
74
+ const fake = startFakeAccessGroupLitellm();
75
+ const same = stack(fake);
76
+ await same.deploy(declare(estate));
77
+ const id = fake.groups()[0]?.['access_group_id'];
78
+ expect(await same.deploy(declare({ ...estate, accessGroupName: 'FAKE-renamed' }))).toEqual({
79
+ Estate: 'update',
80
+ });
81
+ expect(fake.groups()).toHaveLength(1);
82
+ expect(fake.groups()[0]).toMatchObject({
83
+ access_group_id: id,
84
+ access_group_name: 'FAKE-renamed',
85
+ });
86
+ });
87
+
88
+ test('a live row is Unowned: refused without --adopt, taken by name with it, and a matching row writes nothing', async () => {
89
+ const live = groupRow({
90
+ access_group_id: 'FAKE-live-1',
91
+ access_group_name: 'FAKE-estate',
92
+ access_mcp_server_ids: ['FAKE-server-a'],
93
+ access_model_names: ['fake-code', 'fake-flash'],
94
+ description: 'FAKE estate group',
95
+ });
96
+ const fake = startFakeAccessGroupLitellm({ seed: [live] });
97
+ const engine = stack(fake);
98
+ await expect(engine.deploy(declare(estate))).rejects.toThrow();
99
+ expect(writesOf(fake.requests())).toEqual([]);
100
+ expect(Object.values(await engine.deploy(declare(estate), { adopt: true }))).toEqual(['adopted']);
101
+ expect(writesOf(fake.requests())).toEqual([]);
102
+ });
103
+
104
+ test('adopting a group that grants MORE than declared corrects it, and leaves undeclared text and teams alone', async () => {
105
+ const wide = groupRow({
106
+ access_group_id: 'FAKE-live-2',
107
+ access_group_name: 'FAKE-estate',
108
+ access_mcp_server_ids: ['FAKE-server-a', 'FAKE-server-extra'],
109
+ access_model_names: ['fake-code', 'fake-flash', 'fake-extra'],
110
+ assigned_team_ids: ['FAKE-team-1'],
111
+ description: 'written by a person',
112
+ });
113
+ const fake = startFakeAccessGroupLitellm({ seed: [wide] });
114
+ const { description: _kept, ...withoutDescription } = estate;
115
+ await stack(fake).deploy(declare(withoutDescription), { adopt: true });
116
+ expect(fake.groups()[0]).toMatchObject({
117
+ access_mcp_server_ids: ['FAKE-server-a'],
118
+ access_model_names: ['fake-code', 'fake-flash'],
119
+ assigned_team_ids: ['FAKE-team-1'],
120
+ description: 'written by a person',
121
+ });
122
+ // ★ a partial update: the team edge is not in the body at all
123
+ expect(fake.bodies()[0]).not.toHaveProperty('assigned_team_ids');
124
+ expect(fake.bodies()[0]).not.toHaveProperty('description');
125
+ });
126
+
127
+ test('reconcile compares the LIVE row: after LiteLLM prunes a model name itself, the next update puts it back', async () => {
128
+ const fake = startFakeAccessGroupLitellm();
129
+ const same = stack(fake);
130
+ await same.deploy(declare(estate));
131
+ fake.dropModel('fake-flash'); // ⚠️ access_group_model_sync.py: a deleted last deployment
132
+ // ★ Alchemy plans from state, so an unchanged declaration is a no-op even now (beta.79 reads live
133
+ // state only to adopt); a declaration that changes anything reaches reconcile, which reads live
134
+ expect(await same.deploy(declare(estate))).toEqual({ Estate: 'noop' });
135
+ expect(await same.deploy(declare({ ...estate, description: 'FAKE changed' }))).toEqual({
136
+ Estate: 'update',
137
+ });
138
+ expect(fake.bodies().at(-1)).toEqual({
139
+ access_model_names: ['fake-code', 'fake-flash'],
140
+ description: 'FAKE changed',
141
+ });
142
+ });
143
+
144
+ test('a proxy that drops an empty-list edit fails the deploy instead of claiming success', async () => {
145
+ // ⚠️ the fake's own model of a truthiness check on the edit route — unmeasured at 1.103.0
146
+ const fake = startFakeAccessGroupLitellm({ editIgnoresEmpty: true });
147
+ const same = stack(fake);
148
+ await same.deploy(declare(estate));
149
+ await expect(same.deploy(declare({ ...estate, mcpServerIds: [] }))).rejects.toThrow();
150
+ });
151
+
152
+ test('a refused declaration fails the plan before a single request', async () => {
153
+ for (const props of [
154
+ { ...estate, accessGroupName: ' ' },
155
+ { ...estate, accessGroupName: ' padded' },
156
+ { ...estate, description: ' ' },
157
+ { ...estate, modelNames: ['fake-code', 'fake-code'] },
158
+ { ...estate, mcpServerIds: ['FAKE-server-a', ''] },
159
+ ]) {
160
+ const fake = startFakeAccessGroupLitellm();
161
+ await expect(stack(fake).deploy(declare(props))).rejects.toThrow();
162
+ expect(fake.requests()).toEqual([]);
163
+ }
164
+ });
165
+
166
+ test('removing the declaration RETAINS the row: nothing deletes a group unless the stack opts in', async () => {
167
+ const fake = startFakeAccessGroupLitellm();
168
+ const same = stack(fake);
169
+ await same.deploy(declare(estate));
170
+ await same.deploy(Effect.void);
171
+ expect(fake.groups()).toHaveLength(1);
172
+ });
173
+
174
+ const run = <A, E>(fetchFn: typeof globalThis.fetch, effect: Effect.Effect<A, E, unknown>) =>
175
+ Effect.runPromise(
176
+ effect.pipe(
177
+ Effect.provide(FetchHttpClient.layer),
178
+ Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)),
179
+ Effect.provide(credentials({ apiKey: FAKE_KEY, baseUrl: FAKE_BASE })),
180
+ ) as Effect.Effect<A, E, never>,
181
+ );
182
+
183
+ test('delete answers 204 with no body, and a missing id is swallowed only because a real read says so', async () => {
184
+ const live = groupRow({ access_group_id: 'FAKE-live-3', access_group_name: 'FAKE-estate' });
185
+ const fake = startFakeAccessGroupLitellm({ seed: [live] });
186
+ await run(fake.fetch, deleteAccessGroup('FAKE-live-3'));
187
+ expect(fake.groups()).toEqual([]);
188
+ // already gone: the DELETE 404s, the re-list confirms absence, so it is success
189
+ await run(fake.fetch, deleteAccessGroup('FAKE-live-3'));
190
+ });
191
+
192
+ test('a delete refused for lack of rights re-raises the ORIGINAL error and the row stays', async () => {
193
+ const live = groupRow({ access_group_id: 'FAKE-live-4', access_group_name: 'FAKE-estate' });
194
+ const fake = startFakeAccessGroupLitellm({ forbidWrites: true, seed: [live] });
195
+ await expect(run(fake.fetch, deleteAccessGroup('FAKE-live-4'))).rejects.toThrow();
196
+ expect(fake.groups()).toHaveLength(1);
197
+ });
@@ -0,0 +1,151 @@
1
+ /**
2
+ * `LiteLLM.AccessGroup` — one row of LiteLLM's unified access group table
3
+ * (`LiteLLM_AccessGroupTable`, `/v1/unified_access_group`): a named bundle of models and MCP servers
4
+ * that teams and keys are attached to. Types and what is left out: access-group-types.ts.
5
+ *
6
+ * ★ ADOPT BY NAME. `access_group_name` is unique in the table, so `accessGroupName` finds the one
7
+ * live row that carries it. A live row with no state is `Unowned`, so it needs `--adopt`.
8
+ * ★ `defaultRemovalPolicy: 'retain'` — deleting a group detaches it from every team and key that
9
+ * holds it (`delete_access_group` rewrites their `access_group_ids`), and those callers lose the
10
+ * models and servers it granted. Opt in with `.pipe(RemovalPolicy.destroy())`.
11
+ * ★ THE GRANTS ARE THE DECLARATION. `modelNames` and `mcpServerIds` are always compared (an
12
+ * undeclared list means "grants nothing"), so an adopted group that grants more is corrected.
13
+ * ⚠️ NO LICENCE GATE ON THESE ROUTES: `access_group_endpoints.py` has no `premium_user` check
14
+ * (grep, 1.103.0), and docs.litellm.ai/docs/proxy/access_groups makes no Enterprise statement.
15
+ * ⛔ NO LITELLM CREDENTIAL IS A PROP; `litellmProviders`' layer supplies `Credentials`.
16
+ */
17
+ import { Resource } from 'alchemy';
18
+ import { Unowned } from 'alchemy/AdoptPolicy';
19
+ import { isResolved } from 'alchemy/Diff';
20
+ import type { Input } from 'alchemy/Input';
21
+ import * as Provider from 'alchemy/Provider';
22
+ import * as Effect from 'effect/Effect';
23
+ import * as Predicate from 'effect/Predicate';
24
+ import { createBody, differing, firstProblem, updateBody } from './access-group-form.ts';
25
+ import {
26
+ createAccessGroup,
27
+ deleteAccessGroup,
28
+ listAccessGroups,
29
+ updateAccessGroup,
30
+ } from './access-group-operations.ts';
31
+ import type { AccessGroupAttributes, AccessGroupProps } from './access-group-types.ts';
32
+ import {
33
+ LitellmRegistryAbsentAfterWriteError,
34
+ LitellmRegistryNotConvergedError,
35
+ } from './registry-errors.ts';
36
+ import { dieIfInvalid, failIfInvalid } from './registry-support.ts';
37
+
38
+ export type { AccessGroupAttributes, AccessGroupProps };
39
+ export type { RegistryError as AccessGroupError } from './registry-errors.ts';
40
+
41
+ const RESOURCE = 'LiteLLM.AccessGroup';
42
+
43
+ export interface LiteLLMAccessGroup extends Resource<
44
+ 'LiteLLM.AccessGroup',
45
+ AccessGroupProps,
46
+ AccessGroupAttributes
47
+ > {}
48
+
49
+ export const LiteLLMAccessGroup = Resource<LiteLLMAccessGroup>('LiteLLM.AccessGroup', {
50
+ defaultRemovalPolicy: 'retain',
51
+ });
52
+
53
+ export const isLiteLLMAccessGroup = (value: unknown): value is LiteLLMAccessGroup =>
54
+ Predicate.hasProperty(value, 'Type') && value.Type === 'LiteLLM.AccessGroup';
55
+
56
+ /**
57
+ * The live row this declaration means. A RECORDED id wins and is matched by id alone: a row that
58
+ * vanished and was recreated by hand under the same name is not silently taken over (the create
59
+ * would then fail on the unique name, loudly). With no state, the name decides.
60
+ */
61
+ const locate = (
62
+ rows: readonly AccessGroupAttributes[],
63
+ props: AccessGroupProps,
64
+ output: AccessGroupAttributes | undefined,
65
+ ) =>
66
+ output !== undefined
67
+ ? rows.find((row) => row.accessGroupId === output.accessGroupId)
68
+ : rows.find((row) => row.accessGroupName === props.accessGroupName);
69
+
70
+ /** ⛔ TEST-ONLY EXPORT. Not on index.ts's barrel. */
71
+ export const accessGroupHandlers = {
72
+ /** ⛔ With no `output` this is the adoption probe, so the refusals run here too (registry-support.ts). */
73
+ read: ({ olds, output }: { olds: AccessGroupProps; output: AccessGroupAttributes | undefined }) =>
74
+ Effect.gen(function* () {
75
+ if (output === undefined) {
76
+ yield* dieIfInvalid(RESOURCE, String(olds.accessGroupName), firstProblem(olds));
77
+ }
78
+ const live = locate(yield* listAccessGroups(), olds, output);
79
+ if (live === undefined) return undefined;
80
+ return output === undefined ? Unowned(live) : live;
81
+ }),
82
+
83
+ diff: ({
84
+ news,
85
+ output,
86
+ }: {
87
+ news: Input<AccessGroupProps>;
88
+ output: AccessGroupAttributes | undefined;
89
+ }) =>
90
+ Effect.gen(function* () {
91
+ if (!isResolved(news)) return undefined;
92
+ yield* failIfInvalid(RESOURCE, String(news.accessGroupName), firstProblem(news));
93
+ if (output === undefined) return undefined;
94
+ return differing(output, news).length === 0
95
+ ? ({ action: 'noop' } as const)
96
+ : ({ action: 'update' } as const);
97
+ }),
98
+
99
+ reconcile: ({
100
+ news,
101
+ output,
102
+ }: {
103
+ news: AccessGroupProps;
104
+ output: AccessGroupAttributes | undefined;
105
+ }) =>
106
+ Effect.gen(function* () {
107
+ yield* failIfInvalid(RESOURCE, String(news.accessGroupName), firstProblem(news));
108
+ const before = locate(yield* listAccessGroups(), news, output);
109
+ if (before === undefined) yield* createAccessGroup(createBody(news));
110
+ else if (differing(before, news).length > 0) {
111
+ yield* updateAccessGroup(before.accessGroupId, updateBody(news, before));
112
+ }
113
+
114
+ const rows = yield* listAccessGroups();
115
+ const after =
116
+ before === undefined
117
+ ? rows.find((row) => row.accessGroupName === news.accessGroupName)
118
+ : rows.find((row) => row.accessGroupId === before.accessGroupId);
119
+ if (after === undefined) {
120
+ return yield* Effect.fail(
121
+ new LitellmRegistryAbsentAfterWriteError({
122
+ name: news.accessGroupName,
123
+ resource: RESOURCE,
124
+ }),
125
+ );
126
+ }
127
+ const left = differing(after, news);
128
+ if (left.length > 0) {
129
+ return yield* Effect.fail(
130
+ new LitellmRegistryNotConvergedError({
131
+ fields: left,
132
+ name: news.accessGroupName,
133
+ resource: RESOURCE,
134
+ }),
135
+ );
136
+ }
137
+ return after;
138
+ }),
139
+
140
+ delete: ({ output }: { output: AccessGroupAttributes }) =>
141
+ deleteAccessGroup(output.accessGroupId),
142
+
143
+ /** ⛔ EMPTY: adoption is an explicit act (`--adopt`), and no ownership mark exists to filter by. */
144
+ list: () => Effect.succeed([]),
145
+ };
146
+
147
+ export const LiteLLMAccessGroupProvider = () =>
148
+ Provider.effect(
149
+ LiteLLMAccessGroup,
150
+ Effect.succeed(LiteLLMAccessGroup.Provider.of(accessGroupHandlers)),
151
+ );
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The typed refusals `LiteLLM.Budget` raises on top of `@distilled.cloud/litellm`'s own SDK errors.
3
+ */
4
+ import type * as budgets from '@distilled.cloud/litellm/budget_management';
5
+ import * as Data from 'effect/Data';
6
+
7
+ /** A `maxBudget` (a HARD cap: LiteLLM answers 429 once it is spent) declared with no reason. */
8
+ export class LitellmBudgetHardCapUnjustifiedError extends Data.TaggedError(
9
+ 'LitellmBudgetHardCapUnjustifiedError',
10
+ )<{
11
+ readonly budgetId: string | undefined;
12
+ }> {}
13
+
14
+ /**
15
+ * The write returned no error but the row still carries a limit the declaration does not.
16
+ * `/budget/update` may merge with `exclude_none` (the way `/config/pass_through_endpoint` does),
17
+ * in which case a limit can never be cleared by an update. Refused, never claimed as done.
18
+ */
19
+ export class LitellmBudgetFieldNotClearedError extends Data.TaggedError(
20
+ 'LitellmBudgetFieldNotClearedError',
21
+ )<{
22
+ readonly budgetId: string;
23
+ readonly fields: readonly string[];
24
+ }> {}
25
+
26
+ /** `/budget/info` answered something that is not a list of budget rows. */
27
+ export class LitellmBudgetUnreadableError extends Data.TaggedError('LitellmBudgetUnreadableError')<{
28
+ readonly budgetId: string;
29
+ }> {}
30
+
31
+ export type BudgetError =
32
+ | budgets.PostInfoBudgetBudgetInfoError
33
+ | budgets.PostNewBudgetBudgetNewError
34
+ | budgets.UpdateBudgetBudgetUpdatePostError
35
+ | budgets.DeleteBudgetBudgetDeletePostError
36
+ | LitellmBudgetHardCapUnjustifiedError
37
+ | LitellmBudgetFieldNotClearedError
38
+ | LitellmBudgetUnreadableError;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Props, wire bodies and comparison for `LiteLLM.Budget`, testable without a server.
3
+ *
4
+ * ⛔ NO SILENT `max_budget`. LiteLLM's own field doc: "Requests will fail if this budget (in USD)
5
+ * is exceeded" — a hard cap. On 2026-09-28 an undeclared $60/day `max_budget` on
6
+ * `hf-tier-interactive` 429'd the whole claude2 fleet. So `maxBudget` is never defaulted and is
7
+ * refused unless `maxBudgetReason` says why; `softBudget` is the monitoring field (it "will NOT
8
+ * fail if this is exceeded", it fires the alert).
9
+ * ⚠️ NOT MODELLED: `max_parallel_requests`, `model_max_budget`, `budget_reset_at`. The row read
10
+ * back (`BudgetListItem`, generated at LiteLLM 1.100.0) has none of them, so a declaration of
11
+ * them could never be diffed. Add them when a read that returns them is measured.
12
+ */
13
+ import type * as budgets from '@distilled.cloud/litellm/budget_management';
14
+
15
+ export interface BudgetProps {
16
+ /** Live budget id. Set it to adopt an existing row (`hf-tier-agent`); default is a deterministic physical name. */
17
+ readonly budgetId?: string;
18
+ /** Alert threshold in USD. Requests keep flowing when exceeded. The monitoring field. */
19
+ readonly softBudget?: number;
20
+ /** HARD cap in USD: requests fail (429) once spent. Refused unless `maxBudgetReason` is set. */
21
+ readonly maxBudget?: number;
22
+ /** Why a hard cap is wanted. Required with `maxBudget`; recorded in the declaration only. */
23
+ readonly maxBudgetReason?: string;
24
+ /** Reset period, e.g. `'1d'`, `'28d'`. */
25
+ readonly budgetDuration?: string;
26
+ /** Requests per minute. */
27
+ readonly rpmLimit?: number;
28
+ /** Tokens per minute. */
29
+ readonly tpmLimit?: number;
30
+ }
31
+
32
+ export interface BudgetAttributes {
33
+ readonly budgetId: string;
34
+ readonly softBudget: number | null;
35
+ readonly maxBudget: number | null;
36
+ readonly budgetDuration: string | null;
37
+ readonly rpmLimit: number | null;
38
+ readonly tpmLimit: number | null;
39
+ }
40
+
41
+ /** Declared field → wire field. The single list every comparison and body walks. */
42
+ const FIELDS = [
43
+ ['softBudget', 'soft_budget'],
44
+ ['maxBudget', 'max_budget'],
45
+ ['budgetDuration', 'budget_duration'],
46
+ ['rpmLimit', 'rpm_limit'],
47
+ ['tpmLimit', 'tpm_limit'],
48
+ ] as const;
49
+
50
+ export const isHardCapUnjustified = (props: BudgetProps): boolean =>
51
+ props.maxBudget !== undefined && (props.maxBudgetReason ?? '').trim() === '';
52
+
53
+ /** Create body: only what is declared. */
54
+ export const createBody = (
55
+ props: BudgetProps,
56
+ budgetId: string,
57
+ ): budgets.PostNewBudgetBudgetNewRequest => {
58
+ const body: Record<string, unknown> = { budget_id: budgetId };
59
+ for (const [prop, wire] of FIELDS) if (props[prop] !== undefined) body[wire] = props[prop];
60
+ return body as budgets.PostNewBudgetBudgetNewRequest;
61
+ };
62
+
63
+ /** Update body: declared fields, plus an explicit `null` for a live limit the declaration dropped. */
64
+ export const updateBody = (
65
+ props: BudgetProps,
66
+ live: BudgetAttributes,
67
+ ): budgets.UpdateBudgetBudgetUpdatePostRequest => {
68
+ const body: Record<string, unknown> = { budget_id: live.budgetId };
69
+ for (const [prop, wire] of FIELDS) {
70
+ if (props[prop] !== undefined) body[wire] = props[prop];
71
+ else if (live[prop] !== null) body[wire] = null;
72
+ }
73
+ return body as budgets.UpdateBudgetBudgetUpdatePostRequest;
74
+ };
75
+
76
+ /** Wire names of the fields where live and declared disagree. */
77
+ export const differing = (live: BudgetAttributes, props: BudgetProps): readonly string[] =>
78
+ FIELDS.filter(([prop]) => live[prop] !== (props[prop] ?? null)).map(([, wire]) => wire);
79
+
80
+ const numberOrNull = (value: unknown): number | null =>
81
+ value === null || value === undefined || value === '' ? null : Number(value);
82
+
83
+ /**
84
+ * One row of `/budget/info`. `tpm_limit`/`rpm_limit` are `BigInt?` in LiteLLM's schema and come
85
+ * back as decimal strings (the generated `BudgetListItem` says so), hence `Number()`.
86
+ */
87
+ export const toAttributes = (row: Record<string, unknown>): BudgetAttributes => ({
88
+ budgetDuration: typeof row['budget_duration'] === 'string' ? row['budget_duration'] : null,
89
+ budgetId: String(row['budget_id']),
90
+ maxBudget: numberOrNull(row['max_budget']),
91
+ rpmLimit: numberOrNull(row['rpm_limit']),
92
+ softBudget: numberOrNull(row['soft_budget']),
93
+ tpmLimit: numberOrNull(row['tpm_limit']),
94
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The four LiteLLM `/budget/*` calls `LiteLLM.Budget` uses, through the SDK's typed operations.
3
+ *
4
+ * ★ READS GO THROUGH `/budget/list` (the whole table, a bare array), not `/budget/info`. Both
5
+ * answer `body: unknown` in the generated schema, and what `/budget/info` does for an id that is
6
+ * not there is unmeasured; a list answers absence unambiguously.
7
+ * ⚠️ Unlike pass-through endpoints, each budget is its own DB row, so no semaphore.
8
+ * ⚠️ DELETE is idempotent BY THIS FUNCTION: a `BadRequest` re-lists and is swallowed only when the
9
+ * id is genuinely absent, never on message text (pass-through `operations.ts`' rule).
10
+ */
11
+ import * as budgets from '@distilled.cloud/litellm/budget_management';
12
+ import * as Effect from 'effect/Effect';
13
+ import { type LitellmOpContext, throughFetch } from './operations.ts';
14
+ import { type BudgetAttributes, toAttributes } from './budget-form.ts';
15
+ import { LitellmBudgetUnreadableError } from './budget-errors.ts';
16
+
17
+ export const listBudgets = (): Effect.Effect<
18
+ readonly BudgetAttributes[],
19
+ budgets.ListBudgetBudgetListGetError | LitellmBudgetUnreadableError,
20
+ LitellmOpContext
21
+ > =>
22
+ throughFetch(budgets.listBudgetBudgetListGet({})).pipe(
23
+ // ⚠️ MEASURED with the fake: the SDK types this `{ body: unknown }` but hands a bare JSON array
24
+ // back as the array itself, so accept both shapes and refuse anything else.
25
+ Effect.flatMap((response) => {
26
+ const rows: unknown = Array.isArray(response)
27
+ ? response
28
+ : (response as { body?: unknown }).body;
29
+ return Array.isArray(rows)
30
+ ? Effect.succeed(rows.map((row) => toAttributes(row as Record<string, unknown>)))
31
+ : Effect.fail(new LitellmBudgetUnreadableError({ budgetId: '*' }));
32
+ }),
33
+ );
34
+
35
+ export const createBudget = (body: budgets.PostNewBudgetBudgetNewRequest) =>
36
+ throughFetch(budgets.postNewBudgetBudgetNew(body)).pipe(Effect.asVoid);
37
+
38
+ export const updateBudget = (body: budgets.UpdateBudgetBudgetUpdatePostRequest) =>
39
+ throughFetch(budgets.updateBudgetBudgetUpdatePost(body)).pipe(Effect.asVoid);
40
+
41
+ export const deleteBudget = (id: string) =>
42
+ throughFetch(budgets.deleteBudgetBudgetDeletePost({ id })).pipe(
43
+ Effect.asVoid,
44
+ Effect.catchTag('BadRequest', (original) =>
45
+ listBudgets().pipe(
46
+ Effect.flatMap((rows) =>
47
+ rows.some((row) => row.budgetId === id) ? Effect.fail(original) : Effect.void,
48
+ ),
49
+ ),
50
+ ),
51
+ );