@homeflare/alchemy 0.43.0 → 0.45.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 (612) 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/credential-errors.d.ts +131 -0
  17. package/dist/litellm/credential-errors.d.ts.map +1 -0
  18. package/dist/litellm/credential-form.d.ts +79 -0
  19. package/dist/litellm/credential-form.d.ts.map +1 -0
  20. package/dist/litellm/credential-operations.d.ts +84 -0
  21. package/dist/litellm/credential-operations.d.ts.map +1 -0
  22. package/dist/litellm/credential-rewrite.d.ts +6 -0
  23. package/dist/litellm/credential-rewrite.d.ts.map +1 -0
  24. package/dist/litellm/credential-types.d.ts +87 -0
  25. package/dist/litellm/credential-types.d.ts.map +1 -0
  26. package/dist/litellm/credential-values.d.ts +53 -0
  27. package/dist/litellm/credential-values.d.ts.map +1 -0
  28. package/dist/litellm/credential.d.ts +83 -0
  29. package/dist/litellm/credential.d.ts.map +1 -0
  30. package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
  31. package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
  32. package/dist/litellm/fake-credential-litellm.d.ts +54 -0
  33. package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
  34. package/dist/litellm/fake-keys.d.ts +53 -0
  35. package/dist/litellm/fake-keys.d.ts.map +1 -0
  36. package/dist/litellm/fake-litellm.d.ts +4 -1
  37. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  38. package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
  39. package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
  40. package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
  41. package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
  42. package/dist/litellm/fake-model-litellm.d.ts +48 -0
  43. package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
  44. package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
  45. package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
  46. package/dist/litellm/fake-policy-litellm.d.ts +32 -0
  47. package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
  48. package/dist/litellm/fake-registry-base.d.ts +42 -0
  49. package/dist/litellm/fake-registry-base.d.ts.map +1 -0
  50. package/dist/litellm/fake-stack.d.ts +8 -1
  51. package/dist/litellm/fake-stack.d.ts.map +1 -1
  52. package/dist/litellm/fake-team-litellm.d.ts +47 -0
  53. package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
  54. package/dist/litellm/fake-tool-litellm.d.ts +28 -0
  55. package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
  56. package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
  57. package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
  58. package/dist/litellm/index.d.ts +18 -0
  59. package/dist/litellm/index.d.ts.map +1 -1
  60. package/dist/litellm/index.js +3078 -2
  61. package/dist/litellm/index.js.map +65 -4
  62. package/dist/litellm/key-errors.d.ts +169 -0
  63. package/dist/litellm/key-errors.d.ts.map +1 -0
  64. package/dist/litellm/key-form.d.ts +119 -0
  65. package/dist/litellm/key-form.d.ts.map +1 -0
  66. package/dist/litellm/key-harness.d.ts +41 -0
  67. package/dist/litellm/key-harness.d.ts.map +1 -0
  68. package/dist/litellm/key-metadata-errors.d.ts +30 -0
  69. package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
  70. package/dist/litellm/key-metadata.d.ts +22 -0
  71. package/dist/litellm/key-metadata.d.ts.map +1 -0
  72. package/dist/litellm/key-operations.d.ts +63 -0
  73. package/dist/litellm/key-operations.d.ts.map +1 -0
  74. package/dist/litellm/key-secret.d.ts +33 -0
  75. package/dist/litellm/key-secret.d.ts.map +1 -0
  76. package/dist/litellm/key.d.ts +78 -0
  77. package/dist/litellm/key.d.ts.map +1 -0
  78. package/dist/litellm/mcp-server-credential.d.ts +50 -0
  79. package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
  80. package/dist/litellm/mcp-server-errors.d.ts +101 -0
  81. package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
  82. package/dist/litellm/mcp-server-form.d.ts +56 -0
  83. package/dist/litellm/mcp-server-form.d.ts.map +1 -0
  84. package/dist/litellm/mcp-server-locate.d.ts +26 -0
  85. package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
  86. package/dist/litellm/mcp-server-operations.d.ts +63 -0
  87. package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
  88. package/dist/litellm/mcp-server-shadow.d.ts +24 -0
  89. package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
  90. package/dist/litellm/mcp-server-types.d.ts +118 -0
  91. package/dist/litellm/mcp-server-types.d.ts.map +1 -0
  92. package/dist/litellm/mcp-server-url.d.ts +11 -0
  93. package/dist/litellm/mcp-server-url.d.ts.map +1 -0
  94. package/dist/litellm/mcp-server.d.ts +95 -0
  95. package/dist/litellm/mcp-server.d.ts.map +1 -0
  96. package/dist/litellm/model-claim.d.ts +24 -0
  97. package/dist/litellm/model-claim.d.ts.map +1 -0
  98. package/dist/litellm/model-credential.d.ts +12 -0
  99. package/dist/litellm/model-credential.d.ts.map +1 -0
  100. package/dist/litellm/model-errors.d.ts +119 -0
  101. package/dist/litellm/model-errors.d.ts.map +1 -0
  102. package/dist/litellm/model-form.d.ts +78 -0
  103. package/dist/litellm/model-form.d.ts.map +1 -0
  104. package/dist/litellm/model-locate.d.ts +13 -0
  105. package/dist/litellm/model-locate.d.ts.map +1 -0
  106. package/dist/litellm/model-operations.d.ts +59 -0
  107. package/dist/litellm/model-operations.d.ts.map +1 -0
  108. package/dist/litellm/model-types.d.ts +91 -0
  109. package/dist/litellm/model-types.d.ts.map +1 -0
  110. package/dist/litellm/model.d.ts +88 -0
  111. package/dist/litellm/model.d.ts.map +1 -0
  112. package/dist/litellm/policy-attachment-form.d.ts +19 -0
  113. package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
  114. package/dist/litellm/policy-attachment-operations.d.ts +31 -0
  115. package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
  116. package/dist/litellm/policy-attachment-types.d.ts +48 -0
  117. package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
  118. package/dist/litellm/policy-attachment.d.ts +61 -0
  119. package/dist/litellm/policy-attachment.d.ts.map +1 -0
  120. package/dist/litellm/policy-form.d.ts +22 -0
  121. package/dist/litellm/policy-form.d.ts.map +1 -0
  122. package/dist/litellm/policy-operations.d.ts +42 -0
  123. package/dist/litellm/policy-operations.d.ts.map +1 -0
  124. package/dist/litellm/policy-types.d.ts +57 -0
  125. package/dist/litellm/policy-types.d.ts.map +1 -0
  126. package/dist/litellm/policy.d.ts +65 -0
  127. package/dist/litellm/policy.d.ts.map +1 -0
  128. package/dist/litellm/providers.d.ts +1 -1
  129. package/dist/litellm/providers.d.ts.map +1 -1
  130. package/dist/litellm/registry-errors.d.ts +62 -0
  131. package/dist/litellm/registry-errors.d.ts.map +1 -0
  132. package/dist/litellm/registry-support.d.ts +42 -0
  133. package/dist/litellm/registry-support.d.ts.map +1 -0
  134. package/dist/litellm/team-form.d.ts +26 -0
  135. package/dist/litellm/team-form.d.ts.map +1 -0
  136. package/dist/litellm/team-members.d.ts +10 -0
  137. package/dist/litellm/team-members.d.ts.map +1 -0
  138. package/dist/litellm/team-operations.d.ts +38 -0
  139. package/dist/litellm/team-operations.d.ts.map +1 -0
  140. package/dist/litellm/team-permission.d.ts +35 -0
  141. package/dist/litellm/team-permission.d.ts.map +1 -0
  142. package/dist/litellm/team-types.d.ts +101 -0
  143. package/dist/litellm/team-types.d.ts.map +1 -0
  144. package/dist/litellm/team.d.ts +67 -0
  145. package/dist/litellm/team.d.ts.map +1 -0
  146. package/dist/litellm/tool-policy-form.d.ts +21 -0
  147. package/dist/litellm/tool-policy-form.d.ts.map +1 -0
  148. package/dist/litellm/tool-policy-operations.d.ts +30 -0
  149. package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
  150. package/dist/litellm/tool-policy-types.d.ts +46 -0
  151. package/dist/litellm/tool-policy-types.d.ts.map +1 -0
  152. package/dist/litellm/tool-policy.d.ts +62 -0
  153. package/dist/litellm/tool-policy.d.ts.map +1 -0
  154. package/dist/litellm/toolset-form.d.ts +20 -0
  155. package/dist/litellm/toolset-form.d.ts.map +1 -0
  156. package/dist/litellm/toolset-operations.d.ts +39 -0
  157. package/dist/litellm/toolset-operations.d.ts.map +1 -0
  158. package/dist/litellm/toolset-types.d.ts +46 -0
  159. package/dist/litellm/toolset-types.d.ts.map +1 -0
  160. package/dist/litellm/toolset.d.ts +55 -0
  161. package/dist/litellm/toolset.d.ts.map +1 -0
  162. package/dist/openbao/index.js +35 -1
  163. package/dist/openbao/index.js.map +3 -3
  164. package/dist/ownership/rows.d.ts +11 -0
  165. package/dist/ownership/rows.d.ts.map +1 -1
  166. package/dist/postgres/connection.d.ts +14 -2
  167. package/dist/postgres/connection.d.ts.map +1 -1
  168. package/dist/postgres/database-sql.d.ts +27 -8
  169. package/dist/postgres/database-sql.d.ts.map +1 -1
  170. package/dist/postgres/fake-grants-parse.d.ts +37 -0
  171. package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
  172. package/dist/postgres/fake-grants-read.d.ts +61 -0
  173. package/dist/postgres/fake-grants-read.d.ts.map +1 -0
  174. package/dist/postgres/fake-grants-sql.d.ts +46 -0
  175. package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
  176. package/dist/postgres/fake-grants-write.d.ts +29 -0
  177. package/dist/postgres/fake-grants-write.d.ts.map +1 -0
  178. package/dist/postgres/fake-role-sql.d.ts +41 -0
  179. package/dist/postgres/fake-role-sql.d.ts.map +1 -0
  180. package/dist/postgres/fake-schema-sql.d.ts +42 -0
  181. package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
  182. package/dist/postgres/fake-sql-parse.d.ts +23 -0
  183. package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
  184. package/dist/postgres/fake-sql-quote.d.ts +12 -0
  185. package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
  186. package/dist/postgres/fake-sql.d.ts +42 -0
  187. package/dist/postgres/fake-sql.d.ts.map +1 -1
  188. package/dist/postgres/grants-attrs.d.ts +115 -0
  189. package/dist/postgres/grants-attrs.d.ts.map +1 -0
  190. package/dist/postgres/grants-declare.d.ts +88 -0
  191. package/dist/postgres/grants-declare.d.ts.map +1 -0
  192. package/dist/postgres/grants-diff.d.ts +25 -0
  193. package/dist/postgres/grants-diff.d.ts.map +1 -0
  194. package/dist/postgres/grants-errors.d.ts +137 -0
  195. package/dist/postgres/grants-errors.d.ts.map +1 -0
  196. package/dist/postgres/grants-existence.d.ts +14 -0
  197. package/dist/postgres/grants-existence.d.ts.map +1 -0
  198. package/dist/postgres/grants-ops.d.ts +65 -0
  199. package/dist/postgres/grants-ops.d.ts.map +1 -0
  200. package/dist/postgres/grants-ownership.d.ts +18 -0
  201. package/dist/postgres/grants-ownership.d.ts.map +1 -0
  202. package/dist/postgres/grants-plan-restore.d.ts +22 -0
  203. package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
  204. package/dist/postgres/grants-plan.d.ts +65 -0
  205. package/dist/postgres/grants-plan.d.ts.map +1 -0
  206. package/dist/postgres/grants-read-defaults.d.ts +17 -0
  207. package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
  208. package/dist/postgres/grants-read.d.ts +77 -0
  209. package/dist/postgres/grants-read.d.ts.map +1 -0
  210. package/dist/postgres/grants-refuse.d.ts +25 -0
  211. package/dist/postgres/grants-refuse.d.ts.map +1 -0
  212. package/dist/postgres/grants-sql.d.ts +42 -0
  213. package/dist/postgres/grants-sql.d.ts.map +1 -0
  214. package/dist/postgres/grants-words.d.ts +29 -0
  215. package/dist/postgres/grants-words.d.ts.map +1 -0
  216. package/dist/postgres/grants.d.ts +37 -0
  217. package/dist/postgres/grants.d.ts.map +1 -0
  218. package/dist/postgres/index.d.ts +31 -6
  219. package/dist/postgres/index.d.ts.map +1 -1
  220. package/dist/postgres/index.js +1998 -37
  221. package/dist/postgres/index.js.map +46 -7
  222. package/dist/postgres/installed-classifier.d.ts +13 -0
  223. package/dist/postgres/installed-classifier.d.ts.map +1 -0
  224. package/dist/postgres/providers.d.ts +16 -8
  225. package/dist/postgres/providers.d.ts.map +1 -1
  226. package/dist/postgres/psql-executor.d.ts +4 -0
  227. package/dist/postgres/psql-executor.d.ts.map +1 -1
  228. package/dist/postgres/role-attrs.d.ts +112 -0
  229. package/dist/postgres/role-attrs.d.ts.map +1 -0
  230. package/dist/postgres/role-diff.d.ts +22 -0
  231. package/dist/postgres/role-diff.d.ts.map +1 -0
  232. package/dist/postgres/role-errors.d.ts +132 -0
  233. package/dist/postgres/role-errors.d.ts.map +1 -0
  234. package/dist/postgres/role-membership-sql.d.ts +78 -0
  235. package/dist/postgres/role-membership-sql.d.ts.map +1 -0
  236. package/dist/postgres/role-provider.d.ts +11 -0
  237. package/dist/postgres/role-provider.d.ts.map +1 -0
  238. package/dist/postgres/role-scram.d.ts +8 -0
  239. package/dist/postgres/role-scram.d.ts.map +1 -0
  240. package/dist/postgres/role-secrets.d.ts +38 -0
  241. package/dist/postgres/role-secrets.d.ts.map +1 -0
  242. package/dist/postgres/role-sql.d.ts +68 -0
  243. package/dist/postgres/role-sql.d.ts.map +1 -0
  244. package/dist/postgres/role.d.ts +57 -0
  245. package/dist/postgres/role.d.ts.map +1 -0
  246. package/dist/postgres/schema-assert.d.ts +50 -0
  247. package/dist/postgres/schema-assert.d.ts.map +1 -0
  248. package/dist/postgres/schema-attrs.d.ts +51 -0
  249. package/dist/postgres/schema-attrs.d.ts.map +1 -0
  250. package/dist/postgres/schema-delete.d.ts +28 -0
  251. package/dist/postgres/schema-delete.d.ts.map +1 -0
  252. package/dist/postgres/schema-diff.d.ts +14 -0
  253. package/dist/postgres/schema-diff.d.ts.map +1 -0
  254. package/dist/postgres/schema-errors.d.ts +139 -0
  255. package/dist/postgres/schema-errors.d.ts.map +1 -0
  256. package/dist/postgres/schema-identity.d.ts +8 -0
  257. package/dist/postgres/schema-identity.d.ts.map +1 -0
  258. package/dist/postgres/schema-reconcile.d.ts +12 -0
  259. package/dist/postgres/schema-reconcile.d.ts.map +1 -0
  260. package/dist/postgres/schema-sql.d.ts +45 -0
  261. package/dist/postgres/schema-sql.d.ts.map +1 -0
  262. package/dist/postgres/schema-test-kit.d.ts +67 -0
  263. package/dist/postgres/schema-test-kit.d.ts.map +1 -0
  264. package/dist/postgres/schema.d.ts +54 -0
  265. package/dist/postgres/schema.d.ts.map +1 -0
  266. package/dist/proxmox/index.js +35 -1
  267. package/dist/proxmox/index.js.map +3 -3
  268. package/dist/release/fake-engine.d.ts +10 -0
  269. package/dist/release/fake-engine.d.ts.map +1 -1
  270. package/dist/release/index.js +35 -1
  271. package/dist/release/index.js.map +3 -3
  272. package/dist/valkey/acl-attrs.d.ts +94 -0
  273. package/dist/valkey/acl-attrs.d.ts.map +1 -0
  274. package/dist/valkey/acl-compare.d.ts +28 -0
  275. package/dist/valkey/acl-compare.d.ts.map +1 -0
  276. package/dist/valkey/acl-connection.d.ts +6 -0
  277. package/dist/valkey/acl-connection.d.ts.map +1 -0
  278. package/dist/valkey/acl-form.d.ts +62 -0
  279. package/dist/valkey/acl-form.d.ts.map +1 -0
  280. package/dist/valkey/acl-managed.d.ts +4 -0
  281. package/dist/valkey/acl-managed.d.ts.map +1 -0
  282. package/dist/valkey/acl-ops.d.ts +37 -0
  283. package/dist/valkey/acl-ops.d.ts.map +1 -0
  284. package/dist/valkey/acl-profiles.d.ts +53 -0
  285. package/dist/valkey/acl-profiles.d.ts.map +1 -0
  286. package/dist/valkey/acl-reconcile.d.ts +26 -0
  287. package/dist/valkey/acl-reconcile.d.ts.map +1 -0
  288. package/dist/valkey/acl.d.ts +24 -0
  289. package/dist/valkey/acl.d.ts.map +1 -0
  290. package/dist/valkey/connection-service.d.ts +22 -0
  291. package/dist/valkey/connection-service.d.ts.map +1 -0
  292. package/dist/valkey/connection.d.ts +59 -0
  293. package/dist/valkey/connection.d.ts.map +1 -0
  294. package/dist/valkey/errors.d.ts +165 -0
  295. package/dist/valkey/errors.d.ts.map +1 -0
  296. package/dist/valkey/fake-acl.d.ts +29 -0
  297. package/dist/valkey/fake-acl.d.ts.map +1 -0
  298. package/dist/valkey/fake-valkey.d.ts +35 -0
  299. package/dist/valkey/fake-valkey.d.ts.map +1 -0
  300. package/dist/valkey/handler-test-fixture.d.ts +20 -0
  301. package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
  302. package/dist/valkey/index.d.ts +18 -0
  303. package/dist/valkey/index.d.ts.map +1 -0
  304. package/dist/valkey/index.js +964 -0
  305. package/dist/valkey/index.js.map +27 -0
  306. package/dist/valkey/instance-attrs.d.ts +46 -0
  307. package/dist/valkey/instance-attrs.d.ts.map +1 -0
  308. package/dist/valkey/instance-form.d.ts +22 -0
  309. package/dist/valkey/instance-form.d.ts.map +1 -0
  310. package/dist/valkey/instance.d.ts +48 -0
  311. package/dist/valkey/instance.d.ts.map +1 -0
  312. package/dist/valkey/provider-socket-fixture.d.ts +8 -0
  313. package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
  314. package/dist/valkey/providers.d.ts +16 -0
  315. package/dist/valkey/providers.d.ts.map +1 -0
  316. package/dist/valkey/resp.d.ts +38 -0
  317. package/dist/valkey/resp.d.ts.map +1 -0
  318. package/dist/valkey/transport-error.d.ts +36 -0
  319. package/dist/valkey/transport-error.d.ts.map +1 -0
  320. package/dist/valkey/transport.d.ts +50 -0
  321. package/dist/valkey/transport.d.ts.map +1 -0
  322. package/docs/litellm-access-group.md +105 -0
  323. package/docs/litellm-credential.md +60 -0
  324. package/docs/litellm-key-metadata.md +56 -0
  325. package/docs/litellm-key.md +194 -0
  326. package/docs/litellm-mcp.md +187 -0
  327. package/docs/litellm-model.md +111 -0
  328. package/docs/litellm-policy-attachment.md +86 -0
  329. package/docs/litellm-policy.md +103 -0
  330. package/docs/litellm-team.md +117 -0
  331. package/docs/litellm-tool-policy.md +90 -0
  332. package/docs/litellm-toolset.md +92 -0
  333. package/docs/litellm.md +16 -9
  334. package/docs/peers.md +15 -6
  335. package/docs/postgres-grants-example.md +55 -0
  336. package/docs/postgres-grants.md +157 -0
  337. package/docs/postgres-role.md +126 -0
  338. package/docs/postgres-schema.md +139 -0
  339. package/docs/postgres.md +20 -3
  340. package/docs/valkey-transport.md +29 -0
  341. package/docs/valkey.md +187 -0
  342. package/package.json +6 -2
  343. package/src/ceph/ceph-argv.test.ts +1 -1
  344. package/src/launchd/sudo-allowlist.test.ts +2 -2
  345. package/src/linux/sudo-allowlist.test.ts +3 -3
  346. package/src/linux/sudo-runner.test.ts +2 -2
  347. package/src/litellm/access-group-form.ts +105 -0
  348. package/src/litellm/access-group-operations.ts +92 -0
  349. package/src/litellm/access-group-types.ts +49 -0
  350. package/src/litellm/access-group.test.ts +197 -0
  351. package/src/litellm/access-group.ts +151 -0
  352. package/src/litellm/credential-errors.ts +183 -0
  353. package/src/litellm/credential-form.test.ts +151 -0
  354. package/src/litellm/credential-form.ts +211 -0
  355. package/src/litellm/credential-guards.test.ts +241 -0
  356. package/src/litellm/credential-operations.ts +208 -0
  357. package/src/litellm/credential-recovery.test.ts +93 -0
  358. package/src/litellm/credential-rewrite.test.ts +164 -0
  359. package/src/litellm/credential-rewrite.ts +50 -0
  360. package/src/litellm/credential-types.ts +117 -0
  361. package/src/litellm/credential-values.ts +94 -0
  362. package/src/litellm/credential.test.ts +218 -0
  363. package/src/litellm/credential.ts +227 -0
  364. package/src/litellm/fake-access-group-litellm.ts +127 -0
  365. package/src/litellm/fake-credential-litellm.ts +216 -0
  366. package/src/litellm/fake-keys.ts +190 -0
  367. package/src/litellm/fake-litellm.ts +19 -9
  368. package/src/litellm/fake-mcp-litellm.ts +214 -0
  369. package/src/litellm/fake-model-litellm-post-update.ts +103 -0
  370. package/src/litellm/fake-model-litellm.ts +233 -0
  371. package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
  372. package/src/litellm/fake-policy-litellm.ts +186 -0
  373. package/src/litellm/fake-registry-base.ts +69 -0
  374. package/src/litellm/fake-stack.ts +17 -1
  375. package/src/litellm/fake-team-litellm.ts +229 -0
  376. package/src/litellm/fake-tool-litellm.ts +88 -0
  377. package/src/litellm/fake-toolset-litellm.ts +110 -0
  378. package/src/litellm/index.ts +140 -0
  379. package/src/litellm/key-delete.test.ts +212 -0
  380. package/src/litellm/key-errors.ts +241 -0
  381. package/src/litellm/key-form.ts +200 -0
  382. package/src/litellm/key-harness.ts +121 -0
  383. package/src/litellm/key-metadata-errors.ts +49 -0
  384. package/src/litellm/key-metadata.test.ts +156 -0
  385. package/src/litellm/key-metadata.ts +74 -0
  386. package/src/litellm/key-operations.ts +184 -0
  387. package/src/litellm/key-rotation.test.ts +110 -0
  388. package/src/litellm/key-scope.test.ts +135 -0
  389. package/src/litellm/key-secret.test.ts +248 -0
  390. package/src/litellm/key-secret.ts +137 -0
  391. package/src/litellm/key-transport.test.ts +166 -0
  392. package/src/litellm/key.test.ts +231 -0
  393. package/src/litellm/key.ts +213 -0
  394. package/src/litellm/mcp-server-adopt.test.ts +86 -0
  395. package/src/litellm/mcp-server-alias.test.ts +184 -0
  396. package/src/litellm/mcp-server-credential.ts +85 -0
  397. package/src/litellm/mcp-server-errors.ts +141 -0
  398. package/src/litellm/mcp-server-exports.test.ts +15 -0
  399. package/src/litellm/mcp-server-form.test.ts +241 -0
  400. package/src/litellm/mcp-server-form.ts +233 -0
  401. package/src/litellm/mcp-server-handlers.test.ts +150 -0
  402. package/src/litellm/mcp-server-locate.ts +66 -0
  403. package/src/litellm/mcp-server-operations.ts +111 -0
  404. package/src/litellm/mcp-server-registry.test.ts +146 -0
  405. package/src/litellm/mcp-server-retype.test.ts +114 -0
  406. package/src/litellm/mcp-server-shadow.test.ts +91 -0
  407. package/src/litellm/mcp-server-shadow.ts +45 -0
  408. package/src/litellm/mcp-server-tools.test.ts +85 -0
  409. package/src/litellm/mcp-server-types.ts +143 -0
  410. package/src/litellm/mcp-server-url.ts +66 -0
  411. package/src/litellm/mcp-server.test.ts +213 -0
  412. package/src/litellm/mcp-server.ts +225 -0
  413. package/src/litellm/model-claim.ts +69 -0
  414. package/src/litellm/model-credential.ts +45 -0
  415. package/src/litellm/model-errors.ts +168 -0
  416. package/src/litellm/model-foreign-row.test.ts +173 -0
  417. package/src/litellm/model-form.test.ts +228 -0
  418. package/src/litellm/model-form.ts +223 -0
  419. package/src/litellm/model-locate.test.ts +128 -0
  420. package/src/litellm/model-locate.ts +67 -0
  421. package/src/litellm/model-operations.test.ts +93 -0
  422. package/src/litellm/model-operations.ts +146 -0
  423. package/src/litellm/model-params-preserve.test.ts +119 -0
  424. package/src/litellm/model-recovery.test.ts +57 -0
  425. package/src/litellm/model-types.ts +112 -0
  426. package/src/litellm/model.test.ts +222 -0
  427. package/src/litellm/model.ts +237 -0
  428. package/src/litellm/policy-attachment-form.ts +103 -0
  429. package/src/litellm/policy-attachment-operations.ts +74 -0
  430. package/src/litellm/policy-attachment-types.ts +48 -0
  431. package/src/litellm/policy-attachment.test.ts +198 -0
  432. package/src/litellm/policy-attachment.ts +178 -0
  433. package/src/litellm/policy-form.ts +122 -0
  434. package/src/litellm/policy-operations.ts +112 -0
  435. package/src/litellm/policy-types.ts +58 -0
  436. package/src/litellm/policy.test.ts +214 -0
  437. package/src/litellm/policy.ts +146 -0
  438. package/src/litellm/providers.ts +24 -3
  439. package/src/litellm/registry-errors.ts +88 -0
  440. package/src/litellm/registry-exports.test.ts +66 -0
  441. package/src/litellm/registry-support.ts +109 -0
  442. package/src/litellm/team-form.ts +140 -0
  443. package/src/litellm/team-members.test.ts +150 -0
  444. package/src/litellm/team-members.ts +58 -0
  445. package/src/litellm/team-operations.ts +90 -0
  446. package/src/litellm/team-permission.ts +159 -0
  447. package/src/litellm/team-read.test.ts +89 -0
  448. package/src/litellm/team-types.ts +106 -0
  449. package/src/litellm/team.test.ts +206 -0
  450. package/src/litellm/team.ts +159 -0
  451. package/src/litellm/tool-policy-form.ts +94 -0
  452. package/src/litellm/tool-policy-operations.ts +58 -0
  453. package/src/litellm/tool-policy-types.ts +48 -0
  454. package/src/litellm/tool-policy.test.ts +172 -0
  455. package/src/litellm/tool-policy.ts +125 -0
  456. package/src/litellm/toolset-form.ts +104 -0
  457. package/src/litellm/toolset-operations.ts +90 -0
  458. package/src/litellm/toolset-types.ts +47 -0
  459. package/src/litellm/toolset.test.ts +198 -0
  460. package/src/litellm/toolset.ts +151 -0
  461. package/src/ownership/ownership.test.ts +33 -1
  462. package/src/ownership/rows.ts +53 -0
  463. package/src/postgres/connection.ts +42 -7
  464. package/src/postgres/database-bigint-serialization.test.ts +1 -0
  465. package/src/postgres/database-refusals.test.ts +2 -2
  466. package/src/postgres/database-sql.ts +36 -9
  467. package/src/postgres/fake-grants-parse.ts +189 -0
  468. package/src/postgres/fake-grants-read.ts +250 -0
  469. package/src/postgres/fake-grants-sql.ts +207 -0
  470. package/src/postgres/fake-grants-write.ts +90 -0
  471. package/src/postgres/fake-role-sql.ts +240 -0
  472. package/src/postgres/fake-schema-semantics.test.ts +36 -0
  473. package/src/postgres/fake-schema-sql.ts +146 -0
  474. package/src/postgres/fake-sql-parse.ts +86 -0
  475. package/src/postgres/fake-sql-quote.ts +36 -0
  476. package/src/postgres/fake-sql.ts +166 -46
  477. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
  478. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
  479. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
  480. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
  481. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
  482. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
  483. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
  484. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
  485. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
  486. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
  487. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
  488. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
  489. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
  490. package/src/postgres/grants-acl.test.ts +61 -0
  491. package/src/postgres/grants-attrs.ts +185 -0
  492. package/src/postgres/grants-database.test.ts +158 -0
  493. package/src/postgres/grants-declare.ts +172 -0
  494. package/src/postgres/grants-delete-proof.test.ts +154 -0
  495. package/src/postgres/grants-diff.ts +101 -0
  496. package/src/postgres/grants-errors.ts +215 -0
  497. package/src/postgres/grants-existence-guards.test.ts +113 -0
  498. package/src/postgres/grants-existence.ts +101 -0
  499. package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
  500. package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
  501. package/src/postgres/grants-lifecycle.test.ts +181 -0
  502. package/src/postgres/grants-ops.ts +229 -0
  503. package/src/postgres/grants-owner-revoke.test.ts +121 -0
  504. package/src/postgres/grants-ownership.ts +49 -0
  505. package/src/postgres/grants-plan-collateral.test.ts +202 -0
  506. package/src/postgres/grants-plan-diff.test.ts +204 -0
  507. package/src/postgres/grants-plan-restore.ts +43 -0
  508. package/src/postgres/grants-plan-revocations.test.ts +69 -0
  509. package/src/postgres/grants-plan.test.ts +148 -0
  510. package/src/postgres/grants-plan.ts +245 -0
  511. package/src/postgres/grants-provenance.test.ts +134 -0
  512. package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
  513. package/src/postgres/grants-read-absent.test.ts +91 -0
  514. package/src/postgres/grants-read-defaults.ts +51 -0
  515. package/src/postgres/grants-read.ts +250 -0
  516. package/src/postgres/grants-refusals.test.ts +136 -0
  517. package/src/postgres/grants-refuse.ts +119 -0
  518. package/src/postgres/grants-relkind-wire.test.ts +111 -0
  519. package/src/postgres/grants-repair-plan.test.ts +201 -0
  520. package/src/postgres/grants-sql.test.ts +128 -0
  521. package/src/postgres/grants-sql.ts +144 -0
  522. package/src/postgres/grants-words.ts +34 -0
  523. package/src/postgres/grants.ts +110 -0
  524. package/src/postgres/index.ts +129 -5
  525. package/src/postgres/installed-classifier.ts +36 -0
  526. package/src/postgres/providers.ts +33 -9
  527. package/src/postgres/psql-executor-escaping.test.ts +67 -0
  528. package/src/postgres/psql-executor.test.ts +63 -2
  529. package/src/postgres/psql-executor.ts +74 -34
  530. package/src/postgres/role-adopt.test.ts +98 -0
  531. package/src/postgres/role-attrs.ts +137 -0
  532. package/src/postgres/role-diff.ts +93 -0
  533. package/src/postgres/role-errors.ts +206 -0
  534. package/src/postgres/role-guard.test.ts +60 -0
  535. package/src/postgres/role-identity.test.ts +197 -0
  536. package/src/postgres/role-lifecycle.test.ts +228 -0
  537. package/src/postgres/role-membership-options.test.ts +73 -0
  538. package/src/postgres/role-membership-repair.test.ts +153 -0
  539. package/src/postgres/role-membership-sql.ts +223 -0
  540. package/src/postgres/role-password-ascii.test.ts +53 -0
  541. package/src/postgres/role-password-transports.test.ts +123 -0
  542. package/src/postgres/role-password.test.ts +158 -0
  543. package/src/postgres/role-privilege.test.ts +107 -0
  544. package/src/postgres/role-provenance.test.ts +200 -0
  545. package/src/postgres/role-provider.ts +105 -0
  546. package/src/postgres/role-refusals.test.ts +225 -0
  547. package/src/postgres/role-scram.test.ts +38 -0
  548. package/src/postgres/role-scram.ts +40 -0
  549. package/src/postgres/role-secrets.ts +61 -0
  550. package/src/postgres/role-sql.ts +213 -0
  551. package/src/postgres/role-valid-until.test.ts +68 -0
  552. package/src/postgres/role.ts +226 -0
  553. package/src/postgres/schema-assert.ts +139 -0
  554. package/src/postgres/schema-attrs.ts +71 -0
  555. package/src/postgres/schema-delete-proofs.test.ts +192 -0
  556. package/src/postgres/schema-delete.ts +96 -0
  557. package/src/postgres/schema-diff.ts +44 -0
  558. package/src/postgres/schema-drop-2bp01.test.ts +146 -0
  559. package/src/postgres/schema-errors.ts +209 -0
  560. package/src/postgres/schema-handlers.test.ts +193 -0
  561. package/src/postgres/schema-identity.ts +39 -0
  562. package/src/postgres/schema-lifecycle.test.ts +246 -0
  563. package/src/postgres/schema-race.test.ts +70 -0
  564. package/src/postgres/schema-reconcile.ts +100 -0
  565. package/src/postgres/schema-refusals.test.ts +144 -0
  566. package/src/postgres/schema-socket.test.ts +162 -0
  567. package/src/postgres/schema-sql.ts +130 -0
  568. package/src/postgres/schema-takeover.test.ts +149 -0
  569. package/src/postgres/schema-test-kit.ts +85 -0
  570. package/src/postgres/schema.ts +130 -0
  571. package/src/release/binary-alias.test.ts +3 -3
  572. package/src/release/binary-claims.test.ts +3 -3
  573. package/src/release/binary-directory-umask.test.ts +65 -0
  574. package/src/release/fake-engine.ts +15 -0
  575. package/src/valkey/acl-attrs.ts +98 -0
  576. package/src/valkey/acl-compare.ts +57 -0
  577. package/src/valkey/acl-connection.ts +13 -0
  578. package/src/valkey/acl-delete.test.ts +127 -0
  579. package/src/valkey/acl-form.ts +239 -0
  580. package/src/valkey/acl-managed.ts +13 -0
  581. package/src/valkey/acl-ops.ts +122 -0
  582. package/src/valkey/acl-ownership.test.ts +118 -0
  583. package/src/valkey/acl-prefix.test.ts +38 -0
  584. package/src/valkey/acl-profiles.ts +81 -0
  585. package/src/valkey/acl-reconcile.ts +180 -0
  586. package/src/valkey/acl-reserved.test.ts +69 -0
  587. package/src/valkey/acl-revocation.test.ts +94 -0
  588. package/src/valkey/acl-seat-prefix.test.ts +111 -0
  589. package/src/valkey/acl-validation.test.ts +54 -0
  590. package/src/valkey/acl.test.ts +215 -0
  591. package/src/valkey/acl.ts +169 -0
  592. package/src/valkey/connection-service.ts +46 -0
  593. package/src/valkey/connection.ts +180 -0
  594. package/src/valkey/errors.ts +239 -0
  595. package/src/valkey/fake-acl.ts +128 -0
  596. package/src/valkey/fake-valkey.ts +224 -0
  597. package/src/valkey/handler-test-fixture.ts +43 -0
  598. package/src/valkey/handlers.test.ts +205 -0
  599. package/src/valkey/index.ts +54 -0
  600. package/src/valkey/instance-attrs.ts +48 -0
  601. package/src/valkey/instance-form.ts +104 -0
  602. package/src/valkey/instance.test.ts +108 -0
  603. package/src/valkey/instance.ts +160 -0
  604. package/src/valkey/provider-socket-fixture.ts +71 -0
  605. package/src/valkey/providers.test.ts +129 -0
  606. package/src/valkey/providers.ts +21 -0
  607. package/src/valkey/resp.ts +212 -0
  608. package/src/valkey/transport-bounds.test.ts +150 -0
  609. package/src/valkey/transport-error.ts +37 -0
  610. package/src/valkey/transport.test.ts +158 -0
  611. package/src/valkey/transport.ts +109 -0
  612. package/src/valkey/upstream-bounds.test.ts +14 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * `LiteLLM.Key`'s secret: where it may go and where it must not. Every value here is FAKE-*, so a
