@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,151 @@
1
+ /**
2
+ * `credential-form.ts`'s refusals, read shape and comparison — pure: no server, no environment
3
+ * (the values' own behaviour is credential-values.ts's, tested through the resource test).
4
+ *
5
+ * ★ EVERY VALUE IS `FAKE-*`.
6
+ */
7
+ import { expect, test } from 'bun:test';
8
+ import * as Redacted from 'effect/Redacted';
9
+ import {
10
+ createBody,
11
+ differing,
12
+ firstProblem,
13
+ isCredentialRow,
14
+ literalValues,
15
+ patchBody,
16
+ removedInfoKeys,
17
+ toAttributes,
18
+ } from './credential-form.ts';
19
+ import type { CredentialAttributes, CredentialProps } from './credential-types.ts';
20
+ import type { ResolvedValues } from './credential-values.ts';
21
+
22
+ const VARIABLE = 'FAKE_CREDENTIAL_KEY';
23
+ const base: CredentialProps = {
24
+ credentialInfo: { note: 'docs search' },
25
+ credentialName: 'FAKE_api',
26
+ credentialValues: { api_key: { fromEnv: VARIABLE } },
27
+ };
28
+
29
+ /** Live attributes as the resource would hold them, over a metadata map. */
30
+ const attributes = (info: Record<string, unknown>, seal = ''): CredentialAttributes => ({
31
+ credentialInfo: info,
32
+ credentialName: 'FAKE_api',
33
+ valuesSeal: seal,
34
+ });
35
+
36
+ test('firstProblem refuses the combinations nobody meant', () => {
37
+ const cases: readonly [label: string, props: CredentialProps, fragment: string][] = [
38
+ ['a blank name', { ...base, credentialName: ' ' }, 'credentialName'],
39
+ ['a padded name', { ...base, credentialName: ' FAKE_api' }, 'credentialName'],
40
+ ['a non-object metadata map', { ...base, credentialInfo: null as never }, 'credentialInfo'],
41
+ [
42
+ 'a sensitive metadata key',
43
+ { ...base, credentialInfo: { authToken: 'FAKE-meta' } },
44
+ 'authToken',
45
+ ],
46
+ ['an empty values map', { ...base, credentialValues: {} }, 'credentialValues'],
47
+ [
48
+ 'a padded values key',
49
+ { ...base, credentialValues: { ' key': { fromEnv: VARIABLE } } },
50
+ 'credentialValues',
51
+ ],
52
+ [
53
+ 'a literal value',
54
+ { ...base, credentialValues: { api_key: 'FAKE-literal' as never } },
55
+ 'credentialValues',
56
+ ],
57
+ [
58
+ 'a blank variable name',
59
+ { ...base, credentialValues: { api_key: { fromEnv: ' ' } } },
60
+ 'credentialValues',
61
+ ],
62
+ ];
63
+ for (const [label, props, fragment] of cases) {
64
+ expect(firstProblem(props), label).toContain(fragment);
65
+ }
66
+ expect(firstProblem(base)).toBeUndefined();
67
+ });
68
+
69
+ test('toAttributes mirrors only the metadata the vendor answers unmasked, and no values at all', () => {
70
+ const live = toAttributes({
71
+ credential_info: { note: 'docs search', aws_secret: 'FAKE-clear-in-info' },
72
+ credential_name: 'FAKE_api',
73
+ credential_values: { api_key: 'FA****ey', region: 'us-east' },
74
+ });
75
+ expect(live).toEqual({
76
+ credentialInfo: { note: 'docs search' },
77
+ credentialName: 'FAKE_api',
78
+ valueKeys: ['api_key', 'region'],
79
+ infoKeys: ['aws_secret', 'note'],
80
+ valuesSeal: '',
81
+ });
82
+ });
83
+
84
+ test('differing compares per declared key only, deeply, ignoring object key order', () => {
85
+ // This helper checks declared values; removedInfoKeys separately detects undeclared live keys.
86
+ expect(differing(attributes({ note: 'docs search', extra: 'live' }), base)).toEqual([]);
87
+ expect(differing(attributes({ note: 'docs search v2', extra: 'live' }), base)).toEqual([
88
+ 'credential_info.note',
89
+ ]);
90
+ expect(differing(attributes({}), base)).toEqual(['credential_info.note']);
91
+ const nested = { ...base, credentialInfo: { scopes: ['read', 'write'], vendor: { a: 1, b: 2 } } };
92
+ // object key order never matters; array order does — an array is an ordered list in JSON
93
+ expect(
94
+ differing(attributes({ scopes: ['read', 'write'], vendor: { b: 2, a: 1 } }), nested),
95
+ ).toEqual([]);
96
+ expect(
97
+ differing(attributes({ scopes: ['write', 'read'], vendor: { a: 1, b: 2 } }), nested),
98
+ ).toEqual(['credential_info.scopes']);
99
+ expect(differing(attributes({ scopes: ['read'], vendor: { a: 1, b: 2 } }), nested)).toEqual([
100
+ 'credential_info.scopes',
101
+ ]);
102
+ });
103
+
104
+ test('isCredentialRow accepts only a row with a string name', () => {
105
+ expect(isCredentialRow({ credential_name: 'FAKE_api' })).toBe(true);
106
+ expect(isCredentialRow({ credential_values: {} })).toBe(false);
107
+ expect(isCredentialRow('not a row')).toBe(false);
108
+ expect(isCredentialRow(null)).toBe(false);
109
+ });
110
+
111
+ test('createBody carries the name in the body and the values as literals, unwrapped once', () => {
112
+ const resolved: ResolvedValues = {
113
+ missing: [],
114
+ values: { api_key: Redacted.make('FAKE-key-one') },
115
+ };
116
+ expect(literalValues(resolved)).toEqual({ api_key: 'FAKE-key-one' });
117
+ const body = createBody(base, resolved);
118
+ expect(body).toEqual({
119
+ credential_info: { note: 'docs search' },
120
+ credential_name: 'FAKE_api',
121
+ credential_values: { api_key: 'FAKE-key-one' },
122
+ });
123
+ // the body carries the VALUE the proxy stores, never the variable NAME the declaration holds
124
+ expect(JSON.stringify(body)).not.toContain(VARIABLE);
125
+ });
126
+
127
+ test('patchBody carries the name twice (path label and body field) and the info, values optional', () => {
128
+ expect(patchBody(base, undefined)).toEqual({
129
+ credential_info: { note: 'docs search' },
130
+ credential_name: 'FAKE_api',
131
+ credential_name_body: 'FAKE_api',
132
+ });
133
+ expect(patchBody(base, { api_key: 'FAKE-key-one' })).toEqual({
134
+ credential_info: { note: 'docs search' },
135
+ credential_name: 'FAKE_api',
136
+ credential_name_body: 'FAKE_api',
137
+ credential_values: { api_key: 'FAKE-key-one' },
138
+ });
139
+ // the body carries the VALUE, never the variable NAME the declaration holds
140
+ expect(JSON.stringify(patchBody(base, { api_key: 'FAKE-key-one' }))).not.toContain(VARIABLE);
141
+ });
142
+
143
+ test('removedInfoKeys finds live keys absent from the full intended info map', () => {
144
+ const prior = attributes({ note: 'docs search', team: 'a' });
145
+ expect(removedInfoKeys(prior, base)).toEqual(['team']);
146
+ expect(
147
+ removedInfoKeys(prior, { ...base, credentialInfo: { note: 'docs search', team: 'a' } }),
148
+ ).toEqual([]);
149
+ // No live row means there is nothing to remove.
150
+ expect(removedInfoKeys(undefined, base)).toEqual([]);
151
+ });
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Refusals, wire bodies, the read shape and the comparison for `LiteLLM.Credential`, testable
3
+ * without a server. Value handling is credential-values.ts's.
4
+ *
5
+ * ⛔ WHAT IS COMPARED, WITH THE VENDOR'S MASKING IN MIND. Both reads answer `credential_values`
6
+ * with SENSITIVE-keyed values masked (2 + `****` + 2, `*****` for a short string —
7
+ * `litellm_logging.py::_get_masked_values`, measured in the live 1.103.0 container) and
8
+ * `credential_info` in full:
9
+ * - `credential_info` is the complete intended map, including on adoption. Nonempty PATCH
10
+ * normally replaces DB info (`credential_endpoints/endpoints.py:314-319`, 1.103.0), while
11
+ * memory merges (:385-387). Empty info leaves DB unchanged. Dropping a live info key therefore
12
+ * requires a rewrite to converge both stores (`removedInfoKeys`). Every PATCH sends full info.
13
+ * - the values are compared only through the seal of the values this process RESOLVED
14
+ * (`valuesSeal`, credential-values.ts) — never against the masked fragments, which are
15
+ * partial secrets and are never copied into state.
16
+ * ★ A CHANGED ROW IS PATCHED, NOT REWRITTEN. The vendor's PATCH merges VALUES by key
17
+ * (`update_db_credential`:312), so updating a value or an `info` entry is a PATCH carrying the
18
+ * declared `credential_info` and (only when the values are stale) the resolved
19
+ * `credential_values`. A PATCH that fails on the wire leaves the row it was merging into — the
20
+ * fix for the review's finding 3, where a DELETE + POST rewrite left NO row when the POST failed
21
+ * after the DELETE. PATCH cannot REMOVE a value key or an in-memory info key, so dropping either
22
+ * is still a whole-row rewrite (credential.ts decides). Names from the read prove removal. The
23
+ * PATCH body carries `credential_name` IN THE BODY as a second member (`credential_name_body`,
24
+ * wire-named `credential_name`) because the vendor's `UpdateCredentialItem` requires it and a
25
+ * Smithy member has one binding — the path label can't also be the body field; the distilled
26
+ * patch for it is `patches/credential_management/update_credential_credentials__credential_name__patch.json`.
27
+ */
28
+ import type * as credentials from '@distilled.cloud/litellm/credential_management';
29
+ import * as Redacted from 'effect/Redacted';
30
+ import {
31
+ type CredentialAttributes,
32
+ type CredentialProps,
33
+ isBlank,
34
+ isSensitiveKey,
35
+ } from './credential-types.ts';
36
+ import type { ResolvedValues } from './credential-values.ts';
37
+
38
+ /** The first reason a declaration is refused, or `undefined`. Pure: no environment, no server. */
39
+ export const firstProblem = (props: CredentialProps): string | undefined => {
40
+ if (isBlank(props.credentialName) || props.credentialName !== props.credentialName.trim()) {
41
+ return '`credentialName` must be a non-empty string without leading or trailing whitespace';
42
+ }
43
+ if (
44
+ props.credentialInfo !== undefined &&
45
+ (typeof props.credentialInfo !== 'object' || props.credentialInfo === null)
46
+ ) {
47
+ return '`credentialInfo` must be an object of metadata';
48
+ }
49
+ if (props.credentialInfo !== undefined) {
50
+ for (const key of Object.keys(props.credentialInfo)) {
51
+ if (isSensitiveKey(key)) {
52
+ return (
53
+ `\`credentialInfo\` must not declare "${key}": LiteLLM returns \`credential_info\` ` +
54
+ 'unmasked, so declare it in `credentialValues`, which LiteLLM stores encrypted'
55
+ );
56
+ }
57
+ }
58
+ }
59
+ if (Object.keys(props.credentialValues).length === 0) {
60
+ return (
61
+ '`credentialValues` needs at least one entry: LiteLLM refuses a credential with neither ' +
62
+ '`credential_values` nor a model_id, and `model_id` is not modelled here'
63
+ );
64
+ }
65
+ for (const key of Object.keys(props.credentialValues)) {
66
+ if (isBlank(key) || key !== key.trim()) {
67
+ return '`credentialValues` keys must be non-empty strings without padding';
68
+ }
69
+ const entry = props.credentialValues[key];
70
+ if (typeof entry !== 'object' || entry === null || isBlank(entry.fromEnv)) {
71
+ return `\`credentialValues["${key}"]\` takes \`{ fromEnv: 'NAME' }\`, never a value: the value would land in Alchemy's unencrypted state`;
72
+ }
73
+ }
74
+ return undefined;
75
+ };
76
+
77
+ /**
78
+ * Wire names of the fields where the live row and the declaration disagree, for a
79
+ * `NotConvergedError` and for the drift decision. The NAME is identity — a changed name plans a
80
+ * REPLACE in credential.ts and is never compared here. Values are never compared against the
81
+ * live row at all: the seal decides (credential-values.ts).
82
+ */
83
+ export const differing = (
84
+ live: CredentialAttributes,
85
+ props: CredentialProps,
86
+ ): readonly string[] => {
87
+ const out: string[] = [];
88
+ if (props.credentialInfo !== undefined) {
89
+ for (const [key, declared] of Object.entries(props.credentialInfo)) {
90
+ if (!isSensitiveKey(key) && !jsonEquals(live.credentialInfo[key], declared)) {
91
+ out.push(`credential_info.${key}`);
92
+ }
93
+ }
94
+ }
95
+ return out;
96
+ };
97
+
98
+ /** Canonical-JSON deep equality, order-insensitive for objects. */
99
+ const jsonEquals = (left: unknown, right: unknown): boolean =>
100
+ JSON.stringify(canonicalise(left)) === JSON.stringify(canonicalise(right));
101
+
102
+ const canonicalise = (value: unknown): unknown => {
103
+ if (Array.isArray(value)) return value.map(canonicalise);
104
+ if (typeof value === 'object' && value !== null) {
105
+ const out: Record<string, unknown> = {};
106
+ for (const key of Object.keys(value as Record<string, unknown>).sort()) {
107
+ out[key] = canonicalise((value as Record<string, unknown>)[key]);
108
+ }
109
+ return out;
110
+ }
111
+ return value;
112
+ };
113
+
114
+ /**
115
+ * One row of a credentials read, as attributes. ⛔ COPIES NO `credential_values` ENTRY: a masked
116
+ * fragment (`sk****45`) is a partial secret, and a sensitive key the read masks would land half a
117
+ * secret in Alchemy's state. The `credential_info` mirror drops SENSITIVE-KEYED entries (they are
118
+ * returned unmasked and would mirror a clear secret into state). The digest is the caller's
119
+ * (credential.ts fills `valuesSeal` from state; a row cannot supply one).
120
+ */
121
+ export const toAttributes = (row: Record<string, unknown>): CredentialAttributes => {
122
+ const info = (row['credential_info'] ?? {}) as Record<string, unknown>;
123
+ const mirrored: Record<string, unknown> = {};
124
+ for (const [key, value] of Object.entries(info)) {
125
+ if (!isSensitiveKey(key)) mirrored[key] = value;
126
+ }
127
+ return {
128
+ credentialInfo: mirrored,
129
+ credentialName: String(row['credential_name'] ?? ''),
130
+ valueKeys: Object.keys((row['credential_values'] ?? {}) as Record<string, unknown>).sort(),
131
+ infoKeys: Object.keys(info).sort(),
132
+ valuesSeal: '',
133
+ };
134
+ };
135
+
136
+ /** The rows a credentials read carries, `CredentialItem`-shaped. */
137
+ export const isCredentialRow = (row: unknown): row is Record<string, unknown> =>
138
+ typeof row === 'object' &&
139
+ row !== null &&
140
+ typeof (row as Record<string, unknown>)['credential_name'] === 'string';
141
+
142
+ /**
143
+ * The literal values a create or a values-stale PATCH sends, unwrapped from their `Redacted`
144
+ * exactly once. ⛔ EVERY DECLARED ENTRY IS SENT: the whole-row create and the values-stale PATCH
145
+ * both demand every declared value (`requireValues`), so there is no partial send that could
146
+ * leave a stale value behind under a declared key.
147
+ */
148
+ export const literalValues = (resolved: ResolvedValues): Readonly<Record<string, unknown>> =>
149
+ Object.fromEntries(
150
+ Object.entries(resolved.values).map(([key, value]) => [key, Redacted.value(value)]),
151
+ );
152
+
153
+ /** The body `POST /credentials` carries — the name IN THE BODY (measured with a stub fetch). */
154
+ export const createBody = (
155
+ props: CredentialProps,
156
+ resolved: ResolvedValues,
157
+ ): credentials.CreateCredentialCredentialsPostRequest => {
158
+ const body: Record<string, unknown> = {
159
+ credential_info: { ...props.credentialInfo },
160
+ credential_name: props.credentialName,
161
+ credential_values: { ...literalValues(resolved) },
162
+ };
163
+ return body as unknown as credentials.CreateCredentialCredentialsPostRequest;
164
+ };
165
+
166
+ /**
167
+ * The body `PATCH /credentials/{name}` carries — the declared `credential_info` always (the
168
+ * vendor's `UpdateCredentialItem` REQUIRES it, and the DB normally replaces the info map), the
169
+ * resolved `credential_values` only when the caller passes them (a values-stale write; an
170
+ * info-only drift sends none, so it never demands a value the environment does not hold).
171
+ *
172
+ * ⛔ `credential_name` APPEARS TWICE, ON PURPOSE: `credential_name` is the path label (its URI
173
+ * placeholder is the row's address), and `credential_name_body` is the SAME value as a body
174
+ * field wire-named `credential_name` — the vendor's `UpdateCredentialItem` requires the name in
175
+ * the body, a Smithy member can carry only ONE binding, and the kit never renames in place (a
176
+ * changed name is a REPLACE, credential.ts), so the two are always equal here.
177
+ */
178
+ export const patchBody = (
179
+ props: CredentialProps,
180
+ values: Readonly<Record<string, unknown>> | undefined,
181
+ ): credentials.UpdateCredentialCredentialsCredentialNamePatchRequest => {
182
+ const body: Record<string, unknown> = {
183
+ credential_info: { ...props.credentialInfo },
184
+ credential_name: props.credentialName,
185
+ credential_name_body: props.credentialName,
186
+ };
187
+ if (values !== undefined) body.credential_values = { ...values };
188
+ return body as unknown as credentials.UpdateCredentialCredentialsCredentialNamePatchRequest;
189
+ };
190
+
191
+ /**
192
+ * All live info keys absent from the intended map, including on adoption. DB replacement alone
193
+ * cannot clear memory, so these require a rewrite. Only names of sensitive info are remembered.
194
+ */
195
+ export const removedInfoKeys = (
196
+ prior: CredentialAttributes | undefined,
197
+ props: CredentialProps,
198
+ ): readonly string[] => {
199
+ if (prior === undefined) return [];
200
+ const declared = props.credentialInfo ?? {};
201
+ return (prior.infoKeys ?? Object.keys(prior.credentialInfo)).filter(
202
+ (key) => !Object.hasOwn(declared, key),
203
+ );
204
+ };
205
+
206
+ /** Values merge in BOTH stores, so dropped names from the masked read require a rewrite. */
207
+ export const removedValueKeys = (
208
+ live: CredentialAttributes | undefined,
209
+ props: CredentialProps,
210
+ ): readonly string[] =>
211
+ (live?.valueKeys ?? []).filter((key) => !Object.hasOwn(props.credentialValues, key));
@@ -0,0 +1,241 @@
1
+ /**
2
+ * The guards `LiteLLM.Credential` adds on top of the typed SDK calls, tested at the handler level
3
+ * (a value is in the request body of `POST /credentials`, so a create that dies on the wire, or one
4
+ * made while the SDK's debug printing is on, would put it where nobody should see it) and through
5
+ * Alchemy's real Plan and Apply (a rename onto a row another owner holds must be refused, not
6
+ * overwritten).
7
+ *
8
+ * ★ EVERY VALUE IS `FAKE-*`, and a test that asserts a value is absent needs a value it can search
9
+ * for: `FAKE-key-one` is the credential value the declaration resolves from `process.env`.
10
+ */
11
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
12
+ import { credentials } from '@distilled.cloud/litellm/Credentials';
13
+ import { Retry } from '@distilled.cloud/litellm/Retry';
14
+ import * as Effect from 'effect/Effect';
15
+ import * as Layer from 'effect/Layer';
16
+ import * as Logger from 'effect/Logger';
17
+ import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
18
+ import { credentialHandlers } from './credential.ts';
19
+ import type { CredentialProps } from './credential-types.ts';
20
+ import { resolveValues, sealValues } from './credential-values.ts';
21
+ import { credentialRow, startFakeCredentialLitellm } from './fake-credential-litellm.ts';
22
+ import { FAKE_BASE } from './fake-litellm.ts';
23
+ import { fakeStack, writesOf } from './fake-stack.ts';
24
+ import { LiteLLMCredential } from './credential.ts';
25
+
26
+ const KEY = 'sk-test-master';
27
+ const VARIABLE = 'FAKE_CREDENTIAL_GUARD_KEY';
28
+ const VALUE = 'FAKE-key-one';
29
+
30
+ const props: CredentialProps = {
31
+ credentialInfo: { note: 'docs search' },
32
+ credentialName: 'FAKE_api',
33
+ credentialValues: { api_key: { fromEnv: VARIABLE } },
34
+ };
35
+
36
+ beforeEach(() => {
37
+ process.env[VARIABLE] = VALUE;
38
+ });
39
+ afterEach(() => {
40
+ delete process.env[VARIABLE];
41
+ delete process.env.DISTILLED_DEBUG_HTTP;
42
+ });
43
+
44
+ type Fake = ReturnType<typeof startFakeCredentialLitellm>;
45
+
46
+ /** One reconcile effect at the handler level, against the fake, with the SDK's retry OFF. */
47
+ const reconcileEffect = (
48
+ fake: Fake,
49
+ news: CredentialProps,
50
+ output: Parameters<typeof credentialHandlers.reconcile>[0]['output'] = undefined,
51
+ ) =>
52
+ credentialHandlers
53
+ .reconcile({ fqn: 'Cred', instanceId: 'i-1', news, output })
54
+ .pipe(
55
+ Effect.provideService(Retry, { while: () => false }),
56
+ Effect.provide(FetchHttpClient.layer),
57
+ Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fake.fetch)),
58
+ Effect.provide(credentials({ apiKey: KEY, baseUrl: FAKE_BASE })),
59
+ ) as Effect.Effect<unknown, unknown, never>;
60
+
61
+ /** Runs the reconcile to a value, or the failure it raised. */
62
+ const runReconcile = (fake: Fake, news: CredentialProps): Promise<unknown> =>
63
+ Effect.runPromise(Effect.flip(reconcileEffect(fake, news)));
64
+
65
+ /** A fake whose `POST /credentials` drops the connection before any answer. */
66
+ const droppedCreate = (fake: Fake): Fake => ({
67
+ ...fake,
68
+ fetch: (async (input: string | URL | Request, init?: RequestInit) => {
69
+ const request = input instanceof Request ? input : new Request(String(input), init);
70
+ if (new URL(request.url).pathname === '/credentials' && request.method === 'POST') {
71
+ throw new TypeError('fetch failed');
72
+ }
73
+ return fake.fetch(input, init);
74
+ }) as typeof globalThis.fetch,
75
+ });
76
+
77
+ /** A fake whose `PATCH /credentials/{name}` drops the connection before any answer. */
78
+ const droppedPatch = (fake: Fake): Fake => ({
79
+ ...fake,
80
+ fetch: (async (input: string | URL | Request, init?: RequestInit) => {
81
+ const request = input instanceof Request ? input : new Request(String(input), init);
82
+ if (request.method === 'PATCH' && new URL(request.url).pathname.startsWith('/credentials/')) {
83
+ const body = (await request.json()) as Record<string, unknown>;
84
+ expect(Object.keys((body.credential_values ?? {}) as object)).toEqual(['api_key']);
85
+ throw new TypeError('fetch failed');
86
+ }
87
+ return fake.fetch(input, init);
88
+ }) as typeof globalThis.fetch,
89
+ });
90
+
91
+ /** What Effect's JSON logger prints for the value. */
92
+ const jsonLogged = async (value: unknown): Promise<string> => {
93
+ const lines: string[] = [];
94
+ const capture = Logger.make((options) => void lines.push(Logger.formatJson.log(options)));
95
+ await Effect.runPromise(
96
+ Effect.logError('create failed', value).pipe(Effect.provide(Logger.layer([capture]))),
97
+ );
98
+ return lines.join('\n');
99
+ };
100
+
101
+ /** Every reading of `value` a caller could take: serialise, inspect, print, log. */
102
+ const everyReadingOf = async (value: unknown): Promise<string> => {
103
+ const readings = [Bun.inspect(value, { depth: 12 }), String(value), await jsonLogged(value)];
104
+ try {
105
+ readings.push(JSON.stringify(value));
106
+ } catch {
107
+ /* a value JSON cannot serialise leaks nothing that way */
108
+ }
109
+ if (typeof value === 'object' && value !== null && 'message' in value) {
110
+ readings.push(String(value.message), 'stack' in value ? String(value.stack) : '');
111
+ }
112
+ return readings.join('\n');
113
+ };
114
+
115
+ describe('a rename onto a row another owner holds', () => {
116
+ test('is refused at apply, the foreign row untouched, nothing written', async () => {
117
+ const fake = startFakeCredentialLitellm({
118
+ masterKey: KEY,
119
+ seed: [
120
+ credentialRow({
121
+ credential_info: { note: 'another owner' },
122
+ credential_name: 'FAKE_api2',
123
+ credential_values: { api_key: 'FAKE-elsewhere' },
124
+ }),
125
+ ],
126
+ });
127
+ const engine = fakeStack({ apiKey: KEY, baseUrl: FAKE_BASE }, fake.fetch);
128
+ const declare = (p: CredentialProps) =>
129
+ Effect.gen(function* () {
130
+ yield* LiteLLMCredential('Cred', p);
131
+ });
132
+ // Own `FAKE_api` first, then rename onto the seeded `FAKE_api2`.
133
+ await engine.deploy(declare(props));
134
+ const before = fake.requests().length;
135
+ await expect(engine.deploy(declare({ ...props, credentialName: 'FAKE_api2' }))).rejects.toThrow(
136
+ /already exists/,
137
+ );
138
+ expect(writesOf(fake.requests().slice(before))).toEqual([]);
139
+ expect(fake.rows().find((row) => row['credential_name'] === 'FAKE_api2')).toMatchObject({
140
+ credential_info: { note: 'another owner' },
141
+ credential_values: { api_key: 'FAKE-elsewhere' },
142
+ });
143
+ });
144
+
145
+ test('is refused even under --adopt (a replace is never probed for adoption)', async () => {
146
+ const fake = startFakeCredentialLitellm({
147
+ masterKey: KEY,
148
+ seed: [credentialRow({ credential_name: 'FAKE_api2', credential_values: { api_key: 'x' } })],
149
+ });
150
+ const engine = fakeStack({ apiKey: KEY, baseUrl: FAKE_BASE }, fake.fetch);
151
+ const declare = (p: CredentialProps) =>
152
+ Effect.gen(function* () {
153
+ yield* LiteLLMCredential('Cred', p);
154
+ });
155
+ await engine.deploy(declare(props));
156
+ const before = fake.requests().length;
157
+ await expect(
158
+ engine.deploy(declare({ ...props, credentialName: 'FAKE_api2' }), { adopt: true }),
159
+ ).rejects.toThrow(/--adopt does not cover/);
160
+ expect(writesOf(fake.requests().slice(before))).toEqual([]);
161
+ });
162
+ });
163
+
164
+ describe('a create that dies on the wire', () => {
165
+ test('fails typed, names the credential, and holds no request and no value', async () => {
166
+ const fake = droppedCreate(startFakeCredentialLitellm({ masterKey: KEY }));
167
+ const error = await runReconcile(fake, props);
168
+ expect(error).toMatchObject({
169
+ _tag: 'LitellmCredentialTransportError',
170
+ credentialName: 'FAKE_api',
171
+ reason: 'TransportError',
172
+ });
173
+ expect(Object.keys(error as object)).not.toContain('request');
174
+ expect(Object.keys(error as object)).not.toContain('cause');
175
+ expect(await everyReadingOf(error)).not.toContain(VALUE);
176
+ });
177
+ });
178
+
179
+ describe('an update that dies on the wire', () => {
180
+ test('leaves the row in place, fails typed, holds no value', async () => {
181
+ // The owned row has a stale seal: this failing PATCH MUST carry credential values.
182
+ const fake = droppedPatch(
183
+ startFakeCredentialLitellm({
184
+ masterKey: KEY,
185
+ seed: [
186
+ credentialRow({
187
+ credential_info: { note: 'docs search' },
188
+ credential_name: 'FAKE_api',
189
+ credential_values: { api_key: VALUE },
190
+ }),
191
+ ],
192
+ }),
193
+ );
194
+ const sealed = sealValues(resolveValues(props).values);
195
+ process.env[VARIABLE] = 'FAKE-key-rotated';
196
+ const error = await Effect.runPromise(
197
+ Effect.flip(
198
+ reconcileEffect(fake, props, {
199
+ credentialInfo: { note: 'docs search' },
200
+ credentialName: 'FAKE_api',
201
+ valuesSeal: sealed,
202
+ }),
203
+ ),
204
+ );
205
+ expect(error).toMatchObject({
206
+ _tag: 'LitellmCredentialTransportError',
207
+ credentialName: 'FAKE_api',
208
+ reason: 'TransportError',
209
+ });
210
+ expect(Object.keys(error as object)).not.toContain('request');
211
+ expect(await everyReadingOf(error)).not.toContain('FAKE-key-rotated');
212
+ // ★ finding 3: a PATCH that dies leaves the row it was merging into — the old DELETE + POST
213
+ // rewrite left NO row when the POST failed after the DELETE.
214
+ expect(fake.rows()).toHaveLength(1);
215
+ expect(fake.rows()[0]?.['credential_info']).toEqual({ note: 'docs search' });
216
+ expect(fake.rows()[0]?.['credential_values']).toEqual({ api_key: VALUE });
217
+ });
218
+ });
219
+
220
+ describe('a create while the SDK prints bodies', () => {
221
+ test('is refused before any request, naming the credential', async () => {
222
+ process.env.DISTILLED_DEBUG_HTTP = '1';
223
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
224
+ const error = await runReconcile(fake, props);
225
+ expect(error).toMatchObject({
226
+ _tag: 'LitellmCredentialDebugLoggingError',
227
+ credentialName: 'FAKE_api',
228
+ });
229
+ // Refuse at the top of reconcile: even a read can expose unmasked non-sensitive values.
230
+ expect(writesOf(fake.requests())).toEqual([]);
231
+ });
232
+ });
233
+
234
+ describe('an absent credential is caught as absence, never a failure', () => {
235
+ test('reconcile of a create with no live row converges (the read 404 is CredentialNotFound)', async () => {
236
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
237
+ const result = await Effect.runPromise(reconcileEffect(fake, props));
238
+ expect(result).toMatchObject({ credentialName: 'FAKE_api' });
239
+ expect(writesOf(fake.requests())).toEqual(['POST /credentials']);
240
+ });
241
+ });