@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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake of LiteLLM's `/policies` routes for tests — TEST ONLY. Skeleton and the rules every fake
|
|
3
|
+
* follows: fake-registry-base.ts. (The attachment routes are `fake-policy-attachment-litellm.ts`.)
|
|
4
|
+
*
|
|
5
|
+
* ★ MEASURED FROM THE LIVE 1.103.0 CONTAINER'S SOURCE (2026-09-30, `policy_endpoints.py` and
|
|
6
|
+
* `policy_registry.py`, read, not called): a create makes version 1 AS PRODUCTION, and a second create
|
|
7
|
+
* of the name is a 400; the version list is a `find_many` by name (an unknown name is an empty list,
|
|
8
|
+
* 200); a new version is a DRAFT cloned from production (404 when there is no production) with the
|
|
9
|
+
* next version number; `PUT /policies/{id}` edits a DRAFT only (400 otherwise, 404 missing) and writes
|
|
10
|
+
* only fields that are not null; `draft → published` and `published → production` (which demotes the
|
|
11
|
+
* old production to `published`) are the only promotions, and `draft → production` is a 400;
|
|
12
|
+
* `DELETE …/all-versions` answers 200 whether or not anything existed; the list merges in
|
|
13
|
+
* config.yaml policies (`definition_location: "config"`).
|
|
14
|
+
* ⚠️ NOT MODELLED: `pipeline`, the in-memory registry, `is_latest` bookkeeping beyond what a test reads.
|
|
15
|
+
*/
|
|
16
|
+
import { FAKE_KEY, type FakeRecord, json, recordingFetch } from './fake-registry-base.ts';
|
|
17
|
+
import type { Row } from './registry-support.ts';
|
|
18
|
+
|
|
19
|
+
export interface FakePolicyLitellm extends FakeRecord {
|
|
20
|
+
/** Every stored version, exactly as the proxy holds it. */
|
|
21
|
+
readonly versions: () => readonly Row[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FakePolicyOptions {
|
|
25
|
+
readonly masterKey?: string;
|
|
26
|
+
readonly seed?: readonly Row[];
|
|
27
|
+
/** Policies defined in config.yaml: they appear in `/policies/list` with `definition_location: config`. */
|
|
28
|
+
readonly configPolicies?: readonly string[];
|
|
29
|
+
/** A `PUT /policies/{id}` drops an empty list instead of writing it. */
|
|
30
|
+
readonly editIgnoresEmpty?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const policyRow = (fields: Row): Row => ({
|
|
34
|
+
condition: null,
|
|
35
|
+
description: null,
|
|
36
|
+
guardrails_add: [],
|
|
37
|
+
guardrails_remove: [],
|
|
38
|
+
inherit: null,
|
|
39
|
+
is_latest: true,
|
|
40
|
+
pipeline: null,
|
|
41
|
+
version_number: 1,
|
|
42
|
+
version_status: 'production',
|
|
43
|
+
...fields,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const startFakePolicyLitellm = (options: FakePolicyOptions = {}): FakePolicyLitellm => {
|
|
47
|
+
let rows: Row[] = (options.seed ?? []).map((row) => ({ ...row }));
|
|
48
|
+
let issued = 0;
|
|
49
|
+
const nextId = () => `FAKE-policy-${String((issued += 1)).padStart(4, '0')}`;
|
|
50
|
+
|
|
51
|
+
const record = recordingFetch(options.masterKey ?? FAKE_KEY, ({ body, request, url }) => {
|
|
52
|
+
const versionsOf = /^\/policies\/name\/([^/]+)\/versions$/.exec(url.pathname);
|
|
53
|
+
const allOf = /^\/policies\/name\/([^/]+)\/all-versions$/.exec(url.pathname);
|
|
54
|
+
const statusOf = /^\/policies\/([^/]+)\/status$/.exec(url.pathname);
|
|
55
|
+
const byId = /^\/policies\/([^/]+)$/.exec(url.pathname);
|
|
56
|
+
|
|
57
|
+
if (versionsOf !== null && request.method === 'GET') {
|
|
58
|
+
const name = decodeURIComponent(versionsOf[1] ?? '');
|
|
59
|
+
const found = rows.filter((row) => row['policy_name'] === name);
|
|
60
|
+
return json(200, { policy_name: name, total_count: found.length, versions: found });
|
|
61
|
+
}
|
|
62
|
+
if (url.pathname === '/policies/list' && request.method === 'GET') {
|
|
63
|
+
const only = url.searchParams.get('version_status');
|
|
64
|
+
const db = rows.filter((row) => only === null || row['version_status'] === only);
|
|
65
|
+
const held = new Set(
|
|
66
|
+
rows
|
|
67
|
+
.filter((row) => row['version_status'] === 'production')
|
|
68
|
+
.map((row) => row['policy_name']),
|
|
69
|
+
);
|
|
70
|
+
const config = (options.configPolicies ?? [])
|
|
71
|
+
.filter((name) => !held.has(name))
|
|
72
|
+
.map((name) =>
|
|
73
|
+
policyRow({ definition_location: 'config', policy_id: name, policy_name: name }),
|
|
74
|
+
);
|
|
75
|
+
return json(200, { policies: [...db, ...config], total_count: db.length + config.length });
|
|
76
|
+
}
|
|
77
|
+
if (url.pathname === '/policies' && request.method === 'POST') {
|
|
78
|
+
if (
|
|
79
|
+
rows.some(
|
|
80
|
+
(row) => row['policy_name'] === body['policy_name'] && row['version_number'] === 1,
|
|
81
|
+
)
|
|
82
|
+
) {
|
|
83
|
+
return json(400, {
|
|
84
|
+
detail: `Policy with name '${String(body['policy_name'])}' already exists`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const created = policyRow({
|
|
88
|
+
condition: body['condition'] ?? null,
|
|
89
|
+
description: body['description'] ?? null,
|
|
90
|
+
guardrails_add: body['guardrails_add'] ?? [],
|
|
91
|
+
guardrails_remove: body['guardrails_remove'] ?? [],
|
|
92
|
+
inherit: body['inherit'] ?? null,
|
|
93
|
+
policy_id: nextId(),
|
|
94
|
+
policy_name: body['policy_name'],
|
|
95
|
+
});
|
|
96
|
+
rows = [...rows, created];
|
|
97
|
+
return json(200, created);
|
|
98
|
+
}
|
|
99
|
+
if (versionsOf !== null && request.method === 'POST') {
|
|
100
|
+
const name = decodeURIComponent(versionsOf[1] ?? '');
|
|
101
|
+
const source = rows.find(
|
|
102
|
+
(row) => row['policy_name'] === name && row['version_status'] === 'production',
|
|
103
|
+
);
|
|
104
|
+
if (source === undefined) {
|
|
105
|
+
return json(404, { detail: `No production version found for policy '${name}'` });
|
|
106
|
+
}
|
|
107
|
+
const latest = Math.max(
|
|
108
|
+
...rows
|
|
109
|
+
.filter((row) => row['policy_name'] === name)
|
|
110
|
+
.map((row) => Number(row['version_number'])),
|
|
111
|
+
);
|
|
112
|
+
const draft = {
|
|
113
|
+
...source,
|
|
114
|
+
is_latest: true,
|
|
115
|
+
parent_version_id: source['policy_id'],
|
|
116
|
+
policy_id: nextId(),
|
|
117
|
+
version_number: latest + 1,
|
|
118
|
+
version_status: 'draft',
|
|
119
|
+
};
|
|
120
|
+
rows = [
|
|
121
|
+
...rows.map((row) => (row['policy_name'] === name ? { ...row, is_latest: false } : row)),
|
|
122
|
+
draft,
|
|
123
|
+
];
|
|
124
|
+
return json(200, draft);
|
|
125
|
+
}
|
|
126
|
+
if (statusOf !== null && request.method === 'PUT') {
|
|
127
|
+
const id = decodeURIComponent(statusOf[1] ?? '');
|
|
128
|
+
const row = rows.find((each) => each['policy_id'] === id);
|
|
129
|
+
if (row === undefined) return json(404, { detail: `Policy with ID ${id} not found` });
|
|
130
|
+
const target = body['version_status'];
|
|
131
|
+
if (target === 'published' && row['version_status'] !== 'draft') {
|
|
132
|
+
return json(400, { detail: 'Only draft versions can be published.' });
|
|
133
|
+
}
|
|
134
|
+
if (target === 'production' && row['version_status'] !== 'published') {
|
|
135
|
+
return json(400, {
|
|
136
|
+
detail: 'Cannot promote draft directly to production. Publish the version first.',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (target !== 'published' && target !== 'production')
|
|
140
|
+
return json(400, { detail: 'Invalid status' });
|
|
141
|
+
rows = rows.map((each) => {
|
|
142
|
+
if (each['policy_id'] === id) return { ...each, version_status: target };
|
|
143
|
+
return target === 'production' &&
|
|
144
|
+
each['policy_name'] === row['policy_name'] &&
|
|
145
|
+
each['version_status'] === 'production'
|
|
146
|
+
? { ...each, version_status: 'published' }
|
|
147
|
+
: each;
|
|
148
|
+
});
|
|
149
|
+
return json(
|
|
150
|
+
200,
|
|
151
|
+
rows.find((each) => each['policy_id'] === id),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (byId !== null && request.method === 'PUT') {
|
|
155
|
+
const id = decodeURIComponent(byId[1] ?? '');
|
|
156
|
+
const row = rows.find((each) => each['policy_id'] === id);
|
|
157
|
+
if (row === undefined) return json(404, { detail: `Policy with ID ${id} not found` });
|
|
158
|
+
if (row['version_status'] !== 'draft') {
|
|
159
|
+
return json(400, {
|
|
160
|
+
detail:
|
|
161
|
+
'Only draft versions can be updated. Publish or create a new version to change published/production.',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const patch = Object.fromEntries(
|
|
165
|
+
Object.entries(body).filter(
|
|
166
|
+
([, value]) =>
|
|
167
|
+
value !== null &&
|
|
168
|
+
!(options.editIgnoresEmpty === true && Array.isArray(value) && value.length === 0),
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
rows = rows.map((each) => (each['policy_id'] === id ? { ...each, ...patch } : each));
|
|
172
|
+
return json(
|
|
173
|
+
200,
|
|
174
|
+
rows.find((each) => each['policy_id'] === id),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (allOf !== null && request.method === 'DELETE') {
|
|
178
|
+
const name = decodeURIComponent(allOf[1] ?? '');
|
|
179
|
+
rows = rows.filter((row) => row['policy_name'] !== name);
|
|
180
|
+
return json(200, { message: `All versions of policy '${name}' deleted successfully` });
|
|
181
|
+
}
|
|
182
|
+
return json(404, { detail: 'not found' });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return { ...record, versions: () => rows.map((row) => ({ ...row })) };
|
|
186
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared skeleton of the registry fakes (`fake-team-litellm.ts`, `fake-access-group-litellm.ts`,
|
|
3
|
+
* `fake-toolset-litellm.ts`, `fake-policy-litellm.ts`, `fake-tool-litellm.ts`) — TEST ONLY, never
|
|
4
|
+
* imported by `index.ts`.
|
|
5
|
+
*
|
|
6
|
+
* ★ Like `fake-litellm.ts` this is a `fetch` function handed to Effect's real `FetchHttpClient`
|
|
7
|
+
* through its `Fetch` reference, so a test exercises distilled's REAL path assembly, JSON encoding
|
|
8
|
+
* and status-to-error matching. Only the wire responses a scenario needs are written by each fake.
|
|
9
|
+
* ★ ONE FAKE PER ROUTE FAMILY, each small, and this file is the part they would otherwise repeat:
|
|
10
|
+
* the bearer check, the request log, and the body log a test reads to see what was SENT.
|
|
11
|
+
* ⚠️ EVERY FAKE'S ANSWERS ARE ITS OWN CHOICE UNLESS ITS HEADER SAYS "MEASURED": shapes come from the
|
|
12
|
+
* generated 1.103.0 schema and from reading the 1.103.0 source, never from a live proxy. A test that
|
|
13
|
+
* leans on a choice says so.
|
|
14
|
+
* ⚠️ NEVER ANSWER 5xx. The SDK retries 5xx with backoff, which would cost a test tens of seconds for
|
|
15
|
+
* nothing; a failing write is a 400/403/409 here.
|
|
16
|
+
* ★ `FAKE-*` VALUES ONLY.
|
|
17
|
+
*/
|
|
18
|
+
import type { Row } from './registry-support.ts';
|
|
19
|
+
|
|
20
|
+
/** One request that reached a fake. */
|
|
21
|
+
export interface FakeRequest {
|
|
22
|
+
readonly method: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** What every registry fake exposes besides its own rows. */
|
|
27
|
+
export interface FakeRecord {
|
|
28
|
+
readonly fetch: typeof globalThis.fetch;
|
|
29
|
+
readonly requests: () => readonly FakeRequest[];
|
|
30
|
+
/** The JSON bodies of every request that carried one, in order (`POST`/`PUT`/`PATCH`/`DELETE`). */
|
|
31
|
+
readonly bodies: () => readonly Row[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const FAKE_KEY = 'sk-test-master';
|
|
35
|
+
|
|
36
|
+
export const json = (status: number, body: unknown): Response =>
|
|
37
|
+
new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' }, status });
|
|
38
|
+
|
|
39
|
+
/** A success with no body, as `DELETE` answers 202/204 on several routes. */
|
|
40
|
+
export const empty = (status: number): Response => new Response(null, { status });
|
|
41
|
+
|
|
42
|
+
export interface Call {
|
|
43
|
+
readonly request: Request;
|
|
44
|
+
readonly url: URL;
|
|
45
|
+
/** The parsed JSON body, or `{}` when the request had none. */
|
|
46
|
+
readonly body: Row;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const recordingFetch = (
|
|
50
|
+
masterKey: string,
|
|
51
|
+
route: (call: Call) => Response | Promise<Response>,
|
|
52
|
+
): FakeRecord => {
|
|
53
|
+
const requests: FakeRequest[] = [];
|
|
54
|
+
const bodies: Row[] = [];
|
|
55
|
+
const fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
56
|
+
const request =
|
|
57
|
+
input instanceof Request ? new Request(input, init) : new Request(String(input), init);
|
|
58
|
+
const url = new URL(request.url);
|
|
59
|
+
requests.push({ method: request.method, path: `${url.pathname}${url.search}` });
|
|
60
|
+
if (request.headers.get('authorization') !== `Bearer ${masterKey}`) {
|
|
61
|
+
return json(401, { detail: 'invalid api key' });
|
|
62
|
+
}
|
|
63
|
+
const text = request.method === 'GET' ? '' : await request.text();
|
|
64
|
+
const body = text.trim() === '' ? {} : (JSON.parse(text) as Row);
|
|
65
|
+
if (text.trim() !== '') bodies.push(body);
|
|
66
|
+
return route({ body, request, url });
|
|
67
|
+
}) as typeof globalThis.fetch;
|
|
68
|
+
return { bodies: () => [...bodies], fetch, requests: () => [...requests] };
|
|
69
|
+
};
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
* files, no file logger. Just the provider, an in-memory state store, and the stack's services.
|
|
14
14
|
*/
|
|
15
15
|
import { credentials } from '@distilled.cloud/litellm/Credentials';
|
|
16
|
+
import { Retry } from '@distilled.cloud/litellm/Retry';
|
|
16
17
|
import * as Alchemy from 'alchemy';
|
|
17
18
|
import { AdoptPolicy } from 'alchemy/AdoptPolicy';
|
|
18
19
|
import { provideFreshArtifactStore } from 'alchemy/Artifacts';
|
|
20
|
+
import { encodeState } from 'alchemy/State/StateEncoding';
|
|
19
21
|
import * as Effect from 'effect/Effect';
|
|
20
22
|
import * as Layer from 'effect/Layer';
|
|
21
23
|
import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
|
|
@@ -33,6 +35,11 @@ export type DeployOptions = { readonly adopt?: boolean };
|
|
|
33
35
|
export interface FakeStack {
|
|
34
36
|
/** Plan, then apply, `body`. Resolves to the plan; rejects with the apply's failure. */
|
|
35
37
|
readonly deploy: (body: StackBody, options?: DeployOptions) => Promise<Planned>;
|
|
38
|
+
/**
|
|
39
|
+
* Everything the state store holds, as JSON, `Redacted` values REVEALED the way Alchemy persists
|
|
40
|
+
* them (`encodeState`). A test asserts a secret is nowhere in it.
|
|
41
|
+
*/
|
|
42
|
+
readonly snapshot: () => string;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
type Node = { readonly action: string };
|
|
@@ -66,8 +73,10 @@ export const fakeStack = (
|
|
|
66
73
|
creds: { readonly apiKey: string; readonly baseUrl: string },
|
|
67
74
|
fetchFn: typeof globalThis.fetch,
|
|
68
75
|
name = 'PassThroughStack',
|
|
76
|
+
settings: { readonly noRetry?: boolean } = {},
|
|
69
77
|
): FakeStack => {
|
|
70
|
-
const
|
|
78
|
+
const backing: Parameters<typeof Alchemy.inMemoryState>[0] = {};
|
|
79
|
+
const state = Alchemy.inMemoryState(backing);
|
|
71
80
|
const providers = litellmProviders(credentials(creds));
|
|
72
81
|
// ⚠️ THE CAST STAYS. `StackBody` is `Effect<unknown, unknown, unknown>` so a test can pass any
|
|
73
82
|
// declaration, and that channel is not a legal `Alchemy.Stack` body. The uncast construction
|
|
@@ -83,6 +92,11 @@ export const fakeStack = (
|
|
|
83
92
|
) => Effect.Effect<PlanView, unknown, never>;
|
|
84
93
|
const apply = Alchemy.apply as unknown as (planned: PlanView) => Effect.Effect<unknown, unknown>;
|
|
85
94
|
|
|
95
|
+
// ★ `noRetry` IS FOR A TEST OF A TRANSIENT FAILURE: the SDK's default policy would sit out its
|
|
96
|
+
// backoff before the failure the test wants to see propagates.
|
|
97
|
+
const retrying: <A, E, R>(io: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> =
|
|
98
|
+
settings.noRetry === true ? Effect.provideService(Retry, { while: () => false }) : (io) => io;
|
|
99
|
+
|
|
86
100
|
const deploy = (body: StackBody, options: DeployOptions = {}) =>
|
|
87
101
|
Effect.gen(function* () {
|
|
88
102
|
const compiled = yield* stack(name, { providers, state }, body);
|
|
@@ -94,6 +108,7 @@ export const fakeStack = (
|
|
|
94
108
|
}).pipe(
|
|
95
109
|
options.adopt === undefined ? (e) => e : Effect.provideService(AdoptPolicy, options.adopt),
|
|
96
110
|
Effect.provideService(Alchemy.Stage, 'test'),
|
|
111
|
+
retrying,
|
|
97
112
|
Effect.provide(FetchHttpClient.layer),
|
|
98
113
|
Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)),
|
|
99
114
|
Effect.scoped,
|
|
@@ -101,6 +116,7 @@ export const fakeStack = (
|
|
|
101
116
|
|
|
102
117
|
return {
|
|
103
118
|
deploy: (body, options) => Effect.runPromise(deploy(body, options) as Effect.Effect<Planned>),
|
|
119
|
+
snapshot: () => JSON.stringify(encodeState(backing)),
|
|
104
120
|
};
|
|
105
121
|
};
|
|
106
122
|
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake of LiteLLM's `/team/*` routes for tests — TEST ONLY. Skeleton and the rules every fake
|
|
3
|
+
* follows: fake-registry-base.ts.
|
|
4
|
+
*
|
|
5
|
+
* ★ MEASURED FROM THE LIVE 1.103.0 CONTAINER'S SOURCE (2026-09-30, `team_endpoints.py`,
|
|
6
|
+
* `object_permission_utils.py`, read, not called): `/team/new` refuses a taken id (400) and ADDS THE
|
|
7
|
+
* CALLER AS AN `admin` MEMBER (`_should_auto_add_team_creator`), which `autoAddsCreator` models;
|
|
8
|
+
* `/team/update` is partial (`exclude_unset`) and merges `object_permission` BY TOP-LEVEL FIELD (a
|
|
9
|
+
* field left out keeps its value, `[]` replaces it); `/team/info` answers a missing team 404 in the
|
|
10
|
+
* `ProxyException` envelope; `member_add` and `member_update` refuse an `admin` role on a proxy
|
|
11
|
+
* without a licence (400, `community`, the default); `/team/delete` 404s a missing team and DELETES THE
|
|
12
|
+
* TEAM'S KEYS (`deletedKeys` records that); `/team/member_delete` is not modelled because nothing here
|
|
13
|
+
* may call it, and any request to it is recorded so a test can assert none was made.
|
|
14
|
+
* ★ ALSO MEASURED: `/team/info` and `/team/delete` answer 404 for ANY exception on the lookup, database
|
|
15
|
+
* errors included, which `infoLies` and `deleteLies` model (the team exists and the answer is 404).
|
|
16
|
+
* ⚠️ NOT MODELLED: budgets, metadata, the Enterprise fields, `access_group` mirroring into groups.
|
|
17
|
+
*/
|
|
18
|
+
import { FAKE_KEY, type FakeRecord, json, recordingFetch } from './fake-registry-base.ts';
|
|
19
|
+
import type { Row } from './registry-support.ts';
|
|
20
|
+
|
|
21
|
+
export interface FakeTeamLitellm extends FakeRecord {
|
|
22
|
+
/** Every stored team, exactly as the proxy holds it (`object_permission` included). */
|
|
23
|
+
readonly teams: () => readonly Row[];
|
|
24
|
+
/** Team ids whose keys a `/team/delete` removed, in order. */
|
|
25
|
+
readonly deletedKeys: () => readonly string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FakeTeamOptions {
|
|
29
|
+
readonly masterKey?: string;
|
|
30
|
+
readonly seed?: readonly Row[];
|
|
31
|
+
/** No licence: an `admin` role is a 400. Default `true`. */
|
|
32
|
+
readonly community?: boolean;
|
|
33
|
+
/** `/team/new` adds the caller as an admin member. Default `true` (measured). */
|
|
34
|
+
readonly autoAddsCreator?: boolean;
|
|
35
|
+
/** `/team/info` answers 404 whether or not the team exists (a swallowed database error). */
|
|
36
|
+
readonly infoLies?: boolean;
|
|
37
|
+
/** `/team/delete` answers 404 whether or not the team exists. */
|
|
38
|
+
readonly deleteLies?: boolean;
|
|
39
|
+
/** `mcp_tool_permissions` comes back as a JSON string, as the source says it can. */
|
|
40
|
+
readonly toolPermissionsAsString?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* `/team/info` also carries what a real one does and a resource must never copy: the team's KEYS
|
|
43
|
+
* (`FAKE-hashed-key`), its memberships, and metadata that can hold callback variables.
|
|
44
|
+
*/
|
|
45
|
+
readonly leaky?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const teamRow = (fields: Row): Row => ({
|
|
49
|
+
access_group_ids: [],
|
|
50
|
+
blocked: false,
|
|
51
|
+
members_with_roles: [],
|
|
52
|
+
models: [],
|
|
53
|
+
object_permission: null,
|
|
54
|
+
...fields,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const CALLER = 'default_user_id';
|
|
58
|
+
|
|
59
|
+
const notFound = (id: string) =>
|
|
60
|
+
json(404, {
|
|
61
|
+
error: {
|
|
62
|
+
code: '404',
|
|
63
|
+
message: `Team not found, passed team id: ${id}.`,
|
|
64
|
+
param: 'None',
|
|
65
|
+
type: 'auth_error',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** `{**existing, **sent}`: the merge by top-level field `prepare_object_permission_upsert` does. */
|
|
70
|
+
const mergePermission = (existing: unknown, sent: unknown): Row => ({
|
|
71
|
+
...(existing as Row | null),
|
|
72
|
+
...(sent as Row | null),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const startFakeTeamLitellm = (options: FakeTeamOptions = {}): FakeTeamLitellm => {
|
|
76
|
+
let rows: Row[] = (options.seed ?? []).map((row) => ({ ...row }));
|
|
77
|
+
const deletedKeys: string[] = [];
|
|
78
|
+
const community = options.community ?? true;
|
|
79
|
+
|
|
80
|
+
const info = (row: Row): Row => {
|
|
81
|
+
const permission = row['object_permission'] as Row | null;
|
|
82
|
+
const shown =
|
|
83
|
+
permission !== null &&
|
|
84
|
+
options.toolPermissionsAsString === true &&
|
|
85
|
+
permission['mcp_tool_permissions'] !== undefined
|
|
86
|
+
? {
|
|
87
|
+
...permission,
|
|
88
|
+
mcp_tool_permissions: JSON.stringify(permission['mcp_tool_permissions']),
|
|
89
|
+
}
|
|
90
|
+
: permission;
|
|
91
|
+
return {
|
|
92
|
+
keys: [],
|
|
93
|
+
team_id: row['team_id'],
|
|
94
|
+
team_info: { ...row, object_permission: shown },
|
|
95
|
+
team_memberships: [],
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const record = recordingFetch(options.masterKey ?? FAKE_KEY, ({ body, request, url }) => {
|
|
100
|
+
if (url.pathname === '/team/info' && request.method === 'GET') {
|
|
101
|
+
const id = url.searchParams.get('team_id') ?? '';
|
|
102
|
+
const row = rows.find((each) => each['team_id'] === id);
|
|
103
|
+
return row === undefined || options.infoLies === true ? notFound(id) : json(200, info(row));
|
|
104
|
+
}
|
|
105
|
+
if (url.pathname === '/team/list' && request.method === 'GET') return json(200, rows);
|
|
106
|
+
if (url.pathname === '/team/new' && request.method === 'POST') {
|
|
107
|
+
const id = String(body['team_id']);
|
|
108
|
+
if (rows.some((row) => row['team_id'] === id)) {
|
|
109
|
+
return json(400, {
|
|
110
|
+
detail: { error: `Team id = ${id} already exists. Please use a different team id.` },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const members =
|
|
114
|
+
options.autoAddsCreator === false
|
|
115
|
+
? []
|
|
116
|
+
: [{ role: 'admin', user_email: null, user_id: CALLER }];
|
|
117
|
+
const { object_permission: permission, ...rest } = body;
|
|
118
|
+
rows = [
|
|
119
|
+
...rows,
|
|
120
|
+
teamRow({
|
|
121
|
+
...rest,
|
|
122
|
+
members_with_roles: members,
|
|
123
|
+
object_permission: permission === undefined ? null : mergePermission(null, permission),
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
return json(200, rows.at(-1));
|
|
127
|
+
}
|
|
128
|
+
if (url.pathname === '/team/update' && request.method === 'POST') {
|
|
129
|
+
const at = rows.findIndex((row) => row['team_id'] === body['team_id']);
|
|
130
|
+
if (at === -1)
|
|
131
|
+
return json(404, {
|
|
132
|
+
detail: { error: `Team not found, passed team_id=${String(body['team_id'])}` },
|
|
133
|
+
});
|
|
134
|
+
const { object_permission: permission, team_id: _id, ...rest } = body;
|
|
135
|
+
rows = rows.map((row, i) =>
|
|
136
|
+
i === at
|
|
137
|
+
? {
|
|
138
|
+
...row,
|
|
139
|
+
...rest,
|
|
140
|
+
...(permission === undefined
|
|
141
|
+
? {}
|
|
142
|
+
: { object_permission: mergePermission(row['object_permission'], permission) }),
|
|
143
|
+
}
|
|
144
|
+
: row,
|
|
145
|
+
);
|
|
146
|
+
return json(200, { data: rows[at], team_id: body['team_id'] });
|
|
147
|
+
}
|
|
148
|
+
if (url.pathname === '/team/member_add' && request.method === 'POST') {
|
|
149
|
+
const at = rows.findIndex((row) => row['team_id'] === body['team_id']);
|
|
150
|
+
if (at === -1) return json(404, { detail: { error: 'Team not found' } });
|
|
151
|
+
const wanted = (Array.isArray(body['member']) ? body['member'] : [body['member']]) as Row[];
|
|
152
|
+
if (community && wanted.some((member) => member['role'] === 'admin')) {
|
|
153
|
+
return json(400, {
|
|
154
|
+
detail: {
|
|
155
|
+
error:
|
|
156
|
+
'Assigning team admins is a premium feature. You must be a LiteLLM Enterprise user',
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const roster = rows[at]?.['members_with_roles'] as Row[];
|
|
161
|
+
const fresh = wanted.filter(
|
|
162
|
+
(member) => !roster.some((each) => each['user_id'] === member['user_id']),
|
|
163
|
+
);
|
|
164
|
+
if (fresh.length === 0)
|
|
165
|
+
return json(400, { error: { message: 'All users are already in team.' } });
|
|
166
|
+
rows = rows.map((row, i) =>
|
|
167
|
+
i === at
|
|
168
|
+
? {
|
|
169
|
+
...row,
|
|
170
|
+
members_with_roles: [
|
|
171
|
+
...roster,
|
|
172
|
+
...fresh.map((member) => ({
|
|
173
|
+
role: member['role'],
|
|
174
|
+
user_email: null,
|
|
175
|
+
user_id: member['user_id'],
|
|
176
|
+
})),
|
|
177
|
+
],
|
|
178
|
+
}
|
|
179
|
+
: row,
|
|
180
|
+
);
|
|
181
|
+
return json(200, rows[at]);
|
|
182
|
+
}
|
|
183
|
+
if (url.pathname === '/team/member_update' && request.method === 'POST') {
|
|
184
|
+
const at = rows.findIndex((row) => row['team_id'] === body['team_id']);
|
|
185
|
+
if (community && body['role'] === 'admin') {
|
|
186
|
+
return json(
|
|
187
|
+
400,
|
|
188
|
+
'Assigning team admins is a premium feature. You must be a LiteLLM Enterprise user',
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const roster = (rows[at]?.['members_with_roles'] as Row[] | undefined) ?? [];
|
|
192
|
+
if (at === -1 || !roster.some((each) => each['user_id'] === body['user_id'])) {
|
|
193
|
+
return json(404, {
|
|
194
|
+
detail: { error: `User ${String(body['user_id'])} is not a member of team` },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
rows = rows.map((row, i) =>
|
|
198
|
+
i === at
|
|
199
|
+
? {
|
|
200
|
+
...row,
|
|
201
|
+
members_with_roles: roster.map((each) =>
|
|
202
|
+
each['user_id'] === body['user_id'] ? { ...each, role: body['role'] } : each,
|
|
203
|
+
),
|
|
204
|
+
}
|
|
205
|
+
: row,
|
|
206
|
+
);
|
|
207
|
+
return json(200, { team_id: body['team_id'] });
|
|
208
|
+
}
|
|
209
|
+
if (url.pathname === '/team/delete' && request.method === 'POST') {
|
|
210
|
+
const ids = (body['team_ids'] as string[] | undefined) ?? [];
|
|
211
|
+
const missing = ids.find((id) => !rows.some((row) => row['team_id'] === id));
|
|
212
|
+
if (missing !== undefined || options.deleteLies === true) {
|
|
213
|
+
return json(404, {
|
|
214
|
+
detail: { error: `Team not found, passed team_id=${missing ?? ids[0] ?? ''}` },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
deletedKeys.push(...ids);
|
|
218
|
+
rows = rows.filter((row) => !ids.includes(String(row['team_id'])));
|
|
219
|
+
return json(200, {});
|
|
220
|
+
}
|
|
221
|
+
return json(404, { detail: 'not found' });
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
...record,
|
|
226
|
+
deletedKeys: () => [...deletedKeys],
|
|
227
|
+
teams: () => rows.map((row) => ({ ...row })),
|
|
228
|
+
};
|
|
229
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake of LiteLLM's `/v1/tool/{tool_name}` and `/v1/tool/policy` routes for tests — TEST ONLY.
|
|
3
|
+
* Skeleton and the rules every fake follows: fake-registry-base.ts.
|
|
4
|
+
*
|
|
5
|
+
* ★ MEASURED FROM THE LIVE 1.103.0 CONTAINER'S SOURCE (2026-09-30, `tool_management_endpoints.py` and
|
|
6
|
+
* `db/tool_registry_writer.py`, read, not called): the write is an UPSERT on `tool_name` (a new row
|
|
7
|
+
* takes a sent field, else `untrusted`; an existing row takes only the fields sent); neither field
|
|
8
|
+
* sent is a 400; a team or key id turns the write into an override of `blocked_tools`, which this
|
|
9
|
+
* fake does not model and answers 400 to (nothing here sends one); the read of an unknown tool is a
|
|
10
|
+
* 404; there is NO delete route for a row. `getSwallowsErrors` models `db_get_tool` catching every
|
|
11
|
+
* exception and answering `None`, which the route turns into a 404 for a tool that exists.
|
|
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 FakeToolLitellm extends FakeRecord {
|
|
17
|
+
readonly tools: () => readonly Row[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FakeToolOptions {
|
|
21
|
+
readonly masterKey?: string;
|
|
22
|
+
readonly seed?: readonly Row[];
|
|
23
|
+
/** The by-name `GET` answers 404 whatever exists (a swallowed database error). */
|
|
24
|
+
readonly getSwallowsErrors?: boolean;
|
|
25
|
+
/** `POST /v1/tool/policy` ignores an `output_policy` (a proxy that drops one field). */
|
|
26
|
+
readonly dropsOutputPolicy?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const toolRow = (fields: Row): Row => ({
|
|
30
|
+
call_count: 0,
|
|
31
|
+
input_policy: 'untrusted',
|
|
32
|
+
output_policy: 'untrusted',
|
|
33
|
+
...fields,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const startFakeToolLitellm = (options: FakeToolOptions = {}): FakeToolLitellm => {
|
|
37
|
+
let rows: Row[] = (options.seed ?? []).map((row) => ({ ...row }));
|
|
38
|
+
let issued = 0;
|
|
39
|
+
|
|
40
|
+
const record = recordingFetch(options.masterKey ?? FAKE_KEY, ({ body, request, url }) => {
|
|
41
|
+
if (url.pathname === '/v1/tool/policy' && request.method === 'POST') {
|
|
42
|
+
if (body['team_id'] !== undefined || body['key_hash'] !== undefined) {
|
|
43
|
+
return json(400, { detail: 'overrides are not modelled by this fake' });
|
|
44
|
+
}
|
|
45
|
+
if (body['input_policy'] === undefined && body['output_policy'] === undefined) {
|
|
46
|
+
return json(400, {
|
|
47
|
+
detail: 'At least one of input_policy or output_policy must be provided',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const name = String(body['tool_name']);
|
|
51
|
+
const sent = Object.fromEntries(
|
|
52
|
+
Object.entries({
|
|
53
|
+
input_policy: body['input_policy'],
|
|
54
|
+
output_policy: body['output_policy'],
|
|
55
|
+
}).filter(
|
|
56
|
+
([key, value]) =>
|
|
57
|
+
value !== undefined && !(options.dropsOutputPolicy === true && key === 'output_policy'),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
const at = rows.findIndex((row) => row['tool_name'] === name);
|
|
61
|
+
if (at === -1) {
|
|
62
|
+
issued += 1;
|
|
63
|
+
rows = [
|
|
64
|
+
...rows,
|
|
65
|
+
toolRow({
|
|
66
|
+
tool_id: `FAKE-tool-${String(issued).padStart(4, '0')}`,
|
|
67
|
+
tool_name: name,
|
|
68
|
+
...sent,
|
|
69
|
+
}),
|
|
70
|
+
];
|
|
71
|
+
} else {
|
|
72
|
+
rows = rows.map((row, i) => (i === at ? { ...row, ...sent } : row));
|
|
73
|
+
}
|
|
74
|
+
return json(200, { tool_name: name, updated: true, ...sent });
|
|
75
|
+
}
|
|
76
|
+
const byName = /^\/v1\/tool\/(.+)$/.exec(url.pathname);
|
|
77
|
+
if (byName !== null && request.method === 'GET') {
|
|
78
|
+
const name = decodeURIComponent(byName[1] ?? '');
|
|
79
|
+
const found = rows.find((row) => row['tool_name'] === name);
|
|
80
|
+
return found === undefined || options.getSwallowsErrors === true
|
|
81
|
+
? json(404, { detail: `Tool '${name}' not found` })
|
|
82
|
+
: json(200, found);
|
|
83
|
+
}
|
|
84
|
+
return json(404, { detail: 'not found' });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return { ...record, tools: () => rows.map((row) => ({ ...row })) };
|
|
88
|
+
};
|