@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,187 @@
|
|
|
1
|
+
# LiteLLM — `LiteLLM.MCPServer`
|
|
2
|
+
|
|
3
|
+
One row of LiteLLM's `LiteLLM_MCPServerTable` (`/v1/mcp/server`): an upstream MCP server that the
|
|
4
|
+
proxy's gateway exposes to virtual keys and teams. Typed calls come from
|
|
5
|
+
`@distilled.cloud/litellm/mcp_management`, generated from LiteLLM **v1.103.0**; credentials and the
|
|
6
|
+
family's other refusals are in [litellm.md](./litellm.md).
|
|
7
|
+
|
|
8
|
+
## The distilled operations it uses
|
|
9
|
+
|
|
10
|
+
Nothing is hand-rolled: every call is one of these, through `mcp-server-operations.ts`.
|
|
11
|
+
|
|
12
|
+
| step | operation (`@distilled.cloud/litellm/mcp_management`) | route |
|
|
13
|
+
| ------ | ----------------------------------------------------- | ----------------------------------- |
|
|
14
|
+
| list | `fetchAllMcpServersV1McpServerGet` | `GET /v1/mcp/server` |
|
|
15
|
+
| read | `fetchMcpServerV1McpServerServerIdGet` | `GET /v1/mcp/server/{server_id}` |
|
|
16
|
+
| create | `addMcpServerV1McpServerPost` | `POST /v1/mcp/server` |
|
|
17
|
+
| update | `editMcpServerV1McpServerPut` | `PUT /v1/mcp/server` |
|
|
18
|
+
| delete | `removeMcpServerV1McpServerServerIdDelete` | `DELETE /v1/mcp/server/{server_id}` |
|
|
19
|
+
|
|
20
|
+
Not used: the submission and approval flow, import, toolsets and the health check.
|
|
21
|
+
|
|
22
|
+
⛔ **The list is not the table.** Measured on the live 1.103.0 container (2026-09-29, source only):
|
|
23
|
+
`GET /v1/mcp/server` answers the proxy's **in-memory registry**. A reload fills it only from rows whose
|
|
24
|
+
approval status is null, active or approved, skips a row whose build throws, and the caller's key can
|
|
25
|
+
narrow it; `add_mcp_server` only logs a failed registry refresh after the row is committed. So a row
|
|
26
|
+
absent from the list can be live. The by-id read asks the **table** first (a 404 only when the table,
|
|
27
|
+
the registry and a lookup by name all miss), so it is what decides where the list cannot. It has side
|
|
28
|
+
effects (it registers the row and health-checks the upstream), so it runs only when the list misses.
|
|
29
|
+
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { LiteLLMMCPServer, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
34
|
+
|
|
35
|
+
// An existing row, adopted by its name (needs --adopt), closed to every key except one group.
|
|
36
|
+
export class Docs extends LiteLLMMCPServer('Docs', {
|
|
37
|
+
serverName: 'example_docs',
|
|
38
|
+
url: 'https://mcp.example.com/mcp',
|
|
39
|
+
transport: 'http',
|
|
40
|
+
authType: 'none',
|
|
41
|
+
mcpAccessGroups: ['example-knowledge'],
|
|
42
|
+
}) {}
|
|
43
|
+
|
|
44
|
+
// A static credential is the NAME of an environment variable in the deploying process. The tool
|
|
45
|
+
// whitelist is pinned by declaring `allowedTools`; leave it out to keep whatever the proxy holds.
|
|
46
|
+
export class Keyed extends LiteLLMMCPServer('Keyed', {
|
|
47
|
+
serverName: 'example_keyed',
|
|
48
|
+
url: 'https://keyed.example.com/sse',
|
|
49
|
+
transport: 'sse',
|
|
50
|
+
authType: 'bearer_token',
|
|
51
|
+
authValue: { fromEnv: 'EXAMPLE_MCP_TOKEN' },
|
|
52
|
+
allowedTools: ['search', 'fetch'],
|
|
53
|
+
}) {}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then provide `litellmProviders()` alongside the stack's other providers.
|
|
57
|
+
|
|
58
|
+
## Behaviour
|
|
59
|
+
|
|
60
|
+
| Concern | Rule |
|
|
61
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| Identity | The live row whose `server_name` is `serverName`. `serverId` pins one row instead. Two rows with the same name are refused (`LitellmMcpServerAmbiguousNameError`). |
|
|
63
|
+
| Adopt | A live row with no state is `Unowned`; it needs `--adopt`. Adopting a row that already matches writes nothing. The plan says `adopted` even when reconcile then writes. A row the list lacks is adopted only by declaring its `serverId`. |
|
|
64
|
+
| Id | A create asks for `serverId`, else a deterministic physical name. The id LiteLLM answers is the one recorded, so a proxy that issues its own id is still tracked. |
|
|
65
|
+
| Replace | Only a changed declared `serverId`, create-first. A changed `serverName` is an update that re-sends the live alias, so the tool prefix stays. Declare `alias` to change the prefix. |
|
|
66
|
+
| Removal | `defaultRemovalPolicy: 'retain'`. Opt in with `RemovalPolicy.destroy()`. A replace under `retain` leaves the old row live. |
|
|
67
|
+
| Delete | Always sends the DELETE (it acts on the table). If it fails, the failure is swallowed only when a by-id read of the table says the row is gone; a row still there, or a read that fails, re-raises the original error. |
|
|
68
|
+
| Read | The list first, then the table by id when the list lacks the row (the id it would use: recorded, declared, or the deterministic one). Copies no credential, header or environment value; a URL is redacted on the way in. |
|
|
69
|
+
| Write check | Reconcile reads back (list, then table) and fails (`LitellmMcpServerNotConvergedError`) if a declared field did not land, or (`LitellmMcpServerAbsentAfterWriteError`) if the row is in neither. A created row the list does not show yet is recorded, not retried. |
|
|
70
|
+
|
|
71
|
+
## Access grants
|
|
72
|
+
|
|
73
|
+
`allowAllKeys` (default `false`) and `mcpAccessGroups` (default empty) are **always compared**, because
|
|
74
|
+
they decide which keys can reach a server: a live row that has them wider than declared is corrected.
|
|
75
|
+
Lists compare as sets.
|
|
76
|
+
|
|
77
|
+
⛔ **`allowedTools` is compared and sent only when declared, like `alias` and `description`.** An empty
|
|
78
|
+
list is the **open** state, not a closed one. Measured on the live 1.103.0 container (read-only, source
|
|
79
|
+
only): `server_applies_tool_allowlist` (`mcp_server/utils.py`) is "the `mcp_info` enforce flag, or a
|
|
80
|
+
non-empty `allowed_tools`", and `filter_tools_by_allowed_tools` (`server.py` lines 1692-1698) returns
|
|
81
|
+
every tool when that is false. `mcp_info` is not modelled here, so:
|
|
82
|
+
|
|
83
|
+
- Leave `allowedTools` out and the live whitelist is left exactly as it is: adopting a whitelisted row
|
|
84
|
+
never widens it.
|
|
85
|
+
- Declare a list and it is authoritative: the live list becomes exactly that set.
|
|
86
|
+
- Declare `[]` only to say "no restriction". It is sent as it is.
|
|
87
|
+
|
|
88
|
+
⚠️ **The plan does not show an adopted row's write.** Alchemy's adoption branch plans `adopted` for a
|
|
89
|
+
live row with no state, whether or not reconcile then writes (`mcp-server-tools.test.ts` pins this), so
|
|
90
|
+
a corrected `allowAllKeys` or `mcpAccessGroups` on an adopted row appears in the plan only as `adopted`.
|
|
91
|
+
Later deploys of a row this stack owns plan `update`. `alias` and `description` are compared **only when
|
|
92
|
+
declared**, so adopting a row never clears text a person wrote.
|
|
93
|
+
|
|
94
|
+
⛔ **An update never lets LiteLLM default the alias.** Measured on the live container by running its own
|
|
95
|
+
`validate_and_normalize_mcp_server_payload` and `_prepare_mcp_server_data` on an edit body (no database):
|
|
96
|
+
a body with a `server_name` and no `alias` comes out with `alias = server_name`, and the alias **is
|
|
97
|
+
written**; a body with neither sends none. The alias is the tool prefix, so that would rename every tool
|
|
98
|
+
of a row aliased `search` (`search-*` to `estate_web-*`) for every key and seat. So an update sends
|
|
99
|
+
`server_name` only when it changes, and re-sends the live alias on a rename. A renamed row with no alias
|
|
100
|
+
gets the new name as its alias, LiteLLM's own default.
|
|
101
|
+
|
|
102
|
+
⚠️ **`description` cannot be written to a row whose `mcp_info` has a `description` key.** The list answers
|
|
103
|
+
`mcp_info.description` when the key exists (even as null), else the column, and this resource writes only
|
|
104
|
+
the column. Live, `linear` and `Memos_CF` have the key. A declared description that differs is refused
|
|
105
|
+
before any write (`LitellmMcpServerDescriptionShadowedError`); the same text, or none declared, is fine.
|
|
106
|
+
|
|
107
|
+
Team access is not here: the create and edit requests have no team field (`teams` exists only on the
|
|
108
|
+
row a read returns). Grant a team access from the team side (its `object_permission`).
|
|
109
|
+
|
|
110
|
+
## The credential: a name in, a seal out
|
|
111
|
+
|
|
112
|
+
⛔ **`authValue` is `{ fromEnv: 'NAME' }`, not a `Redacted` value.** Alchemy persists props and
|
|
113
|
+
attributes unencrypted, and `StateEncoding.ts` writes a `Redacted` value's inner string beside its tag
|
|
114
|
+
(measured on `alchemy@2.0.0-beta.79`), so a `Redacted` prop would sit in the state store as plaintext.
|
|
115
|
+
The variable is read from the deploying process at call time, held there as `Redacted` so a log line
|
|
116
|
+
prints `<redacted>`, and unwrapped in one place: the wire body. See
|
|
117
|
+
[provider-standard.md](./provider-standard.md) (S25) and `src/secrets/write-only.ts`.
|
|
118
|
+
|
|
119
|
+
Static types (`api_key`, `bearer_token`, `basic`, `authorization`, `token`) require `authValue`;
|
|
120
|
+
`none` and `oauth2` refuse it. LiteLLM is not assumed to return a credential, so a plan compares the
|
|
121
|
+
variable's current value with a **seal** (a salted scrypt digest) kept in the attributes:
|
|
122
|
+
|
|
123
|
+
| state | when | plan |
|
|
124
|
+
| --------- | --------------------------------------------------------- | ------------------------------------------ |
|
|
125
|
+
| `match` | the variable is set and the seal was made from it | no change |
|
|
126
|
+
| `stale` | the variable is set and there is no seal, or it disagrees | update: the declared credential is written |
|
|
127
|
+
| `unknown` | the variable is unset in this process | no change: a plan-only run is not drift |
|
|
128
|
+
| `none` | no credential is declared | no change |
|
|
129
|
+
|
|
130
|
+
A **create** needs the variable and fails naming it (`LitellmMcpServerCredentialEnvUnsetError`) if it
|
|
131
|
+
is unset or empty, and so does **any write that changes `authType` to a static type** (from `none`,
|
|
132
|
+
`oauth2` or another static type): before anything is sent. ⛔ Measured on the live 1.103.0 container
|
|
133
|
+
(`mcp_server/db.py` lines 1013-1014): an edit whose auth class differs from the stored one and that
|
|
134
|
+
sends no credential wipes the stored credential, and each static type is its own class. Sent without
|
|
135
|
+
the value, the row would end with none while the seal still matched the old value, so a later deploy
|
|
136
|
+
would be a no-op and the server would stay unauthenticated. Any other update sends the credential
|
|
137
|
+
whenever the process has it. Moving a server off a static type sends `credentials: null` and forgets
|
|
138
|
+
the seal. ⚠️ A seal of a guessable secret is still guessable: use a random token.
|
|
139
|
+
|
|
140
|
+
## Refused before any request
|
|
141
|
+
|
|
142
|
+
- A `serverName` or `alias` outside `^[A-Za-z0-9._]{1,128}$`. LiteLLM 1.103 runs the MCP SDK's
|
|
143
|
+
`validate_tool_name` on both, so a space, `/`, `:`, `@`, `-` or more than 128 characters is a 400
|
|
144
|
+
only at apply. A blank `alias` is the same refusal.
|
|
145
|
+
- A blank `description` (`''` or whitespace). LiteLLM copies the column into `mcp_info` only when
|
|
146
|
+
the value is truthy, so a declared blank one is written and read back as absent and never converges.
|
|
147
|
+
- A blank or padded `serverName`, `stdio` (it runs a command on the proxy host), and any auth type
|
|
148
|
+
outside `none`, `oauth2` and the five static ones (the rest need credential fields not modelled).
|
|
149
|
+
- A URL with userinfo, a fragment, or a query parameter whose name looks like a credential (`token`,
|
|
150
|
+
`api_key`, `secret`, `auth`, …). The URL is a prop and lands in state; put the secret in `authValue`.
|
|
151
|
+
- A blank entry in `allowedTools` or `mcpAccessGroups`.
|
|
152
|
+
|
|
153
|
+
## Not modelled
|
|
154
|
+
|
|
155
|
+
`stdio` and its `command`/`args`/`env`; `static_headers`, `extra_headers` and `env_vars` (a header or
|
|
156
|
+
variable can carry a secret); OAuth client registration and endpoints; `mcp_info`, tool renames, BYOK;
|
|
157
|
+
`timeout`, `max_concurrent_requests`, `available_on_public_internet`; `teams`. A row that uses them can
|
|
158
|
+
be adopted, and what is not declared is not sent. The edit route is a partial update at 1.103.0
|
|
159
|
+
(`mcp_server/db.py`, `exclude_unset=True`, read from the live container, not exercised), so an unsent
|
|
160
|
+
field keeps its stored value; measure it once on a scratch proxy before the first live update of an
|
|
161
|
+
adopted OAuth row (below).
|
|
162
|
+
|
|
163
|
+
## Unmeasured at 1.103.0
|
|
164
|
+
|
|
165
|
+
No live proxy was contacted. The fake (`fake-mcp-litellm.ts`) is built from the generated schema and
|
|
166
|
+
makes its own choices for each of these, except the clear-on-auth-change rule above, which it models
|
|
167
|
+
from the source; every one is guarded by the read back, so a wrong guess fails loudly rather than
|
|
168
|
+
reporting a converged row.
|
|
169
|
+
|
|
170
|
+
- Whether an empty list, a `false` or a `null` lands on an edit. (That `PUT /v1/mcp/server` is a
|
|
171
|
+
partial update is read from the source, not called.)
|
|
172
|
+
- Whether a create honours a supplied `server_id`, and what a duplicate id or name answers.
|
|
173
|
+
- Whether the list redacts `credentials` (the resource copies none either way), whether it is paged or
|
|
174
|
+
filtered for the master key, and whether it includes config-file servers, which cannot be edited.
|
|
175
|
+
- What a delete or an edit of a missing id answers on the wire. Read from the source: a delete of a
|
|
176
|
+
missing row and a by-id read that misses everywhere are 404. `mcp_management` declares only
|
|
177
|
+
`UnprocessableEntity`, and the distilled `patches/` that type 404 cover other tags, so the by-id read
|
|
178
|
+
tests the SDK's `NotFound` class (`instanceof`), never a message. A distilled patch for this tag would
|
|
179
|
+
type it; it belongs in the distilled clone.
|
|
180
|
+
- Whether LiteLLM normalises a URL. If it does, declare the URL the way the row returns it, or the
|
|
181
|
+
read back names `url`.
|
|
182
|
+
|
|
183
|
+
## Before the first live adoption
|
|
184
|
+
|
|
185
|
+
Adopting a row that already matches is a no-op. Before an update reaches a live OAuth row, exercise the
|
|
186
|
+
edit route once against a scratch LiteLLM (a row with `static_headers` and OAuth endpoints, then an edit
|
|
187
|
+
of `allowAllKeys` alone) and record whether the other fields survive; the source says they do.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# LiteLLM — `LiteLLM.Model`
|
|
2
|
+
|
|
3
|
+
One deployment row of LiteLLM's model registry (`/model/info`): an upstream model the proxy's
|
|
4
|
+
router serves under a group name. A group name can carry several deployments — that is LiteLLM's
|
|
5
|
+
load balancing — and this resource owns exactly one row of it. Typed calls come from
|
|
6
|
+
`@distilled.cloud/litellm/model_management`, generated from LiteLLM **v1.103.0**; the family's
|
|
7
|
+
credentials and other refusals in [litellm.md](./litellm.md).
|
|
8
|
+
|
|
9
|
+
## distilled operations it uses
|
|
10
|
+
|
|
11
|
+
Nothing hand-rolled: every call is one of these, through `model-operations.ts`.
|
|
12
|
+
|
|
13
|
+
| step | operation (`@distilled.cloud/litellm/model_management`) | route |
|
|
14
|
+
| ------ | ------------------------------------------------------- | -------------------------- |
|
|
15
|
+
| list | `getModelInfoV1ModelInfo` | `GET /model/info` |
|
|
16
|
+
| read | `getModelInfoV1ModelInfo` (`litellm_model_id` filter) | `GET /model/info?id=…` |
|
|
17
|
+
| create | `addNewModelModelNewPost` | `POST /model/new` |
|
|
18
|
+
| update | `patchModelModelModelIdUpdatePatch` | `PATCH /model/{id}/update` |
|
|
19
|
+
| delete | `deleteModelModelDeletePost` | `POST /model/delete` |
|
|
20
|
+
|
|
21
|
+
A missing id on the by-id read is HTTP 400 at v1.100.0 and v1.103.0 (`model_info_v1`), and the same
|
|
22
|
+
400 is also "no rights". Absence is a re-list that lacks the id, never the error text.
|
|
23
|
+
|
|
24
|
+
The list is the table and one GET answers a name, an id and absence. The by-id read is kept only
|
|
25
|
+
for the read-back of a row the list may not show yet (a create commits before the list refreshes)
|
|
26
|
+
— the same order `LiteLLM.MCPServer` keeps for its registry.
|
|
27
|
+
|
|
28
|
+
## Example
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { LiteLLMModel, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
32
|
+
|
|
33
|
+
// An existing row, adopted by group name (needs --adopt). The id of the live row is adopted;
|
|
34
|
+
// omitting accessGroups declares "no groups", which clears them on adoption.
|
|
35
|
+
export class Grok extends LiteLLMModel('Grok', {
|
|
36
|
+
modelName: 'grok',
|
|
37
|
+
model: 'xai/grok-4.7',
|
|
38
|
+
accessGroups: ['ct100-agents'],
|
|
39
|
+
}) {}
|
|
40
|
+
|
|
41
|
+
// A credential is the NAME of the environment variable the PROXY resolves in its own
|
|
42
|
+
// environment — never a value. A custom gateway goes through apiBase.
|
|
43
|
+
export class Canary extends LiteLLMModel('Canary', {
|
|
44
|
+
modelName: 'canary',
|
|
45
|
+
model: 'cloudflare/@cf/meta/llama-3.1-8b-instruct',
|
|
46
|
+
apiBase: 'https://gateway.example.com/v1',
|
|
47
|
+
apiKey: { fromEnv: 'CANARY_UPSTREAM_KEY' },
|
|
48
|
+
mode: 'chat',
|
|
49
|
+
baseModel: 'meta/llama-3.1-8b-instruct',
|
|
50
|
+
}) {}
|
|
51
|
+
|
|
52
|
+
// A pinned id adopts that exact row even when the name is shared by several deployments.
|
|
53
|
+
export class Pinned extends LiteLLMModel('Pinned', {
|
|
54
|
+
id: 'model-abc123',
|
|
55
|
+
modelName: 'grok',
|
|
56
|
+
model: 'xai/grok-4.7',
|
|
57
|
+
}) {}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then provide `litellmProviders()` alongside the stack's other providers.
|
|
61
|
+
|
|
62
|
+
## Behaviour
|
|
63
|
+
|
|
64
|
+
| Concern | Rule |
|
|
65
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| adopt | By `modelName` (the live row whose group name matches), or by `id` to pin one. A name matching more than one listed row is refused, never guessed at. A live row with no state needs `--adopt` — a same-named foreign row is never taken over silently (`LitellmModelForeignRowError`), and a config-file row (`db_model: false`) is refused even with `--adopt`. |
|
|
67
|
+
| default removal | `retain` — removal takes the deployment out of its routing group for every key that reaches it; opt in with `.pipe(RemovalPolicy.destroy())`. |
|
|
68
|
+
| rename | A changed `modelName` without a pinned `id` is a new group: the old row survives under `retain`, the new group gets a fresh id. With a pinned `id` the same row is renamed in place. |
|
|
69
|
+
| changed `id` | The id is identity: a different declared id is a `replace`, create-first, old row retained. |
|
|
70
|
+
| credential | `apiKey: { fromEnv: 'NAME' }` only — sent as LiteLLM's own `os.environ/NAME` reference, so the proxy resolves it in its own environment. Never a value (a value would land in Alchemy's unencrypted state). |
|
|
71
|
+
| omitted `apiKey` | An adopted row is never blanked: omitting `apiKey` sends no `api_key`, and the live credential stays. Declaring one is what moves the row onto `os.environ/NAME`. |
|
|
72
|
+
| encrypted params | The v1.103.0 read answers `litellm_params` DECRYPTED but STRIPS `api_key` (measured), so the comparison is the visible fields the read returns plus `paramsSeal`, a digest of the DECLARED values — never a digest of the live row. |
|
|
73
|
+
| update scope | Everything an existing row needs goes to ONE `PATCH /model/{id}/update` (`update_db_model` MERGES: sent keys land, the rest of the row survives). `POST /model/update` is never sent: v1.103.0 `update_model` REBUILDS `litellm_params` from the parsed request model and REPLACES the column, deleting a stored key the model does not declare (`extra_headers`, `weight`, `order`). |
|
|
74
|
+
| read-back | Every write is read back: a field the proxy did not apply fails the deploy (`LitellmModelNotConvergedError`) instead of claiming success. A row that cannot be found after a write fails it too. |
|
|
75
|
+
| delete | Idempotent by this resource, not by the vendor: a `BadRequest` re-lists, and only a genuinely-absent id counts as already deleted — a refused delete on a live row re-fails. |
|
|
76
|
+
| list | Empty: adoption is an explicit act (`--adopt`), and no ownership mark exists to filter by. |
|
|
77
|
+
|
|
78
|
+
A refused unpinned rename leaves a `replacing` row. Reverting the name finds this stack's own
|
|
79
|
+
still-serving deployment and refuses with `LitellmModelPriorGenerationError`: drop the state row
|
|
80
|
+
(`alchemy state rm`, which leaves the deployment) and deploy with `--adopt`. Do not delete the live
|
|
81
|
+
row — under `destroy()`, garbage collection deletes the old generation by that same id. A declared-id
|
|
82
|
+
replace whose read-back fails after `POST /model/new` resumes on the next deploy with `--adopt`.
|
|
83
|
+
|
|
84
|
+
## Refusals
|
|
85
|
+
|
|
86
|
+
A plan is refused, before any write, when the declaration:
|
|
87
|
+
|
|
88
|
+
- names `modelName` blank or carrying leading/trailing whitespace;
|
|
89
|
+
- declares `model` without a provider prefix (`xai/…`, `cloudflare/@cf/…`) — LiteLLM routes by it;
|
|
90
|
+
- declares `model` with the `openai/` prefix on a group whose name starts with `grok` — Grok is
|
|
91
|
+
xAI's, declare `xai/…`;
|
|
92
|
+
- declares `apiBase` carrying userinfo, a fragment or a secret-looking query parameter;
|
|
93
|
+
- declares `apiKey` as anything but `{ fromEnv: 'NAME' }`, or names a blank variable.
|
|
94
|
+
|
|
95
|
+
## Not measured at v1.103.0, guarded instead
|
|
96
|
+
|
|
97
|
+
- Whether `/model/new` honours a supplied `model_info.id`: the answer is an untyped body, so the id
|
|
98
|
+
asked for is the one tracked, and a read-back that cannot find it fails the deploy
|
|
99
|
+
(`LitellmModelAbsentAfterWriteError`) rather than recording a row it cannot identify.
|
|
100
|
+
- What the real proxy answers for a duplicate create and a delete of a missing id: the fake's 400s
|
|
101
|
+
there are its own choices. A PATCH of a missing id is HTTP 404 (`patch_model` at v1.103.0).
|
|
102
|
+
- The rest of `litellm_params` (rpm/tpm, timeouts, fallbacks, wildcard routing, …), cost fields on
|
|
103
|
+
`model_info` beyond what the read returns, `teams`, and config-file rows (`db_model: false`) are
|
|
104
|
+
not modelled on purpose. v1.103.0 fills every unset `litellm_params` field with its pydantic
|
|
105
|
+
default before a params write (`updateLiteLLMParams`): a `None` default keeps the stored value,
|
|
106
|
+
a non-`None` default overwrites it — so the PATCH sends `null` for `use_in_pass_through`,
|
|
107
|
+
`use_litellm_proxy`, `use_xai_oauth`, `allow_client_keepalive_override` and
|
|
108
|
+
`merge_reasoning_content_in_choices`, the five whose default is `false`. A bare matching adopt
|
|
109
|
+
records `paramsSeal` locally and writes nothing; an adopt whose declaration also manages
|
|
110
|
+
`api_key` or `api_base` PATCHes once, because those params never appear on a read and only a
|
|
111
|
+
write can converge them.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# LiteLLM — `LiteLLM.PolicyAttachment`
|
|
2
|
+
|
|
3
|
+
One row of LiteLLM's policy attachment table (`LiteLLM_PolicyAttachmentTable`, `/policies/attachments`): **where**
|
|
4
|
+
a [policy](./litellm-policy.md) applies, by team alias, key alias, model and tag. Typed calls come from
|
|
5
|
+
`@distilled.cloud/litellm/policy_engine`, generated from LiteLLM **v1.103.0**; credentials and the family's
|
|
6
|
+
other refusals are in [litellm.md](./litellm.md).
|
|
7
|
+
|
|
8
|
+
Everything marked "read" was read from the **live 1.103.0 container's source** on 2026-09-30 (no request was
|
|
9
|
+
sent to a proxy). The docs page is docs.litellm.ai/docs/proxy/guardrails/guardrail_policies.
|
|
10
|
+
|
|
11
|
+
## The distilled operations it uses
|
|
12
|
+
|
|
13
|
+
| step | operation (`@distilled.cloud/litellm/policy_engine`) | route |
|
|
14
|
+
| ------ | ------------------------------------------------------------- | ----------------------------------- |
|
|
15
|
+
| list | `listPolicyAttachmentsPoliciesAttachmentsListGet` | `GET /policies/attachments/list` |
|
|
16
|
+
| create | `createPolicyAttachmentPoliciesAttachmentsPost` | `POST /policies/attachments` |
|
|
17
|
+
| delete | `deletePolicyAttachmentPoliciesAttachmentsAttachmentIdDelete` | `DELETE /policies/attachments/{id}` |
|
|
18
|
+
|
|
19
|
+
There is **no update operation**, because the API has none (`policy_endpoints.py` has create, get, list and
|
|
20
|
+
delete). The by-id `GET` is not used: the list carries every field and answers absence without a 404.
|
|
21
|
+
|
|
22
|
+
## ⛔ An attachment with no selector is GLOBAL
|
|
23
|
+
|
|
24
|
+
A request gets the policy when it matches **all** of the attachment's selectors (`PolicyMatcher.scope_matches`,
|
|
25
|
+
`policy_matcher.py`). `PolicyScope.get_teams()`, `get_keys()` and `get_models()` each default to `["*"]` when
|
|
26
|
+
empty, and empty `tags` are simply not checked (`policy_types.py`). So an attachment that names only a policy
|
|
27
|
+
applies to **every request**, and `{ teams: ['x'] }` covers every key and every model of team x.
|
|
28
|
+
|
|
29
|
+
An attachment with no `scope`, `teams`, `keys`, `models` or `tags` is therefore **refused** unless `scope: '*'`
|
|
30
|
+
says a global attachment is meant.
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { LiteLLMPolicyAttachment, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
36
|
+
|
|
37
|
+
export class CodeOnly extends LiteLLMPolicyAttachment('CodeOnly', {
|
|
38
|
+
policyName: baseline.policyName, // a LiteLLM.Policy: the dependency edge is the Output
|
|
39
|
+
teams: [estate.teamAlias], // team ALIASES, from a LiteLLM.Team: it must exist before the attachment
|
|
40
|
+
models: ['cf-*'],
|
|
41
|
+
priority: 10,
|
|
42
|
+
}) {}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Teams, keys and models are matched by alias or model name with `*` wildcards (`prefix-*`). `tags` match
|
|
46
|
+
key and team `metadata.tags`. A `teams` entry is `LiteLLM.Team`'s `teamAlias`, **not its id**, and an alias is
|
|
47
|
+
not unique in LiteLLM, so two teams sharing one are both matched.
|
|
48
|
+
|
|
49
|
+
## Behaviour
|
|
50
|
+
|
|
51
|
+
| Concern | Rule |
|
|
52
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| Identity | **Content.** The id is the proxy's and there is no name, so two attachments are "the same" when the policy, scope, the four selector sets and the priority all are. |
|
|
54
|
+
| Adopt | A live attachment that equals the declaration, with no state, is `Unowned` and needs `--adopt`. Two identical live rows are refused (`LitellmRegistryAmbiguousError`), never guessed at. |
|
|
55
|
+
| Change | Always a **`replace`, create-first**: the new attachment is created, then the old one is deleted. For a moment both apply. Guardrails are additive, so that can only add enforcement, never drop it. |
|
|
56
|
+
| Removal | The **default** removal policy (`destroy`), deliberately not `retain`. Under `retain` a replace would leave the old attachment live beside the new one for good. An attachment holds no data. |
|
|
57
|
+
| Delete | Idempotent by a real read: a failed DELETE (a missing id is a 404) is swallowed only when the list no longer has the id. A 403 re-raises the original error. |
|
|
58
|
+
| Config file | Attachments defined in config.yaml (`definition_location: "config"`, ids `config-<n>`) can never be deleted through the API, so they are never listed, adopted or removed here. |
|
|
59
|
+
| Write check | Reconcile lists again and fails (`LitellmRegistryNotConvergedError` / `…AbsentAfterWriteError`) if the created attachment differs from the declaration or is not there. |
|
|
60
|
+
|
|
61
|
+
## What the proxy refuses, and this resource does not repeat
|
|
62
|
+
|
|
63
|
+
`create_policy_attachment` answers **404** for a policy with no production version, and **400** for a concrete
|
|
64
|
+
team, key or model selector that resolves to nothing (`PolicyValidator.find_invalid_scope_entries`); wildcard
|
|
65
|
+
patterns are let through and may match nothing today. Declaring `teams` from `team.teamAlias` gives the
|
|
66
|
+
dependency edge that makes the team exist first. Most other failures are a plain **500** (the handlers wrap
|
|
67
|
+
everything in `except Exception`), which the SDK retries with backoff.
|
|
68
|
+
|
|
69
|
+
## Refused before any request
|
|
70
|
+
|
|
71
|
+
A blank or padded `policyName`; a `scope` other than `'*'`; a blank, padded or repeated selector entry; no
|
|
72
|
+
selector at all (above); a `priority` that is not a 32-bit integer.
|
|
73
|
+
|
|
74
|
+
## Licence gating (measured)
|
|
75
|
+
|
|
76
|
+
None in the routes: `policy_endpoints.py`, `attachment_registry.py`, `policy_matcher.py` and
|
|
77
|
+
`policy_validator.py` have no `premium_user` reference (`grep -ci premium`: 0 in each). The docs page states
|
|
78
|
+
that "team/key-based policy attachment requires a LiteLLM Enterprise license". Whether that describes these
|
|
79
|
+
routes, or the gated `policies` metadata of a team or key, or request-time matching on a community proxy, is
|
|
80
|
+
**UNVERIFIED**: nothing was run, and the source shows no gate in the attachment path.
|
|
81
|
+
|
|
82
|
+
## Unmeasured at 1.103.0
|
|
83
|
+
|
|
84
|
+
No live proxy was contacted. The fake (`fake-policy-attachment-litellm.ts`) is built from the source; each
|
|
85
|
+
choice is in its header. The typed fix for this tag's undeclared 400, 404 and 500 answers is a distilled patch,
|
|
86
|
+
which belongs in the distilled clone and is not part of this change.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# LiteLLM — `LiteLLM.Policy`
|
|
2
|
+
|
|
3
|
+
The **production version** of one named policy in LiteLLM's policy engine (`LiteLLM_PolicyTable`,
|
|
4
|
+
`/policies`): guardrails to add, guardrails to remove from an inherited parent, and an optional model
|
|
5
|
+
condition. Where it applies is a [`LiteLLM.PolicyAttachment`](./litellm-policy-attachment.md). Typed calls
|
|
6
|
+
come from `@distilled.cloud/litellm/policy_engine`, generated from LiteLLM **v1.103.0**; credentials and
|
|
7
|
+
the family's other refusals are in [litellm.md](./litellm.md).
|
|
8
|
+
|
|
9
|
+
Everything marked "read" was read from the **live 1.103.0 container's source** on 2026-09-30 (no request was
|
|
10
|
+
sent to a proxy). The docs page is docs.litellm.ai/docs/proxy/guardrails/guardrail_policies.
|
|
11
|
+
|
|
12
|
+
## ⛔ A policy is versioned; this resource manages the production version
|
|
13
|
+
|
|
14
|
+
LiteLLM keeps every version (`draft` → `published` → `production`) and only **production** is enforced.
|
|
15
|
+
`POST /policies` creates version 1 **as production**, and `PUT /policies/{id}` edits a **draft only** (400
|
|
16
|
+
otherwise; `policy_endpoints.py`, `policy_registry.py`). So a change is four calls:
|
|
17
|
+
|
|
18
|
+
1. `POST /policies/name/{name}/versions` — a new draft cloned from production;
|
|
19
|
+
2. `PUT /policies/{draft}` — edit the fields that differ;
|
|
20
|
+
3. `PUT /policies/{draft}/status` `published`;
|
|
21
|
+
4. `PUT /policies/{draft}/status` `production` — which demotes the old production to `published`.
|
|
22
|
+
|
|
23
|
+
The old version stays as history. `policyId` in the attributes is the **production** version's id and changes
|
|
24
|
+
with every promotion. A run that stops midway leaves a draft or a published copy, both inert (drafts are not
|
|
25
|
+
loaded into memory); the next run starts again from production and **never touches a version it did not
|
|
26
|
+
make**, because promoting a person's draft would put their unmanaged fields into production.
|
|
27
|
+
(The docs page describes no version lifecycle at all; the source is authoritative.)
|
|
28
|
+
|
|
29
|
+
## The distilled operations it uses
|
|
30
|
+
|
|
31
|
+
| step | operation (`@distilled.cloud/litellm/policy_engine`) | route |
|
|
32
|
+
| -------- | ---------------------------------------------------------------- | ---------------------------------------------- |
|
|
33
|
+
| versions | `listPolicyVersionsPoliciesNamePolicyNameVersionsGet` | `GET /policies/name/{name}/versions` |
|
|
34
|
+
| config | `listPoliciesPoliciesListGet` | `GET /policies/list?version_status=production` |
|
|
35
|
+
| create | `createPolicyPoliciesPost` | `POST /policies` |
|
|
36
|
+
| draft | `createPolicyVersionPoliciesNamePolicyNameVersionsPost` | `POST /policies/name/{name}/versions` |
|
|
37
|
+
| edit | `updatePolicyPoliciesPolicyIdPut` | `PUT /policies/{id}` |
|
|
38
|
+
| status | `updatePolicyVersionStatusPoliciesPolicyIdStatusPut` | `PUT /policies/{id}/status` |
|
|
39
|
+
| delete | `deleteAllPolicyVersionsPoliciesNamePolicyNameAllVersionsDelete` | `DELETE /policies/name/{name}/all-versions` |
|
|
40
|
+
|
|
41
|
+
## Example
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { LiteLLMPolicy, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
45
|
+
|
|
46
|
+
export class Baseline extends LiteLLMPolicy('Baseline', {
|
|
47
|
+
policyName: 'estate-baseline',
|
|
48
|
+
description: 'Guardrails every estate request gets',
|
|
49
|
+
guardrailsAdd: ['pii-mask', 'prompt-injection'],
|
|
50
|
+
conditionModel: 'cf-*',
|
|
51
|
+
}) {}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Behaviour
|
|
55
|
+
|
|
56
|
+
| Concern | Rule |
|
|
57
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| Identity | `policyName`. A different name is a different policy: a `replace`, create-first, the old one retained. |
|
|
59
|
+
| Adopt | A live **production** version with no state is `Unowned` and needs `--adopt`; found by name. A matching one writes nothing. |
|
|
60
|
+
| Guardrails | `guardrailsAdd` and `guardrailsRemove` are always compared, as sets (default `[]`). |
|
|
61
|
+
| Other | `description`, `inherit` and `conditionModel` are compared **only when declared**. |
|
|
62
|
+
| Removal | `defaultRemovalPolicy: 'retain'`. Opt in with `RemovalPolicy.destroy()`. `delete` removes **every version** (`delete_many` by name) and answers 200 whether or not any existed. |
|
|
63
|
+
| Write check | Reconcile lists the versions again and fails (`LitellmRegistryNotConvergedError` / `…AbsentAfterWriteError`) if production does not match. |
|
|
64
|
+
|
|
65
|
+
⚠️ **A field cannot be cleared through the API.** `update_policy_in_db` writes only fields that are not `None`,
|
|
66
|
+
so removing `description`, `inherit` or `conditionModel` from the declaration leaves the live value (they are
|
|
67
|
+
"compared only when declared" for that reason). The two guardrail lists can be written as `[]`.
|
|
68
|
+
|
|
69
|
+
## Two guards on a create
|
|
70
|
+
|
|
71
|
+
- **A config.yaml policy is never shadowed by accident.** `/policies/list` merges DB rows with config policies
|
|
72
|
+
(`definition_location: "config"`), and a production DB policy of the same name **overrides** the config one at
|
|
73
|
+
runtime. A create for a name that belongs to a config policy is refused.
|
|
74
|
+
- **No production version is never guessed at.** A name whose versions are all `draft` or `published` (someone
|
|
75
|
+
demoted it, so it is inactive) is refused with the count, not promoted.
|
|
76
|
+
|
|
77
|
+
## Refused before any request
|
|
78
|
+
|
|
79
|
+
A blank or padded `policyName`; a blank `description`, `inherit` or `conditionModel`; `inherit` naming the policy
|
|
80
|
+
itself; a blank, padded or repeated guardrail; a guardrail both added and removed.
|
|
81
|
+
|
|
82
|
+
## Licence gating (measured)
|
|
83
|
+
|
|
84
|
+
None in the policy routes: `policy_endpoints.py`, `policy_registry.py`, `attachment_registry.py`,
|
|
85
|
+
`policy_matcher.py` and `policy_validator.py` have no `premium_user` reference (`grep -ci premium`: 0 in each).
|
|
86
|
+
The docs page says team/key-based policy attachment needs Enterprise; whether a community proxy enforces a
|
|
87
|
+
team-scoped attachment is **UNVERIFIED**. The `policies` and `guardrails` **metadata of a team or key IS
|
|
88
|
+
gated** (`_premium_user_check`, `litellm_pre_call_utils.py` lines 2949-3023 and `common_utils.py` line 464),
|
|
89
|
+
which is why `LiteLLM.Team` never sends them.
|
|
90
|
+
|
|
91
|
+
## Not modelled
|
|
92
|
+
|
|
93
|
+
`pipeline` (an ordered guardrail pipeline): a new version is cloned from production, so an adopted policy's
|
|
94
|
+
pipeline survives every update, and none is ever sent. Whether the named guardrails exist is not checked
|
|
95
|
+
(LiteLLM does not check it on write).
|
|
96
|
+
|
|
97
|
+
## Unmeasured at 1.103.0
|
|
98
|
+
|
|
99
|
+
No live proxy was contacted. The fake (`fake-policy-litellm.ts`) is built from the source; each choice is in its
|
|
100
|
+
header. Most failures of these routes are a plain **500** (the handlers wrap everything in `except Exception`),
|
|
101
|
+
and the SDK retries 5xx with backoff, so a genuine failure is slow to surface; nothing here reads the text of
|
|
102
|
+
one. The typed fix for this tag's undeclared 400 and 404 is a distilled patch, which belongs in the distilled
|
|
103
|
+
clone and is not part of this change.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# LiteLLM — `LiteLLM.Team`
|
|
2
|
+
|
|
3
|
+
One row of LiteLLM's team table (`LiteLLM_TeamTable`, `/team/*`): the **models, MCP grants and member roster**
|
|
4
|
+
that bound every key of a team. Typed calls come from `@distilled.cloud/litellm/team_management`, generated
|
|
5
|
+
from LiteLLM **v1.103.0**; credentials and the family's other refusals are in [litellm.md](./litellm.md).
|
|
6
|
+
|
|
7
|
+
Everything marked "read" was read from the **live 1.103.0 container's source** on 2026-09-30 (no request was
|
|
8
|
+
sent to a proxy). Docs: docs.litellm.ai/docs/mcp_grant_access, /docs/proxy/users.
|
|
9
|
+
|
|
10
|
+
## The distilled operations it uses
|
|
11
|
+
|
|
12
|
+
Nothing is hand-rolled: every call is one of these, through `team-operations.ts`.
|
|
13
|
+
|
|
14
|
+
| step | operation (`@distilled.cloud/litellm/team_management`) | route |
|
|
15
|
+
| ------ | ------------------------------------------------------ | ------------------------------------------- |
|
|
16
|
+
| read | `getTeamInfoTeamInfo` | `GET /team/info?team_id=` |
|
|
17
|
+
| create | `postNewTeamTeamNew` | `POST /team/new` |
|
|
18
|
+
| update | `updateTeamTeamUpdatePost` | `POST /team/update` |
|
|
19
|
+
| add | `teamMemberAddTeamMemberAddPost` | `POST /team/member_add` |
|
|
20
|
+
| role | `teamMemberUpdateTeamMemberUpdatePost` | `POST /team/member_update` |
|
|
21
|
+
| delete | `deleteTeamTeamDeletePost` | `POST /team/delete` |
|
|
22
|
+
| list | `listTeamTeamListGet` | `GET /team/list` (only to confirm a delete) |
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { LiteLLMTeam, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
28
|
+
|
|
29
|
+
export class Estate extends LiteLLMTeam('Estate', {
|
|
30
|
+
teamId: 'example-estate', // adopts the live team (needs --adopt)
|
|
31
|
+
teamAlias: 'example-estate',
|
|
32
|
+
models: ['cf-code', 'cf-flash'],
|
|
33
|
+
accessGroupIds: [group.accessGroupId],
|
|
34
|
+
objectPermission: {
|
|
35
|
+
mcpServers: [], // ⛔ see "the MCP ceiling": leave the team list empty
|
|
36
|
+
mcpToolsets: [web.toolsetId], // toolset IDS
|
|
37
|
+
blockedTools: ['estate_shell-run'],
|
|
38
|
+
},
|
|
39
|
+
members: [{ userId: 'tim' }],
|
|
40
|
+
}) {}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## ⛔ The team's `mcpServers` is a ceiling that intersects every key's grants
|
|
44
|
+
|
|
45
|
+
Where both a team and a key list servers, **the key reaches only the intersection** (`user_api_key_auth_mcp.py`
|
|
46
|
+
lines 1482-1503; docs.litellm.ai/docs/mcp_grant_access). "An empty set means this level does not restrict."
|
|
47
|
+
A NON-EMPTY team `mcpServers` therefore silently removes access from any key granted a server the team does
|
|
48
|
+
not list. The fix for a team that carries one is to declare `mcpServers: []` and grant servers on keys, on
|
|
49
|
+
toolsets (`mcpToolsets`) or through unified access groups. This resource does not decide that: it makes it a
|
|
50
|
+
reviewed line of source. `mcpToolPermissions`, `mcpToolsets` and `mcpAccessGroups` are separate fields with
|
|
51
|
+
their own rules (`mcp_grant_access`), and a toolset grant restricts even when it resolves to no servers.
|
|
52
|
+
|
|
53
|
+
## Behaviour
|
|
54
|
+
|
|
55
|
+
| Concern | Rule |
|
|
56
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| Identity | `teamId` (else a deterministic physical name for a create). `team_alias` is **not unique**, so it cannot find a team. A different declared id is a `replace`, create-first, the old team retained. |
|
|
58
|
+
| Adopt | A live team with that id and no state is `Unowned` and needs `--adopt`. A matching one writes nothing. |
|
|
59
|
+
| Declared = truth | `models`, `blocked`, `accessGroupIds`, each object-permission field and the roster are compared and sent **only when declared**. `teamAlias` is always compared. |
|
|
60
|
+
| Update | Partial (`data.json(exclude_unset=True)`, `team_endpoints.py` line 2418): only the team id and what differs. Nothing else about the team is sent. |
|
|
61
|
+
| Object permission | A **merge by top-level field** (`object_permission_utils.py` lines 114-118): a field left out keeps its value, `[]` replaces it. So five fields are managed and the other seven are left alone. |
|
|
62
|
+
| Removal | `defaultRemovalPolicy: 'retain'`. Opt in with `RemovalPolicy.destroy()`, which **deletes every key of the team** (below). |
|
|
63
|
+
| Write check | Reconcile reads back and fails (`LitellmRegistryNotConvergedError` / `…AbsentAfterWriteError`) if a declared field did not land or the team is not there. |
|
|
64
|
+
|
|
65
|
+
## The roster is additive only
|
|
66
|
+
|
|
67
|
+
⛔ `/team/member_delete` does not only take a user off the roster: it **deletes every virtual key that user
|
|
68
|
+
created for the team** (`team_endpoints.py` lines 3701-3748). A seat's key would vanish with a roster edit. So a
|
|
69
|
+
live member the declaration does not list is **never removed and never an error**, and a declared member with
|
|
70
|
+
another role is updated through `member_update`.
|
|
71
|
+
|
|
72
|
+
- Members are added by `userId` only, in **one** `member_add` call. Adding an id that has no user yet creates its
|
|
73
|
+
user row (`add_new_member`), and only PROXY_ADMIN may do that (`_validate_member_user_id_provisioning`).
|
|
74
|
+
- ⚠️ `/team/new` adds **the caller as an `admin` member** unless the proxy sets
|
|
75
|
+
`disable_auto_add_proxy_admin_to_teams` (`_should_auto_add_team_creator`, line 1352). That extra member is not
|
|
76
|
+
drift: undeclared members are ignored.
|
|
77
|
+
- `role: 'admin'` is **Enterprise** on both routes (`team_endpoints.py` lines 2713-2723 and 3806-3810): a proxy
|
|
78
|
+
without a licence answers 400, the deploy fails with it, and the roster stays as it was.
|
|
79
|
+
|
|
80
|
+
## Delete deletes the keys
|
|
81
|
+
|
|
82
|
+
⛔ `/team/delete` is "delete team and associated team keys" (`team_endpoints.py` line 4258, and
|
|
83
|
+
`delete_data(team_id_list=…, table_name="key")` at line 4374). It is the most destructive call in this family,
|
|
84
|
+
and the reason for `retain`. It is idempotent only after a read that cannot lie: the route's own 404 also covers a
|
|
85
|
+
database error (its lookup sits in `except Exception: raise HTTPException(404)`), so success is reported only
|
|
86
|
+
when `GET /team/list` no longer has the id. A team still listed re-raises the **original** error.
|
|
87
|
+
|
|
88
|
+
## Refused before any request
|
|
89
|
+
|
|
90
|
+
A blank or padded `teamAlias` or `teamId`; a blank, padded or repeated entry in `models`, `accessGroupIds` or any
|
|
91
|
+
object-permission list; the sentinel `all-proxy-mcpservers` in `mcpServers` (it grants **every** MCP server, and
|
|
92
|
+
only a proxy admin may, `object_permission_utils.py` line 480); a blank tool-permission key; a repeated or blank
|
|
93
|
+
`userId`; a role other than `user` or `admin`.
|
|
94
|
+
|
|
95
|
+
## Licence gating (measured)
|
|
96
|
+
|
|
97
|
+
`team_endpoints.py` has 19 `premium` references (`grep -ci premium`). The fields this resource **manages** are not
|
|
98
|
+
gated except `members[].role: 'admin'`. The gated ones are never sent: the premium metadata fields
|
|
99
|
+
(`disable_global_guardrails`, `guardrails`, `policies`, `tags`, `team_member_key_duration`, `prompts`, `logging`,
|
|
100
|
+
`secret_manager_settings`, `allowed_passthrough_routes`: a 403 when truthy, `_types.py` lines 4843-4853 and
|
|
101
|
+
`common_utils.py` lines 443-468) and `model_max_budget` (a 403, `common_utils.py` lines 73-86).
|
|
102
|
+
|
|
103
|
+
## Not modelled
|
|
104
|
+
|
|
105
|
+
Budgets and rate limits (a team's `max_budget` is a HARD cap that 429s every key: the 2026-09-28 incident, see
|
|
106
|
+
[litellm-budget.md](./litellm-budget.md)); `metadata`; `router_settings`; `model_aliases`; the organisation; the
|
|
107
|
+
Enterprise fields above; and the other `object_permission` fields (vector stores, agents, agent access groups,
|
|
108
|
+
`models`, search tools, `mcp_tool_search_enabled`, skills). None is ever sent, so an adopted team keeps them.
|
|
109
|
+
|
|
110
|
+
## Unmeasured at 1.103.0
|
|
111
|
+
|
|
112
|
+
No live proxy was contacted. The fake (`fake-team-litellm.ts`) is built from the source; each choice is in its
|
|
113
|
+
header. Not exercised: what `/team/info` returns for a team whose `object_permission_id` points at a missing row,
|
|
114
|
+
and how the SDK's `{ body: unknown }` typing meets the real answer (the tests pass through the real SDK decode).
|
|
115
|
+
`/team/info` declares no 404 in the SDK (create, update and delete do, by distilled patch), so its 404 is read by
|
|
116
|
+
class (`registry-support.ts`); the typed fix is a distilled patch, which belongs in the distilled clone and is not
|
|
117
|
+
part of this change.
|