3
+ * test can search for it — a secret nobody can find in a place proves nothing about that place.
4
+ */
5
+ import { describe, expect, test } from 'bun:test';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Redacted from 'effect/Redacted';
8
+ import { keyHandlers } from './key.ts';
9
+ import { generateKey } from './key-operations.ts';
10
+ import {
11
+ FAKE_KEY,
12
+ OTHER_FAKE_KEY,
13
+ VAR,
14
+ declare,
15
+ failureOf,
16
+ keyStack,
17
+ liveRow,
18
+ newFake,
19
+ runAgainst,
20
+ tokenFor,
21
+ withEnv,
22
+ } from './key-harness.ts';
23
+
24
+ const seat = { key: { fromEnv: VAR }, keyAlias: 'seat-a' } as const;
25
+ const reconcile = (news: Parameters<typeof keyHandlers.reconcile>[0]['news']) =>
26
+ keyHandlers.reconcile({ fqn: 'Seat', instanceId: 'i-1', news, output: undefined });
27
+
28
+ describe('create returns the key Redacted, and it goes nowhere else', () => {
29
+ test("the SDK hands /key/generate's key back Redacted: it prints as <redacted>", async () => {
30
+ const fake = newFake();
31
+ const response = await runAgainst(fake, generateKey({ key: FAKE_KEY, key_alias: 'seat-a' }));
32
+ expect(Redacted.isRedacted(response.key)).toBe(true);
33
+ expect(String(response.key)).toBe('<redacted>');
34
+ expect(JSON.stringify(response)).not.toContain(FAKE_KEY);
35
+ expect(Redacted.value(response.key as Redacted.Redacted<string>)).toBe(FAKE_KEY);
36
+ });
37
+
38
+ test('the value reaches the proxy once and is in no state Alchemy persists', async () => {
39
+ const fake = newFake();
40
+ const stack = keyStack(fake);
41
+ await withEnv({ [VAR]: FAKE_KEY }, () => stack.deploy(declare({ ...seat, models: ['m'] })));
42
+ expect(fake.keys.received()).toEqual([FAKE_KEY]);
43
+ expect(
44
+ fake.keys.writes().filter((w) => JSON.stringify(w.body).includes(FAKE_KEY)),
45
+ ).toHaveLength(1);
46
+ const state = stack.snapshot();
47
+ expect(state).not.toContain(FAKE_KEY);
48
+ expect(state).toContain(VAR); // the NAME is what state keeps
49
+ expect(state).not.toContain('FAKE-TOKEN'); // and neither is the row's hash
50
+ });
51
+
52
+ test('the attributes a create returns carry no key material at all', async () => {
53
+ const fake = newFake();
54
+ const attributes = await withEnv({ [VAR]: FAKE_KEY }, () => runAgainst(fake, reconcile(seat)));
55
+ expect(Object.keys(attributes).sort()).toEqual([
56
+ 'allowedRoutes',
57
+ 'budgetId',
58
+ 'duration',
59
+ 'expires',
60
+ 'keyAlias',
61
+ 'metadata',
62
+ 'models',
63
+ 'teamId',
64
+ 'withheld',
65
+ ]);
66
+ expect(JSON.stringify(attributes)).not.toContain(FAKE_KEY);
67
+ });
68
+ });
69
+
70
+ describe('a read-only plan never reveals or needs the key', () => {
71
+ test('diff with an unset variable makes no verification request', async () => {
72
+ const fake = newFake();
73
+ const attributes = await withEnv({ [VAR]: FAKE_KEY }, () => runAgainst(fake, reconcile(seat)));
74
+ const before = fake.requests().length;
75
+ const result = await withEnv({ NEVER_SET_FAKE_VAR: undefined }, () =>
76
+ runAgainst(
77
+ fake,
78
+ keyHandlers.diff({
79
+ news: { key: { fromEnv: 'NEVER_SET_FAKE_VAR' }, keyAlias: 'seat-a', models: ['x'] },
80
+ output: attributes,
81
+ }),
82
+ ),
83
+ );
84
+ expect(result).toEqual({ action: 'update' });
85
+ expect(fake.requests()).toHaveLength(before);
86
+ });
87
+
88
+ test('read surfaces no token hash, for an adopted row or a managed one', async () => {
89
+ const fake = newFake({ keySeed: [liveRow({ models: ['m'] })] });
90
+ const olds = { keyAlias: 'seat-a' };
91
+ const adopted = await runAgainst(fake, keyHandlers.read({ olds, output: undefined }));
92
+ expect(JSON.stringify(adopted)).not.toContain('FAKE-TOKEN');
93
+ expect(JSON.stringify(adopted)).toContain('"models":["m"]');
94
+ });
95
+ });
96
+
97
+ describe('a value that cannot be used is refused before any write', () => {
98
+ const refused = async (env: string | undefined, props = seat) => {
99
+ const fake = newFake();
100
+ const failure = await failureOf(
101
+ withEnv({ [VAR]: env }, () => keyStack(fake).deploy(declare(props))),
102
+ );
103
+ expect(fake.keys.writes()).toEqual([]);
104
+ return failure;
105
+ };
106
+
107
+ test('an unset variable names the variable and the alias', async () => {
108
+ const message = await refused(undefined);
109
+ expect(message).toContain(VAR);
110
+ expect(message).toContain('seat-a');
111
+ });
112
+
113
+ test('an EMPTY variable is a missing one, not an empty key', async () => {
114
+ expect(await refused('')).toContain(VAR);
115
+ });
116
+
117
+ test('a value that breaks the /key/generate rule is refused, and the value is not repeated', async () => {
118
+ for (const bad of ['sk-FAKE-short', 'FAKE-no-sk-prefix-0000000000']) {
119
+ const message = await refused(bad);
120
+ expect(message).toContain("must start with 'sk-'");
121
+ expect(message).not.toContain(bad);
122
+ }
123
+ });
124
+
125
+ test('the typed errors carry the variable and rule, never the value', async () => {
126
+ const fake = newFake();
127
+ const error = await withEnv({ [VAR]: 'sk-FAKE-short' }, () =>
128
+ runAgainst(fake, Effect.flip(reconcile(seat))),
129
+ );
130
+ expect(error).toMatchObject({ _tag: 'LitellmKeyValueMalformedError', variable: VAR });
131
+ expect(JSON.stringify(error)).not.toContain('sk-FAKE-short');
132
+ });
133
+
134
+ test('a create with no `key` declared is refused, not left to mint a value nobody holds', async () => {
135
+ const fake = newFake();
136
+ const error = await runAgainst(fake, Effect.flip(reconcile({ keyAlias: 'seat-a' })));
137
+ expect(error).toMatchObject({ _tag: 'LitellmKeyValueRequiredError', keyAlias: 'seat-a' });
138
+ expect(fake.keys.writes()).toEqual([]);
139
+ });
140
+ });
141
+
142
+ describe('the value is only ever for a create', () => {
143
+ test("an existing key's value is read only to compare, never sent", async () => {
144
+ // The row's token matches OTHER_FAKE_KEY, so the in-memory compare passes — proving the value
145
+ // was read for the check but never put on the wire.
146
+ const fake = newFake({
147
+ keySeed: [liveRow({ models: ['old'], token: tokenFor(OTHER_FAKE_KEY) })],
148
+ });
149
+ await withEnv({ [VAR]: OTHER_FAKE_KEY }, () =>
150
+ keyStack(fake).deploy(declare({ ...seat, models: ['new'] }), { adopt: true }),
151
+ );
152
+ expect(fake.keys.received()).toEqual([]);
153
+ expect(JSON.stringify(fake.keys.writes())).not.toContain(OTHER_FAKE_KEY);
154
+ });
155
+
156
+ test('an existing key whose declared value is not the key it holds is refused', async () => {
157
+ // The row's token is some OTHER key's hash; the declared value cannot be the row's, so nothing
158
+ // is written and the refusal names the variable, never the value.
159
+ const fake = newFake({ keySeed: [liveRow({ models: ['old'] })] });
160
+ const message = await failureOf(
161
+ withEnv({ [VAR]: OTHER_FAKE_KEY }, () =>
162
+ keyStack(fake).deploy(declare({ ...seat, models: ['new'] }), { adopt: true }),
163
+ ),
164
+ );
165
+ expect(message).toContain(VAR);
166
+ expect(message).toContain('is not the key');
167
+ expect(message).not.toContain(OTHER_FAKE_KEY);
168
+ expect(fake.keys.writes()).toEqual([]);
169
+ });
170
+
171
+ test('a proxy that mints its own value instead is caught, and its row removed again', async () => {
172
+ const fake = newFake({ keyIgnoresUserValue: true });
173
+ const message = await failureOf(
174
+ withEnv({ [VAR]: FAKE_KEY }, () => keyStack(fake).deploy(declare(seat))),
175
+ );
176
+ expect(message).toContain('different value');
177
+ expect(message).toContain('deleted again');
178
+ expect(message).not.toContain(FAKE_KEY);
179
+ expect(fake.keys.rows()).toEqual([]);
180
+ expect(fake.keys.writes().map((w) => w.path)).toEqual(['/key/generate', '/key/delete']);
181
+ });
182
+ });
183
+
184
+ /**
185
+ * Runs `body` with `console.error` captured. The SDK really does print bodies under
186
+ * `DISTILLED_DEBUG_HTTP`; capturing keeps the suite quiet and lets a test say what was printed.
187
+ */
188
+ const printedBy = async (body: () => Promise<unknown>): Promise<string> => {
189
+ const printed: string[] = [];
190
+ const original = console.error;
191
+ console.error = (...args: unknown[]) => void printed.push(args.join(' '));
192
+ try {
193
+ await body();
194
+ } catch {
195
+ // the deploy's own failure is asserted by the caller, from what it left behind
196
+ } finally {
197
+ console.error = original;
198
+ }
199
+ return printed.join('\n');
200
+ };
201
+
202
+ describe('what could still leak the value, or lose the key', () => {
203
+ test('DISTILLED_DEBUG_HTTP refuses a create: the SDK would print the key to stderr', async () => {
204
+ const fake = newFake();
205
+ let message = '';
206
+ const printed = await printedBy(async () => {
207
+ message = await failureOf(
208
+ withEnv({ [VAR]: FAKE_KEY, DISTILLED_DEBUG_HTTP: '1' }, () =>
209
+ keyStack(fake).deploy(declare(seat)),
210
+ ),
211
+ );
212
+ });
213
+ expect(message).toContain('DISTILLED_DEBUG_HTTP');
214
+ expect(message).not.toContain(FAKE_KEY);
215
+ expect(fake.keys.writes()).toEqual([]);
216
+ expect(printed).not.toContain(FAKE_KEY);
217
+ });
218
+
219
+ test('DISTILLED_DEBUG_HTTP does not stop an update, which never carries the value', async () => {
220
+ const fake = newFake({
221
+ keySeed: [liveRow({ models: ['old'], token: tokenFor(FAKE_KEY) })],
222
+ });
223
+ const printed = await printedBy(() =>
224
+ withEnv({ DISTILLED_DEBUG_HTTP: '1', [VAR]: FAKE_KEY }, () =>
225
+ keyStack(fake).deploy(declare({ ...seat, models: ['new'] }), { adopt: true }),
226
+ ),
227
+ );
228
+ expect(fake.keys.writes().map((w) => w.path)).toEqual(['/key/update']);
229
+ expect(printed).toContain('/key/update'); // the printing path ran…
230
+ expect(printed).not.toContain(FAKE_KEY); // …with no value in it
231
+ });
232
+
233
+ test('a value with a trailing newline is refused, not sent', async () => {
234
+ const fake = newFake();
235
+ const message = await failureOf(
236
+ withEnv({ [VAR]: `${FAKE_KEY}\n` }, () => keyStack(fake).deploy(declare(seat))),
237
+ );
238
+ expect(message).toContain('no whitespace');
239
+ expect(fake.keys.writes()).toEqual([]);
240
+ });
241
+
242
+ test('an empty alias is refused before any request', async () => {
243
+ const fake = newFake();
244
+ const message = await failureOf(keyStack(fake).deploy(declare({ keyAlias: ' ' })));
245
+ expect(message).toContain('keyAlias is empty');
246
+ expect(fake.requests()).toEqual([]);
247
+ });
248
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Where `LiteLLM.Key`'s secret comes from, and the only place it is unwrapped.
3
+ *
4
+ * ⛔ THE KEY VALUE IS WRITE-ONLY, THE SAME MECHANISM EVERY OTHER KIT SECRET USES
5
+ * (`../secrets/write-only.ts`: PBS webhook secrets, the Cloudflare MCP entry, Forgejo org secrets).
6
+ * The prop holds the NAME of an environment variable, `{ fromEnv: 'SEAT_KEY' }`; the deploying
7
+ * process reads it at call time; the value goes over the wire once, in `/key/generate`'s body.
8
+ * Alchemy persists props AND attributes unencrypted — a `Redacted` is TAGGED, not encrypted
9
+ * (`State/StateEncoding.ts` writes the inner value beside the tag) — so neither may hold it.
10
+ * S25 of the provider standard says the same; `MeshNode` (docs/mesh-node.md) is the other family
11
+ * that keeps a generated secret out of state.
12
+ * ★ WHY NOT LET LITELLM MINT IT AND RETURN IT. `/key/generate` does return the new key, and the SDK
13
+ * decodes it `Redacted` (`T.SensitiveValue`). But a resource has exactly two places to put it, an
14
+ * attribute (state, in the clear) or nowhere; and LiteLLM never shows the plaintext again, only a
15
+ * hash. A key minted and dropped is a row nobody can use. So the value is minted OUTSIDE — a random
16
+ * `sk-…` in the vault, the same one the seat reads — and handed to LiteLLM, which the docstring
17
+ * for `key` supports: "User defined key value. Must start with 'sk-' and be at least 16
18
+ * characters long."
19
+ * ⚠️ AN ADOPTED KEY'S VALUE IS COMPARED WITH THE LIVE ROW, IN MEMORY ONLY. LiteLLM keeps the sha256
20
+ * hex of the key (`hash_token`, measured in the 1.103.0 wheel's `proxy/utils.py`), and `/key/list`
21
+ * with `return_full_object` returns it as the row's `token`. When a declaration names `key:
22
+ * { fromEnv }` for a key that already exists, the resolved value is sha256'd and compared with that
23
+ * hash (never with the value, and the hash is never persisted — key-form.ts's `toAttributes` still
24
+ * drops `token`). A mismatch is refused (`LitellmKeyValueMismatchError`): a seat reading the same
25
+ * variable would hold a key that authenticates nothing while the plan reported success. There is
26
+ * nothing to WRITE about a mismatch — `/key/update` drops `key` from its body
27
+ * (`prepare_key_update_data` pops it, `key_management_endpoints.py:2469`), so it cannot change a
28
+ * key's value, and `/key/regenerate` is an Enterprise feature (its docstring, :5673) — so the
29
+ * operator fixes the variable or drops the `key`. A changed variable changes nothing on an
30
+ * existing key: rotate by declaring a new alias. Plan and reconcile both check a set variable;
31
+ * an unset or empty variable skips verification so a deployer need not hold the seat key.
32
+ * ⚠️ A PROXY MAY REFUSE THE WHOLE APPROACH: with the dashboard's `disable_custom_api_keys` setting
33
+ * on, `/key/generate` answers 403 to ANY user-defined key ("Keys must be auto-generated",
34
+ * `_check_custom_key_allowed`, :485-496). That surfaces as the SDK's typed `Forbidden`. UNVERIFIED
35
+ * whether the estate's proxy has it on; adopting existing keys is unaffected.
36
+ */
37
+ import { createHash } from 'node:crypto';
38
+ import * as Effect from 'effect/Effect';
39
+ import * as Redacted from 'effect/Redacted';
40
+ import { type FromEnv, resolveAll } from '../secrets/write-only.ts';
41
+ import {
42
+ LitellmKeyDebugLoggingError,
43
+ LitellmKeyValueMalformedError,
44
+ LitellmKeyValueMismatchError,
45
+ LitellmKeyValueMissingError,
46
+ LitellmKeyValueRequiredError,
47
+ } from './key-errors.ts';
48
+
49
+ /** From `/key/generate`'s own docstring for the `key` field (distilled-litellm, LiteLLM 1.103.0). */
50
+ const PREFIX = 'sk-';
51
+ const MIN_LENGTH = 16;
52
+ /**
53
+ * ⚠️ NO WHITESPACE is the house's own addition, not LiteLLM's: a secret renderer's trailing newline
54
+ * would be sent as part of the value, and a seat that trims what it reads would then hold a
55
+ * different key from the one LiteLLM stored (`resolveAll` deliberately does not trim).
56
+ */
57
+ const RULE = `a user-defined key must start with '${PREFIX}', be at least ${MIN_LENGTH} characters and contain no whitespace`;
58
+
59
+ /**
60
+ * The declared value, `Redacted`, or a typed refusal naming the alias, the variable and the rule.
61
+ * Called only when a create is actually about to happen, never by `diff` or `read`, so a plan
62
+ * (which needs no secret) runs without the variable.
63
+ * ⚠️ AN EMPTY VARIABLE IS A MISSING ONE (`resolveAll`): a secret renderer that failed writes
64
+ * `NAME=` and sending `''` would create a key that authenticates with nothing.
65
+ */
66
+ export const resolveKeyValue = (
67
+ keyAlias: string,
68
+ ref: FromEnv | undefined,
69
+ ): Effect.Effect<
70
+ Redacted.Redacted<string>,
71
+ LitellmKeyValueRequiredError | LitellmKeyValueMissingError | LitellmKeyValueMalformedError
72
+ > =>
73
+ Effect.gen(function* () {
74
+ if (ref === undefined) return yield* new LitellmKeyValueRequiredError({ keyAlias });
75
+ const variable = ref.fromEnv;
76
+ const value = resolveAll({ key: ref }).values['key'];
77
+ if (value === undefined) return yield* new LitellmKeyValueMissingError({ keyAlias, variable });
78
+ if (!value.startsWith(PREFIX) || value.length < MIN_LENGTH || /\s/.test(value)) {
79
+ return yield* new LitellmKeyValueMalformedError({ keyAlias, rule: RULE, variable });
80
+ }
81
+ return Redacted.make(value);
82
+ });
83
+
84
+ /** Whether `/key/generate` echoed the value it was sent (the SDK hands it back `Redacted`). */
85
+ export const echoes = (
86
+ returned: string | Redacted.Redacted<string>,
87
+ sent: Redacted.Redacted<string>,
88
+ ): boolean =>
89
+ (Redacted.isRedacted(returned) ? Redacted.value(returned) : returned) === Redacted.value(sent);
90
+
91
+ /**
92
+ * The sha256 hex of a key value, the same digest LiteLLM stores as `hash_token` (`proxy/utils.py`).
93
+ * ★ MATCHES THE VENDOR'S HASH, not a house format: the row's `token` is the vendor's own store, so
94
+ * the digest must be reproducible from the value with the vendor's algorithm, not a salt (a salt
95
+ * would make every comparison fail). Nothing here is persisted — the value is in memory only.
96
+ */
97
+ const sha256 = (value: string): string => createHash('sha256').update(value).digest('hex');
98
+
99
+ /**
100
+ * Whether an ADOPTED key's declared `key: { fromEnv }` value is the key the live row holds.
101
+ *
102
+ * ⚠️ COMPARED AS A SHA256 AGAINST THE ROW'S `token`, never value-against-value, and the hash is
103
+ * never written anywhere: a `Redacted` value unwrapped only long enough to digest it, then gone.
104
+ * The row's `token` (`hash_token`) is the vendor's own sha256 hex of the key.
105
+ */
106
+ export const verifyKeyValue = (
107
+ keyAlias: string,
108
+ ref: FromEnv | undefined,
109
+ token: string | null,
110
+ ): Effect.Effect<void, LitellmKeyValueMismatchError> =>
111
+ Effect.gen(function* () {
112
+ if (ref === undefined) return;
113
+ const variable = ref.fromEnv;
114
+ const value = resolveAll({ key: ref }).values['key'];
115
+ if (value === undefined) return;
116
+ if (token === null || sha256(value) !== token) {
117
+ return yield* new LitellmKeyValueMismatchError({ keyAlias, variable });
118
+ }
119
+ });
120
+
121
+ /**
122
+ * ⛔ THE SDK PRINTS BODIES WHILE `DISTILLED_DEBUG_HTTP` IS SET (`@distilled.cloud/core`
123
+ * `protocol-http.ts`: the first 400 characters of every request body; `protocol-rest.ts`: of every
124
+ * response), and `/key/generate` carries the key in both. Measured by reading the pinned source
125
+ * 2026-09-29; `mesh-node-token.ts` refuses for the same reason. Only a create needs it: no other
126
+ * call in this resource carries the value. Reads `process.env` because that is what distilled reads.
127
+ */
128
+ export const refuseDebugLogging = (
129
+ keyAlias: string,
130
+ ): Effect.Effect<void, LitellmKeyDebugLoggingError> =>
131
+ (globalThis.process?.env?.['DISTILLED_DEBUG_HTTP'] ?? '') !== ''
132
+ ? Effect.fail(new LitellmKeyDebugLoggingError({ keyAlias }))
133
+ : Effect.void;
134
+
135
+ /** Whether verification can run; unset/empty variables skip the check, never a create. */
136
+ export const hasKeyValue = (ref: FromEnv | undefined): boolean =>
137
+ ref !== undefined && resolveAll({ key: ref }).values['key'] !== undefined;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `LiteLLM.Key`'s create when the connection to the proxy fails: the key is IN the request body of
3
+ * `/key/generate`, and an `HttpClientError` carries its request, so a create that dies on the wire
4
+ * would hand the plaintext to whoever logs, inspects or serialises the failure. Every place a failed
5
+ * create can be read from is searched here for a value the tests can find (`FAKE_KEY`).
6
+ */
7
+ import { describe, expect, test } from 'bun:test';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Logger from 'effect/Logger';
10
+ import { keyHandlers } from './key.ts';
11
+ import {
12
+ FAKE_KEY,
13
+ type Fake,
14
+ VAR,
15
+ declare,
16
+ keyStack,
17
+ newFake,
18
+ runAgainst,
19
+ withEnv,
20
+ } from './key-harness.ts';
21
+
22
+ const seat = { key: { fromEnv: VAR }, keyAlias: 'seat-a' } as const;
23
+ const reconcile = () =>
24
+ keyHandlers.reconcile({ fqn: 'Seat', instanceId: 'i-1', news: seat, output: undefined });
25
+
26
+ const pathOf = (input: string | URL | Request) =>
27
+ new URL(input instanceof Request ? input.url : String(input)).pathname;
28
+
29
+ /** The fake, with `/key/generate` answered by `onGenerate` and every other route its own. */
30
+ const generatingWith = (fake: Fake, onGenerate: () => Promise<Response>): Fake => ({
31
+ ...fake,
32
+ fetch: (async (input: string | URL | Request, init?: RequestInit) =>
33
+ pathOf(input) === '/key/generate'
34
+ ? onGenerate()
35
+ : fake.fetch(input, init)) as typeof globalThis.fetch,
36
+ });
37
+
38
+ /** The connection drops: `fetch` rejects before any answer. */
39
+ const droppedConnection = (fake: Fake): Fake =>
40
+ generatingWith(fake, () => Promise.reject(new TypeError('fetch failed')));
41
+
42
+ /** The answer starts (200, headers) and its body then fails mid-read: the SDK reads it with `orDie`. */
43
+ const brokenAnswer = (fake: Fake): Fake =>
44
+ generatingWith(fake, () =>
45
+ Promise.resolve(
46
+ new Response(
47
+ new ReadableStream({
48
+ start: (controller) => controller.error(new Error('connection reset mid-body')),
49
+ }),
50
+ { headers: { 'content-type': 'application/json' }, status: 200 },
51
+ ),
52
+ ),
53
+ );
54
+
55
+ /** What Effect's own JSON logger prints for `Effect.logError('create failed', value)`. */
56
+ const jsonLogged = async (value: unknown): Promise<string> => {
57
+ const lines: string[] = [];
58
+ const capture = Logger.make((options) => void lines.push(Logger.formatJson.log(options)));
59
+ await Effect.runPromise(
60
+ Effect.logError('create failed', value).pipe(Effect.provide(Logger.layer([capture]))),
61
+ );
62
+ return lines.join('\n');
63
+ };
64
+
65
+ /** Every reading of `value` a caller could take: serialise, deep-inspect, print, log. */
66
+ const everyReadingOf = async (value: unknown): Promise<string> => {
67
+ const readings = [Bun.inspect(value, { depth: 12 }), String(value), await jsonLogged(value)];
68
+ try {
69
+ readings.push(JSON.stringify(value));
70
+ } catch {
71
+ // a value JSON cannot serialise leaks nothing that way
72
+ }
73
+ if (value instanceof Error) readings.push(value.message, String(value.stack));
74
+ return readings.join('\n');
75
+ };
76
+
77
+ const rejectionOf = (attempt: Promise<unknown>): Promise<unknown> =>
78
+ attempt.then(
79
+ () => undefined,
80
+ (error: unknown) => error,
81
+ );
82
+
83
+ describe('a create that dies on the wire', () => {
84
+ test('fails with a typed error that names the alias and holds no request', async () => {
85
+ const fake = droppedConnection(newFake());
86
+ const error = await withEnv({ [VAR]: FAKE_KEY }, () =>
87
+ runAgainst(fake, Effect.flip(reconcile())),
88
+ );
89
+ expect(error).toMatchObject({
90
+ _tag: 'LitellmKeyTransportError',
91
+ keyAlias: 'seat-a',
92
+ reason: 'TransportError',
93
+ });
94
+ expect(Object.keys(error)).not.toContain('request');
95
+ expect(Object.keys(error)).not.toContain('cause');
96
+ expect(await everyReadingOf(error)).not.toContain(FAKE_KEY);
97
+ });
98
+
99
+ test('a create whose ANSWER was lost leaves a live key, and the next deploy takes it with --adopt', async () => {
100
+ const fake = newFake();
101
+ let answerLost = true;
102
+ const flaky: Fake = {
103
+ ...fake,
104
+ fetch: (async (input: string | URL | Request, init?: RequestInit) => {
105
+ const answer = await fake.fetch(input, init);
106
+ if (answerLost && pathOf(input) === '/key/generate') throw new TypeError('fetch failed');
107
+ return answer;
108
+ }) as typeof globalThis.fetch,
109
+ };
110
+ const stack = keyStack(flaky, { noRetry: true });
111
+ const first = await rejectionOf(
112
+ withEnv({ [VAR]: FAKE_KEY }, () => stack.deploy(declare(seat))),
113
+ );
114
+ expect(String((first as Error).message ?? first)).toContain('/key/generate request failed');
115
+ expect(fake.keys.rows()).toHaveLength(1); // the key DID land: the message says "MAY", and it was right
116
+
117
+ answerLost = false;
118
+ const refused = await rejectionOf(
119
+ withEnv({ [VAR]: FAKE_KEY }, () => stack.deploy(declare(seat))),
120
+ );
121
+ expect(String(refused)).toContain('adopt'); // what the error message tells the operator to use
122
+ await withEnv({ [VAR]: FAKE_KEY }, () => stack.deploy(declare(seat), { adopt: true }));
123
+ expect(fake.keys.writes().map((write) => write.path)).toEqual(['/key/generate']);
124
+ expect(fake.keys.rows()).toHaveLength(1);
125
+ });
126
+
127
+ test('through the engine the deploy rejects with nothing of the key in it', async () => {
128
+ const fake = droppedConnection(newFake());
129
+ const rejection = await rejectionOf(
130
+ withEnv({ [VAR]: FAKE_KEY }, () => keyStack(fake, { noRetry: true }).deploy(declare(seat))),
131
+ );
132
+ expect(rejection).toBeDefined();
133
+ expect(await everyReadingOf(rejection)).not.toContain(FAKE_KEY);
134
+ expect(fake.keys.writes()).toEqual([]);
135
+ });
136
+
137
+ test('a body that fails mid-read (a defect in the SDK) leaks nothing either', async () => {
138
+ const fake = brokenAnswer(newFake());
139
+ const exit = await withEnv({ [VAR]: FAKE_KEY }, () =>
140
+ runAgainst(fake, Effect.exit(reconcile())),
141
+ );
142
+ expect(exit._tag).toBe('Failure');
143
+ expect(await everyReadingOf(exit)).not.toContain(FAKE_KEY);
144
+ const rejection = await rejectionOf(
145
+ withEnv({ [VAR]: FAKE_KEY }, () => keyStack(fake, { noRetry: true }).deploy(declare(seat))),
146
+ );
147
+ expect(rejection).toBeDefined();
148
+ expect(await everyReadingOf(rejection)).not.toContain(FAKE_KEY);
149
+ });
150
+ });
151
+
152
+ describe('a transport failure on a call that carries no value keeps the SDK error', () => {
153
+ test('/key/list dropping is still the SDK error, so a read failure keeps its detail', async () => {
154
+ const fake: Fake = {
155
+ ...newFake(),
156
+ fetch: (async (_input: string | URL | Request): Promise<Response> => {
157
+ throw new TypeError('fetch failed');
158
+ }) as typeof globalThis.fetch,
159
+ };
160
+ const error = await runAgainst(
161
+ fake,
162
+ Effect.flip(keyHandlers.read({ olds: { keyAlias: 'seat-a' }, output: undefined })),
163
+ );
164
+ expect(error).toMatchObject({ _tag: 'HttpClientError' });
165
+ });
166
+ });