@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.
- package/README.md +10 -1
- package/dist/caddy/index.js +35 -1
- package/dist/caddy/index.js.map +3 -3
- package/dist/launchd/index.js +35 -1
- package/dist/launchd/index.js.map +3 -3
- package/dist/linux/index.js +35 -1
- package/dist/linux/index.js.map +3 -3
- package/dist/litellm/access-group-form.d.ts +26 -0
- package/dist/litellm/access-group-form.d.ts.map +1 -0
- package/dist/litellm/access-group-operations.d.ts +37 -0
- package/dist/litellm/access-group-operations.d.ts.map +1 -0
- package/dist/litellm/access-group-types.d.ts +49 -0
- package/dist/litellm/access-group-types.d.ts.map +1 -0
- package/dist/litellm/access-group.d.ts +55 -0
- package/dist/litellm/access-group.d.ts.map +1 -0
- package/dist/litellm/credential-errors.d.ts +131 -0
- package/dist/litellm/credential-errors.d.ts.map +1 -0
- package/dist/litellm/credential-form.d.ts +79 -0
- package/dist/litellm/credential-form.d.ts.map +1 -0
- package/dist/litellm/credential-operations.d.ts +84 -0
- package/dist/litellm/credential-operations.d.ts.map +1 -0
- package/dist/litellm/credential-rewrite.d.ts +6 -0
- package/dist/litellm/credential-rewrite.d.ts.map +1 -0
- package/dist/litellm/credential-types.d.ts +87 -0
- package/dist/litellm/credential-types.d.ts.map +1 -0
- package/dist/litellm/credential-values.d.ts +53 -0
- package/dist/litellm/credential-values.d.ts.map +1 -0
- package/dist/litellm/credential.d.ts +83 -0
- package/dist/litellm/credential.d.ts.map +1 -0
- package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
- package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-credential-litellm.d.ts +54 -0
- package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-keys.d.ts +53 -0
- package/dist/litellm/fake-keys.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +4 -1
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
- package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm.d.ts +48 -0
- package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-litellm.d.ts +32 -0
- package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-registry-base.d.ts +42 -0
- package/dist/litellm/fake-registry-base.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +8 -1
- package/dist/litellm/fake-stack.d.ts.map +1 -1
- package/dist/litellm/fake-team-litellm.d.ts +47 -0
- package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-tool-litellm.d.ts +28 -0
- package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
- package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +18 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +3078 -2
- package/dist/litellm/index.js.map +65 -4
- package/dist/litellm/key-errors.d.ts +169 -0
- package/dist/litellm/key-errors.d.ts.map +1 -0
- package/dist/litellm/key-form.d.ts +119 -0
- package/dist/litellm/key-form.d.ts.map +1 -0
- package/dist/litellm/key-harness.d.ts +41 -0
- package/dist/litellm/key-harness.d.ts.map +1 -0
- package/dist/litellm/key-metadata-errors.d.ts +30 -0
- package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
- package/dist/litellm/key-metadata.d.ts +22 -0
- package/dist/litellm/key-metadata.d.ts.map +1 -0
- package/dist/litellm/key-operations.d.ts +63 -0
- package/dist/litellm/key-operations.d.ts.map +1 -0
- package/dist/litellm/key-secret.d.ts +33 -0
- package/dist/litellm/key-secret.d.ts.map +1 -0
- package/dist/litellm/key.d.ts +78 -0
- package/dist/litellm/key.d.ts.map +1 -0
- package/dist/litellm/mcp-server-credential.d.ts +50 -0
- package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
- package/dist/litellm/mcp-server-errors.d.ts +101 -0
- package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
- package/dist/litellm/mcp-server-form.d.ts +56 -0
- package/dist/litellm/mcp-server-form.d.ts.map +1 -0
- package/dist/litellm/mcp-server-locate.d.ts +26 -0
- package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
- package/dist/litellm/mcp-server-operations.d.ts +63 -0
- package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
- package/dist/litellm/mcp-server-shadow.d.ts +24 -0
- package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
- package/dist/litellm/mcp-server-types.d.ts +118 -0
- package/dist/litellm/mcp-server-types.d.ts.map +1 -0
- package/dist/litellm/mcp-server-url.d.ts +11 -0
- package/dist/litellm/mcp-server-url.d.ts.map +1 -0
- package/dist/litellm/mcp-server.d.ts +95 -0
- package/dist/litellm/mcp-server.d.ts.map +1 -0
- package/dist/litellm/model-claim.d.ts +24 -0
- package/dist/litellm/model-claim.d.ts.map +1 -0
- package/dist/litellm/model-credential.d.ts +12 -0
- package/dist/litellm/model-credential.d.ts.map +1 -0
- package/dist/litellm/model-errors.d.ts +119 -0
- package/dist/litellm/model-errors.d.ts.map +1 -0
- package/dist/litellm/model-form.d.ts +78 -0
- package/dist/litellm/model-form.d.ts.map +1 -0
- package/dist/litellm/model-locate.d.ts +13 -0
- package/dist/litellm/model-locate.d.ts.map +1 -0
- package/dist/litellm/model-operations.d.ts +59 -0
- package/dist/litellm/model-operations.d.ts.map +1 -0
- package/dist/litellm/model-types.d.ts +91 -0
- package/dist/litellm/model-types.d.ts.map +1 -0
- package/dist/litellm/model.d.ts +88 -0
- package/dist/litellm/model.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-form.d.ts +19 -0
- package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-operations.d.ts +31 -0
- package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-types.d.ts +48 -0
- package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
- package/dist/litellm/policy-attachment.d.ts +61 -0
- package/dist/litellm/policy-attachment.d.ts.map +1 -0
- package/dist/litellm/policy-form.d.ts +22 -0
- package/dist/litellm/policy-form.d.ts.map +1 -0
- package/dist/litellm/policy-operations.d.ts +42 -0
- package/dist/litellm/policy-operations.d.ts.map +1 -0
- package/dist/litellm/policy-types.d.ts +57 -0
- package/dist/litellm/policy-types.d.ts.map +1 -0
- package/dist/litellm/policy.d.ts +65 -0
- package/dist/litellm/policy.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +1 -1
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/litellm/registry-errors.d.ts +62 -0
- package/dist/litellm/registry-errors.d.ts.map +1 -0
- package/dist/litellm/registry-support.d.ts +42 -0
- package/dist/litellm/registry-support.d.ts.map +1 -0
- package/dist/litellm/team-form.d.ts +26 -0
- package/dist/litellm/team-form.d.ts.map +1 -0
- package/dist/litellm/team-members.d.ts +10 -0
- package/dist/litellm/team-members.d.ts.map +1 -0
- package/dist/litellm/team-operations.d.ts +38 -0
- package/dist/litellm/team-operations.d.ts.map +1 -0
- package/dist/litellm/team-permission.d.ts +35 -0
- package/dist/litellm/team-permission.d.ts.map +1 -0
- package/dist/litellm/team-types.d.ts +101 -0
- package/dist/litellm/team-types.d.ts.map +1 -0
- package/dist/litellm/team.d.ts +67 -0
- package/dist/litellm/team.d.ts.map +1 -0
- package/dist/litellm/tool-policy-form.d.ts +21 -0
- package/dist/litellm/tool-policy-form.d.ts.map +1 -0
- package/dist/litellm/tool-policy-operations.d.ts +30 -0
- package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
- package/dist/litellm/tool-policy-types.d.ts +46 -0
- package/dist/litellm/tool-policy-types.d.ts.map +1 -0
- package/dist/litellm/tool-policy.d.ts +62 -0
- package/dist/litellm/tool-policy.d.ts.map +1 -0
- package/dist/litellm/toolset-form.d.ts +20 -0
- package/dist/litellm/toolset-form.d.ts.map +1 -0
- package/dist/litellm/toolset-operations.d.ts +39 -0
- package/dist/litellm/toolset-operations.d.ts.map +1 -0
- package/dist/litellm/toolset-types.d.ts +46 -0
- package/dist/litellm/toolset-types.d.ts.map +1 -0
- package/dist/litellm/toolset.d.ts +55 -0
- package/dist/litellm/toolset.d.ts.map +1 -0
- package/dist/openbao/index.js +35 -1
- package/dist/openbao/index.js.map +3 -3
- package/dist/ownership/rows.d.ts +11 -0
- package/dist/ownership/rows.d.ts.map +1 -1
- package/dist/postgres/connection.d.ts +14 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +27 -8
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/fake-grants-parse.d.ts +37 -0
- package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
- package/dist/postgres/fake-grants-read.d.ts +61 -0
- package/dist/postgres/fake-grants-read.d.ts.map +1 -0
- package/dist/postgres/fake-grants-sql.d.ts +46 -0
- package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
- package/dist/postgres/fake-grants-write.d.ts +29 -0
- package/dist/postgres/fake-grants-write.d.ts.map +1 -0
- package/dist/postgres/fake-role-sql.d.ts +41 -0
- package/dist/postgres/fake-role-sql.d.ts.map +1 -0
- package/dist/postgres/fake-schema-sql.d.ts +42 -0
- package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
- package/dist/postgres/fake-sql-parse.d.ts +23 -0
- package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
- package/dist/postgres/fake-sql-quote.d.ts +12 -0
- package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +42 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -1
- package/dist/postgres/grants-attrs.d.ts +115 -0
- package/dist/postgres/grants-attrs.d.ts.map +1 -0
- package/dist/postgres/grants-declare.d.ts +88 -0
- package/dist/postgres/grants-declare.d.ts.map +1 -0
- package/dist/postgres/grants-diff.d.ts +25 -0
- package/dist/postgres/grants-diff.d.ts.map +1 -0
- package/dist/postgres/grants-errors.d.ts +137 -0
- package/dist/postgres/grants-errors.d.ts.map +1 -0
- package/dist/postgres/grants-existence.d.ts +14 -0
- package/dist/postgres/grants-existence.d.ts.map +1 -0
- package/dist/postgres/grants-ops.d.ts +65 -0
- package/dist/postgres/grants-ops.d.ts.map +1 -0
- package/dist/postgres/grants-ownership.d.ts +18 -0
- package/dist/postgres/grants-ownership.d.ts.map +1 -0
- package/dist/postgres/grants-plan-restore.d.ts +22 -0
- package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
- package/dist/postgres/grants-plan.d.ts +65 -0
- package/dist/postgres/grants-plan.d.ts.map +1 -0
- package/dist/postgres/grants-read-defaults.d.ts +17 -0
- package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
- package/dist/postgres/grants-read.d.ts +77 -0
- package/dist/postgres/grants-read.d.ts.map +1 -0
- package/dist/postgres/grants-refuse.d.ts +25 -0
- package/dist/postgres/grants-refuse.d.ts.map +1 -0
- package/dist/postgres/grants-sql.d.ts +42 -0
- package/dist/postgres/grants-sql.d.ts.map +1 -0
- package/dist/postgres/grants-words.d.ts +29 -0
- package/dist/postgres/grants-words.d.ts.map +1 -0
- package/dist/postgres/grants.d.ts +37 -0
- package/dist/postgres/grants.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +31 -6
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +1998 -37
- package/dist/postgres/index.js.map +46 -7
- package/dist/postgres/installed-classifier.d.ts +13 -0
- package/dist/postgres/installed-classifier.d.ts.map +1 -0
- package/dist/postgres/providers.d.ts +16 -8
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +4 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -1
- package/dist/postgres/role-attrs.d.ts +112 -0
- package/dist/postgres/role-attrs.d.ts.map +1 -0
- package/dist/postgres/role-diff.d.ts +22 -0
- package/dist/postgres/role-diff.d.ts.map +1 -0
- package/dist/postgres/role-errors.d.ts +132 -0
- package/dist/postgres/role-errors.d.ts.map +1 -0
- package/dist/postgres/role-membership-sql.d.ts +78 -0
- package/dist/postgres/role-membership-sql.d.ts.map +1 -0
- package/dist/postgres/role-provider.d.ts +11 -0
- package/dist/postgres/role-provider.d.ts.map +1 -0
- package/dist/postgres/role-scram.d.ts +8 -0
- package/dist/postgres/role-scram.d.ts.map +1 -0
- package/dist/postgres/role-secrets.d.ts +38 -0
- package/dist/postgres/role-secrets.d.ts.map +1 -0
- package/dist/postgres/role-sql.d.ts +68 -0
- package/dist/postgres/role-sql.d.ts.map +1 -0
- package/dist/postgres/role.d.ts +57 -0
- package/dist/postgres/role.d.ts.map +1 -0
- package/dist/postgres/schema-assert.d.ts +50 -0
- package/dist/postgres/schema-assert.d.ts.map +1 -0
- package/dist/postgres/schema-attrs.d.ts +51 -0
- package/dist/postgres/schema-attrs.d.ts.map +1 -0
- package/dist/postgres/schema-delete.d.ts +28 -0
- package/dist/postgres/schema-delete.d.ts.map +1 -0
- package/dist/postgres/schema-diff.d.ts +14 -0
- package/dist/postgres/schema-diff.d.ts.map +1 -0
- package/dist/postgres/schema-errors.d.ts +139 -0
- package/dist/postgres/schema-errors.d.ts.map +1 -0
- package/dist/postgres/schema-identity.d.ts +8 -0
- package/dist/postgres/schema-identity.d.ts.map +1 -0
- package/dist/postgres/schema-reconcile.d.ts +12 -0
- package/dist/postgres/schema-reconcile.d.ts.map +1 -0
- package/dist/postgres/schema-sql.d.ts +45 -0
- package/dist/postgres/schema-sql.d.ts.map +1 -0
- package/dist/postgres/schema-test-kit.d.ts +67 -0
- package/dist/postgres/schema-test-kit.d.ts.map +1 -0
- package/dist/postgres/schema.d.ts +54 -0
- package/dist/postgres/schema.d.ts.map +1 -0
- package/dist/proxmox/index.js +35 -1
- package/dist/proxmox/index.js.map +3 -3
- package/dist/release/fake-engine.d.ts +10 -0
- package/dist/release/fake-engine.d.ts.map +1 -1
- package/dist/release/index.js +35 -1
- package/dist/release/index.js.map +3 -3
- package/dist/valkey/acl-attrs.d.ts +94 -0
- package/dist/valkey/acl-attrs.d.ts.map +1 -0
- package/dist/valkey/acl-compare.d.ts +28 -0
- package/dist/valkey/acl-compare.d.ts.map +1 -0
- package/dist/valkey/acl-connection.d.ts +6 -0
- package/dist/valkey/acl-connection.d.ts.map +1 -0
- package/dist/valkey/acl-form.d.ts +62 -0
- package/dist/valkey/acl-form.d.ts.map +1 -0
- package/dist/valkey/acl-managed.d.ts +4 -0
- package/dist/valkey/acl-managed.d.ts.map +1 -0
- package/dist/valkey/acl-ops.d.ts +37 -0
- package/dist/valkey/acl-ops.d.ts.map +1 -0
- package/dist/valkey/acl-profiles.d.ts +53 -0
- package/dist/valkey/acl-profiles.d.ts.map +1 -0
- package/dist/valkey/acl-reconcile.d.ts +26 -0
- package/dist/valkey/acl-reconcile.d.ts.map +1 -0
- package/dist/valkey/acl.d.ts +24 -0
- package/dist/valkey/acl.d.ts.map +1 -0
- package/dist/valkey/connection-service.d.ts +22 -0
- package/dist/valkey/connection-service.d.ts.map +1 -0
- package/dist/valkey/connection.d.ts +59 -0
- package/dist/valkey/connection.d.ts.map +1 -0
- package/dist/valkey/errors.d.ts +165 -0
- package/dist/valkey/errors.d.ts.map +1 -0
- package/dist/valkey/fake-acl.d.ts +29 -0
- package/dist/valkey/fake-acl.d.ts.map +1 -0
- package/dist/valkey/fake-valkey.d.ts +35 -0
- package/dist/valkey/fake-valkey.d.ts.map +1 -0
- package/dist/valkey/handler-test-fixture.d.ts +20 -0
- package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
- package/dist/valkey/index.d.ts +18 -0
- package/dist/valkey/index.d.ts.map +1 -0
- package/dist/valkey/index.js +964 -0
- package/dist/valkey/index.js.map +27 -0
- package/dist/valkey/instance-attrs.d.ts +46 -0
- package/dist/valkey/instance-attrs.d.ts.map +1 -0
- package/dist/valkey/instance-form.d.ts +22 -0
- package/dist/valkey/instance-form.d.ts.map +1 -0
- package/dist/valkey/instance.d.ts +48 -0
- package/dist/valkey/instance.d.ts.map +1 -0
- package/dist/valkey/provider-socket-fixture.d.ts +8 -0
- package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
- package/dist/valkey/providers.d.ts +16 -0
- package/dist/valkey/providers.d.ts.map +1 -0
- package/dist/valkey/resp.d.ts +38 -0
- package/dist/valkey/resp.d.ts.map +1 -0
- package/dist/valkey/transport-error.d.ts +36 -0
- package/dist/valkey/transport-error.d.ts.map +1 -0
- package/dist/valkey/transport.d.ts +50 -0
- package/dist/valkey/transport.d.ts.map +1 -0
- package/docs/litellm-access-group.md +105 -0
- package/docs/litellm-credential.md +60 -0
- package/docs/litellm-key-metadata.md +56 -0
- package/docs/litellm-key.md +194 -0
- package/docs/litellm-mcp.md +187 -0
- package/docs/litellm-model.md +111 -0
- package/docs/litellm-policy-attachment.md +86 -0
- package/docs/litellm-policy.md +103 -0
- package/docs/litellm-team.md +117 -0
- package/docs/litellm-tool-policy.md +90 -0
- package/docs/litellm-toolset.md +92 -0
- package/docs/litellm.md +16 -9
- package/docs/peers.md +15 -6
- package/docs/postgres-grants-example.md +55 -0
- package/docs/postgres-grants.md +157 -0
- package/docs/postgres-role.md +126 -0
- package/docs/postgres-schema.md +139 -0
- package/docs/postgres.md +20 -3
- package/docs/valkey-transport.md +29 -0
- package/docs/valkey.md +187 -0
- package/package.json +6 -2
- package/src/ceph/ceph-argv.test.ts +1 -1
- package/src/launchd/sudo-allowlist.test.ts +2 -2
- package/src/linux/sudo-allowlist.test.ts +3 -3
- package/src/linux/sudo-runner.test.ts +2 -2
- package/src/litellm/access-group-form.ts +105 -0
- package/src/litellm/access-group-operations.ts +92 -0
- package/src/litellm/access-group-types.ts +49 -0
- package/src/litellm/access-group.test.ts +197 -0
- package/src/litellm/access-group.ts +151 -0
- package/src/litellm/credential-errors.ts +183 -0
- package/src/litellm/credential-form.test.ts +151 -0
- package/src/litellm/credential-form.ts +211 -0
- package/src/litellm/credential-guards.test.ts +241 -0
- package/src/litellm/credential-operations.ts +208 -0
- package/src/litellm/credential-recovery.test.ts +93 -0
- package/src/litellm/credential-rewrite.test.ts +164 -0
- package/src/litellm/credential-rewrite.ts +50 -0
- package/src/litellm/credential-types.ts +117 -0
- package/src/litellm/credential-values.ts +94 -0
- package/src/litellm/credential.test.ts +218 -0
- package/src/litellm/credential.ts +227 -0
- package/src/litellm/fake-access-group-litellm.ts +127 -0
- package/src/litellm/fake-credential-litellm.ts +216 -0
- package/src/litellm/fake-keys.ts +190 -0
- package/src/litellm/fake-litellm.ts +19 -9
- package/src/litellm/fake-mcp-litellm.ts +214 -0
- package/src/litellm/fake-model-litellm-post-update.ts +103 -0
- package/src/litellm/fake-model-litellm.ts +233 -0
- package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
- package/src/litellm/fake-policy-litellm.ts +186 -0
- package/src/litellm/fake-registry-base.ts +69 -0
- package/src/litellm/fake-stack.ts +17 -1
- package/src/litellm/fake-team-litellm.ts +229 -0
- package/src/litellm/fake-tool-litellm.ts +88 -0
- package/src/litellm/fake-toolset-litellm.ts +110 -0
- package/src/litellm/index.ts +140 -0
- package/src/litellm/key-delete.test.ts +212 -0
- package/src/litellm/key-errors.ts +241 -0
- package/src/litellm/key-form.ts +200 -0
- package/src/litellm/key-harness.ts +121 -0
- package/src/litellm/key-metadata-errors.ts +49 -0
- package/src/litellm/key-metadata.test.ts +156 -0
- package/src/litellm/key-metadata.ts +74 -0
- package/src/litellm/key-operations.ts +184 -0
- package/src/litellm/key-rotation.test.ts +110 -0
- package/src/litellm/key-scope.test.ts +135 -0
- package/src/litellm/key-secret.test.ts +248 -0
- package/src/litellm/key-secret.ts +137 -0
- package/src/litellm/key-transport.test.ts +166 -0
- package/src/litellm/key.test.ts +231 -0
- package/src/litellm/key.ts +213 -0
- package/src/litellm/mcp-server-adopt.test.ts +86 -0
- package/src/litellm/mcp-server-alias.test.ts +184 -0
- package/src/litellm/mcp-server-credential.ts +85 -0
- package/src/litellm/mcp-server-errors.ts +141 -0
- package/src/litellm/mcp-server-exports.test.ts +15 -0
- package/src/litellm/mcp-server-form.test.ts +241 -0
- package/src/litellm/mcp-server-form.ts +233 -0
- package/src/litellm/mcp-server-handlers.test.ts +150 -0
- package/src/litellm/mcp-server-locate.ts +66 -0
- package/src/litellm/mcp-server-operations.ts +111 -0
- package/src/litellm/mcp-server-registry.test.ts +146 -0
- package/src/litellm/mcp-server-retype.test.ts +114 -0
- package/src/litellm/mcp-server-shadow.test.ts +91 -0
- package/src/litellm/mcp-server-shadow.ts +45 -0
- package/src/litellm/mcp-server-tools.test.ts +85 -0
- package/src/litellm/mcp-server-types.ts +143 -0
- package/src/litellm/mcp-server-url.ts +66 -0
- package/src/litellm/mcp-server.test.ts +213 -0
- package/src/litellm/mcp-server.ts +225 -0
- package/src/litellm/model-claim.ts +69 -0
- package/src/litellm/model-credential.ts +45 -0
- package/src/litellm/model-errors.ts +168 -0
- package/src/litellm/model-foreign-row.test.ts +173 -0
- package/src/litellm/model-form.test.ts +228 -0
- package/src/litellm/model-form.ts +223 -0
- package/src/litellm/model-locate.test.ts +128 -0
- package/src/litellm/model-locate.ts +67 -0
- package/src/litellm/model-operations.test.ts +93 -0
- package/src/litellm/model-operations.ts +146 -0
- package/src/litellm/model-params-preserve.test.ts +119 -0
- package/src/litellm/model-recovery.test.ts +57 -0
- package/src/litellm/model-types.ts +112 -0
- package/src/litellm/model.test.ts +222 -0
- package/src/litellm/model.ts +237 -0
- package/src/litellm/policy-attachment-form.ts +103 -0
- package/src/litellm/policy-attachment-operations.ts +74 -0
- package/src/litellm/policy-attachment-types.ts +48 -0
- package/src/litellm/policy-attachment.test.ts +198 -0
- package/src/litellm/policy-attachment.ts +178 -0
- package/src/litellm/policy-form.ts +122 -0
- package/src/litellm/policy-operations.ts +112 -0
- package/src/litellm/policy-types.ts +58 -0
- package/src/litellm/policy.test.ts +214 -0
- package/src/litellm/policy.ts +146 -0
- package/src/litellm/providers.ts +24 -3
- package/src/litellm/registry-errors.ts +88 -0
- package/src/litellm/registry-exports.test.ts +66 -0
- package/src/litellm/registry-support.ts +109 -0
- package/src/litellm/team-form.ts +140 -0
- package/src/litellm/team-members.test.ts +150 -0
- package/src/litellm/team-members.ts +58 -0
- package/src/litellm/team-operations.ts +90 -0
- package/src/litellm/team-permission.ts +159 -0
- package/src/litellm/team-read.test.ts +89 -0
- package/src/litellm/team-types.ts +106 -0
- package/src/litellm/team.test.ts +206 -0
- package/src/litellm/team.ts +159 -0
- package/src/litellm/tool-policy-form.ts +94 -0
- package/src/litellm/tool-policy-operations.ts +58 -0
- package/src/litellm/tool-policy-types.ts +48 -0
- package/src/litellm/tool-policy.test.ts +172 -0
- package/src/litellm/tool-policy.ts +125 -0
- package/src/litellm/toolset-form.ts +104 -0
- package/src/litellm/toolset-operations.ts +90 -0
- package/src/litellm/toolset-types.ts +47 -0
- package/src/litellm/toolset.test.ts +198 -0
- package/src/litellm/toolset.ts +151 -0
- package/src/ownership/ownership.test.ts +33 -1
- package/src/ownership/rows.ts +53 -0
- package/src/postgres/connection.ts +42 -7
- package/src/postgres/database-bigint-serialization.test.ts +1 -0
- package/src/postgres/database-refusals.test.ts +2 -2
- package/src/postgres/database-sql.ts +36 -9
- package/src/postgres/fake-grants-parse.ts +189 -0
- package/src/postgres/fake-grants-read.ts +250 -0
- package/src/postgres/fake-grants-sql.ts +207 -0
- package/src/postgres/fake-grants-write.ts +90 -0
- package/src/postgres/fake-role-sql.ts +240 -0
- package/src/postgres/fake-schema-semantics.test.ts +36 -0
- package/src/postgres/fake-schema-sql.ts +146 -0
- package/src/postgres/fake-sql-parse.ts +86 -0
- package/src/postgres/fake-sql-quote.ts +36 -0
- package/src/postgres/fake-sql.ts +166 -46
- package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
- package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
- package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
- package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
- package/src/postgres/grants-acl.test.ts +61 -0
- package/src/postgres/grants-attrs.ts +185 -0
- package/src/postgres/grants-database.test.ts +158 -0
- package/src/postgres/grants-declare.ts +172 -0
- package/src/postgres/grants-delete-proof.test.ts +154 -0
- package/src/postgres/grants-diff.ts +101 -0
- package/src/postgres/grants-errors.ts +215 -0
- package/src/postgres/grants-existence-guards.test.ts +113 -0
- package/src/postgres/grants-existence.ts +101 -0
- package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
- package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
- package/src/postgres/grants-lifecycle.test.ts +181 -0
- package/src/postgres/grants-ops.ts +229 -0
- package/src/postgres/grants-owner-revoke.test.ts +121 -0
- package/src/postgres/grants-ownership.ts +49 -0
- package/src/postgres/grants-plan-collateral.test.ts +202 -0
- package/src/postgres/grants-plan-diff.test.ts +204 -0
- package/src/postgres/grants-plan-restore.ts +43 -0
- package/src/postgres/grants-plan-revocations.test.ts +69 -0
- package/src/postgres/grants-plan.test.ts +148 -0
- package/src/postgres/grants-plan.ts +245 -0
- package/src/postgres/grants-provenance.test.ts +134 -0
- package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
- package/src/postgres/grants-read-absent.test.ts +91 -0
- package/src/postgres/grants-read-defaults.ts +51 -0
- package/src/postgres/grants-read.ts +250 -0
- package/src/postgres/grants-refusals.test.ts +136 -0
- package/src/postgres/grants-refuse.ts +119 -0
- package/src/postgres/grants-relkind-wire.test.ts +111 -0
- package/src/postgres/grants-repair-plan.test.ts +201 -0
- package/src/postgres/grants-sql.test.ts +128 -0
- package/src/postgres/grants-sql.ts +144 -0
- package/src/postgres/grants-words.ts +34 -0
- package/src/postgres/grants.ts +110 -0
- package/src/postgres/index.ts +129 -5
- package/src/postgres/installed-classifier.ts +36 -0
- package/src/postgres/providers.ts +33 -9
- package/src/postgres/psql-executor-escaping.test.ts +67 -0
- package/src/postgres/psql-executor.test.ts +63 -2
- package/src/postgres/psql-executor.ts +74 -34
- package/src/postgres/role-adopt.test.ts +98 -0
- package/src/postgres/role-attrs.ts +137 -0
- package/src/postgres/role-diff.ts +93 -0
- package/src/postgres/role-errors.ts +206 -0
- package/src/postgres/role-guard.test.ts +60 -0
- package/src/postgres/role-identity.test.ts +197 -0
- package/src/postgres/role-lifecycle.test.ts +228 -0
- package/src/postgres/role-membership-options.test.ts +73 -0
- package/src/postgres/role-membership-repair.test.ts +153 -0
- package/src/postgres/role-membership-sql.ts +223 -0
- package/src/postgres/role-password-ascii.test.ts +53 -0
- package/src/postgres/role-password-transports.test.ts +123 -0
- package/src/postgres/role-password.test.ts +158 -0
- package/src/postgres/role-privilege.test.ts +107 -0
- package/src/postgres/role-provenance.test.ts +200 -0
- package/src/postgres/role-provider.ts +105 -0
- package/src/postgres/role-refusals.test.ts +225 -0
- package/src/postgres/role-scram.test.ts +38 -0
- package/src/postgres/role-scram.ts +40 -0
- package/src/postgres/role-secrets.ts +61 -0
- package/src/postgres/role-sql.ts +213 -0
- package/src/postgres/role-valid-until.test.ts +68 -0
- package/src/postgres/role.ts +226 -0
- package/src/postgres/schema-assert.ts +139 -0
- package/src/postgres/schema-attrs.ts +71 -0
- package/src/postgres/schema-delete-proofs.test.ts +192 -0
- package/src/postgres/schema-delete.ts +96 -0
- package/src/postgres/schema-diff.ts +44 -0
- package/src/postgres/schema-drop-2bp01.test.ts +146 -0
- package/src/postgres/schema-errors.ts +209 -0
- package/src/postgres/schema-handlers.test.ts +193 -0
- package/src/postgres/schema-identity.ts +39 -0
- package/src/postgres/schema-lifecycle.test.ts +246 -0
- package/src/postgres/schema-race.test.ts +70 -0
- package/src/postgres/schema-reconcile.ts +100 -0
- package/src/postgres/schema-refusals.test.ts +144 -0
- package/src/postgres/schema-socket.test.ts +162 -0
- package/src/postgres/schema-sql.ts +130 -0
- package/src/postgres/schema-takeover.test.ts +149 -0
- package/src/postgres/schema-test-kit.ts +85 -0
- package/src/postgres/schema.ts +130 -0
- package/src/release/binary-alias.test.ts +3 -3
- package/src/release/binary-claims.test.ts +3 -3
- package/src/release/binary-directory-umask.test.ts +65 -0
- package/src/release/fake-engine.ts +15 -0
- package/src/valkey/acl-attrs.ts +98 -0
- package/src/valkey/acl-compare.ts +57 -0
- package/src/valkey/acl-connection.ts +13 -0
- package/src/valkey/acl-delete.test.ts +127 -0
- package/src/valkey/acl-form.ts +239 -0
- package/src/valkey/acl-managed.ts +13 -0
- package/src/valkey/acl-ops.ts +122 -0
- package/src/valkey/acl-ownership.test.ts +118 -0
- package/src/valkey/acl-prefix.test.ts +38 -0
- package/src/valkey/acl-profiles.ts +81 -0
- package/src/valkey/acl-reconcile.ts +180 -0
- package/src/valkey/acl-reserved.test.ts +69 -0
- package/src/valkey/acl-revocation.test.ts +94 -0
- package/src/valkey/acl-seat-prefix.test.ts +111 -0
- package/src/valkey/acl-validation.test.ts +54 -0
- package/src/valkey/acl.test.ts +215 -0
- package/src/valkey/acl.ts +169 -0
- package/src/valkey/connection-service.ts +46 -0
- package/src/valkey/connection.ts +180 -0
- package/src/valkey/errors.ts +239 -0
- package/src/valkey/fake-acl.ts +128 -0
- package/src/valkey/fake-valkey.ts +224 -0
- package/src/valkey/handler-test-fixture.ts +43 -0
- package/src/valkey/handlers.test.ts +205 -0
- package/src/valkey/index.ts +54 -0
- package/src/valkey/instance-attrs.ts +48 -0
- package/src/valkey/instance-form.ts +104 -0
- package/src/valkey/instance.test.ts +108 -0
- package/src/valkey/instance.ts +160 -0
- package/src/valkey/provider-socket-fixture.ts +71 -0
- package/src/valkey/providers.test.ts +129 -0
- package/src/valkey/providers.ts +21 -0
- package/src/valkey/resp.ts +212 -0
- package/src/valkey/transport-bounds.test.ts +150 -0
- package/src/valkey/transport-error.ts +37 -0
- package/src/valkey/transport.test.ts +158 -0
- package/src/valkey/transport.ts +109 -0
- package/src/valkey/upstream-bounds.test.ts +14 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake of LiteLLM's `/v1/mcp/server` routes for tests — TEST ONLY, never imported by `index.ts`.
|
|
3
|
+
* Like `fake-litellm.ts` it is a `fetch` function handed to Effect's real `FetchHttpClient` through
|
|
4
|
+
* its `Fetch` reference, so a test exercises distilled's REAL path assembly, JSON encoding and
|
|
5
|
+
* status-to-error matching; only the wire responses a scenario needs are written here.
|
|
6
|
+
*
|
|
7
|
+
* ★ A SEPARATE FILE FROM `fake-litellm.ts` ON PURPOSE: that fake carries the pass-through and
|
|
8
|
+
* budget routes and is at its size budget; a fake per route family keeps each one small.
|
|
9
|
+
* ⚠️ SHAPES ARE FROM THE GENERATED 1.103.0 SCHEMA (`mcp_management.ts`), NOT A LIVE READ. What the
|
|
10
|
+
* real proxy does on a duplicate create, an edit of a missing id, a delete of a missing id, and
|
|
11
|
+
* whether its list redacts `credentials` is UNMEASURED; the answers below (400, 404, 404, redacted
|
|
12
|
+
* unless `echoCredentials`) are the fake's own choices, and every test that leans on one says so.
|
|
13
|
+
* ★ ONE RULE IS MEASURED, NOT CHOSEN: an edit that changes `auth_type` and sends no `credentials` key
|
|
14
|
+
* clears the stored credential (litellm 1.103.0, `db.py` lines 1013-1014, read from the live
|
|
15
|
+
* container; every static type is its own credential class). The engine tests that rely on it are
|
|
16
|
+
* `mcp-server-retype.test.ts`.
|
|
17
|
+
* ★ THREE MORE RULES ARE MEASURED (2026-09-29, the live 1.103.0 container, read from source and, for
|
|
18
|
+
* the alias rule, by running its own normaliser on a body): (1) `GET /v1/mcp/server` is the
|
|
19
|
+
* in-memory REGISTRY, not the table, so a row can be committed and not listed (`registryMisses`,
|
|
20
|
+
* `hideCreated`); the by-id `GET` reads the table first and REGISTERS the row it finds. (2) The
|
|
21
|
+
* list's `description` is `mcp_info.description` when that key exists, else the column. (3) A
|
|
22
|
+
* write with a `server_name` and no `alias` defaults the alias to the name and WRITES it; `-` in
|
|
23
|
+
* either name is a 400, and a space in an alias becomes `_`.
|
|
24
|
+
* ⚠️ `servers()` RETURNS WHAT REACHED THE PROXY, credentials included, so a test can assert what
|
|
25
|
+
* was SENT. The list route (`GET`) is what a resource can read, and it hides them by default.
|
|
26
|
+
* ★ `FAKE-*` VALUES ONLY. Nothing here is, or looks like, a real credential.
|
|
27
|
+
*/
|
|
28
|
+
/** One request that reached the fake. Its own type, so this file depends on no other fake. */
|
|
29
|
+
export interface FakeMcpRequest {
|
|
30
|
+
readonly method: string;
|
|
31
|
+
readonly path: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Row = Record<string, unknown>;
|
|
35
|
+
|
|
36
|
+
export interface FakeMcpLitellm {
|
|
37
|
+
readonly fetch: typeof globalThis.fetch;
|
|
38
|
+
/** Every stored row exactly as the proxy holds it, `credentials` included. */
|
|
39
|
+
readonly servers: () => readonly Row[];
|
|
40
|
+
readonly requests: () => readonly FakeMcpRequest[];
|
|
41
|
+
/** The JSON bodies of every `POST`/`PUT`, in order. */
|
|
42
|
+
readonly bodies: () => readonly Row[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface FakeMcpOptions {
|
|
46
|
+
readonly masterKey?: string;
|
|
47
|
+
readonly seed?: readonly Row[];
|
|
48
|
+
/** `GET` echoes the stored `credentials` instead of hiding them (a worst-case proxy). */
|
|
49
|
+
readonly echoCredentials?: boolean;
|
|
50
|
+
/** `PUT` drops an empty list and a `false`, like a truthiness check would. */
|
|
51
|
+
readonly editIgnoresFalsy?: boolean;
|
|
52
|
+
/** `POST` ignores a supplied `server_id` and issues its own. */
|
|
53
|
+
readonly issuesOwnId?: boolean;
|
|
54
|
+
/** Every `DELETE` answers 400, whether or not the row exists (no rights). */
|
|
55
|
+
readonly forbidDelete?: boolean;
|
|
56
|
+
/** Ids committed to the table that the registry does not hold (a failed reload), so the list omits them. */
|
|
57
|
+
readonly registryMisses?: readonly string[];
|
|
58
|
+
/** A `POST` commits the row but its registry refresh fails (LiteLLM only logs it). */
|
|
59
|
+
readonly hideCreated?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The by-id `GET` answers 403, whether or not the row exists. ⚠️ Not 500: the SDK retries 5xx with
|
|
62
|
+
* backoff (`Retry.Retry`), which would take a test tens of seconds to no purpose.
|
|
63
|
+
*/
|
|
64
|
+
readonly readFails?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const json = (status: number, body: unknown) =>
|
|
68
|
+
new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' }, status });
|
|
69
|
+
|
|
70
|
+
const ISSUED_ID = 'FAKE-issued-id-0001';
|
|
71
|
+
|
|
72
|
+
/** A minimal 1.103.0-shaped row, as the list route returns it. */
|
|
73
|
+
export const serverRow = (fields: Row): Row => ({
|
|
74
|
+
allow_all_keys: false,
|
|
75
|
+
allowed_tools: [],
|
|
76
|
+
auth_type: null,
|
|
77
|
+
mcp_access_groups: [],
|
|
78
|
+
transport: 'http',
|
|
79
|
+
...fields,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/** LiteLLM's `normalize_server_name`: spaces become underscores. */
|
|
83
|
+
const normalised = (name: unknown): string => String(name).replaceAll(' ', '_');
|
|
84
|
+
|
|
85
|
+
/** What the list shows as `description`: `mcp_info.description` when the KEY exists, else the column. */
|
|
86
|
+
const effectiveDescription = (row: Row): unknown => {
|
|
87
|
+
const info = row['mcp_info'] as Row | null | undefined;
|
|
88
|
+
if (info !== null && info !== undefined && 'description' in info) return info['description'];
|
|
89
|
+
return row['description'] === undefined || row['description'] === '' ? null : row['description'];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* `validate_and_normalize_mcp_server_payload`: the alias LiteLLM will write, or `undefined` for none,
|
|
94
|
+
* or the 400 body. A `server_name` with no alias gives the alias the name.
|
|
95
|
+
*/
|
|
96
|
+
const aliasFor = (body: Row): { alias?: string; refusal?: string } => {
|
|
97
|
+
for (const field of ['server_name', 'alias']) {
|
|
98
|
+
if (typeof body[field] === 'string' && body[field].includes('-')) {
|
|
99
|
+
return { refusal: `Server name cannot contain '-'. Found: ${body[field]}` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (typeof body['alias'] === 'string' && body['alias'] !== '') {
|
|
103
|
+
return { alias: normalised(body['alias']) };
|
|
104
|
+
}
|
|
105
|
+
return typeof body['server_name'] === 'string' && body['server_name'] !== ''
|
|
106
|
+
? { alias: normalised(body['server_name']) }
|
|
107
|
+
: {};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const startFakeMcpLitellm = (options: FakeMcpOptions = {}): FakeMcpLitellm => {
|
|
111
|
+
const masterKey = options.masterKey ?? 'sk-test-master';
|
|
112
|
+
let rows: Row[] = (options.seed ?? []).map((row) => ({ ...row }));
|
|
113
|
+
const requests: FakeMcpRequest[] = [];
|
|
114
|
+
const bodies: Row[] = [];
|
|
115
|
+
const misses = new Set(options.registryMisses ?? []);
|
|
116
|
+
/** The ids the in-memory registry holds. */
|
|
117
|
+
const registered = new Set(
|
|
118
|
+
rows.map((row) => String(row['server_id'])).filter((id) => !misses.has(id)),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const stored = (row: Row): Row =>
|
|
122
|
+
options.echoCredentials === true ? row : { ...row, credentials: null };
|
|
123
|
+
const listed = (row: Row): Row => ({ ...stored(row), description: effectiveDescription(row) });
|
|
124
|
+
|
|
125
|
+
const fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
126
|
+
const request =
|
|
127
|
+
input instanceof Request ? new Request(input, init) : new Request(String(input), init);
|
|
128
|
+
const url = new URL(request.url);
|
|
129
|
+
requests.push({ method: request.method, path: `${url.pathname}${url.search}` });
|
|
130
|
+
if (request.headers.get('authorization') !== `Bearer ${masterKey}`) {
|
|
131
|
+
return json(401, { detail: 'invalid api key' });
|
|
132
|
+
}
|
|
133
|
+
if (url.pathname === '/v1/mcp/server' && request.method === 'GET') {
|
|
134
|
+
return json(200, rows.filter((row) => registered.has(String(row['server_id']))).map(listed));
|
|
135
|
+
}
|
|
136
|
+
if (url.pathname === '/v1/mcp/server' && request.method === 'POST') {
|
|
137
|
+
const body = (await request.json()) as Row;
|
|
138
|
+
bodies.push(body);
|
|
139
|
+
const id = options.issuesOwnId === true ? ISSUED_ID : String(body['server_id']);
|
|
140
|
+
if (rows.some((row) => row['server_id'] === id)) {
|
|
141
|
+
return json(400, { detail: { error: `MCP server ${id} already exists` } });
|
|
142
|
+
}
|
|
143
|
+
const { alias, refusal } = aliasFor(body);
|
|
144
|
+
if (refusal !== undefined) return json(400, { detail: { error: refusal } });
|
|
145
|
+
const created = serverRow({ ...body, alias, server_id: id });
|
|
146
|
+
rows = [...rows, created];
|
|
147
|
+
if (options.hideCreated !== true) registered.add(id);
|
|
148
|
+
return json(200, stored(created));
|
|
149
|
+
}
|
|
150
|
+
if (url.pathname === '/v1/mcp/server' && request.method === 'PUT') {
|
|
151
|
+
const body = (await request.json()) as Row;
|
|
152
|
+
bodies.push(body);
|
|
153
|
+
const at = rows.findIndex((row) => row['server_id'] === body['server_id']);
|
|
154
|
+
if (at === -1) return json(404, { detail: { error: 'MCP server not found' } });
|
|
155
|
+
const { alias, refusal } = aliasFor(body);
|
|
156
|
+
if (refusal !== undefined) return json(400, { detail: { error: refusal } });
|
|
157
|
+
const written: Row = { ...body, ...(alias === undefined ? {} : { alias }) };
|
|
158
|
+
const patch: Row = Object.fromEntries(
|
|
159
|
+
Object.entries(written).filter(([, value]) =>
|
|
160
|
+
options.editIgnoresFalsy === true
|
|
161
|
+
? value !== false && !(Array.isArray(value) && value.length === 0)
|
|
162
|
+
: true,
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
// ★ measured: a changed auth class with no `credentials` key wipes the stored credential
|
|
166
|
+
if (
|
|
167
|
+
typeof body['auth_type'] === 'string' &&
|
|
168
|
+
body['auth_type'] !== rows[at]?.['auth_type'] &&
|
|
169
|
+
!('credentials' in body)
|
|
170
|
+
) {
|
|
171
|
+
patch['credentials'] = null;
|
|
172
|
+
}
|
|
173
|
+
rows = rows.map((row, i) => (i === at ? { ...row, ...patch } : row));
|
|
174
|
+
registered.add(String(body['server_id']));
|
|
175
|
+
return json(200, stored(rows[at] ?? {}));
|
|
176
|
+
}
|
|
177
|
+
const byId = /^\/v1\/mcp\/server\/([^/]+)$/.exec(url.pathname);
|
|
178
|
+
if (byId !== null && request.method === 'GET') {
|
|
179
|
+
if (options.readFails === true) return json(403, { detail: 'not_allowed_access' });
|
|
180
|
+
const id = decodeURIComponent(byId[1] ?? '');
|
|
181
|
+
// ★ measured: the table first (and the row is registered), then the registry by id, name or alias
|
|
182
|
+
const found =
|
|
183
|
+
rows.find((row) => row['server_id'] === id) ??
|
|
184
|
+
rows.find(
|
|
185
|
+
(row) =>
|
|
186
|
+
registered.has(String(row['server_id'])) &&
|
|
187
|
+
[row['server_id'], row['server_name'], row['alias']].includes(id),
|
|
188
|
+
);
|
|
189
|
+
if (found === undefined) {
|
|
190
|
+
return json(404, { detail: { error: `MCP Server with id ${id} not found` } });
|
|
191
|
+
}
|
|
192
|
+
registered.add(String(found['server_id']));
|
|
193
|
+
return json(200, stored(found));
|
|
194
|
+
}
|
|
195
|
+
if (byId !== null && request.method === 'DELETE') {
|
|
196
|
+
if (options.forbidDelete === true) return json(400, { detail: 'not_allowed_access' });
|
|
197
|
+
const id = decodeURIComponent(byId[1] ?? '');
|
|
198
|
+
if (!rows.some((row) => row['server_id'] === id)) {
|
|
199
|
+
return json(404, { detail: { error: `MCP server ${id} not found` } });
|
|
200
|
+
}
|
|
201
|
+
rows = rows.filter((row) => row['server_id'] !== id);
|
|
202
|
+
registered.delete(id);
|
|
203
|
+
return json(200, {});
|
|
204
|
+
}
|
|
205
|
+
return json(404, { detail: 'not found' });
|
|
206
|
+
}) as typeof globalThis.fetch;
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
bodies: () => [...bodies],
|
|
210
|
+
fetch,
|
|
211
|
+
requests: () => [...requests],
|
|
212
|
+
servers: () => rows.map((row) => ({ ...row })),
|
|
213
|
+
};
|
|
214
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /model/update handler for the fake LiteLLM proxy.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `fake-model-litellm.ts` so each file stays under the repository line cap.
|
|
5
|
+
* See that file for context on what the fake is and what is measured vs. assumed.
|
|
6
|
+
*/
|
|
7
|
+
import type { Row } from './fake-model-litellm.ts';
|
|
8
|
+
|
|
9
|
+
type PostUpdateOptions = Readonly<{
|
|
10
|
+
/** `POST /model/update` drops a `false` and an empty list, like a truthiness check would. */
|
|
11
|
+
readonly editIgnoresFalsy?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* `POST /model/update` parses `litellm_params` the way v1.103.0 `updateLiteLLMParams` does:
|
|
14
|
+
* every unset field is filled with its pydantic default before the write. `None` keeps the
|
|
15
|
+
* stored value; a non-`None` default (`false` on the five flags below) overwrites it.
|
|
16
|
+
*/
|
|
17
|
+
readonly fillsParamDefaults?: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The five `litellm_params` fields whose pydantic default is `False`, not `None`
|
|
22
|
+
* (`litellm/types/router.py` at v1.103.0). `updateLiteLLMParams` fills every unset field with
|
|
23
|
+
* that default before `update_model` walks the parsed model, so an omitted key is written
|
|
24
|
+
* `false` and a stored `true` is clobbered. A JSON `null` is the `None` that keeps the stored
|
|
25
|
+
* value. A key the parsed model does not declare survives PATCH but is DELETED by POST's
|
|
26
|
+
* column rebuild (`extra_headers`, `weight`, `order` have no field).
|
|
27
|
+
*/
|
|
28
|
+
const NON_NONE_PARAM_DEFAULTS = [
|
|
29
|
+
'use_in_pass_through',
|
|
30
|
+
'use_litellm_proxy',
|
|
31
|
+
'use_xai_oauth',
|
|
32
|
+
'allow_client_keepalive_override',
|
|
33
|
+
'merge_reasoning_content_in_choices',
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* v1.103.0 `updateLiteLLMParams`: an omitted field takes its pydantic default. The five flags
|
|
38
|
+
* default to `False`, not `None`. An explicit `null` stays `null` (it was set, not omitted) so a
|
|
39
|
+
* later `exclude_none` can drop it and keep the stored value.
|
|
40
|
+
*/
|
|
41
|
+
export const withOmittedFalseDefaults = (sent: Row): Row => {
|
|
42
|
+
const filled: Row = { ...sent };
|
|
43
|
+
for (const key of NON_NONE_PARAM_DEFAULTS) {
|
|
44
|
+
if (!(key in filled)) filled[key] = false;
|
|
45
|
+
}
|
|
46
|
+
return filled;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export interface PostModelUpdateResult {
|
|
50
|
+
readonly response: Response;
|
|
51
|
+
/** Merged row to write back into the table. */
|
|
52
|
+
readonly merged: Row;
|
|
53
|
+
/** Index into the original rows array that `merged` should replace. */
|
|
54
|
+
readonly at: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle `POST /model/update`.
|
|
59
|
+
*
|
|
60
|
+
* ⛔ v1.103.0 `update_model` REBUILDS `litellm_params`: it parses the request model
|
|
61
|
+
* (every unset field filled with its pydantic default, `None` keeps the stored value)
|
|
62
|
+
* and REPLACES the column, so a stored key the model does not declare is DELETED.
|
|
63
|
+
* `editIgnoresFalsy` models a proxy that REFUSES a `false` or an empty list: the edit
|
|
64
|
+
* never lands, so the row's current value survives. Drop the incoming falsy value
|
|
65
|
+
* BEFORE the rebuild — filtering after erases the field and hides the dropped edit.
|
|
66
|
+
*/
|
|
67
|
+
export const handlePostModelUpdate = (args: {
|
|
68
|
+
readonly body: Row;
|
|
69
|
+
readonly rows: readonly Row[];
|
|
70
|
+
readonly rowId: (row: Row) => string;
|
|
71
|
+
readonly json: (status: number, body: unknown) => Response;
|
|
72
|
+
readonly options: PostUpdateOptions;
|
|
73
|
+
}): PostModelUpdateResult => {
|
|
74
|
+
const { body, rows, rowId, json, options } = args;
|
|
75
|
+
const id = String(((body['model_info'] ?? {}) as Row)['id']);
|
|
76
|
+
const at = rows.findIndex((row) => rowId(row) === id);
|
|
77
|
+
if (at === -1) {
|
|
78
|
+
return { at: -1, merged: {}, response: json(400, { detail: { error: 'model not found' } }) };
|
|
79
|
+
}
|
|
80
|
+
const dropFalsy = <T extends Row>(incoming: T): T =>
|
|
81
|
+
options.editIgnoresFalsy === true
|
|
82
|
+
? (Object.fromEntries(
|
|
83
|
+
Object.entries(incoming).filter(
|
|
84
|
+
([, value]) => value !== false && !(Array.isArray(value) && value.length === 0),
|
|
85
|
+
),
|
|
86
|
+
) as T)
|
|
87
|
+
: incoming;
|
|
88
|
+
const current = rows[at] as Row;
|
|
89
|
+
const sent = dropFalsy((body['litellm_params'] ?? {}) as Row);
|
|
90
|
+
const stored = { ...(current['litellm_params'] as Row) };
|
|
91
|
+
const parsed = options.fillsParamDefaults === true ? withOmittedFalseDefaults(sent) : sent;
|
|
92
|
+
// `None` keeps the stored value; every other field is the parsed model's — nothing else
|
|
93
|
+
// of the stored row survives the rebuild (undeclared keys are dropped).
|
|
94
|
+
const rewritten = Object.fromEntries(
|
|
95
|
+
Object.entries(parsed).map(([key, value]) => [key, value === null ? stored[key] : value]),
|
|
96
|
+
);
|
|
97
|
+
const merged: Row = {
|
|
98
|
+
...current,
|
|
99
|
+
...(body['model_name'] === undefined ? {} : { model_name: body['model_name'] }),
|
|
100
|
+
...(body['litellm_params'] === undefined ? {} : { litellm_params: rewritten }),
|
|
101
|
+
};
|
|
102
|
+
return { at, merged, response: json(200, {}) };
|
|
103
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake of LiteLLM's `/model/*` routes for tests — TEST ONLY, never imported by `index.ts`.
|
|
3
|
+
* Like `fake-mcp-litellm.ts` it is a `fetch` function handed to Effect's real `FetchHttpClient`
|
|
4
|
+
* through its `Fetch` reference, so a test exercises distilled's REAL path assembly, JSON encoding
|
|
5
|
+
* and status-to-error matching; only the wire responses a scenario needs are written here.
|
|
6
|
+
*
|
|
7
|
+
* ★ A SEPARATE FILE FROM `fake-litellm.ts` ON PURPOSE: that fake carries the pass-through and
|
|
8
|
+
* budget routes and is at its size budget; a fake per route family keeps each one small.
|
|
9
|
+
* ⚠️ SHAPES ARE FROM THE GENERATED 1.103.0 SCHEMA (`model_management.ts`), NOT A LIVE READ. A
|
|
10
|
+
* missing id on the by-id read is HTTP 400, measured in `model_info_v1` at v1.100.0 and
|
|
11
|
+
* v1.103.0. POST `/model/update` REBUILDS `litellm_params` from the parsed request model and
|
|
12
|
+
* REPLACES the column (deleting stored keys the model does not declare); PATCH merges
|
|
13
|
+
* (`update_db_model`) after `updateLiteLLMParams` and `exclude_none`. A PATCH of a missing id
|
|
14
|
+
* is HTTP 404 (`patch_model` at v1.103.0). A duplicate create and a delete of a missing id
|
|
15
|
+
* stay the fake's own 400s, and every test that leans on one says so.
|
|
16
|
+
* ★ THE LIST IS THE TABLE, the way `GET /model/info` reads: a row is listed as soon as it is
|
|
17
|
+
* committed — no in-memory registry like the MCP server list. `listOmits` models the opposite,
|
|
18
|
+
* rows the list does not answer but the by-id read does, UNMEASURED, to exercise the read-back
|
|
19
|
+
* fallback (model-operations.ts).
|
|
20
|
+
* ★ THE READ ANSWERS THE MEASURED 1.103.0 SHAPE: `litellm_params` DECRYPTED but with `api_key`
|
|
21
|
+
* STRIPPED (measured on a live proxy; see the comment on `readable`), while the stored row
|
|
22
|
+
* keeps every key it was sent, so `models()` still asserts what a resource SENT.
|
|
23
|
+
* ★ `FAKE-*` VALUES ONLY. Nothing here is, or looks like, a real credential.
|
|
24
|
+
*/
|
|
25
|
+
import {
|
|
26
|
+
handlePostModelUpdate,
|
|
27
|
+
withOmittedFalseDefaults,
|
|
28
|
+
} from './fake-model-litellm-post-update.ts';
|
|
29
|
+
|
|
30
|
+
/** One request that reached the fake. Its own type, so this file depends on no other fake. */
|
|
31
|
+
export interface FakeModelRequest {
|
|
32
|
+
readonly method: string;
|
|
33
|
+
readonly path: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type Row = Record<string, unknown>;
|
|
37
|
+
|
|
38
|
+
export interface FakeModelLitellm {
|
|
39
|
+
readonly fetch: typeof globalThis.fetch;
|
|
40
|
+
/** Every stored row exactly as the proxy holds it, `litellm_params` included. */
|
|
41
|
+
readonly models: () => readonly Row[];
|
|
42
|
+
readonly requests: () => readonly FakeModelRequest[];
|
|
43
|
+
/** The JSON bodies of every `POST`, in order. */
|
|
44
|
+
readonly bodies: () => readonly Row[];
|
|
45
|
+
/**
|
|
46
|
+
* The next `count` successful `POST /model/new` calls each make the following `GET /model/info`
|
|
47
|
+
* answer 500 once — a read-back that fails after the row is committed.
|
|
48
|
+
*/
|
|
49
|
+
readonly failNextReadBacks: (count: number) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface FakeModelOptions {
|
|
53
|
+
readonly masterKey?: string;
|
|
54
|
+
readonly seed?: readonly Row[];
|
|
55
|
+
/** `POST /model/new` ignores the supplied `model_info.id` and issues its own. */
|
|
56
|
+
readonly issuesOwnId?: boolean;
|
|
57
|
+
/** Every `POST /model/delete` answers 400, whether or not the row exists (no rights). */
|
|
58
|
+
readonly forbidDelete?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Every by-id `GET /model/info` answers 400 even when the row is listed. Models "no rights" /
|
|
61
|
+
* a bad filter: a 400 is absence only when a re-list also lacks the id (model-operations.ts).
|
|
62
|
+
*/
|
|
63
|
+
readonly byIdRefused?: boolean;
|
|
64
|
+
/** `POST /model/update` drops a `false` and an empty list, like a truthiness check would. */
|
|
65
|
+
readonly editIgnoresFalsy?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* `POST /model/update` and `PATCH /model/{id}/update` parse `litellm_params` the way v1.103.0
|
|
68
|
+
* `updateLiteLLMParams` does: every unset field is filled with its pydantic default before the
|
|
69
|
+
* write. `None` keeps the stored value; a non-`None` default (`false` on the five flags) overwrites
|
|
70
|
+
* it. POST then REPLACES the column. PATCH merges after `exclude_none`, so an explicit JSON `null`
|
|
71
|
+
* is dropped and the stored value survives, while an omitted flag is written `false`.
|
|
72
|
+
*/
|
|
73
|
+
readonly fillsParamDefaults?: boolean;
|
|
74
|
+
/** Ids committed to the table that the list omits; the by-id read still answers them. */
|
|
75
|
+
readonly listOmits?: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const json = (status: number, body: unknown) =>
|
|
79
|
+
new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' }, status });
|
|
80
|
+
|
|
81
|
+
const ISSUED_ID = 'FAKE-issued-id-0001';
|
|
82
|
+
|
|
83
|
+
/** A minimal 1.103.0-shaped row, as the table holds it: the id lives in `model_info`. */
|
|
84
|
+
export const modelRow = (fields: Row): Row => ({
|
|
85
|
+
litellm_params: {},
|
|
86
|
+
model_info: {},
|
|
87
|
+
model_name: '',
|
|
88
|
+
...fields,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/** The id of a stored row, wherever the proxy carried it. */
|
|
92
|
+
const rowId = (row: Row): string => {
|
|
93
|
+
const info = (row['model_info'] ?? {}) as Row;
|
|
94
|
+
return String(row['id'] ?? info['id']);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const startFakeModelLitellm = (options: FakeModelOptions = {}): FakeModelLitellm => {
|
|
98
|
+
const masterKey = options.masterKey ?? 'sk-test-master';
|
|
99
|
+
let rows: Row[] = (options.seed ?? []).map((row) => ({ ...row }));
|
|
100
|
+
const requests: FakeModelRequest[] = [];
|
|
101
|
+
const bodies: Row[] = [];
|
|
102
|
+
const omitted = new Set(options.listOmits ?? []);
|
|
103
|
+
let readBacksLeft = 0;
|
|
104
|
+
let failNextGet = false;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The measured 1.103.0 read: `litellm_params` decrypted but with `api_key` stripped — never a
|
|
108
|
+
* ciphertext object, never the credential reference. The stored row keeps everything
|
|
109
|
+
* (`models()`), so what was SENT stays assertable.
|
|
110
|
+
*/
|
|
111
|
+
const readable = (row: Row): Row => {
|
|
112
|
+
const { api_key: _stripped, ...params } = row['litellm_params'] as Row;
|
|
113
|
+
return { ...row, litellm_params: params };
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
117
|
+
const request =
|
|
118
|
+
input instanceof Request ? new Request(input, init) : new Request(String(input), init);
|
|
119
|
+
const url = new URL(request.url);
|
|
120
|
+
requests.push({ method: request.method, path: `${url.pathname}${url.search}` });
|
|
121
|
+
if (request.headers.get('authorization') !== `Bearer ${masterKey}`) {
|
|
122
|
+
return json(401, { detail: 'invalid api key' });
|
|
123
|
+
}
|
|
124
|
+
if (url.pathname === '/model/info' && request.method === 'GET') {
|
|
125
|
+
if (failNextGet) {
|
|
126
|
+
failNextGet = false;
|
|
127
|
+
return json(500, { detail: { error: 'injected read-back failure' } });
|
|
128
|
+
}
|
|
129
|
+
const wanted = url.searchParams.get('litellm_model_id');
|
|
130
|
+
const listed = rows.filter((row) => !omitted.has(rowId(row)));
|
|
131
|
+
if (wanted === null) return json(200, { data: listed.map(readable) });
|
|
132
|
+
const found =
|
|
133
|
+
listed.find((row) => rowId(row) === wanted) ?? rows.find((row) => rowId(row) === wanted);
|
|
134
|
+
// ★ v1.100.0 and v1.103.0 `model_info_v1` raise HTTP 400 when the router has no such
|
|
135
|
+
// deployment (`proxy_server.py`, "Model id = … not found on litellm proxy"), not 404.
|
|
136
|
+
// The SDK still decodes that undeclared 400 as BadRequest. A 400 is also "no rights".
|
|
137
|
+
if (options.byIdRefused === true || found === undefined) {
|
|
138
|
+
return json(400, {
|
|
139
|
+
detail: { error: `Model id = ${wanted} not found on litellm proxy` },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return json(200, { data: [readable(found)] });
|
|
143
|
+
}
|
|
144
|
+
if (url.pathname === '/model/new' && request.method === 'POST') {
|
|
145
|
+
const body = (await request.json()) as Row;
|
|
146
|
+
bodies.push(body);
|
|
147
|
+
const info = { ...((body['model_info'] ?? {}) as Row) };
|
|
148
|
+
if (options.issuesOwnId === true) info['id'] = ISSUED_ID;
|
|
149
|
+
const id = String(info['id']);
|
|
150
|
+
if (rows.some((row) => rowId(row) === id)) {
|
|
151
|
+
return json(400, { detail: { error: `Model with id ${id} already exists` } });
|
|
152
|
+
}
|
|
153
|
+
rows = [...rows, modelRow({ ...body, model_info: info })];
|
|
154
|
+
if (readBacksLeft > 0) {
|
|
155
|
+
readBacksLeft -= 1;
|
|
156
|
+
failNextGet = true;
|
|
157
|
+
}
|
|
158
|
+
return json(200, {});
|
|
159
|
+
}
|
|
160
|
+
if (url.pathname === '/model/update' && request.method === 'POST') {
|
|
161
|
+
const body = (await request.json()) as Row;
|
|
162
|
+
bodies.push(body);
|
|
163
|
+
const update = handlePostModelUpdate({ body, json, options, rowId, rows });
|
|
164
|
+
if (update.at !== -1) {
|
|
165
|
+
rows = rows.map((row, i) => (i === update.at ? update.merged : row));
|
|
166
|
+
}
|
|
167
|
+
return update.response;
|
|
168
|
+
}
|
|
169
|
+
const patch = /^\/model\/([^/]+)\/update$/.exec(url.pathname);
|
|
170
|
+
if (patch !== null && request.method === 'PATCH') {
|
|
171
|
+
const body = (await request.json()) as Row;
|
|
172
|
+
bodies.push(body);
|
|
173
|
+
const id = decodeURIComponent(patch[1] ?? '');
|
|
174
|
+
const at = rows.findIndex((row) => rowId(row) === id);
|
|
175
|
+
// ★ v1.103.0 `patch_model` answers 404 when the table has no such id
|
|
176
|
+
// (`model_management_endpoints.py`, `HTTP_404_NOT_FOUND`), not 400.
|
|
177
|
+
if (at === -1) return json(404, { detail: { error: `Model ${id} not found on proxy.` } });
|
|
178
|
+
// ★ v1.103.0 `update_db_model` MERGES onto the stored row (the opposite of POST's rebuild).
|
|
179
|
+
// An empty access_groups clears the groups. editIgnoresFalsy drops that clear. The
|
|
180
|
+
// params parse below is `updateLiteLLMParams` plus `exclude_none` when the flag is on.
|
|
181
|
+
const dropFalsy = <T extends Row>(incoming: T): T =>
|
|
182
|
+
options.editIgnoresFalsy === true
|
|
183
|
+
? (Object.fromEntries(
|
|
184
|
+
Object.entries(incoming).filter(
|
|
185
|
+
([, value]) => value !== false && !(Array.isArray(value) && value.length === 0),
|
|
186
|
+
),
|
|
187
|
+
) as T)
|
|
188
|
+
: incoming;
|
|
189
|
+
const current = rows[at] as Row;
|
|
190
|
+
const info = dropFalsy((body['model_info'] ?? {}) as Row);
|
|
191
|
+
const sent = dropFalsy((body['litellm_params'] ?? {}) as Row);
|
|
192
|
+
const stored = { ...(current['litellm_params'] as Row) };
|
|
193
|
+
// ★ v1.103.0 parses `litellm_params` as `updateLiteLLMParams` (omitted flags become
|
|
194
|
+
// `false`) and then `model_dump(exclude_none=True)`. An explicit JSON `null` is `None`
|
|
195
|
+
// and is dropped, so the stored value survives; an omitted flag is written `false`.
|
|
196
|
+
const parsed = options.fillsParamDefaults === true ? withOmittedFalseDefaults(sent) : sent;
|
|
197
|
+
const applied = Object.fromEntries(Object.entries(parsed).filter(([, v]) => v !== null));
|
|
198
|
+
const params: Row = { ...stored, ...applied };
|
|
199
|
+
const merged: Row = {
|
|
200
|
+
...current,
|
|
201
|
+
...(body['model_name'] === undefined ? {} : { model_name: body['model_name'] }),
|
|
202
|
+
...(body['litellm_params'] === undefined ? {} : { litellm_params: params }),
|
|
203
|
+
...(body['model_info'] === undefined
|
|
204
|
+
? {}
|
|
205
|
+
: { model_info: { ...(current['model_info'] as Row), ...info } }),
|
|
206
|
+
};
|
|
207
|
+
rows = rows.map((row, i) => (i === at ? merged : row));
|
|
208
|
+
return json(200, {});
|
|
209
|
+
}
|
|
210
|
+
if (url.pathname === '/model/delete' && request.method === 'POST') {
|
|
211
|
+
const body = (await request.json()) as Row;
|
|
212
|
+
bodies.push(body);
|
|
213
|
+
if (options.forbidDelete === true) return json(400, { detail: 'not_allowed_access' });
|
|
214
|
+
const id = String(body['id']);
|
|
215
|
+
if (!rows.some((row) => rowId(row) === id)) {
|
|
216
|
+
return json(400, { detail: { error: `model with id ${id} not found` } });
|
|
217
|
+
}
|
|
218
|
+
rows = rows.filter((row) => rowId(row) !== id);
|
|
219
|
+
return json(200, {});
|
|
220
|
+
}
|
|
221
|
+
return json(404, { detail: 'not found' });
|
|
222
|
+
}) as typeof globalThis.fetch;
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
bodies: () => [...bodies],
|
|
226
|
+
failNextReadBacks: (count) => {
|
|
227
|
+
readBacksLeft = count;
|
|
228
|
+
},
|
|
229
|
+
fetch,
|
|
230
|
+
models: () => rows.map((row) => ({ ...row })),
|
|
231
|
+
requests: () => [...requests],
|
|
232
|
+
};
|
|
233
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake of LiteLLM's `/policies/attachments` routes for tests — TEST ONLY. Skeleton and the rules
|
|
3
|
+
* every fake follows: fake-registry-base.ts.
|
|
4
|
+
*
|
|
5
|
+
* ★ MEASURED FROM THE LIVE 1.103.0 CONTAINER'S SOURCE (2026-09-30, `policy_endpoints.py` and
|
|
6
|
+
* `attachment_registry.py`, read, not called): a create needs the policy to have a PRODUCTION version
|
|
7
|
+
* (404 otherwise), refuses a CONCRETE team, key or model selector that resolves to nothing (400;
|
|
8
|
+
* wildcards are let through), and answers the row with a proxy-issued id; the list merges in config.yaml
|
|
9
|
+
* attachments (`definition_location: "config"`, ids `config-<n>`); a delete of a missing id is a 404.
|
|
10
|
+
* There is NO update route, and the fake answers 405 to one so a resource that tried would fail.
|
|
11
|
+
* ⚠️ NOT MODELLED: the in-memory registry and how a request is matched (`policy_matcher.py`).
|
|
12
|
+
*/
|
|
13
|
+
import { FAKE_KEY, type FakeRecord, json, recordingFetch } from './fake-registry-base.ts';
|
|
14
|
+
import type { Row } from './registry-support.ts';
|
|
15
|
+
|
|
16
|
+
export interface FakePolicyAttachmentLitellm extends FakeRecord {
|
|
17
|
+
/** Every stored database attachment, exactly as the proxy holds it. */
|
|
18
|
+
readonly attachments: () => readonly Row[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FakePolicyAttachmentOptions {
|
|
22
|
+
readonly masterKey?: string;
|
|
23
|
+
readonly seed?: readonly Row[];
|
|
24
|
+
/** Policies that have a production version. A create for any other name is a 404. */
|
|
25
|
+
readonly policies?: readonly string[];
|
|
26
|
+
/** Team aliases that exist. A concrete (non-wildcard) `teams` entry outside this set is a 400. */
|
|
27
|
+
readonly knownTeams?: readonly string[];
|
|
28
|
+
/** Attachments defined in config.yaml: they appear in the list and can never be deleted. */
|
|
29
|
+
readonly configAttachments?: readonly Row[];
|
|
30
|
+
/** `DELETE` answers 403, as for a key that is not allowed. */
|
|
31
|
+
readonly forbidDelete?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const attachmentRow = (fields: Row): Row => ({
|
|
35
|
+
keys: [],
|
|
36
|
+
models: [],
|
|
37
|
+
priority: null,
|
|
38
|
+
scope: null,
|
|
39
|
+
tags: [],
|
|
40
|
+
teams: [],
|
|
41
|
+
...fields,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const startFakePolicyAttachmentLitellm = (
|
|
45
|
+
options: FakePolicyAttachmentOptions = {},
|
|
46
|
+
): FakePolicyAttachmentLitellm => {
|
|
47
|
+
let rows: Row[] = (options.seed ?? []).map((row) => ({ ...row }));
|
|
48
|
+
let issued = 0;
|
|
49
|
+
|
|
50
|
+
const record = recordingFetch(options.masterKey ?? FAKE_KEY, ({ body, request, url }) => {
|
|
51
|
+
const byId = /^\/policies\/attachments\/([^/]+)$/.exec(url.pathname);
|
|
52
|
+
if (url.pathname === '/policies/attachments/list' && request.method === 'GET') {
|
|
53
|
+
const config = (options.configAttachments ?? []).map((row, index) =>
|
|
54
|
+
attachmentRow({ attachment_id: `config-${index}`, definition_location: 'config', ...row }),
|
|
55
|
+
);
|
|
56
|
+
const all = [...rows, ...config];
|
|
57
|
+
return json(200, { attachments: all, total_count: all.length });
|
|
58
|
+
}
|
|
59
|
+
if (url.pathname === '/policies/attachments' && request.method === 'POST') {
|
|
60
|
+
if (!(options.policies ?? []).includes(String(body['policy_name']))) {
|
|
61
|
+
return json(404, {
|
|
62
|
+
detail: `Policy '${String(body['policy_name'])}' not found. Create the policy first.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const teams = (body['teams'] as string[] | undefined) ?? [];
|
|
66
|
+
const missing = teams.filter(
|
|
67
|
+
(team) => !team.includes('*') && !(options.knownTeams ?? []).includes(team),
|
|
68
|
+
);
|
|
69
|
+
if (missing.length > 0) return json(400, { detail: `Unknown team: ${missing.join(', ')}` });
|
|
70
|
+
issued += 1;
|
|
71
|
+
const created = attachmentRow({
|
|
72
|
+
attachment_id: `FAKE-attachment-${String(issued).padStart(4, '0')}`,
|
|
73
|
+
keys: body['keys'] ?? [],
|
|
74
|
+
models: body['models'] ?? [],
|
|
75
|
+
policy_name: body['policy_name'],
|
|
76
|
+
priority: body['priority'] ?? null,
|
|
77
|
+
scope: body['scope'] ?? null,
|
|
78
|
+
tags: body['tags'] ?? [],
|
|
79
|
+
teams,
|
|
80
|
+
});
|
|
81
|
+
rows = [...rows, created];
|
|
82
|
+
return json(200, created);
|
|
83
|
+
}
|
|
84
|
+
if (byId !== null && request.method === 'DELETE') {
|
|
85
|
+
const id = decodeURIComponent(byId[1] ?? '');
|
|
86
|
+
if (options.forbidDelete === true) return json(403, { detail: 'not_allowed_access' });
|
|
87
|
+
if (!rows.some((row) => row['attachment_id'] === id)) {
|
|
88
|
+
return json(404, { detail: `Attachment with ID ${id} not found` });
|
|
89
|
+
}
|
|
90
|
+
rows = rows.filter((row) => row['attachment_id'] !== id);
|
|
91
|
+
return json(200, { message: `Attachment ${id} deleted successfully` });
|
|
92
|
+
}
|
|
93
|
+
return json(405, { detail: `unhandled ${request.method} ${url.pathname}` });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return { ...record, attachments: () => rows.map((row) => ({ ...row })) };
|
|
97
|
+
};
|