@antseed/cli 0.1.179 → 0.1.181
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 +5 -3
- package/dist/buyer-identities/store.d.ts +39 -2
- package/dist/buyer-identities/store.d.ts.map +1 -1
- package/dist/buyer-identities/store.js +99 -10
- package/dist/buyer-identities/store.js.map +1 -1
- package/dist/cli/commands/apps/index.js +8 -8
- package/dist/cli/commands/buyer/identity.d.ts +3 -1
- package/dist/cli/commands/buyer/identity.d.ts.map +1 -1
- package/dist/cli/commands/buyer/identity.js +55 -8
- package/dist/cli/commands/buyer/identity.js.map +1 -1
- package/dist/cli/commands/buyer/identity.test.d.ts +2 -0
- package/dist/cli/commands/buyer/identity.test.d.ts.map +1 -0
- package/dist/cli/commands/buyer/identity.test.js +168 -0
- package/dist/cli/commands/buyer/identity.test.js.map +1 -0
- package/dist/cli/commands/buyer/set-authorized-wallet.d.ts +10 -0
- package/dist/cli/commands/buyer/set-authorized-wallet.d.ts.map +1 -1
- package/dist/cli/commands/buyer/set-authorized-wallet.js +26 -4
- package/dist/cli/commands/buyer/set-authorized-wallet.js.map +1 -1
- package/dist/cli/commands/buyer/set-authorized-wallet.test.js +37 -2
- package/dist/cli/commands/buyer/set-authorized-wallet.test.js.map +1 -1
- package/dist/cli/commands/buyer/start.d.ts +15 -0
- package/dist/cli/commands/buyer/start.d.ts.map +1 -1
- package/dist/cli/commands/buyer/start.js +158 -49
- package/dist/cli/commands/buyer/start.js.map +1 -1
- package/dist/cli/commands/buyer/start.test.js +16 -1
- package/dist/cli/commands/buyer/start.test.js.map +1 -1
- package/dist/cli/commands/chat.d.ts +3 -0
- package/dist/cli/commands/chat.d.ts.map +1 -0
- package/dist/cli/commands/chat.js +189 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/gateway/activity.d.ts +15 -0
- package/dist/cli/commands/gateway/activity.d.ts.map +1 -0
- package/dist/cli/commands/gateway/activity.js +233 -0
- package/dist/cli/commands/gateway/activity.js.map +1 -0
- package/dist/cli/commands/gateway/admin-token.d.ts +3 -0
- package/dist/cli/commands/gateway/admin-token.d.ts.map +1 -0
- package/dist/cli/commands/gateway/admin-token.js +90 -0
- package/dist/cli/commands/gateway/admin-token.js.map +1 -0
- package/dist/cli/commands/gateway/console-link.d.ts +20 -0
- package/dist/cli/commands/gateway/console-link.d.ts.map +1 -0
- package/dist/cli/commands/gateway/console-link.js +92 -0
- package/dist/cli/commands/gateway/console-link.js.map +1 -0
- package/dist/cli/commands/gateway/gateway-commands.test.d.ts +2 -0
- package/dist/cli/commands/gateway/gateway-commands.test.d.ts.map +1 -0
- package/dist/cli/commands/gateway/gateway-commands.test.js +230 -0
- package/dist/cli/commands/gateway/gateway-commands.test.js.map +1 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts +2 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts.map +1 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.js +373 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.js.map +1 -0
- package/dist/cli/commands/gateway/index.d.ts +8 -0
- package/dist/cli/commands/gateway/index.d.ts.map +1 -1
- package/dist/cli/commands/gateway/index.js +93 -14
- package/dist/cli/commands/gateway/index.js.map +1 -1
- package/dist/cli/commands/gateway/key.d.ts +4 -0
- package/dist/cli/commands/gateway/key.d.ts.map +1 -1
- package/dist/cli/commands/gateway/key.js +297 -182
- package/dist/cli/commands/gateway/key.js.map +1 -1
- package/dist/cli/commands/gateway/member.d.ts +3 -0
- package/dist/cli/commands/gateway/member.d.ts.map +1 -0
- package/dist/cli/commands/gateway/member.js +302 -0
- package/dist/cli/commands/gateway/member.js.map +1 -0
- package/dist/cli/commands/gateway/migrate.d.ts +18 -0
- package/dist/cli/commands/gateway/migrate.d.ts.map +1 -0
- package/dist/cli/commands/gateway/migrate.js +158 -0
- package/dist/cli/commands/gateway/migrate.js.map +1 -0
- package/dist/cli/commands/gateway/operator.d.ts +11 -0
- package/dist/cli/commands/gateway/operator.d.ts.map +1 -0
- package/dist/cli/commands/gateway/operator.js +199 -0
- package/dist/cli/commands/gateway/operator.js.map +1 -0
- package/dist/cli/commands/gateway/operator.test.d.ts +2 -0
- package/dist/cli/commands/gateway/operator.test.d.ts.map +1 -0
- package/dist/cli/commands/gateway/operator.test.js +151 -0
- package/dist/cli/commands/gateway/operator.test.js.map +1 -0
- package/dist/cli/commands/gateway/peer-list.d.ts +3 -0
- package/dist/cli/commands/gateway/peer-list.d.ts.map +1 -0
- package/dist/cli/commands/gateway/peer-list.js +107 -0
- package/dist/cli/commands/gateway/peer-list.js.map +1 -0
- package/dist/cli/commands/gateway/policy-options.d.ts +23 -0
- package/dist/cli/commands/gateway/policy-options.d.ts.map +1 -0
- package/dist/cli/commands/gateway/policy-options.js +156 -0
- package/dist/cli/commands/gateway/policy-options.js.map +1 -0
- package/dist/cli/commands/gateway/preset.d.ts +3 -0
- package/dist/cli/commands/gateway/preset.d.ts.map +1 -0
- package/dist/cli/commands/gateway/preset.js +181 -0
- package/dist/cli/commands/gateway/preset.js.map +1 -0
- package/dist/cli/commands/gateway/routing.d.ts +3 -0
- package/dist/cli/commands/gateway/routing.d.ts.map +1 -0
- package/dist/cli/commands/gateway/routing.js +110 -0
- package/dist/cli/commands/gateway/routing.js.map +1 -0
- package/dist/cli/commands/gateway/settings.d.ts +4 -0
- package/dist/cli/commands/gateway/settings.d.ts.map +1 -0
- package/dist/cli/commands/gateway/settings.js +314 -0
- package/dist/cli/commands/gateway/settings.js.map +1 -0
- package/dist/cli/commands/gateway/shared.d.ts +55 -6
- package/dist/cli/commands/gateway/shared.d.ts.map +1 -1
- package/dist/cli/commands/gateway/shared.js +206 -26
- package/dist/cli/commands/gateway/shared.js.map +1 -1
- package/dist/cli/commands/gateway/workspace.d.ts +3 -0
- package/dist/cli/commands/gateway/workspace.d.ts.map +1 -0
- package/dist/cli/commands/gateway/workspace.js +250 -0
- package/dist/cli/commands/gateway/workspace.js.map +1 -0
- package/dist/cli/commands/network/bootstrap.js +1 -1
- package/dist/cli/commands/seller/setup.js +1 -1
- package/dist/cli/commands/seller/start.d.ts.map +1 -1
- package/dist/cli/commands/seller/start.js +1 -2
- package/dist/cli/commands/seller/start.js.map +1 -1
- package/dist/cli/commands/system-proxy/index.js +1 -1
- package/dist/cli/commands/tunnel/start.d.ts.map +1 -1
- package/dist/cli/commands/tunnel/start.js +23 -5
- package/dist/cli/commands/tunnel/start.js.map +1 -1
- package/dist/cli/commands/wrapped-tools.js +8 -8
- package/dist/cli/commands/wrapped-tools.test.js +5 -5
- package/dist/cli/index.js +12 -44
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/program.d.ts +4 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +49 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/program.test.d.ts +2 -0
- package/dist/cli/program.test.d.ts.map +1 -0
- package/dist/cli/program.test.js +38 -0
- package/dist/cli/program.test.js.map +1 -0
- package/dist/cli/prompt-hidden.d.ts +3 -0
- package/dist/cli/prompt-hidden.d.ts.map +1 -0
- package/dist/cli/prompt-hidden.js +38 -0
- package/dist/cli/prompt-hidden.js.map +1 -0
- package/dist/cli/wallet-passphrase.d.ts +48 -0
- package/dist/cli/wallet-passphrase.d.ts.map +1 -0
- package/dist/cli/wallet-passphrase.js +119 -0
- package/dist/cli/wallet-passphrase.js.map +1 -0
- package/dist/cli/wallet-passphrase.test.d.ts +2 -0
- package/dist/cli/wallet-passphrase.test.d.ts.map +1 -0
- package/dist/cli/wallet-passphrase.test.js +79 -0
- package/dist/cli/wallet-passphrase.test.js.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +0 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effective.d.ts +12 -0
- package/dist/config/effective.d.ts.map +1 -1
- package/dist/config/effective.js +39 -6
- package/dist/config/effective.js.map +1 -1
- package/dist/config/effective.test.js +9 -1
- package/dist/config/effective.test.js.map +1 -1
- package/dist/config/loader.d.ts +6 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +12 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/loader.test.js +7 -1
- package/dist/config/loader.test.js.map +1 -1
- package/dist/config/types.d.ts +14 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +19 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/gateway/accounting.d.ts +33 -3
- package/dist/gateway/accounting.d.ts.map +1 -1
- package/dist/gateway/accounting.js +86 -7
- package/dist/gateway/accounting.js.map +1 -1
- package/dist/gateway/admission-benchmark.test.d.ts +2 -0
- package/dist/gateway/admission-benchmark.test.d.ts.map +1 -0
- package/dist/gateway/admission-benchmark.test.js +75 -0
- package/dist/gateway/admission-benchmark.test.js.map +1 -0
- package/dist/gateway/auth/auth.test.d.ts +4 -0
- package/dist/gateway/auth/auth.test.d.ts.map +1 -0
- package/dist/gateway/auth/auth.test.js +337 -0
- package/dist/gateway/auth/auth.test.js.map +1 -0
- package/dist/gateway/auth/credentials.test.d.ts +2 -0
- package/dist/gateway/auth/credentials.test.d.ts.map +1 -0
- package/dist/gateway/auth/credentials.test.js +132 -0
- package/dist/gateway/auth/credentials.test.js.map +1 -0
- package/dist/gateway/auth/db.d.ts +156 -0
- package/dist/gateway/auth/db.d.ts.map +1 -0
- package/dist/gateway/auth/db.js +390 -0
- package/dist/gateway/auth/db.js.map +1 -0
- package/dist/gateway/auth/hardening.test.d.ts +2 -0
- package/dist/gateway/auth/hardening.test.d.ts.map +1 -0
- package/dist/gateway/auth/hardening.test.js +155 -0
- package/dist/gateway/auth/hardening.test.js.map +1 -0
- package/dist/gateway/auth/http.d.ts +53 -0
- package/dist/gateway/auth/http.d.ts.map +1 -0
- package/dist/gateway/auth/http.js +165 -0
- package/dist/gateway/auth/http.js.map +1 -0
- package/dist/gateway/auth/index.d.ts +26 -0
- package/dist/gateway/auth/index.d.ts.map +1 -0
- package/dist/gateway/auth/index.js +1014 -0
- package/dist/gateway/auth/index.js.map +1 -0
- package/dist/gateway/auth/oidc.d.ts +57 -0
- package/dist/gateway/auth/oidc.d.ts.map +1 -0
- package/dist/gateway/auth/oidc.js +159 -0
- package/dist/gateway/auth/oidc.js.map +1 -0
- package/dist/gateway/auth/oidc.test.d.ts +2 -0
- package/dist/gateway/auth/oidc.test.d.ts.map +1 -0
- package/dist/gateway/auth/oidc.test.js +223 -0
- package/dist/gateway/auth/oidc.test.js.map +1 -0
- package/dist/gateway/auth/reauth.test.d.ts +2 -0
- package/dist/gateway/auth/reauth.test.d.ts.map +1 -0
- package/dist/gateway/auth/reauth.test.js +108 -0
- package/dist/gateway/auth/reauth.test.js.map +1 -0
- package/dist/gateway/auth/store-integration.test.d.ts +2 -0
- package/dist/gateway/auth/store-integration.test.d.ts.map +1 -0
- package/dist/gateway/auth/store-integration.test.js +100 -0
- package/dist/gateway/auth/store-integration.test.js.map +1 -0
- package/dist/gateway/auth/test-helpers.d.ts +167 -0
- package/dist/gateway/auth/test-helpers.d.ts.map +1 -0
- package/dist/gateway/auth/test-helpers.js +368 -0
- package/dist/gateway/auth/test-helpers.js.map +1 -0
- package/dist/gateway/auth/types.d.ts +145 -0
- package/dist/gateway/auth/types.d.ts.map +1 -0
- package/dist/gateway/auth/types.js +2 -0
- package/dist/gateway/auth/types.js.map +1 -0
- package/dist/gateway/auth/wallet.d.ts +15 -0
- package/dist/gateway/auth/wallet.d.ts.map +1 -0
- package/dist/gateway/auth/wallet.js +43 -0
- package/dist/gateway/auth/wallet.js.map +1 -0
- package/dist/gateway/bundle.d.ts +127 -0
- package/dist/gateway/bundle.d.ts.map +1 -0
- package/dist/gateway/bundle.js +604 -0
- package/dist/gateway/bundle.js.map +1 -0
- package/dist/gateway/bundle.test.d.ts +2 -0
- package/dist/gateway/bundle.test.d.ts.map +1 -0
- package/dist/gateway/bundle.test.js +277 -0
- package/dist/gateway/bundle.test.js.map +1 -0
- package/dist/gateway/buyer-control.d.ts +33 -0
- package/dist/gateway/buyer-control.d.ts.map +1 -0
- package/dist/gateway/buyer-control.js +93 -0
- package/dist/gateway/buyer-control.js.map +1 -0
- package/dist/gateway/buyer-policy-probe.d.ts +41 -0
- package/dist/gateway/buyer-policy-probe.d.ts.map +1 -0
- package/dist/gateway/buyer-policy-probe.js +99 -0
- package/dist/gateway/buyer-policy-probe.js.map +1 -0
- package/dist/gateway/buyer-policy-probe.test.d.ts +2 -0
- package/dist/gateway/buyer-policy-probe.test.d.ts.map +1 -0
- package/dist/gateway/buyer-policy-probe.test.js +128 -0
- package/dist/gateway/buyer-policy-probe.test.js.map +1 -0
- package/dist/gateway/buyer-process.d.ts +30 -0
- package/dist/gateway/buyer-process.d.ts.map +1 -0
- package/dist/gateway/buyer-process.js +101 -0
- package/dist/gateway/buyer-process.js.map +1 -0
- package/dist/gateway/buyer-process.test.d.ts +2 -0
- package/dist/gateway/buyer-process.test.d.ts.map +1 -0
- package/dist/gateway/buyer-process.test.js +112 -0
- package/dist/gateway/buyer-process.test.js.map +1 -0
- package/dist/gateway/chat/routes.d.ts +45 -0
- package/dist/gateway/chat/routes.d.ts.map +1 -0
- package/dist/gateway/chat/routes.js +439 -0
- package/dist/gateway/chat/routes.js.map +1 -0
- package/dist/gateway/chat/routes.test.d.ts +2 -0
- package/dist/gateway/chat/routes.test.d.ts.map +1 -0
- package/dist/gateway/chat/routes.test.js +304 -0
- package/dist/gateway/chat/routes.test.js.map +1 -0
- package/dist/gateway/chat/tools.d.ts +35 -0
- package/dist/gateway/chat/tools.d.ts.map +1 -0
- package/dist/gateway/chat/tools.js +347 -0
- package/dist/gateway/chat/tools.js.map +1 -0
- package/dist/gateway/chat/tools.test.d.ts +2 -0
- package/dist/gateway/chat/tools.test.d.ts.map +1 -0
- package/dist/gateway/chat/tools.test.js +136 -0
- package/dist/gateway/chat/tools.test.js.map +1 -0
- package/dist/gateway/console-api/access.d.ts +52 -0
- package/dist/gateway/console-api/access.d.ts.map +1 -0
- package/dist/gateway/console-api/access.js +171 -0
- package/dist/gateway/console-api/access.js.map +1 -0
- package/dist/gateway/console-api/deps.d.ts +52 -0
- package/dist/gateway/console-api/deps.d.ts.map +1 -0
- package/dist/gateway/console-api/deps.js +2 -0
- package/dist/gateway/console-api/deps.js.map +1 -0
- package/dist/gateway/console-api/handlers/admin-tokens.d.ts +8 -0
- package/dist/gateway/console-api/handlers/admin-tokens.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/admin-tokens.js +48 -0
- package/dist/gateway/console-api/handlers/admin-tokens.js.map +1 -0
- package/dist/gateway/console-api/handlers/audit.d.ts +8 -0
- package/dist/gateway/console-api/handlers/audit.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/audit.js +19 -0
- package/dist/gateway/console-api/handlers/audit.js.map +1 -0
- package/dist/gateway/console-api/handlers/audit.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/audit.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/audit.test.js +241 -0
- package/dist/gateway/console-api/handlers/audit.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/data-settings.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/data-settings.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/data-settings.test.js +264 -0
- package/dist/gateway/console-api/handlers/data-settings.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/index.d.ts +4 -0
- package/dist/gateway/console-api/handlers/index.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/index.js +24 -0
- package/dist/gateway/console-api/handlers/index.js.map +1 -0
- package/dist/gateway/console-api/handlers/keys.d.ts +13 -0
- package/dist/gateway/console-api/handlers/keys.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/keys.js +128 -0
- package/dist/gateway/console-api/handlers/keys.js.map +1 -0
- package/dist/gateway/console-api/handlers/keys.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/keys.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/keys.test.js +166 -0
- package/dist/gateway/console-api/handlers/keys.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/network-buyer.d.ts +22 -0
- package/dist/gateway/console-api/handlers/network-buyer.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network-buyer.js +39 -0
- package/dist/gateway/console-api/handlers/network-buyer.js.map +1 -0
- package/dist/gateway/console-api/handlers/network-mapping.d.ts +33 -0
- package/dist/gateway/console-api/handlers/network-mapping.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network-mapping.js +219 -0
- package/dist/gateway/console-api/handlers/network-mapping.js.map +1 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.d.ts +35 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.js +121 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.js.map +1 -0
- package/dist/gateway/console-api/handlers/network.d.ts +15 -0
- package/dist/gateway/console-api/handlers/network.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network.js +57 -0
- package/dist/gateway/console-api/handlers/network.js.map +1 -0
- package/dist/gateway/console-api/handlers/network.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/network.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network.test.js +383 -0
- package/dist/gateway/console-api/handlers/network.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/org.d.ts +10 -0
- package/dist/gateway/console-api/handlers/org.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/org.js +150 -0
- package/dist/gateway/console-api/handlers/org.js.map +1 -0
- package/dist/gateway/console-api/handlers/peer-lists.d.ts +5 -0
- package/dist/gateway/console-api/handlers/peer-lists.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/peer-lists.js +43 -0
- package/dist/gateway/console-api/handlers/peer-lists.js.map +1 -0
- package/dist/gateway/console-api/handlers/presets.d.ts +9 -0
- package/dist/gateway/console-api/handlers/presets.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/presets.js +79 -0
- package/dist/gateway/console-api/handlers/presets.js.map +1 -0
- package/dist/gateway/console-api/handlers/routing.d.ts +5 -0
- package/dist/gateway/console-api/handlers/routing.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/routing.js +20 -0
- package/dist/gateway/console-api/handlers/routing.js.map +1 -0
- package/dist/gateway/console-api/handlers/settings.d.ts +10 -0
- package/dist/gateway/console-api/handlers/settings.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/settings.js +38 -0
- package/dist/gateway/console-api/handlers/settings.js.map +1 -0
- package/dist/gateway/console-api/handlers/status.d.ts +9 -0
- package/dist/gateway/console-api/handlers/status.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/status.js +47 -0
- package/dist/gateway/console-api/handlers/status.js.map +1 -0
- package/dist/gateway/console-api/handlers/status.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/status.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/status.test.js +82 -0
- package/dist/gateway/console-api/handlers/status.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/usage.d.ts +10 -0
- package/dist/gateway/console-api/handlers/usage.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/usage.js +68 -0
- package/dist/gateway/console-api/handlers/usage.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.d.ts +29 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.js +46 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-chain.d.ts +26 -0
- package/dist/gateway/console-api/handlers/wallet-chain.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-chain.js +80 -0
- package/dist/gateway/console-api/handlers/wallet-chain.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.d.ts +45 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.js +202 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.d.ts +41 -0
- package/dist/gateway/console-api/handlers/wallet-operator.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.js +198 -0
- package/dist/gateway/console-api/handlers/wallet-operator.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.js +195 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.d.ts +61 -0
- package/dist/gateway/console-api/handlers/wallet.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.js +221 -0
- package/dist/gateway/console-api/handlers/wallet.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/wallet.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.test.js +581 -0
- package/dist/gateway/console-api/handlers/wallet.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/workspaces.d.ts +12 -0
- package/dist/gateway/console-api/handlers/workspaces.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/workspaces.js +96 -0
- package/dist/gateway/console-api/handlers/workspaces.js.map +1 -0
- package/dist/gateway/console-api/operator-types.d.ts +68 -0
- package/dist/gateway/console-api/operator-types.d.ts.map +1 -0
- package/dist/gateway/console-api/operator-types.js +21 -0
- package/dist/gateway/console-api/operator-types.js.map +1 -0
- package/dist/gateway/console-api/router.d.ts +97 -0
- package/dist/gateway/console-api/router.d.ts.map +1 -0
- package/dist/gateway/console-api/router.js +69 -0
- package/dist/gateway/console-api/router.js.map +1 -0
- package/dist/gateway/console-api/routes.test.d.ts +2 -0
- package/dist/gateway/console-api/routes.test.d.ts.map +1 -0
- package/dist/gateway/console-api/routes.test.js +331 -0
- package/dist/gateway/console-api/routes.test.js.map +1 -0
- package/dist/gateway/console-api/serialize.d.ts +33 -0
- package/dist/gateway/console-api/serialize.d.ts.map +1 -0
- package/dist/gateway/console-api/serialize.js +179 -0
- package/dist/gateway/console-api/serialize.js.map +1 -0
- package/dist/gateway/console-api/server.d.ts +38 -0
- package/dist/gateway/console-api/server.d.ts.map +1 -0
- package/dist/gateway/console-api/server.js +308 -0
- package/dist/gateway/console-api/server.js.map +1 -0
- package/dist/gateway/console-api/server.test.d.ts +2 -0
- package/dist/gateway/console-api/server.test.d.ts.map +1 -0
- package/dist/gateway/console-api/server.test.js +149 -0
- package/dist/gateway/console-api/server.test.js.map +1 -0
- package/dist/gateway/console-api/test-support.d.ts +59 -0
- package/dist/gateway/console-api/test-support.d.ts.map +1 -0
- package/dist/gateway/console-api/test-support.js +120 -0
- package/dist/gateway/console-api/test-support.js.map +1 -0
- package/dist/gateway/console-api/types.d.ts +779 -0
- package/dist/gateway/console-api/types.d.ts.map +1 -0
- package/dist/gateway/console-api/types.js +6 -0
- package/dist/gateway/console-api/types.js.map +1 -0
- package/dist/gateway/console-location.d.ts +21 -0
- package/dist/gateway/console-location.d.ts.map +1 -0
- package/dist/gateway/console-location.js +41 -0
- package/dist/gateway/console-location.js.map +1 -0
- package/dist/gateway/console-recovery.d.ts +34 -0
- package/dist/gateway/console-recovery.d.ts.map +1 -0
- package/dist/gateway/console-recovery.js +94 -0
- package/dist/gateway/console-recovery.js.map +1 -0
- package/dist/gateway/console-recovery.test.d.ts +2 -0
- package/dist/gateway/console-recovery.test.d.ts.map +1 -0
- package/dist/gateway/console-recovery.test.js +106 -0
- package/dist/gateway/console-recovery.test.js.map +1 -0
- package/dist/gateway/console.d.ts +18 -0
- package/dist/gateway/console.d.ts.map +1 -0
- package/dist/gateway/console.js +101 -0
- package/dist/gateway/console.js.map +1 -0
- package/dist/gateway/console.test.d.ts +2 -0
- package/dist/gateway/console.test.d.ts.map +1 -0
- package/dist/gateway/console.test.js +165 -0
- package/dist/gateway/console.test.js.map +1 -0
- package/dist/gateway/errors.d.ts +3 -0
- package/dist/gateway/errors.d.ts.map +1 -0
- package/dist/gateway/errors.js +5 -0
- package/dist/gateway/errors.js.map +1 -0
- package/dist/gateway/exposure.d.ts +33 -0
- package/dist/gateway/exposure.d.ts.map +1 -0
- package/dist/gateway/exposure.js +146 -0
- package/dist/gateway/exposure.js.map +1 -0
- package/dist/gateway/exposure.test.d.ts +2 -0
- package/dist/gateway/exposure.test.d.ts.map +1 -0
- package/dist/gateway/exposure.test.js +93 -0
- package/dist/gateway/exposure.test.js.map +1 -0
- package/dist/gateway/gateway-policy.test.d.ts +2 -0
- package/dist/gateway/gateway-policy.test.d.ts.map +1 -0
- package/dist/gateway/gateway-policy.test.js +404 -0
- package/dist/gateway/gateway-policy.test.js.map +1 -0
- package/dist/gateway/gateway.test.js +47 -25
- package/dist/gateway/gateway.test.js.map +1 -1
- package/dist/gateway/keys.d.ts +2 -0
- package/dist/gateway/keys.d.ts.map +1 -1
- package/dist/gateway/keys.js +2 -0
- package/dist/gateway/keys.js.map +1 -1
- package/dist/gateway/limits.d.ts +32 -8
- package/dist/gateway/limits.d.ts.map +1 -1
- package/dist/gateway/limits.js +27 -11
- package/dist/gateway/limits.js.map +1 -1
- package/dist/gateway/observability.d.ts +51 -0
- package/dist/gateway/observability.d.ts.map +1 -0
- package/dist/gateway/observability.js +136 -0
- package/dist/gateway/observability.js.map +1 -0
- package/dist/gateway/policy-resolver.d.ts +73 -0
- package/dist/gateway/policy-resolver.d.ts.map +1 -0
- package/dist/gateway/policy-resolver.js +142 -0
- package/dist/gateway/policy-resolver.js.map +1 -0
- package/dist/gateway/policy-resolver.test.d.ts +2 -0
- package/dist/gateway/policy-resolver.test.d.ts.map +1 -0
- package/dist/gateway/policy-resolver.test.js +273 -0
- package/dist/gateway/policy-resolver.test.js.map +1 -0
- package/dist/gateway/request-errors.test.d.ts +2 -0
- package/dist/gateway/request-errors.test.d.ts.map +1 -0
- package/dist/gateway/request-errors.test.js +108 -0
- package/dist/gateway/request-errors.test.js.map +1 -0
- package/dist/gateway/request-shaping.d.ts +25 -0
- package/dist/gateway/request-shaping.d.ts.map +1 -0
- package/dist/gateway/request-shaping.js +66 -0
- package/dist/gateway/request-shaping.js.map +1 -0
- package/dist/gateway/response-usage.d.ts +35 -0
- package/dist/gateway/response-usage.d.ts.map +1 -0
- package/dist/gateway/response-usage.js +167 -0
- package/dist/gateway/response-usage.js.map +1 -0
- package/dist/gateway/response-usage.test.d.ts +2 -0
- package/dist/gateway/response-usage.test.d.ts.map +1 -0
- package/dist/gateway/response-usage.test.js +78 -0
- package/dist/gateway/response-usage.test.js.map +1 -0
- package/dist/gateway/runtime.d.ts +25 -0
- package/dist/gateway/runtime.d.ts.map +1 -1
- package/dist/gateway/runtime.js +39 -19
- package/dist/gateway/runtime.js.map +1 -1
- package/dist/gateway/server.d.ts +68 -2
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +532 -116
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/services/audit.d.ts +16 -0
- package/dist/gateway/services/audit.d.ts.map +1 -0
- package/dist/gateway/services/audit.js +18 -0
- package/dist/gateway/services/audit.js.map +1 -0
- package/dist/gateway/services/context.d.ts +66 -0
- package/dist/gateway/services/context.d.ts.map +1 -0
- package/dist/gateway/services/context.js +52 -0
- package/dist/gateway/services/context.js.map +1 -0
- package/dist/gateway/services/keys.d.ts +78 -0
- package/dist/gateway/services/keys.d.ts.map +1 -0
- package/dist/gateway/services/keys.js +232 -0
- package/dist/gateway/services/keys.js.map +1 -0
- package/dist/gateway/services/members.d.ts +72 -0
- package/dist/gateway/services/members.d.ts.map +1 -0
- package/dist/gateway/services/members.js +161 -0
- package/dist/gateway/services/members.js.map +1 -0
- package/dist/gateway/services/network.d.ts +36 -0
- package/dist/gateway/services/network.d.ts.map +1 -0
- package/dist/gateway/services/network.js +147 -0
- package/dist/gateway/services/network.js.map +1 -0
- package/dist/gateway/services/operator.d.ts +98 -0
- package/dist/gateway/services/operator.d.ts.map +1 -0
- package/dist/gateway/services/operator.js +170 -0
- package/dist/gateway/services/operator.js.map +1 -0
- package/dist/gateway/services/operator.test.d.ts +2 -0
- package/dist/gateway/services/operator.test.d.ts.map +1 -0
- package/dist/gateway/services/operator.test.js +184 -0
- package/dist/gateway/services/operator.test.js.map +1 -0
- package/dist/gateway/services/peer-lists.d.ts +20 -0
- package/dist/gateway/services/peer-lists.d.ts.map +1 -0
- package/dist/gateway/services/peer-lists.js +134 -0
- package/dist/gateway/services/peer-lists.js.map +1 -0
- package/dist/gateway/services/presets.d.ts +34 -0
- package/dist/gateway/services/presets.d.ts.map +1 -0
- package/dist/gateway/services/presets.js +134 -0
- package/dist/gateway/services/presets.js.map +1 -0
- package/dist/gateway/services/routing.d.ts +13 -0
- package/dist/gateway/services/routing.d.ts.map +1 -0
- package/dist/gateway/services/routing.js +18 -0
- package/dist/gateway/services/routing.js.map +1 -0
- package/dist/gateway/services/services.test.d.ts +2 -0
- package/dist/gateway/services/services.test.d.ts.map +1 -0
- package/dist/gateway/services/services.test.js +476 -0
- package/dist/gateway/services/services.test.js.map +1 -0
- package/dist/gateway/services/settings.d.ts +68 -0
- package/dist/gateway/services/settings.d.ts.map +1 -0
- package/dist/gateway/services/settings.js +580 -0
- package/dist/gateway/services/settings.js.map +1 -0
- package/dist/gateway/services/tokens.d.ts +29 -0
- package/dist/gateway/services/tokens.d.ts.map +1 -0
- package/dist/gateway/services/tokens.js +48 -0
- package/dist/gateway/services/tokens.js.map +1 -0
- package/dist/gateway/services/usage.d.ts +55 -0
- package/dist/gateway/services/usage.d.ts.map +1 -0
- package/dist/gateway/services/usage.js +225 -0
- package/dist/gateway/services/usage.js.map +1 -0
- package/dist/gateway/services/wallet-address-env.test.d.ts +2 -0
- package/dist/gateway/services/wallet-address-env.test.d.ts.map +1 -0
- package/dist/gateway/services/wallet-address-env.test.js +119 -0
- package/dist/gateway/services/wallet-address-env.test.js.map +1 -0
- package/dist/gateway/services/wallet-address.d.ts +40 -0
- package/dist/gateway/services/wallet-address.d.ts.map +1 -0
- package/dist/gateway/services/wallet-address.js +116 -0
- package/dist/gateway/services/wallet-address.js.map +1 -0
- package/dist/gateway/services/wallet-address.test.d.ts +2 -0
- package/dist/gateway/services/wallet-address.test.d.ts.map +1 -0
- package/dist/gateway/services/wallet-address.test.js +84 -0
- package/dist/gateway/services/wallet-address.test.js.map +1 -0
- package/dist/gateway/services/workspaces.d.ts +75 -0
- package/dist/gateway/services/workspaces.d.ts.map +1 -0
- package/dist/gateway/services/workspaces.js +228 -0
- package/dist/gateway/services/workspaces.js.map +1 -0
- package/dist/gateway/spend-durability.test.d.ts +2 -0
- package/dist/gateway/spend-durability.test.d.ts.map +1 -0
- package/dist/gateway/spend-durability.test.js +330 -0
- package/dist/gateway/spend-durability.test.js.map +1 -0
- package/dist/gateway/spend-feed.d.ts +11 -1
- package/dist/gateway/spend-feed.d.ts.map +1 -1
- package/dist/gateway/spend-feed.js +17 -4
- package/dist/gateway/spend-feed.js.map +1 -1
- package/dist/gateway/spend-feed.test.d.ts +2 -0
- package/dist/gateway/spend-feed.test.d.ts.map +1 -0
- package/dist/gateway/spend-feed.test.js +34 -0
- package/dist/gateway/spend-feed.test.js.map +1 -0
- package/dist/gateway/store-data.test.d.ts +2 -0
- package/dist/gateway/store-data.test.d.ts.map +1 -0
- package/dist/gateway/store-data.test.js +346 -0
- package/dist/gateway/store-data.test.js.map +1 -0
- package/dist/gateway/store-migrations.d.ts +6 -0
- package/dist/gateway/store-migrations.d.ts.map +1 -0
- package/dist/gateway/store-migrations.js +301 -0
- package/dist/gateway/store-migrations.js.map +1 -0
- package/dist/gateway/store.d.ts +595 -10
- package/dist/gateway/store.d.ts.map +1 -1
- package/dist/gateway/store.js +1242 -120
- package/dist/gateway/store.js.map +1 -1
- package/dist/gateway/store.test.d.ts +2 -0
- package/dist/gateway/store.test.d.ts.map +1 -0
- package/dist/gateway/store.test.js +185 -0
- package/dist/gateway/store.test.js.map +1 -0
- package/dist/proxy/buyer-chain-reader.d.ts +68 -0
- package/dist/proxy/buyer-chain-reader.d.ts.map +1 -0
- package/dist/proxy/buyer-chain-reader.js +152 -0
- package/dist/proxy/buyer-chain-reader.js.map +1 -0
- package/dist/proxy/buyer-chain-reader.test.d.ts +2 -0
- package/dist/proxy/buyer-chain-reader.test.d.ts.map +1 -0
- package/dist/proxy/buyer-chain-reader.test.js +102 -0
- package/dist/proxy/buyer-chain-reader.test.js.map +1 -0
- package/dist/proxy/buyer-proxy.d.ts +169 -3
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +873 -117
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +31 -0
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/chain-read-cache.d.ts +47 -0
- package/dist/proxy/chain-read-cache.d.ts.map +1 -0
- package/dist/proxy/chain-read-cache.js +110 -0
- package/dist/proxy/chain-read-cache.js.map +1 -0
- package/dist/proxy/chain-read-cache.test.d.ts +2 -0
- package/dist/proxy/chain-read-cache.test.d.ts.map +1 -0
- package/dist/proxy/chain-read-cache.test.js +75 -0
- package/dist/proxy/chain-read-cache.test.js.map +1 -0
- package/dist/proxy/chain-rpc-test-support.d.ts +31 -0
- package/dist/proxy/chain-rpc-test-support.d.ts.map +1 -0
- package/dist/proxy/chain-rpc-test-support.js +136 -0
- package/dist/proxy/chain-rpc-test-support.js.map +1 -0
- package/dist/proxy/chain-rpc.d.ts +121 -0
- package/dist/proxy/chain-rpc.d.ts.map +1 -0
- package/dist/proxy/chain-rpc.js +420 -0
- package/dist/proxy/chain-rpc.js.map +1 -0
- package/dist/proxy/chain-rpc.test.d.ts +2 -0
- package/dist/proxy/chain-rpc.test.d.ts.map +1 -0
- package/dist/proxy/chain-rpc.test.js +180 -0
- package/dist/proxy/chain-rpc.test.js.map +1 -0
- package/dist/proxy/deposit-watch-hub.d.ts +47 -0
- package/dist/proxy/deposit-watch-hub.d.ts.map +1 -0
- package/dist/proxy/deposit-watch-hub.js +111 -0
- package/dist/proxy/deposit-watch-hub.js.map +1 -0
- package/dist/proxy/deposit-watch-hub.test.d.ts +2 -0
- package/dist/proxy/deposit-watch-hub.test.d.ts.map +1 -0
- package/dist/proxy/deposit-watch-hub.test.js +133 -0
- package/dist/proxy/deposit-watch-hub.test.js.map +1 -0
- package/dist/proxy/deposit-watcher.d.ts +44 -4
- package/dist/proxy/deposit-watcher.d.ts.map +1 -1
- package/dist/proxy/deposit-watcher.js +76 -11
- package/dist/proxy/deposit-watcher.js.map +1 -1
- package/dist/proxy/deposit-watcher.test.js +54 -1
- package/dist/proxy/deposit-watcher.test.js.map +1 -1
- package/dist/proxy/gateway-policy.test.d.ts +2 -0
- package/dist/proxy/gateway-policy.test.d.ts.map +1 -0
- package/dist/proxy/gateway-policy.test.js +576 -0
- package/dist/proxy/gateway-policy.test.js.map +1 -0
- package/dist/proxy/network-models.d.ts +2 -0
- package/dist/proxy/network-models.d.ts.map +1 -1
- package/dist/proxy/network-models.js +3 -1
- package/dist/proxy/network-models.js.map +1 -1
- package/dist/proxy/route-policy.d.ts +97 -0
- package/dist/proxy/route-policy.d.ts.map +1 -0
- package/dist/proxy/route-policy.js +230 -0
- package/dist/proxy/route-policy.js.map +1 -0
- package/dist/proxy/route-policy.test.d.ts +2 -0
- package/dist/proxy/route-policy.test.d.ts.map +1 -0
- package/dist/proxy/route-policy.test.js +120 -0
- package/dist/proxy/route-policy.test.js.map +1 -0
- package/dist/proxy/spend-attribution.d.ts +41 -10
- package/dist/proxy/spend-attribution.d.ts.map +1 -1
- package/dist/proxy/spend-attribution.js +133 -37
- package/dist/proxy/spend-attribution.js.map +1 -1
- package/dist/proxy/tee-control.d.ts +3 -1
- package/dist/proxy/tee-control.d.ts.map +1 -1
- package/dist/proxy/tee-control.js +5 -3
- package/dist/proxy/tee-control.js.map +1 -1
- package/dist/proxy/telemetry.d.ts +2 -1
- package/dist/proxy/telemetry.d.ts.map +1 -1
- package/dist/proxy/telemetry.js +10 -5
- package/dist/proxy/telemetry.js.map +1 -1
- package/dist/routing-policy/policy.d.ts +149 -0
- package/dist/routing-policy/policy.d.ts.map +1 -0
- package/dist/routing-policy/policy.js +450 -0
- package/dist/routing-policy/policy.js.map +1 -0
- package/dist/routing-policy/policy.test.d.ts +2 -0
- package/dist/routing-policy/policy.test.d.ts.map +1 -0
- package/dist/routing-policy/policy.test.js +134 -0
- package/dist/routing-policy/policy.test.js.map +1 -0
- package/package.json +15 -7
package/dist/gateway/store.js
CHANGED
|
@@ -1,101 +1,417 @@
|
|
|
1
1
|
import { DEFAULT_BUYER_IDENTITY } from '@antseed/node';
|
|
2
2
|
import Database from 'better-sqlite3';
|
|
3
|
-
import {
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { chmodSync, existsSync, mkdirSync } from 'node:fs';
|
|
4
5
|
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
CREATE TABLE ledger_entries (
|
|
39
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
-
kind TEXT NOT NULL CHECK (kind IN ('spend', 'credit')),
|
|
41
|
-
key_id TEXT NOT NULL,
|
|
42
|
-
buyer_identity TEXT NOT NULL,
|
|
43
|
-
amount_usdc INTEGER NOT NULL,
|
|
44
|
-
external_ref TEXT NOT NULL UNIQUE,
|
|
45
|
-
request_tag TEXT,
|
|
46
|
-
seller_peer_id TEXT,
|
|
47
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
48
|
-
cached_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
49
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
50
|
-
note TEXT,
|
|
51
|
-
created_at INTEGER NOT NULL
|
|
52
|
-
);
|
|
53
|
-
CREATE INDEX ledger_entries_key_time ON ledger_entries(key_id, kind, created_at);
|
|
54
|
-
`,
|
|
55
|
-
`
|
|
56
|
-
ALTER TABLE api_keys ADD COLUMN topup_enabled INTEGER NOT NULL DEFAULT 0;
|
|
57
|
-
`,
|
|
58
|
-
];
|
|
6
|
+
import { isRoutingPolicy, normalizePolicy } from '../routing-policy/policy.js';
|
|
7
|
+
import { ADMIN_TOKEN_PREFIX, apiKeyHint, generateApiKey, hashApiKey, newKeyId } from './keys.js';
|
|
8
|
+
import { BUDGET_PERIODS, LIMIT_PERIODS, NO_BUDGET_LIMITS, fullLimits, periodStart, } from './limits.js';
|
|
9
|
+
import { MIGRATIONS } from './store-migrations.js';
|
|
10
|
+
export const DEFAULT_WORKSPACE_ID = 'ws_default';
|
|
11
|
+
function chatThreadSummary(row) {
|
|
12
|
+
return {
|
|
13
|
+
id: row.id,
|
|
14
|
+
workspaceId: row.workspace_id,
|
|
15
|
+
owner: { kind: row.owner_kind, id: row.owner_id },
|
|
16
|
+
title: row.title,
|
|
17
|
+
model: row.model,
|
|
18
|
+
peerId: row.peer_id,
|
|
19
|
+
createdAt: row.created_at,
|
|
20
|
+
updatedAt: row.updated_at,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* End users are whatever a key holder sends, so a report by user keeps the
|
|
25
|
+
* largest ones and folds the rest into one row.
|
|
26
|
+
*/
|
|
27
|
+
export const MAX_USER_GROUPS = 100;
|
|
28
|
+
export const OTHER_USERS_GROUP = '\u0000others';
|
|
29
|
+
/** Best effort: a file we do not own (or a filesystem without modes) keeps its mode. */
|
|
30
|
+
function restrictMode(path, mode) {
|
|
31
|
+
try {
|
|
32
|
+
if (existsSync(path))
|
|
33
|
+
chmodSync(path, mode);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// not ours to change
|
|
37
|
+
}
|
|
38
|
+
}
|
|
59
39
|
export function gatewayDir(dataDir) {
|
|
60
40
|
return join(dataDir, 'gateway');
|
|
61
41
|
}
|
|
42
|
+
function newId(prefix) {
|
|
43
|
+
return `${prefix}_${randomBytes(6).toString('hex')}`;
|
|
44
|
+
}
|
|
45
|
+
function parsePolicy(raw) {
|
|
46
|
+
if (!raw)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
return isRoutingPolicy(parsed) ? normalizePolicy(parsed) : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function policyJson(policy) {
|
|
57
|
+
return policy ? JSON.stringify(normalizePolicy(policy)) : null;
|
|
58
|
+
}
|
|
59
|
+
function parseJson(raw, fallback) {
|
|
60
|
+
if (!raw)
|
|
61
|
+
return fallback;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(raw);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return fallback;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function rowLimits(row, prefix = '') {
|
|
70
|
+
const limit = (period) => row[`${prefix}${period}_limit_usdc`];
|
|
71
|
+
return {
|
|
72
|
+
daily: limit('daily'),
|
|
73
|
+
weekly: limit('weekly'),
|
|
74
|
+
monthly: limit('monthly'),
|
|
75
|
+
total: limit('total'),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
62
78
|
/**
|
|
63
|
-
* Durable state for the API-key gateway: keys, request log
|
|
64
|
-
*
|
|
65
|
-
*
|
|
79
|
+
* Durable state for the API-key gateway and its console: keys, request log,
|
|
80
|
+
* the per-key ledger, and the organization around them (workspaces, members,
|
|
81
|
+
* invites, presets, peer lists, management tokens, settings). SQLite in WAL
|
|
82
|
+
* mode so `antseed gateway key …` commands can change keys while a gateway
|
|
83
|
+
* process is serving them.
|
|
66
84
|
*/
|
|
67
85
|
export class GatewayStore {
|
|
68
86
|
_now;
|
|
69
87
|
_db;
|
|
88
|
+
_statements = new Map();
|
|
89
|
+
_settings = new Map();
|
|
90
|
+
_touched = new Map();
|
|
70
91
|
constructor(dataDir, _now = () => Date.now()) {
|
|
71
92
|
this._now = _now;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
const dir = gatewayDir(dataDir);
|
|
94
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
95
|
+
// Key hashes, sessions and request bodies live here: owner-only.
|
|
96
|
+
restrictMode(dir, 0o700);
|
|
97
|
+
const file = join(dir, 'gateway.db');
|
|
98
|
+
this._db = new Database(file, { timeout: 5000 });
|
|
75
99
|
this._db.pragma('busy_timeout = 5000');
|
|
100
|
+
this._enableWal();
|
|
76
101
|
this._db.pragma('foreign_keys = ON');
|
|
102
|
+
for (const path of [file, `${file}-wal`, `${file}-shm`])
|
|
103
|
+
restrictMode(path, 0o600);
|
|
77
104
|
this._migrate();
|
|
78
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Switching a database to WAL needs it to itself, and SQLite answers
|
|
108
|
+
* SQLITE_BUSY at once (no busy handler) while other processes have it
|
|
109
|
+
* open, e.g. several opening an old database together: retry for a while.
|
|
110
|
+
*/
|
|
111
|
+
_enableWal() {
|
|
112
|
+
const deadline = Date.now() + 5_000;
|
|
113
|
+
for (;;) {
|
|
114
|
+
try {
|
|
115
|
+
this._db.pragma('journal_mode = WAL');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error.code !== 'SQLITE_BUSY' || Date.now() > deadline)
|
|
120
|
+
throw error;
|
|
121
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10 + Math.floor(Math.random() * 20));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** The underlying database; the console's auth module keeps its own tables in it. */
|
|
126
|
+
get database() {
|
|
127
|
+
return this._db;
|
|
128
|
+
}
|
|
79
129
|
close() {
|
|
130
|
+
this._statements.clear();
|
|
80
131
|
this._db.close();
|
|
81
132
|
}
|
|
133
|
+
_count(sql, ...params) {
|
|
134
|
+
return this._stmt(sql).get(...params).count;
|
|
135
|
+
}
|
|
136
|
+
/** A prepared statement, cached by its SQL (hot paths run the same few statements on every request). */
|
|
137
|
+
_stmt(sql) {
|
|
138
|
+
let statement = this._statements.get(sql);
|
|
139
|
+
if (!statement) {
|
|
140
|
+
if (this._statements.size >= MAX_CACHED_STATEMENTS)
|
|
141
|
+
this._statements.clear();
|
|
142
|
+
statement = this._db.prepare(sql);
|
|
143
|
+
this._statements.set(sql, statement);
|
|
144
|
+
}
|
|
145
|
+
return statement;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Several processes (the gateway, `antseed gateway key …`) may open an old
|
|
149
|
+
* database at once: the version is re-read inside an IMMEDIATE transaction,
|
|
150
|
+
* which holds the write lock, so only the first one applies a migration.
|
|
151
|
+
*/
|
|
82
152
|
_migrate() {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
153
|
+
if (this._db.pragma('user_version', { simple: true }) >= MIGRATIONS.length)
|
|
154
|
+
return;
|
|
155
|
+
this._db.transaction(() => {
|
|
156
|
+
const version = this._db.pragma('user_version', { simple: true });
|
|
157
|
+
for (let index = version; index < MIGRATIONS.length; index += 1) {
|
|
86
158
|
this._db.exec(MIGRATIONS[index]);
|
|
87
159
|
this._db.pragma(`user_version = ${index + 1}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
160
|
+
}
|
|
161
|
+
}).immediate();
|
|
162
|
+
}
|
|
163
|
+
// ── Workspaces ──────────────────────────────────────────────────────────
|
|
164
|
+
getWorkspace(id) {
|
|
165
|
+
const row = this._stmt('SELECT * FROM workspaces WHERE id = ? AND deleted_at IS NULL').get(id);
|
|
166
|
+
return row ? toWorkspace(row) : null;
|
|
167
|
+
}
|
|
168
|
+
listWorkspaces() {
|
|
169
|
+
const rows = this._stmt('SELECT * FROM workspaces WHERE deleted_at IS NULL ORDER BY is_default DESC, created_at, id').all();
|
|
170
|
+
return rows.map(toWorkspace);
|
|
171
|
+
}
|
|
172
|
+
defaultWorkspace() {
|
|
173
|
+
return this.getWorkspace(DEFAULT_WORKSPACE_ID);
|
|
174
|
+
}
|
|
175
|
+
workspaceForKey(keyId) {
|
|
176
|
+
const row = this._stmt('SELECT workspace_id FROM api_keys WHERE id = ?').get(keyId);
|
|
177
|
+
return row?.workspace_id ? this.getWorkspace(row.workspace_id) : null;
|
|
178
|
+
}
|
|
179
|
+
createWorkspace(input) {
|
|
180
|
+
const id = newId('ws');
|
|
181
|
+
const limits = input.limits ?? NO_BUDGET_LIMITS;
|
|
182
|
+
this._stmt(`
|
|
183
|
+
INSERT INTO workspaces (id, name, buyer_identity, is_default, wallet_address, daily_limit_usdc, weekly_limit_usdc, monthly_limit_usdc, total_limit_usdc, routing_policy, org_routing_policy, created_at)
|
|
184
|
+
VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
185
|
+
`).run(id, input.name, input.buyerIdentity, input.walletAddress ?? null, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(input.routingPolicy), policyJson(input.orgRoutingPolicy), this._now());
|
|
186
|
+
return this.getWorkspace(id);
|
|
187
|
+
}
|
|
188
|
+
/** Caches the wallet address of every workspace paying with this identity. */
|
|
189
|
+
setWalletAddress(buyerIdentity, address) {
|
|
190
|
+
this._stmt('UPDATE workspaces SET wallet_address = ? WHERE buyer_identity = ?').run(address, buyerIdentity);
|
|
191
|
+
}
|
|
192
|
+
updateWorkspace(id, patch) {
|
|
193
|
+
const current = this.getWorkspace(id);
|
|
194
|
+
if (!current)
|
|
195
|
+
throw new Error(`Unknown workspace "${id}".`);
|
|
196
|
+
const limits = { ...current.limits, ...(patch.limits ?? {}) };
|
|
197
|
+
const policy = patch.routingPolicy === undefined ? current.routingPolicy : patch.routingPolicy;
|
|
198
|
+
const orgPolicy = patch.orgRoutingPolicy === undefined ? current.orgRoutingPolicy : patch.orgRoutingPolicy;
|
|
199
|
+
this._stmt(`
|
|
200
|
+
UPDATE workspaces SET name = ?, daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?, total_limit_usdc = ?, routing_policy = ?,
|
|
201
|
+
org_routing_policy = ?
|
|
202
|
+
WHERE id = ?
|
|
203
|
+
`).run(patch.name ?? current.name, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(policy), policyJson(orgPolicy), id);
|
|
204
|
+
return this.getWorkspace(id);
|
|
205
|
+
}
|
|
206
|
+
/** Soft delete: history keeps pointing at it. The default workspace cannot be deleted. */
|
|
207
|
+
deleteWorkspace(id) {
|
|
208
|
+
if (id === DEFAULT_WORKSPACE_ID)
|
|
209
|
+
throw new Error('The Default workspace cannot be deleted.');
|
|
210
|
+
this._db.transaction(() => {
|
|
211
|
+
this._stmt('UPDATE workspaces SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL').run(this._now(), id);
|
|
212
|
+
this._stmt('DELETE FROM workspace_members WHERE workspace_id = ?').run(id);
|
|
213
|
+
})();
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The workspace that pays with this identity, created (named after the
|
|
217
|
+
* identity) when there is none. Keeps `antseed gateway key create
|
|
218
|
+
* --identity/--new-identity` working.
|
|
219
|
+
*/
|
|
220
|
+
ensureWorkspaceForIdentity(buyerIdentity) {
|
|
221
|
+
if (buyerIdentity === DEFAULT_BUYER_IDENTITY)
|
|
222
|
+
return this.defaultWorkspace();
|
|
223
|
+
const row = this._stmt('SELECT * FROM workspaces WHERE buyer_identity = ? AND deleted_at IS NULL ORDER BY created_at, id LIMIT 1')
|
|
224
|
+
.get(buyerIdentity);
|
|
225
|
+
return row ? toWorkspace(row) : this.createWorkspace({ name: buyerIdentity, buyerIdentity });
|
|
226
|
+
}
|
|
227
|
+
countWorkspaceKeys(workspaceId, activeOnly = true) {
|
|
228
|
+
return this._count(`SELECT COUNT(*) AS count FROM api_keys WHERE workspace_id = ?${activeOnly ? " AND status = 'active'" : ''}`, workspaceId);
|
|
229
|
+
}
|
|
230
|
+
countWorkspaceMembers(workspaceId) {
|
|
231
|
+
return this._count('SELECT COUNT(*) AS count FROM workspace_members WHERE workspace_id = ?', workspaceId);
|
|
232
|
+
}
|
|
233
|
+
workspaceMembers(workspaceId) {
|
|
234
|
+
const rows = this._stmt(`
|
|
235
|
+
SELECT m.*, wm.role AS ws_role FROM workspace_members wm JOIN members m ON m.id = wm.member_id
|
|
236
|
+
WHERE wm.workspace_id = ? ORDER BY m.created_at, m.id
|
|
237
|
+
`).all(workspaceId);
|
|
238
|
+
return rows.map((row) => ({ member: toMember(row), role: row.ws_role }));
|
|
239
|
+
}
|
|
240
|
+
setWorkspaceMember(workspaceId, memberId, role) {
|
|
241
|
+
this._stmt(`
|
|
242
|
+
INSERT INTO workspace_members (workspace_id, member_id, role) VALUES (?, ?, ?)
|
|
243
|
+
ON CONFLICT (workspace_id, member_id) DO UPDATE SET role = excluded.role
|
|
244
|
+
`).run(workspaceId, memberId, role);
|
|
245
|
+
}
|
|
246
|
+
removeWorkspaceMember(workspaceId, memberId) {
|
|
247
|
+
return this._stmt('DELETE FROM workspace_members WHERE workspace_id = ? AND member_id = ?').run(workspaceId, memberId).changes > 0;
|
|
248
|
+
}
|
|
249
|
+
// ── Members ─────────────────────────────────────────────────────────────
|
|
250
|
+
getMember(id) {
|
|
251
|
+
const row = this._stmt('SELECT * FROM members WHERE id = ?').get(id);
|
|
252
|
+
return row ? toMember(row) : null;
|
|
253
|
+
}
|
|
254
|
+
findMemberByEmail(email) {
|
|
255
|
+
const row = this._stmt('SELECT * FROM members WHERE lower(email) = lower(?)').get(email.trim());
|
|
256
|
+
return row ? toMember(row) : null;
|
|
257
|
+
}
|
|
258
|
+
listMembers() {
|
|
259
|
+
return this._stmt('SELECT * FROM members ORDER BY created_at, id').all().map(toMember);
|
|
260
|
+
}
|
|
261
|
+
/** Workspaces (not deleted) the member belongs to, with their role. */
|
|
262
|
+
memberWorkspaceRoles(memberId) {
|
|
263
|
+
const rows = this._stmt(`
|
|
264
|
+
SELECT wm.workspace_id, wm.role FROM workspace_members wm JOIN workspaces w ON w.id = wm.workspace_id
|
|
265
|
+
WHERE wm.member_id = ? AND w.deleted_at IS NULL
|
|
266
|
+
`).all(memberId);
|
|
267
|
+
return new Map(rows.map((row) => [row.workspace_id, row.role]));
|
|
268
|
+
}
|
|
269
|
+
/** True once an active owner exists. */
|
|
270
|
+
isSetupComplete() {
|
|
271
|
+
return this._stmt("SELECT 1 FROM members WHERE org_role = 'owner' AND status = 'active' LIMIT 1").get() !== undefined;
|
|
272
|
+
}
|
|
273
|
+
countActiveOwners() {
|
|
274
|
+
return this._count("SELECT COUNT(*) AS count FROM members WHERE org_role = 'owner' AND status = 'active'");
|
|
275
|
+
}
|
|
276
|
+
/** The first owner, created when the console is claimed; admin of the Default workspace. */
|
|
277
|
+
createOwner(input) {
|
|
278
|
+
return this._db.transaction(() => {
|
|
279
|
+
if (this.isSetupComplete())
|
|
280
|
+
throw new Error('The console already has an owner.');
|
|
281
|
+
const member = this._insertMember({ label: input.label, email: input.email, orgRole: 'owner', status: 'active' });
|
|
282
|
+
this.setWorkspaceMember(DEFAULT_WORKSPACE_ID, member.id, 'admin');
|
|
283
|
+
return member;
|
|
284
|
+
})();
|
|
285
|
+
}
|
|
286
|
+
_insertMember(input) {
|
|
287
|
+
const id = newId('mem');
|
|
288
|
+
this._stmt('INSERT INTO members (id, label, email, org_role, status, created_at) VALUES (?, ?, ?, ?, ?, ?)')
|
|
289
|
+
.run(id, input.label, input.email?.trim() || null, input.orgRole, input.status, this._now());
|
|
290
|
+
return this.getMember(id);
|
|
291
|
+
}
|
|
292
|
+
updateMember(id, patch) {
|
|
293
|
+
const current = this.getMember(id);
|
|
294
|
+
if (!current)
|
|
295
|
+
throw new Error(`Unknown member "${id}".`);
|
|
296
|
+
const limits = { ...current.limits, ...(patch.limits ?? {}) };
|
|
297
|
+
this._stmt(`
|
|
298
|
+
UPDATE members SET label = ?, email = ?, org_role = ?, daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?,
|
|
299
|
+
total_limit_usdc = ?, routing_policy = ?, max_keys = ? WHERE id = ?
|
|
300
|
+
`).run(patch.label ?? current.label, patch.email === undefined ? current.email : (patch.email?.trim() || null), patch.orgRole ?? current.orgRole, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(patch.routingPolicy === undefined ? current.routingPolicy : patch.routingPolicy), patch.maxKeys === undefined ? current.maxKeys : patch.maxKeys, id);
|
|
301
|
+
return this.getMember(id);
|
|
302
|
+
}
|
|
303
|
+
setMemberStatus(id, status) {
|
|
304
|
+
const result = this._stmt('UPDATE members SET status = ? WHERE id = ?').run(status, id);
|
|
305
|
+
if (result.changes === 0)
|
|
306
|
+
throw new Error(`Unknown member "${id}".`);
|
|
307
|
+
return this.getMember(id);
|
|
308
|
+
}
|
|
309
|
+
// ── Invites ─────────────────────────────────────────────────────────────
|
|
310
|
+
/**
|
|
311
|
+
* Creates the invited member (status 'invited', already in its workspaces)
|
|
312
|
+
* and a single-use link token, of which only the SHA-256 is stored.
|
|
313
|
+
*/
|
|
314
|
+
createInvite(input) {
|
|
315
|
+
const token = randomBytes(32).toString('base64url');
|
|
316
|
+
const invite = this._db.transaction(() => {
|
|
317
|
+
const member = this._insertMember({ label: input.label, email: input.email, orgRole: input.orgRole, status: 'invited' });
|
|
318
|
+
for (const entry of input.workspaces)
|
|
319
|
+
this.setWorkspaceMember(entry.workspaceId, member.id, entry.role);
|
|
320
|
+
const id = newId('inv');
|
|
321
|
+
this._stmt(`
|
|
322
|
+
INSERT INTO invites (id, token_hash, member_id, label, email, org_role, workspace_roles, created_by, expires_at, created_at)
|
|
323
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
324
|
+
`).run(id, hashApiKey(token), member.id, input.label, member.email, input.orgRole, JSON.stringify(input.workspaces), input.createdBy, input.expiresAt, this._now());
|
|
325
|
+
return this.getInvite(id);
|
|
326
|
+
})();
|
|
327
|
+
return { invite, token };
|
|
328
|
+
}
|
|
329
|
+
getInvite(id) {
|
|
330
|
+
const row = this._stmt('SELECT * FROM invites WHERE id = ?').get(id);
|
|
331
|
+
return row ? toInvite(row) : null;
|
|
332
|
+
}
|
|
333
|
+
/** Invites not yet used (expired ones included, so they can be cleaned up). */
|
|
334
|
+
listInvites() {
|
|
335
|
+
return this._stmt('SELECT * FROM invites WHERE used_at IS NULL ORDER BY created_at DESC').all().map(toInvite);
|
|
336
|
+
}
|
|
337
|
+
/** Deletes an unused invite and the member it created, if that member never joined. */
|
|
338
|
+
deleteInvite(id) {
|
|
339
|
+
return this._db.transaction(() => {
|
|
340
|
+
const invite = this.getInvite(id);
|
|
341
|
+
if (!invite || invite.usedAt !== null)
|
|
342
|
+
return false;
|
|
343
|
+
this._stmt('DELETE FROM invites WHERE id = ?').run(id);
|
|
344
|
+
const member = this.getMember(invite.memberId);
|
|
345
|
+
if (member?.status === 'invited') {
|
|
346
|
+
this._stmt('DELETE FROM workspace_members WHERE member_id = ?').run(member.id);
|
|
347
|
+
this._stmt('DELETE FROM members WHERE id = ?').run(member.id);
|
|
348
|
+
}
|
|
349
|
+
return true;
|
|
350
|
+
})();
|
|
351
|
+
}
|
|
352
|
+
/** The invite behind a token hash while it is usable, without consuming it. */
|
|
353
|
+
peekInvite(tokenHash, now) {
|
|
354
|
+
const row = this._stmt('SELECT * FROM invites WHERE token_hash = ? AND used_at IS NULL AND expires_at > ?').get(tokenHash, now);
|
|
355
|
+
if (!row)
|
|
356
|
+
return null;
|
|
357
|
+
const member = this.getMember(row.member_id);
|
|
358
|
+
return member && member.status === 'invited' ? { invite: toInvite(row), member } : null;
|
|
359
|
+
}
|
|
360
|
+
/** Token hash of a member's newest live (unused, unexpired) invite, for SSO activation by email. */
|
|
361
|
+
liveInviteHash(memberId, now) {
|
|
362
|
+
const row = this._stmt('SELECT token_hash FROM invites WHERE member_id = ? AND used_at IS NULL AND expires_at > ? ORDER BY created_at DESC LIMIT 1')
|
|
363
|
+
.get(memberId, now);
|
|
364
|
+
return row?.token_hash ?? null;
|
|
365
|
+
}
|
|
366
|
+
/** Why an invite token is not usable: already used, expired, or unknown (also for a live one). */
|
|
367
|
+
inviteState(tokenHash, now) {
|
|
368
|
+
const row = this._stmt('SELECT used_at, expires_at FROM invites WHERE token_hash = ?').get(tokenHash);
|
|
369
|
+
if (!row)
|
|
370
|
+
return 'unknown';
|
|
371
|
+
if (row.used_at !== null)
|
|
372
|
+
return 'used';
|
|
373
|
+
return row.expires_at <= now ? 'expired' : 'unknown';
|
|
374
|
+
}
|
|
375
|
+
/** Marks the invite used and its member active; null when the token is unknown, used or expired. */
|
|
376
|
+
consumeInvite(tokenHash, now) {
|
|
377
|
+
return this._db.transaction(() => {
|
|
378
|
+
const found = this.peekInvite(tokenHash, now);
|
|
379
|
+
if (!found)
|
|
380
|
+
return null;
|
|
381
|
+
this._stmt('UPDATE invites SET used_at = ? WHERE id = ?').run(now, found.invite.id);
|
|
382
|
+
return this.setMemberStatus(found.member.id, 'active');
|
|
383
|
+
})();
|
|
90
384
|
}
|
|
91
385
|
// ── Keys ────────────────────────────────────────────────────────────────
|
|
386
|
+
/**
|
|
387
|
+
* Creates a key in `workspaceId`, or, for callers that only name an
|
|
388
|
+
* identity (the CLI), in the workspace paying with that identity.
|
|
389
|
+
*/
|
|
92
390
|
createKey(input) {
|
|
391
|
+
let workspace;
|
|
392
|
+
if (input.workspaceId) {
|
|
393
|
+
const found = this.getWorkspace(input.workspaceId);
|
|
394
|
+
if (!found)
|
|
395
|
+
throw new Error(`Unknown workspace "${input.workspaceId}".`);
|
|
396
|
+
if (input.buyerIdentity && input.buyerIdentity !== found.buyerIdentity) {
|
|
397
|
+
throw new Error(`Workspace "${found.name}" pays with identity "${found.buyerIdentity}", not "${input.buyerIdentity}".`);
|
|
398
|
+
}
|
|
399
|
+
workspace = found;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
workspace = this.ensureWorkspaceForIdentity(input.buyerIdentity ?? DEFAULT_BUYER_IDENTITY);
|
|
403
|
+
}
|
|
93
404
|
const generated = generateApiKey();
|
|
94
405
|
const id = newKeyId();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
406
|
+
const limits = fullLimits(input.limits);
|
|
407
|
+
const ownerLimits = input.ownerLimits ?? NO_BUDGET_LIMITS;
|
|
408
|
+
this._stmt(`
|
|
409
|
+
INSERT INTO api_keys (id, label, key_hash, key_hint, buyer_identity, workspace_id, owner_member_id, source,
|
|
410
|
+
daily_limit_usdc, weekly_limit_usdc, monthly_limit_usdc, total_limit_usdc, routing_policy,
|
|
411
|
+
owner_daily_limit_usdc, owner_weekly_limit_usdc, owner_monthly_limit_usdc, owner_total_limit_usdc, owner_routing_policy,
|
|
412
|
+
topup_enabled, expires_at, created_at)
|
|
413
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
414
|
+
`).run(id, input.label, generated.hash, generated.hint, workspace.buyerIdentity, workspace.id, input.ownerMemberId ?? null, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(input.routingPolicy), ownerLimits.daily, ownerLimits.weekly, ownerLimits.monthly, ownerLimits.total, policyJson(input.ownerRoutingPolicy), input.topupEnabled ? 1 : 0, input.expiresAt, this._now());
|
|
99
415
|
return { key: this.getKey(id), secret: generated.secret };
|
|
100
416
|
}
|
|
101
417
|
/**
|
|
@@ -105,17 +421,17 @@ export class GatewayStore {
|
|
|
105
421
|
*/
|
|
106
422
|
syncEnvironmentKey(secret) {
|
|
107
423
|
const hash = hashApiKey(secret);
|
|
108
|
-
const existing = this.
|
|
424
|
+
const existing = this._stmt("SELECT id FROM api_keys WHERE source = 'tunnel-env'").get();
|
|
109
425
|
if (existing) {
|
|
110
|
-
this.
|
|
426
|
+
this._stmt("UPDATE api_keys SET key_hash = ?, key_hint = ?, status = 'active', revoked_at = NULL WHERE id = ?")
|
|
111
427
|
.run(hash, apiKeyHint(secret), existing.id);
|
|
112
428
|
return this.getKey(existing.id);
|
|
113
429
|
}
|
|
114
430
|
const id = newKeyId();
|
|
115
|
-
this.
|
|
116
|
-
INSERT INTO api_keys (id, label, key_hash, key_hint, buyer_identity, source, created_at)
|
|
117
|
-
VALUES (?, 'Tunnel key', ?, ?, ?, 'tunnel-env', ?)
|
|
118
|
-
`).run(id, hash, apiKeyHint(secret), DEFAULT_BUYER_IDENTITY, this._now());
|
|
431
|
+
this._stmt(`
|
|
432
|
+
INSERT INTO api_keys (id, label, key_hash, key_hint, buyer_identity, workspace_id, source, created_at)
|
|
433
|
+
VALUES (?, 'Tunnel key', ?, ?, ?, ?, 'tunnel-env', ?)
|
|
434
|
+
`).run(id, hash, apiKeyHint(secret), DEFAULT_BUYER_IDENTITY, DEFAULT_WORKSPACE_ID, this._now());
|
|
119
435
|
return this.getKey(id);
|
|
120
436
|
}
|
|
121
437
|
/**
|
|
@@ -124,111 +440,829 @@ export class GatewayStore {
|
|
|
124
440
|
* revoked key, or null when there was no active one.
|
|
125
441
|
*/
|
|
126
442
|
retireEnvironmentKey() {
|
|
127
|
-
const existing = this.
|
|
443
|
+
const existing = this._stmt("SELECT id FROM api_keys WHERE source = 'tunnel-env' AND status = 'active'").get();
|
|
128
444
|
return existing ? this.revokeKey(existing.id) : null;
|
|
129
445
|
}
|
|
130
446
|
findKeyBySecret(secret) {
|
|
131
|
-
const row = this.
|
|
447
|
+
const row = this._stmt('SELECT * FROM api_keys WHERE key_hash = ?').get(hashApiKey(secret));
|
|
132
448
|
return row ? toKey(row) : null;
|
|
133
449
|
}
|
|
134
450
|
getKey(id) {
|
|
135
|
-
const row = this.
|
|
451
|
+
const row = this._stmt('SELECT * FROM api_keys WHERE id = ?').get(id);
|
|
136
452
|
return row ? toKey(row) : null;
|
|
137
453
|
}
|
|
138
|
-
listKeys() {
|
|
139
|
-
const
|
|
140
|
-
|
|
454
|
+
listKeys(filter = {}) {
|
|
455
|
+
const where = [];
|
|
456
|
+
const params = [];
|
|
457
|
+
if (filter.workspaceId) {
|
|
458
|
+
where.push('workspace_id = ?');
|
|
459
|
+
params.push(filter.workspaceId);
|
|
460
|
+
}
|
|
461
|
+
if (filter.ownerMemberId) {
|
|
462
|
+
where.push('owner_member_id = ?');
|
|
463
|
+
params.push(filter.ownerMemberId);
|
|
464
|
+
}
|
|
465
|
+
const sql = `SELECT * FROM api_keys ${whereClause(where)} ORDER BY created_at, id`;
|
|
466
|
+
return this._stmt(sql).all(...params).map(toKey);
|
|
141
467
|
}
|
|
142
468
|
countActiveKeys() {
|
|
143
|
-
return this.
|
|
469
|
+
return this._count("SELECT COUNT(*) AS count FROM api_keys WHERE status = 'active'");
|
|
470
|
+
}
|
|
471
|
+
countActiveKeysForMember(memberId) {
|
|
472
|
+
return this._count("SELECT COUNT(*) AS count FROM api_keys WHERE status = 'active' AND owner_member_id = ?", memberId);
|
|
144
473
|
}
|
|
145
474
|
setLimits(id, limits) {
|
|
146
475
|
const key = this.getKey(id);
|
|
147
476
|
if (!key)
|
|
148
477
|
throw new Error(`Unknown key "${id}".`);
|
|
149
478
|
const next = { ...key.limits, ...limits };
|
|
150
|
-
this.
|
|
151
|
-
.run(next.daily, next.monthly, next.total, id);
|
|
479
|
+
this._stmt('UPDATE api_keys SET daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?, total_limit_usdc = ? WHERE id = ?')
|
|
480
|
+
.run(next.daily, next.weekly ?? null, next.monthly, next.total, id);
|
|
152
481
|
return this.getKey(id);
|
|
153
482
|
}
|
|
154
483
|
setTopupEnabled(id, enabled) {
|
|
155
|
-
const result = this.
|
|
484
|
+
const result = this._stmt('UPDATE api_keys SET topup_enabled = ? WHERE id = ?').run(enabled ? 1 : 0, id);
|
|
156
485
|
if (result.changes === 0)
|
|
157
486
|
throw new Error(`Unknown key "${id}".`);
|
|
158
487
|
return this.getKey(id);
|
|
159
488
|
}
|
|
489
|
+
updateKey(id, patch) {
|
|
490
|
+
const key = this.getKey(id);
|
|
491
|
+
if (!key)
|
|
492
|
+
throw new Error(`Unknown key "${id}".`);
|
|
493
|
+
const limits = { ...key.limits, ...(patch.limits ?? {}) };
|
|
494
|
+
const ownerLimits = { ...key.ownerLimits, ...(patch.ownerLimits ?? {}) };
|
|
495
|
+
this._stmt(`
|
|
496
|
+
UPDATE api_keys SET label = ?, daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?, total_limit_usdc = ?,
|
|
497
|
+
routing_policy = ?, owner_daily_limit_usdc = ?, owner_weekly_limit_usdc = ?, owner_monthly_limit_usdc = ?, owner_total_limit_usdc = ?,
|
|
498
|
+
owner_routing_policy = ?, topup_enabled = ?, expires_at = ?, owner_member_id = ? WHERE id = ?
|
|
499
|
+
`).run(patch.label ?? key.label, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(patch.routingPolicy === undefined ? key.routingPolicy : patch.routingPolicy), ownerLimits.daily, ownerLimits.weekly, ownerLimits.monthly, ownerLimits.total, policyJson(patch.ownerRoutingPolicy === undefined ? key.ownerRoutingPolicy : patch.ownerRoutingPolicy), (patch.topupEnabled ?? key.topupEnabled) ? 1 : 0, patch.expiresAt === undefined ? key.expiresAt : patch.expiresAt, patch.ownerMemberId === undefined ? key.ownerMemberId : patch.ownerMemberId, id);
|
|
500
|
+
return this.getKey(id);
|
|
501
|
+
}
|
|
502
|
+
/** New secret for the same key id; usage history, limits and policy stay. */
|
|
503
|
+
rotateKey(id) {
|
|
504
|
+
const generated = generateApiKey();
|
|
505
|
+
const result = this._stmt("UPDATE api_keys SET key_hash = ?, key_hint = ? WHERE id = ? AND status = 'active'")
|
|
506
|
+
.run(generated.hash, generated.hint, id);
|
|
507
|
+
if (result.changes === 0)
|
|
508
|
+
throw new Error(this.getKey(id) ? `Key "${id}" is revoked.` : `Unknown key "${id}".`);
|
|
509
|
+
return { key: this.getKey(id), secret: generated.secret };
|
|
510
|
+
}
|
|
160
511
|
revokeKey(id) {
|
|
161
|
-
const result = this.
|
|
512
|
+
const result = this._stmt("UPDATE api_keys SET status = 'revoked', revoked_at = ? WHERE id = ? AND status = 'active'")
|
|
162
513
|
.run(this._now(), id);
|
|
163
514
|
if (result.changes === 0 && !this.getKey(id))
|
|
164
515
|
throw new Error(`Unknown key "${id}".`);
|
|
165
516
|
return this.getKey(id);
|
|
166
517
|
}
|
|
518
|
+
/** Revokes every active key a member owns; returns their ids. */
|
|
519
|
+
revokeMemberKeys(memberId) {
|
|
520
|
+
const ids = this._stmt("SELECT id FROM api_keys WHERE owner_member_id = ? AND status = 'active'").all(memberId)
|
|
521
|
+
.map((row) => row.id);
|
|
522
|
+
for (const id of ids)
|
|
523
|
+
this.revokeKey(id);
|
|
524
|
+
return ids;
|
|
525
|
+
}
|
|
526
|
+
/** Records use at most once a minute per key (per process), so requests do not each write. */
|
|
167
527
|
touchKey(id) {
|
|
168
|
-
|
|
528
|
+
const now = this._now();
|
|
529
|
+
if (!this._shouldTouch(`key:${id}`, now))
|
|
530
|
+
return;
|
|
531
|
+
this._stmt('UPDATE api_keys SET last_used_at = ? WHERE id = ?').run(now, id);
|
|
532
|
+
}
|
|
533
|
+
_shouldTouch(id, now) {
|
|
534
|
+
const last = this._touched.get(id);
|
|
535
|
+
if (last !== undefined && now - last < TOUCH_INTERVAL_MS && now >= last)
|
|
536
|
+
return false;
|
|
537
|
+
if (this._touched.size >= MAX_TRACKED_TOUCHES)
|
|
538
|
+
this._touched.clear();
|
|
539
|
+
this._touched.set(id, now);
|
|
540
|
+
return true;
|
|
169
541
|
}
|
|
170
542
|
// ── Requests ────────────────────────────────────────────────────────────
|
|
171
543
|
startRequest(input) {
|
|
172
|
-
this.
|
|
173
|
-
INSERT INTO gateway_requests (tag, key_id, buyer_identity, method, path, model, started_at)
|
|
174
|
-
VALUES (?, ?, ?, ?, ?, ?,
|
|
175
|
-
|
|
544
|
+
this._stmt(`
|
|
545
|
+
INSERT INTO gateway_requests (tag, key_id, buyer_identity, method, path, model, started_at, workspace_id, member_id, end_user)
|
|
546
|
+
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
547
|
+
COALESCE(?, (SELECT workspace_id FROM api_keys WHERE id = ?)),
|
|
548
|
+
COALESCE(?, (SELECT owner_member_id FROM api_keys WHERE id = ?)),
|
|
549
|
+
?)
|
|
550
|
+
`).run(input.tag, input.keyId, input.buyerIdentity, input.method, input.path, input.model, input.startedAt, input.workspaceId ?? null, input.keyId, input.memberId ?? null, input.keyId, input.endUser ?? null);
|
|
176
551
|
}
|
|
552
|
+
/** `error` is kept for failed requests (code ≤ 100, message ≤ 500 characters). */
|
|
177
553
|
finishRequest(tag, result) {
|
|
178
|
-
this.
|
|
179
|
-
|
|
554
|
+
this._stmt(`
|
|
555
|
+
UPDATE gateway_requests SET status = ?, buyer_request_id = ?, finished_at = ?, error_code = ?, error_message = ? WHERE tag = ?
|
|
556
|
+
`).run(result.status, result.buyerRequestId, this._now(), result.error?.code?.slice(0, MAX_ERROR_CODE) || null, result.error?.message?.slice(0, MAX_ERROR_MESSAGE) || null, tag);
|
|
557
|
+
}
|
|
558
|
+
/** Seller, latency and end user of a request; null fields leave what is stored. */
|
|
559
|
+
recordRequestOutcome(tag, outcome) {
|
|
560
|
+
this._stmt(`
|
|
561
|
+
UPDATE gateway_requests SET seller_peer_id = COALESCE(?, seller_peer_id), latency_ms = COALESCE(?, latency_ms),
|
|
562
|
+
end_user = COALESCE(?, end_user), served_free = MAX(served_free, ?) WHERE tag = ?
|
|
563
|
+
`).run(outcome.sellerPeerId ?? null, outcome.latencyMs ?? null, outcome.endUser ?? null, outcome.servedFree ? 1 : 0, tag);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* The token counts the response itself reported, read by the gateway as it
|
|
567
|
+
* streamed past. Usage falls back to them while (or if ever) the request
|
|
568
|
+
* has no spend with token counts in the ledger, so they never add up twice.
|
|
569
|
+
*/
|
|
570
|
+
recordResponseUsage(tag, usage) {
|
|
571
|
+
this._stmt(`
|
|
572
|
+
UPDATE gateway_requests SET usage_input_tokens = ?, usage_cached_input_tokens = ?, usage_output_tokens = ? WHERE tag = ?
|
|
573
|
+
`).run(usage.inputTokens, usage.cachedInputTokens, usage.outputTokens, tag);
|
|
574
|
+
}
|
|
575
|
+
/** Only called while content logging is on. */
|
|
576
|
+
recordRequestContent(tag, content) {
|
|
577
|
+
this._stmt(`
|
|
578
|
+
UPDATE gateway_requests SET request_body = COALESCE(?, request_body), response_body = COALESCE(?, response_body) WHERE tag = ?
|
|
579
|
+
`).run(content.requestBody ?? null, content.responseBody ?? null, tag);
|
|
580
|
+
}
|
|
581
|
+
/** Forgets every stored request and response body; for when content logging is turned off. */
|
|
582
|
+
clearRequestContent() {
|
|
583
|
+
return this._stmt(`
|
|
584
|
+
UPDATE gateway_requests SET request_body = NULL, response_body = NULL WHERE request_body IS NOT NULL OR response_body IS NOT NULL
|
|
585
|
+
`).run().changes;
|
|
180
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Drops request-log rows started before `before`, `chunkSize` rows per
|
|
589
|
+
* transaction so a large backlog never holds the write lock for long. Their
|
|
590
|
+
* request counts move into `request_rollups` (per hour, key, workspace,
|
|
591
|
+
* member, model, seller and end user) so usage reports keep counting them;
|
|
592
|
+
* spend and tokens live in the ledger, which is never pruned. Pruned
|
|
593
|
+
* periods are therefore counted at hour granularity. Yields to the event
|
|
594
|
+
* loop between chunks, so requests keep flowing while a backlog drains.
|
|
595
|
+
*/
|
|
596
|
+
async pruneRequests(before, chunkSize = 5_000) {
|
|
597
|
+
const select = this._stmt('SELECT rowid FROM gateway_requests WHERE started_at < ? ORDER BY started_at LIMIT ?').pluck();
|
|
598
|
+
const rollup = this._stmt(`
|
|
599
|
+
INSERT INTO request_rollups (hour_start, key_id, workspace_id, member_id, model, seller_peer_id, end_user, requests, failed_requests)
|
|
600
|
+
SELECT (r.started_at / ${HOUR_MS}) * ${HOUR_MS}, r.key_id, r.workspace_id, r.member_id, r.model, r.seller_peer_id, r.end_user,
|
|
601
|
+
COUNT(*), SUM(${FAILED_SQL})
|
|
602
|
+
FROM gateway_requests r WHERE r.rowid IN (SELECT value FROM json_each(?))
|
|
603
|
+
GROUP BY 1, 2, 3, 4, 5, 6, 7
|
|
604
|
+
`);
|
|
605
|
+
const remove = this._stmt('DELETE FROM gateway_requests WHERE rowid IN (SELECT value FROM json_each(?))');
|
|
606
|
+
const pruneChunk = this._db.transaction(() => {
|
|
607
|
+
const ids = select.all(before, chunkSize);
|
|
608
|
+
if (ids.length === 0)
|
|
609
|
+
return 0;
|
|
610
|
+
const json = JSON.stringify(ids);
|
|
611
|
+
rollup.run(json);
|
|
612
|
+
remove.run(json);
|
|
613
|
+
return ids.length;
|
|
614
|
+
});
|
|
615
|
+
let removed = 0;
|
|
616
|
+
for (;;) {
|
|
617
|
+
const count = pruneChunk.immediate();
|
|
618
|
+
removed += count;
|
|
619
|
+
if (count < chunkSize)
|
|
620
|
+
return removed;
|
|
621
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/** Who a request was attributed to when it started. */
|
|
181
625
|
findRequest(tag) {
|
|
182
|
-
const row = this.
|
|
183
|
-
return row
|
|
626
|
+
const row = this._stmt('SELECT key_id, buyer_identity, workspace_id, member_id, model, end_user FROM gateway_requests WHERE tag = ?').get(tag);
|
|
627
|
+
return row
|
|
628
|
+
? { keyId: row.key_id, buyerIdentity: row.buyer_identity, workspaceId: row.workspace_id, memberId: row.member_id, model: row.model, endUser: row.end_user }
|
|
629
|
+
: null;
|
|
630
|
+
}
|
|
631
|
+
/** Requests per seller since a time, with the median latency this gateway measured. */
|
|
632
|
+
peerStats(sinceMs) {
|
|
633
|
+
const rows = this._stmt(`
|
|
634
|
+
SELECT seller_peer_id AS peer, latency_ms AS latency FROM gateway_requests
|
|
635
|
+
WHERE started_at >= ? AND seller_peer_id IS NOT NULL
|
|
636
|
+
`).all(sinceMs);
|
|
637
|
+
const byPeer = new Map();
|
|
638
|
+
for (const row of rows) {
|
|
639
|
+
const entry = byPeer.get(row.peer) ?? { requests: 0, latencies: [] };
|
|
640
|
+
entry.requests += 1;
|
|
641
|
+
if (row.latency !== null)
|
|
642
|
+
entry.latencies.push(row.latency);
|
|
643
|
+
byPeer.set(row.peer, entry);
|
|
644
|
+
}
|
|
645
|
+
return [...byPeer].map(([peerId, entry]) => {
|
|
646
|
+
const sorted = entry.latencies.sort((a, b) => a - b);
|
|
647
|
+
return { peerId, requests: entry.requests, latencyMsP50: sorted.length ? sorted[Math.floor((sorted.length - 1) / 2)] : null };
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/** Newest first, ordered by (startedAt, tag); pass the last row as `before` for the next page. Bodies are not read. */
|
|
651
|
+
listRequests(filter) {
|
|
652
|
+
const { where, params } = scopeSql(filter, REQUEST_COLUMNS);
|
|
653
|
+
if (filter.status === 'ok')
|
|
654
|
+
where.push('r.status IS NOT NULL AND r.status < 400');
|
|
655
|
+
else if (filter.status === 'error')
|
|
656
|
+
where.push(FAILED_SQL);
|
|
657
|
+
else if (typeof filter.status === 'number') {
|
|
658
|
+
where.push('r.status = ?');
|
|
659
|
+
params.push(filter.status);
|
|
660
|
+
}
|
|
661
|
+
const q = filter.q?.trim();
|
|
662
|
+
if (q) {
|
|
663
|
+
const pattern = `%${escapeLike(q)}%`;
|
|
664
|
+
const columns = ['r.model', 'k.label', 'r.end_user', 'r.tag', 'r.seller_peer_id', 'r.path', 'r.error_code', 'r.error_message'];
|
|
665
|
+
where.push(`(${columns.map((c) => `${c} LIKE ? ESCAPE '\\'`).join(' OR ')})`);
|
|
666
|
+
params.push(...columns.map(() => pattern));
|
|
667
|
+
}
|
|
668
|
+
if (filter.before) {
|
|
669
|
+
where.push('(r.started_at < ? OR (r.started_at = ? AND r.tag < ?))');
|
|
670
|
+
params.push(filter.before.startedAt, filter.before.startedAt, filter.before.tag);
|
|
671
|
+
}
|
|
672
|
+
const limit = Math.max(1, Math.min(filter.limit ?? 50, 100_000));
|
|
673
|
+
return this._requestRows(where, params, limit, false);
|
|
674
|
+
}
|
|
675
|
+
/** One request with its bodies; `keyIds` limits it to what the caller may see. */
|
|
676
|
+
getRequest(tag, keyIds = null) {
|
|
677
|
+
const { where, params } = scopeSql({ keyIds }, REQUEST_COLUMNS);
|
|
678
|
+
where.push('r.tag = ?');
|
|
679
|
+
params.push(tag);
|
|
680
|
+
return this._requestRows(where, params, 1, true)[0] ?? null;
|
|
681
|
+
}
|
|
682
|
+
/** The page of requests first, then one grouped ledger read for just those tags. */
|
|
683
|
+
_requestRows(where, params, limit, withBodies) {
|
|
684
|
+
const rows = this._stmt(`
|
|
685
|
+
WITH page AS (
|
|
686
|
+
SELECT r.tag, r.started_at, r.finished_at, r.key_id, k.label AS key_label, COALESCE(r.workspace_id, k.workspace_id) AS ws_id,
|
|
687
|
+
r.member_id, r.end_user, r.method, r.path, r.model, r.status, r.seller_peer_id, r.latency_ms, r.error_code, r.error_message,
|
|
688
|
+
r.served_free, r.usage_input_tokens, r.usage_cached_input_tokens, r.usage_output_tokens
|
|
689
|
+
${withBodies ? ', r.request_body, r.response_body' : ''}
|
|
690
|
+
FROM gateway_requests r LEFT JOIN api_keys k ON k.id = r.key_id
|
|
691
|
+
${whereClause(where)}
|
|
692
|
+
ORDER BY r.started_at DESC, r.tag DESC LIMIT ?
|
|
693
|
+
),
|
|
694
|
+
spend AS (
|
|
695
|
+
SELECT l.request_tag AS tag, -SUM(l.amount_usdc) AS spent, SUM(l.input_tokens) AS in_tokens,
|
|
696
|
+
SUM(l.cached_input_tokens) AS cached_tokens, SUM(l.output_tokens) AS out_tokens
|
|
697
|
+
FROM ledger_entries l WHERE l.kind = 'spend' AND l.request_tag IN (SELECT tag FROM page) GROUP BY l.request_tag
|
|
698
|
+
)
|
|
699
|
+
SELECT page.*, spend.spent, COALESCE(spend.in_tokens, 0) AS in_tokens, COALESCE(spend.cached_tokens, 0) AS cached_tokens,
|
|
700
|
+
COALESCE(spend.out_tokens, 0) AS out_tokens
|
|
701
|
+
FROM page LEFT JOIN spend ON spend.tag = page.tag
|
|
702
|
+
ORDER BY page.started_at DESC, page.tag DESC
|
|
703
|
+
`).all(...params, limit);
|
|
704
|
+
return rows.map((row) => {
|
|
705
|
+
// Ledger tokens when the spend reported any, else what the response said.
|
|
706
|
+
const ledgerTokens = row['in_tokens'] + row['cached_tokens'] + row['out_tokens'] > 0;
|
|
707
|
+
const status = row['status'];
|
|
708
|
+
const servedFree = row['served_free'] === 1;
|
|
709
|
+
return {
|
|
710
|
+
tag: row['tag'],
|
|
711
|
+
startedAt: row['started_at'],
|
|
712
|
+
finishedAt: row['finished_at'],
|
|
713
|
+
keyId: row['key_id'],
|
|
714
|
+
keyLabel: row['key_label'] ?? row['key_id'],
|
|
715
|
+
workspaceId: row['ws_id'] ?? DEFAULT_WORKSPACE_ID,
|
|
716
|
+
memberId: row['member_id'],
|
|
717
|
+
endUser: row['end_user'],
|
|
718
|
+
method: row['method'],
|
|
719
|
+
path: row['path'],
|
|
720
|
+
model: row['model'],
|
|
721
|
+
status: row['status'],
|
|
722
|
+
sellerPeerId: row['seller_peer_id'],
|
|
723
|
+
latencyMs: row['latency_ms'],
|
|
724
|
+
spentUsdc: row['spent'],
|
|
725
|
+
inputTokens: ledgerTokens ? row['in_tokens'] : row['usage_input_tokens'] ?? 0,
|
|
726
|
+
cachedInputTokens: ledgerTokens ? row['cached_tokens'] : row['usage_cached_input_tokens'] ?? 0,
|
|
727
|
+
outputTokens: ledgerTokens ? row['out_tokens'] : row['usage_output_tokens'] ?? 0,
|
|
728
|
+
errorCode: row['error_code'],
|
|
729
|
+
errorMessage: row['error_message'],
|
|
730
|
+
servedFree,
|
|
731
|
+
costPending: row['spent'] === null && !servedFree && row['model'] !== null && (status === null || status < 400),
|
|
732
|
+
...(withBodies ? { requestBody: row['request_body'], responseBody: row['response_body'] } : {}),
|
|
733
|
+
};
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Totals plus one row per group (each split by `splitBy` when given),
|
|
738
|
+
* attributed as at request time. Request counts come from the request log
|
|
739
|
+
* plus the rollups of pruned rows; spend and tokens from the ledger, and
|
|
740
|
+
* the tokens of requests whose spend carries none (not reported yet, or
|
|
741
|
+
* never) from the usage their responses reported.
|
|
742
|
+
*/
|
|
743
|
+
usageReport(filter, groupBy, splitBy = null) {
|
|
744
|
+
const select = (columns) => `${groupExpression(groupBy, columns)} AS g, ${splitBy ? groupExpression(splitBy, columns) : 'NULL'} AS s`;
|
|
745
|
+
const req = scopeSql(filter, REQUEST_COLUMNS);
|
|
746
|
+
const requestRows = this._stmt(`
|
|
747
|
+
SELECT ${select(REQUEST_COLUMNS)}, COUNT(*) AS requests, COALESCE(SUM(${FAILED_SQL}), 0) AS failed
|
|
748
|
+
FROM gateway_requests r ${whereClause(req.where)} GROUP BY g, s
|
|
749
|
+
`).all(...req.params);
|
|
750
|
+
const roll = scopeSql(filter, ROLLUP_COLUMNS);
|
|
751
|
+
const rollupRows = this._stmt(`
|
|
752
|
+
SELECT ${select(ROLLUP_COLUMNS)}, SUM(u.requests) AS requests, SUM(u.failed_requests) AS failed
|
|
753
|
+
FROM request_rollups u ${whereClause(roll.where)} GROUP BY g, s
|
|
754
|
+
`).all(...roll.params);
|
|
755
|
+
const led = scopeSql(filter, LEDGER_COLUMNS);
|
|
756
|
+
led.where.push("l.kind = 'spend'");
|
|
757
|
+
const ledgerRows = this._stmt(`
|
|
758
|
+
SELECT ${select(LEDGER_COLUMNS)}, COALESCE(-SUM(l.amount_usdc), 0) AS spent, COALESCE(SUM(l.input_tokens), 0) AS input_tokens,
|
|
759
|
+
COALESCE(SUM(l.cached_input_tokens), 0) AS cached_input_tokens, COALESCE(SUM(l.output_tokens), 0) AS output_tokens
|
|
760
|
+
FROM ledger_entries l ${whereClause(led.where)} GROUP BY g, s
|
|
761
|
+
`).all(...led.params);
|
|
762
|
+
const fallback = scopeSql(filter, REQUEST_COLUMNS);
|
|
763
|
+
fallback.where.push(REPORTED_TOKENS_ONLY_SQL);
|
|
764
|
+
const responseRows = this._stmt(`
|
|
765
|
+
SELECT ${select(REQUEST_COLUMNS)}, SUM(r.usage_input_tokens) AS input_tokens, COALESCE(SUM(r.usage_cached_input_tokens), 0) AS cached_input_tokens,
|
|
766
|
+
COALESCE(SUM(r.usage_output_tokens), 0) AS output_tokens
|
|
767
|
+
FROM gateway_requests r ${whereClause(fallback.where)} GROUP BY g, s
|
|
768
|
+
`).all(...fallback.params);
|
|
769
|
+
const groups = new Map();
|
|
770
|
+
const entries = (group, split) => {
|
|
771
|
+
let found = groups.get(group);
|
|
772
|
+
if (!found) {
|
|
773
|
+
found = { totals: emptyUsageTotals(), splits: new Map() };
|
|
774
|
+
groups.set(group, found);
|
|
775
|
+
}
|
|
776
|
+
if (!splitBy)
|
|
777
|
+
return [found.totals];
|
|
778
|
+
let part = found.splits.get(split);
|
|
779
|
+
if (!part) {
|
|
780
|
+
part = emptyUsageTotals();
|
|
781
|
+
found.splits.set(split, part);
|
|
782
|
+
}
|
|
783
|
+
return [found.totals, part];
|
|
784
|
+
};
|
|
785
|
+
for (const row of [...requestRows, ...rollupRows]) {
|
|
786
|
+
for (const target of entries(row.g, row.s)) {
|
|
787
|
+
target.requests += row.requests;
|
|
788
|
+
target.failedRequests += row.failed;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const tokenRows = [...ledgerRows, ...responseRows];
|
|
792
|
+
for (const row of tokenRows) {
|
|
793
|
+
for (const target of entries(row.g, row.s)) {
|
|
794
|
+
target.spentUsdc += row.spent ?? 0;
|
|
795
|
+
target.inputTokens += row.input_tokens;
|
|
796
|
+
target.cachedInputTokens += row.cached_input_tokens;
|
|
797
|
+
target.outputTokens += row.output_tokens;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const totals = emptyUsageTotals();
|
|
801
|
+
for (const { totals: value } of groups.values()) {
|
|
802
|
+
for (const field of Object.keys(totals))
|
|
803
|
+
totals[field] += value[field];
|
|
804
|
+
}
|
|
805
|
+
if (!groupBy)
|
|
806
|
+
return { totals, groups: [] };
|
|
807
|
+
const bySize = (a, b) => b.spentUsdc - a.spentUsdc || b.requests - a.requests;
|
|
808
|
+
const list = [...groups].map(([group, entry]) => ({
|
|
809
|
+
group,
|
|
810
|
+
...entry.totals,
|
|
811
|
+
...(splitBy ? { splits: capUserGroups(splitBy, [...entry.splits].map(([split, part]) => ({ group: split, ...part })).sort(bySize)) } : {}),
|
|
812
|
+
}));
|
|
813
|
+
if (isTimeGrouping(groupBy))
|
|
814
|
+
list.sort((a, b) => String(a.group).localeCompare(String(b.group)));
|
|
815
|
+
else
|
|
816
|
+
list.sort(bySize);
|
|
817
|
+
return { totals, groups: capUserGroups(groupBy, list) };
|
|
184
818
|
}
|
|
185
819
|
// ── Ledger ──────────────────────────────────────────────────────────────
|
|
186
|
-
/**
|
|
820
|
+
/**
|
|
821
|
+
* Returns false when the entry was already recorded (same externalRef).
|
|
822
|
+
* The entry is attributed to the workspace, member, model and end user of
|
|
823
|
+
* its request (or, without one, of its key as it is now) unless given.
|
|
824
|
+
*/
|
|
187
825
|
recordLedgerEntry(entry) {
|
|
188
826
|
if (!Number.isSafeInteger(entry.amountUsdc) || entry.amountUsdc < 0) {
|
|
189
827
|
throw new Error('Ledger amounts are non-negative integer USDC base units; the kind sets the sign.');
|
|
190
828
|
}
|
|
191
829
|
const signed = entry.kind === 'spend' ? -entry.amountUsdc : entry.amountUsdc;
|
|
192
|
-
const
|
|
830
|
+
const request = entry.requestTag ? this.findRequest(entry.requestTag) : null;
|
|
831
|
+
const key = request ? null : this.getKey(entry.keyId);
|
|
832
|
+
const workspaceId = entry.workspaceId ?? request?.workspaceId ?? key?.workspaceId ?? null;
|
|
833
|
+
let memberId = entry.memberId;
|
|
834
|
+
if (memberId === undefined)
|
|
835
|
+
memberId = request ? request.memberId : key?.ownerMemberId ?? null;
|
|
836
|
+
const result = this._stmt(`
|
|
193
837
|
INSERT OR IGNORE INTO ledger_entries
|
|
194
838
|
(kind, key_id, buyer_identity, amount_usdc, external_ref, request_tag, seller_peer_id,
|
|
195
|
-
input_tokens, cached_input_tokens, output_tokens, note, created_at)
|
|
196
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
197
|
-
`).run(entry.kind, entry.keyId, entry.buyerIdentity, signed, entry.externalRef, entry.requestTag ?? null, entry.sellerPeerId ?? null, entry.inputTokens ?? 0, entry.cachedInputTokens ?? 0, entry.outputTokens ?? 0, entry.note ?? null, entry.createdAt);
|
|
839
|
+
input_tokens, cached_input_tokens, output_tokens, note, created_at, workspace_id, member_id, model, end_user)
|
|
840
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
841
|
+
`).run(entry.kind, entry.keyId, entry.buyerIdentity, signed, entry.externalRef, entry.requestTag ?? null, entry.sellerPeerId ?? null, entry.inputTokens ?? 0, entry.cachedInputTokens ?? 0, entry.outputTokens ?? 0, entry.note ?? null, entry.createdAt, workspaceId, memberId, entry.model ?? request?.model ?? null, entry.endUser ?? request?.endUser ?? null);
|
|
198
842
|
return result.changes > 0;
|
|
199
843
|
}
|
|
844
|
+
/** A key's spend for the periods `antseed gateway key` shows. */
|
|
200
845
|
periodSpend(keyId, now = this._now()) {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
846
|
+
const all = this.spendByPeriod({ keyId }, now);
|
|
847
|
+
return Object.fromEntries(LIMIT_PERIODS.map((period) => [period, all[period]]));
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Settled spend per budget period of a key, member or workspace, as
|
|
851
|
+
* attributed when it was spent. Only `periods` are summed (the rest are 0),
|
|
852
|
+
* in one indexed query.
|
|
853
|
+
*/
|
|
854
|
+
spendByPeriod(scope, now = this._now(), periods = BUDGET_PERIODS) {
|
|
855
|
+
const spend = { daily: 0, weekly: 0, monthly: 0, total: 0 };
|
|
856
|
+
if (periods.length === 0)
|
|
857
|
+
return spend;
|
|
858
|
+
// Read from the rollups the ledger trigger keeps (see migration v3):
|
|
859
|
+
// the lifetime total is one row, a dated period at most 31 daily rows,
|
|
860
|
+
// however long the ledger grows. Periods start at UTC midnight, so they
|
|
861
|
+
// align with the daily rows.
|
|
862
|
+
const { kind, id } = budgetScopeRow(scope);
|
|
863
|
+
if (periods.includes('total')) {
|
|
864
|
+
const row = this._stmt('SELECT total_usdc FROM spend_totals WHERE scope_kind = ? AND scope_id = ?').get(kind, id);
|
|
865
|
+
spend.total = row?.total_usdc ?? 0;
|
|
866
|
+
}
|
|
867
|
+
const dated = periods.filter((period) => period !== 'total');
|
|
868
|
+
if (dated.length > 0) {
|
|
869
|
+
const starts = dated.map((period) => periodStart(period, now));
|
|
870
|
+
const row = this._stmt(`
|
|
871
|
+
SELECT ${dated.map((_, index) => `COALESCE(SUM(CASE WHEN day_start >= ? THEN amount_usdc ELSE 0 END), 0) AS p${index}`).join(', ')}
|
|
872
|
+
FROM spend_daily WHERE scope_kind = ? AND scope_id = ? AND day_start >= ?
|
|
873
|
+
`).get(...starts, kind, id, Math.min(...starts));
|
|
874
|
+
dated.forEach((period, index) => { spend[period] = row[`p${index}`] ?? 0; });
|
|
205
875
|
}
|
|
206
876
|
return spend;
|
|
207
877
|
}
|
|
208
878
|
usageStats(keyId, since = 0) {
|
|
209
|
-
const requests = this.
|
|
210
|
-
SELECT COUNT(*) AS requests,
|
|
211
|
-
|
|
212
|
-
FROM gateway_requests WHERE key_id = ? AND started_at >= ?
|
|
879
|
+
const requests = this._stmt(`
|
|
880
|
+
SELECT COUNT(*) AS requests, COALESCE(SUM(${FAILED_SQL}), 0) AS failed
|
|
881
|
+
FROM gateway_requests r WHERE r.key_id = ? AND r.started_at >= ?
|
|
213
882
|
`).get(keyId, since);
|
|
214
|
-
const
|
|
883
|
+
const rolled = this._stmt(`
|
|
884
|
+
SELECT COALESCE(SUM(requests), 0) AS requests, COALESCE(SUM(failed_requests), 0) AS failed
|
|
885
|
+
FROM request_rollups WHERE key_id = ? AND hour_start >= ?
|
|
886
|
+
`).get(keyId, since);
|
|
887
|
+
const ledger = this._stmt(`
|
|
215
888
|
SELECT COALESCE(-SUM(CASE WHEN kind = 'spend' THEN amount_usdc ELSE 0 END), 0) AS spent,
|
|
216
889
|
COALESCE(SUM(CASE WHEN kind = 'credit' THEN amount_usdc ELSE 0 END), 0) AS credited,
|
|
217
890
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
218
891
|
COALESCE(SUM(cached_input_tokens), 0) AS cached_input_tokens,
|
|
219
892
|
COALESCE(SUM(output_tokens), 0) AS output_tokens
|
|
220
893
|
FROM ledger_entries WHERE key_id = ? AND created_at >= ?
|
|
894
|
+
`).get(keyId, since);
|
|
895
|
+
// As in the usage report, requests without metered tokens count the response's own.
|
|
896
|
+
const reported = this._stmt(`
|
|
897
|
+
SELECT COALESCE(SUM(r.usage_input_tokens), 0) AS input_tokens,
|
|
898
|
+
COALESCE(SUM(r.usage_cached_input_tokens), 0) AS cached_input_tokens,
|
|
899
|
+
COALESCE(SUM(r.usage_output_tokens), 0) AS output_tokens
|
|
900
|
+
FROM gateway_requests r
|
|
901
|
+
WHERE r.key_id = ? AND r.started_at >= ? AND ${REPORTED_TOKENS_ONLY_SQL}
|
|
221
902
|
`).get(keyId, since);
|
|
222
903
|
return {
|
|
223
|
-
requests: requests.requests,
|
|
224
|
-
failedRequests: requests.failed,
|
|
904
|
+
requests: requests.requests + rolled.requests,
|
|
905
|
+
failedRequests: requests.failed + rolled.failed,
|
|
225
906
|
spentUsdc: ledger.spent,
|
|
226
907
|
creditedUsdc: ledger.credited,
|
|
227
|
-
inputTokens: ledger.input_tokens,
|
|
228
|
-
cachedInputTokens: ledger.cached_input_tokens,
|
|
229
|
-
outputTokens: ledger.output_tokens,
|
|
908
|
+
inputTokens: ledger.input_tokens + reported.input_tokens,
|
|
909
|
+
cachedInputTokens: ledger.cached_input_tokens + reported.cached_input_tokens,
|
|
910
|
+
outputTokens: ledger.output_tokens + reported.output_tokens,
|
|
230
911
|
};
|
|
231
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Lifetime requests and spend, plus spend since `since`, for many keys in
|
|
915
|
+
* one grouped query per table (rather than `usageStats` per key). Every
|
|
916
|
+
* id asked for gets an entry.
|
|
917
|
+
*/
|
|
918
|
+
keyUsageSummaries(keyIds, since) {
|
|
919
|
+
const summaries = new Map(keyIds.map((id) => [id, { requests: 0, spentUsdc: 0, spentSinceUsdc: 0 }]));
|
|
920
|
+
if (keyIds.length === 0)
|
|
921
|
+
return summaries;
|
|
922
|
+
const ids = JSON.stringify(keyIds);
|
|
923
|
+
const requests = this._stmt(`
|
|
924
|
+
SELECT key_id, COUNT(*) AS requests FROM gateway_requests
|
|
925
|
+
WHERE key_id IN (SELECT value FROM json_each(?)) GROUP BY key_id
|
|
926
|
+
`).all(ids);
|
|
927
|
+
const rolled = this._stmt(`
|
|
928
|
+
SELECT key_id, SUM(requests) AS requests FROM request_rollups
|
|
929
|
+
WHERE key_id IN (SELECT value FROM json_each(?)) GROUP BY key_id
|
|
930
|
+
`).all(ids);
|
|
931
|
+
for (const row of [...requests, ...rolled]) {
|
|
932
|
+
const summary = summaries.get(row.key_id);
|
|
933
|
+
if (summary)
|
|
934
|
+
summary.requests += row.requests;
|
|
935
|
+
}
|
|
936
|
+
const spend = this._stmt(`
|
|
937
|
+
SELECT key_id, -SUM(amount_usdc) AS spent, -SUM(CASE WHEN created_at >= ? THEN amount_usdc ELSE 0 END) AS spent_since
|
|
938
|
+
FROM ledger_entries WHERE key_id IN (SELECT value FROM json_each(?)) AND kind = 'spend' GROUP BY key_id
|
|
939
|
+
`).all(since, ids);
|
|
940
|
+
for (const row of spend) {
|
|
941
|
+
const summary = summaries.get(row.key_id);
|
|
942
|
+
if (!summary)
|
|
943
|
+
continue;
|
|
944
|
+
summary.spentUsdc = row.spent;
|
|
945
|
+
summary.spentSinceUsdc = row.spent_since;
|
|
946
|
+
}
|
|
947
|
+
return summaries;
|
|
948
|
+
}
|
|
949
|
+
// ── Peer lists ──────────────────────────────────────────────────────────
|
|
950
|
+
listPeerLists() {
|
|
951
|
+
return this._stmt('SELECT * FROM peer_lists ORDER BY created_at, id').all().map(toPeerList);
|
|
952
|
+
}
|
|
953
|
+
getPeerList(id) {
|
|
954
|
+
const row = this._stmt('SELECT * FROM peer_lists WHERE id = ?').get(id);
|
|
955
|
+
return row ? toPeerList(row) : null;
|
|
956
|
+
}
|
|
957
|
+
createPeerList(input) {
|
|
958
|
+
const id = newId('pl');
|
|
959
|
+
this._stmt('INSERT INTO peer_lists (id, name, description, peer_ids, created_at) VALUES (?, ?, ?, ?, ?)')
|
|
960
|
+
.run(id, input.name, input.description, JSON.stringify(input.peerIds), this._now());
|
|
961
|
+
return this.getPeerList(id);
|
|
962
|
+
}
|
|
963
|
+
updatePeerList(id, patch) {
|
|
964
|
+
const current = this.getPeerList(id);
|
|
965
|
+
if (!current)
|
|
966
|
+
throw new Error(`Unknown peer list "${id}".`);
|
|
967
|
+
this._stmt('UPDATE peer_lists SET name = ?, description = ?, peer_ids = ? WHERE id = ?').run(patch.name ?? current.name, patch.description === undefined ? current.description : patch.description, JSON.stringify(patch.peerIds ?? current.peerIds), id);
|
|
968
|
+
return this.getPeerList(id);
|
|
969
|
+
}
|
|
970
|
+
deletePeerList(id) {
|
|
971
|
+
return this._stmt('DELETE FROM peer_lists WHERE id = ?').run(id).changes > 0;
|
|
972
|
+
}
|
|
973
|
+
// ── Presets ─────────────────────────────────────────────────────────────
|
|
974
|
+
listPresets() {
|
|
975
|
+
return this._stmt('SELECT * FROM presets ORDER BY created_at, id').all().map(toPreset);
|
|
976
|
+
}
|
|
977
|
+
getPreset(id) {
|
|
978
|
+
const row = this._stmt('SELECT * FROM presets WHERE id = ?').get(id);
|
|
979
|
+
return row ? toPreset(row) : null;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* An org-wide preset wins over a workspace's own with the same slug, so a
|
|
983
|
+
* workspace admin cannot replace an org-mandated preset (and its system
|
|
984
|
+
* prompt) for their workspace.
|
|
985
|
+
*/
|
|
986
|
+
findPresetBySlug(slug, workspaceId) {
|
|
987
|
+
const row = this._stmt(`
|
|
988
|
+
SELECT * FROM presets WHERE slug = ? AND (workspace_id IS NULL OR workspace_id = ?)
|
|
989
|
+
ORDER BY workspace_id IS NOT NULL LIMIT 1
|
|
990
|
+
`).get(slug, workspaceId);
|
|
991
|
+
return row ? toPreset(row) : null;
|
|
992
|
+
}
|
|
993
|
+
createPreset(input) {
|
|
994
|
+
const id = newId('pre');
|
|
995
|
+
this._stmt(`
|
|
996
|
+
INSERT INTO presets (id, slug, name, workspace_id, model, routing_policy, system_prompt, params, created_at)
|
|
997
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
998
|
+
`).run(id, input.slug, input.name, input.workspaceId, input.model, policyJson(input.routingPolicy), input.systemPrompt, JSON.stringify(input.params ?? {}), this._now());
|
|
999
|
+
return this.getPreset(id);
|
|
1000
|
+
}
|
|
1001
|
+
updatePreset(id, patch) {
|
|
1002
|
+
const current = this.getPreset(id);
|
|
1003
|
+
if (!current)
|
|
1004
|
+
throw new Error(`Unknown preset "${id}".`);
|
|
1005
|
+
const next = { ...current, ...patch };
|
|
1006
|
+
this._stmt(`
|
|
1007
|
+
UPDATE presets SET slug = ?, name = ?, model = ?, routing_policy = ?, system_prompt = ?, params = ? WHERE id = ?
|
|
1008
|
+
`).run(next.slug, next.name, next.model, policyJson(next.routingPolicy), next.systemPrompt, JSON.stringify(next.params ?? {}), id);
|
|
1009
|
+
return this.getPreset(id);
|
|
1010
|
+
}
|
|
1011
|
+
deletePreset(id) {
|
|
1012
|
+
return this._stmt('DELETE FROM presets WHERE id = ?').run(id).changes > 0;
|
|
1013
|
+
}
|
|
1014
|
+
// ── Management tokens ───────────────────────────────────────────────────
|
|
1015
|
+
/**
|
|
1016
|
+
* `createdBy` is the creating member's id (null from the CLI); the token
|
|
1017
|
+
* stops working when that member is no longer an active admin or owner.
|
|
1018
|
+
* `expiresAt` null never expires.
|
|
1019
|
+
*/
|
|
1020
|
+
createAdminToken(input) {
|
|
1021
|
+
const secret = `${ADMIN_TOKEN_PREFIX}${randomBytes(32).toString('base64url')}`;
|
|
1022
|
+
const id = newId('tok');
|
|
1023
|
+
this._stmt('INSERT INTO admin_tokens (id, label, token_hash, hint, scope, created_by, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
|
|
1024
|
+
.run(id, input.label, hashApiKey(secret), apiKeyHint(secret), input.scope, input.createdBy, input.expiresAt ?? null, this._now());
|
|
1025
|
+
return { token: this.getAdminToken(id), secret };
|
|
1026
|
+
}
|
|
1027
|
+
getAdminToken(id) {
|
|
1028
|
+
const row = this._stmt('SELECT * FROM admin_tokens WHERE id = ?').get(id);
|
|
1029
|
+
return row ? toAdminToken(row) : null;
|
|
1030
|
+
}
|
|
1031
|
+
listAdminTokens() {
|
|
1032
|
+
return this._stmt('SELECT * FROM admin_tokens WHERE revoked_at IS NULL ORDER BY created_at, id').all()
|
|
1033
|
+
.map(toAdminToken);
|
|
1034
|
+
}
|
|
1035
|
+
/** Active token for a presented secret (hash lookup). */
|
|
1036
|
+
findAdminTokenBySecret(secret) {
|
|
1037
|
+
const row = this._stmt('SELECT * FROM admin_tokens WHERE token_hash = ? AND revoked_at IS NULL').get(hashApiKey(secret));
|
|
1038
|
+
return row ? toAdminToken(row) : null;
|
|
1039
|
+
}
|
|
1040
|
+
/** Like `touchKey`: at most once a minute per token. */
|
|
1041
|
+
touchAdminToken(id) {
|
|
1042
|
+
const now = this._now();
|
|
1043
|
+
if (!this._shouldTouch(`token:${id}`, now))
|
|
1044
|
+
return;
|
|
1045
|
+
this._stmt('UPDATE admin_tokens SET last_used_at = ? WHERE id = ?').run(now, id);
|
|
1046
|
+
}
|
|
1047
|
+
revokeAdminToken(id) {
|
|
1048
|
+
return this._stmt('UPDATE admin_tokens SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL').run(this._now(), id).changes > 0;
|
|
1049
|
+
}
|
|
1050
|
+
/** Revokes every token a member created (when they are disabled or lose admin); returns their ids. */
|
|
1051
|
+
revokeAdminTokensCreatedBy(memberId) {
|
|
1052
|
+
const rows = this._stmt('SELECT id FROM admin_tokens WHERE created_by = ? AND revoked_at IS NULL').all(memberId);
|
|
1053
|
+
const now = this._now();
|
|
1054
|
+
const revoke = this._stmt('UPDATE admin_tokens SET revoked_at = ? WHERE id = ?');
|
|
1055
|
+
this._db.transaction(() => { for (const row of rows)
|
|
1056
|
+
revoke.run(now, row.id); })();
|
|
1057
|
+
return rows.map((row) => row.id);
|
|
1058
|
+
}
|
|
1059
|
+
// ── Audit log ───────────────────────────────────────────────────────────
|
|
1060
|
+
/**
|
|
1061
|
+
* Appends one audit entry. Every mutating console action calls this:
|
|
1062
|
+
*
|
|
1063
|
+
* deps.store.recordAudit({ actor, action, target, details, ip })
|
|
1064
|
+
*
|
|
1065
|
+
* `actor` comes from `auditActor(store, principal)` in console-api/access.ts
|
|
1066
|
+
* (`{ kind: 'cli', id: null }` from CLI commands), `ip` from
|
|
1067
|
+
* `clientIp(request)`. `details` must never contain secrets.
|
|
1068
|
+
*/
|
|
1069
|
+
recordAudit(input) {
|
|
1070
|
+
const id = newId('aud');
|
|
1071
|
+
const at = this._now();
|
|
1072
|
+
this._stmt(`
|
|
1073
|
+
INSERT INTO audit_log (id, at, actor_kind, actor_id, actor_label, action, target_kind, target_id, target_label, details, ip)
|
|
1074
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1075
|
+
`).run(id, at, input.actor.kind, input.actor.id, input.actor.label ?? null, input.action, input.target?.kind ?? null, input.target?.id ?? null, input.target?.label ?? null, JSON.stringify(input.details ?? {}), input.ip ?? null);
|
|
1076
|
+
return {
|
|
1077
|
+
id,
|
|
1078
|
+
at,
|
|
1079
|
+
actor: { kind: input.actor.kind, id: input.actor.id, label: input.actor.label ?? null },
|
|
1080
|
+
action: input.action,
|
|
1081
|
+
target: input.target ? { kind: input.target.kind, id: input.target.id, label: input.target.label ?? null } : null,
|
|
1082
|
+
details: input.details ?? {},
|
|
1083
|
+
ip: input.ip ?? null,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
/** Newest first. `before` is the opaque cursor returned as `nextBefore`. */
|
|
1087
|
+
listAudit(filter = {}) {
|
|
1088
|
+
const limit = Math.max(1, Math.min(500, Math.floor(filter.limit ?? 100)));
|
|
1089
|
+
const where = [];
|
|
1090
|
+
const params = [];
|
|
1091
|
+
const before = filter.before ? Number.parseInt(filter.before, 10) : NaN;
|
|
1092
|
+
if (Number.isFinite(before)) {
|
|
1093
|
+
where.push('seq < ?');
|
|
1094
|
+
params.push(before);
|
|
1095
|
+
}
|
|
1096
|
+
if (filter.actorId) {
|
|
1097
|
+
where.push('actor_id = ?');
|
|
1098
|
+
params.push(filter.actorId);
|
|
1099
|
+
}
|
|
1100
|
+
if (filter.action) {
|
|
1101
|
+
// "key" matches "key" and "key.*"
|
|
1102
|
+
where.push("(action = ? OR action LIKE ? ESCAPE '\\')");
|
|
1103
|
+
params.push(filter.action, `${escapeLike(filter.action)}.%`);
|
|
1104
|
+
}
|
|
1105
|
+
const rows = this._stmt(`SELECT * FROM audit_log ${whereClause(where)} ORDER BY seq DESC LIMIT ?`)
|
|
1106
|
+
.all(...params, limit + 1);
|
|
1107
|
+
const page = rows.slice(0, limit);
|
|
1108
|
+
return {
|
|
1109
|
+
entries: page.map(toAudit),
|
|
1110
|
+
nextBefore: rows.length > limit ? String(page[page.length - 1]['seq']) : null,
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
// ── Chat history ────────────────────────────────────────────────────────
|
|
1114
|
+
/** A member's or a key's conversations in a workspace, newest first (without their messages). */
|
|
1115
|
+
listChatThreads(owner, limit = 200) {
|
|
1116
|
+
return this._stmt(`
|
|
1117
|
+
SELECT id, workspace_id, owner_kind, owner_id, title, model, peer_id, created_at, updated_at FROM chat_threads
|
|
1118
|
+
WHERE owner_kind = ? AND owner_id = ? AND workspace_id = ? ORDER BY updated_at DESC LIMIT ?
|
|
1119
|
+
`).all(owner.kind, owner.id, owner.workspaceId, limit).map(chatThreadSummary);
|
|
1120
|
+
}
|
|
1121
|
+
getChatThread(id) {
|
|
1122
|
+
const row = this._stmt('SELECT * FROM chat_threads WHERE id = ?').get(id);
|
|
1123
|
+
return row ? { ...chatThreadSummary(row), messages: parseJson(row.messages, []) } : null;
|
|
1124
|
+
}
|
|
1125
|
+
/** Creates or replaces a conversation; the owner and creation time of an existing one never change. */
|
|
1126
|
+
saveChatThread(input) {
|
|
1127
|
+
const now = this._now();
|
|
1128
|
+
this._stmt(`
|
|
1129
|
+
INSERT INTO chat_threads (id, workspace_id, owner_kind, owner_id, title, model, peer_id, messages, created_at, updated_at)
|
|
1130
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1131
|
+
ON CONFLICT (id) DO UPDATE SET title = excluded.title, model = excluded.model, peer_id = excluded.peer_id, messages = excluded.messages, updated_at = excluded.updated_at
|
|
1132
|
+
`).run(input.id, input.owner.workspaceId, input.owner.kind, input.owner.id, input.title, input.model, input.peerId, JSON.stringify(input.messages), now, now);
|
|
1133
|
+
}
|
|
1134
|
+
deleteChatThread(id) {
|
|
1135
|
+
return this._stmt('DELETE FROM chat_threads WHERE id = ?').run(id).changes > 0;
|
|
1136
|
+
}
|
|
1137
|
+
// ── Settings ────────────────────────────────────────────────────────────
|
|
1138
|
+
/**
|
|
1139
|
+
* Settings are read on every request, so values are cached for a few
|
|
1140
|
+
* seconds. A write through this store clears its cache at once; a write by
|
|
1141
|
+
* another process (a CLI command) is seen within `SETTINGS_TTL_MS`.
|
|
1142
|
+
*/
|
|
1143
|
+
getSetting(key) {
|
|
1144
|
+
const now = Date.now();
|
|
1145
|
+
let cached = this._settings.get(key);
|
|
1146
|
+
if (!cached || now - cached.at >= SETTINGS_TTL_MS || now < cached.at) {
|
|
1147
|
+
const row = this._stmt('SELECT value FROM settings WHERE key = ?').get(key);
|
|
1148
|
+
cached = { raw: row?.value ?? null, at: now };
|
|
1149
|
+
this._settings.set(key, cached);
|
|
1150
|
+
}
|
|
1151
|
+
// Parsed per call so callers never share (and mutate) one object.
|
|
1152
|
+
return cached.raw === null ? null : parseJson(cached.raw, null);
|
|
1153
|
+
}
|
|
1154
|
+
setSetting(key, value) {
|
|
1155
|
+
this._settings.delete(key);
|
|
1156
|
+
if (value === null || value === undefined) {
|
|
1157
|
+
this._stmt('DELETE FROM settings WHERE key = ?').run(key);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
this._stmt('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value')
|
|
1161
|
+
.run(key, JSON.stringify(value));
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
const REQUEST_COLUMNS = {
|
|
1165
|
+
key: 'r.key_id', workspace: 'r.workspace_id', member: 'r.member_id', model: 'r.model', peer: 'r.seller_peer_id', user: 'r.end_user', time: 'r.started_at',
|
|
1166
|
+
};
|
|
1167
|
+
const LEDGER_COLUMNS = {
|
|
1168
|
+
key: 'l.key_id', workspace: 'l.workspace_id', member: 'l.member_id', model: 'l.model', peer: 'l.seller_peer_id', user: 'l.end_user', time: 'l.created_at',
|
|
1169
|
+
};
|
|
1170
|
+
const ROLLUP_COLUMNS = {
|
|
1171
|
+
key: 'u.key_id', workspace: 'u.workspace_id', member: 'u.member_id', model: 'u.model', peer: 'u.seller_peer_id', user: 'u.end_user', time: 'u.hour_start',
|
|
1172
|
+
};
|
|
1173
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
1174
|
+
const TOUCH_INTERVAL_MS = 60 * 1000;
|
|
1175
|
+
const MAX_TRACKED_TOUCHES = 10_000;
|
|
1176
|
+
const SETTINGS_TTL_MS = 5_000;
|
|
1177
|
+
const MAX_CACHED_STATEMENTS = 500;
|
|
1178
|
+
const MAX_ERROR_CODE = 100;
|
|
1179
|
+
const MAX_ERROR_MESSAGE = 500;
|
|
1180
|
+
/** A failed request: finished with an error status. One still in flight is not a failure. */
|
|
1181
|
+
const FAILED_SQL = '(r.finished_at IS NOT NULL AND (r.status IS NULL OR r.status >= 400))';
|
|
1182
|
+
/**
|
|
1183
|
+
* A request whose tokens come only from its response: its spend recorded no
|
|
1184
|
+
* token counts (free sellers record none), so the response's own count is used.
|
|
1185
|
+
*/
|
|
1186
|
+
const REPORTED_TOKENS_ONLY_SQL = `(r.usage_input_tokens IS NOT NULL AND NOT EXISTS (
|
|
1187
|
+
SELECT 1 FROM ledger_entries t WHERE t.request_tag = r.tag AND t.kind = 'spend' AND t.input_tokens + t.cached_input_tokens + t.output_tokens > 0
|
|
1188
|
+
))`;
|
|
1189
|
+
/** WHERE clauses shared by request-log, rollup and ledger queries. */
|
|
1190
|
+
function scopeSql(filter, columns) {
|
|
1191
|
+
const where = [];
|
|
1192
|
+
const params = [];
|
|
1193
|
+
if (filter.keyIds) {
|
|
1194
|
+
where.push(`${columns.key} IN (SELECT value FROM json_each(?))`);
|
|
1195
|
+
params.push(JSON.stringify(filter.keyIds));
|
|
1196
|
+
}
|
|
1197
|
+
if (filter.keyId) {
|
|
1198
|
+
where.push(`${columns.key} = ?`);
|
|
1199
|
+
params.push(filter.keyId);
|
|
1200
|
+
}
|
|
1201
|
+
if (filter.workspaceId) {
|
|
1202
|
+
where.push(`${columns.workspace} = ?`);
|
|
1203
|
+
params.push(filter.workspaceId);
|
|
1204
|
+
}
|
|
1205
|
+
if (filter.memberId) {
|
|
1206
|
+
where.push(`${columns.member} = ?`);
|
|
1207
|
+
params.push(filter.memberId);
|
|
1208
|
+
}
|
|
1209
|
+
if (filter.model) {
|
|
1210
|
+
where.push(`${columns.model} = ?`);
|
|
1211
|
+
params.push(filter.model);
|
|
1212
|
+
}
|
|
1213
|
+
if (filter.from != null) {
|
|
1214
|
+
where.push(`${columns.time} >= ?`);
|
|
1215
|
+
params.push(filter.from);
|
|
1216
|
+
}
|
|
1217
|
+
if (filter.to != null) {
|
|
1218
|
+
where.push(`${columns.time} < ?`);
|
|
1219
|
+
params.push(filter.to);
|
|
1220
|
+
}
|
|
1221
|
+
return { where, params };
|
|
1222
|
+
}
|
|
1223
|
+
function whereClause(where) {
|
|
1224
|
+
return where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
|
|
1225
|
+
}
|
|
1226
|
+
/** Escapes LIKE wildcards; the queries declare backslash as the escape character. */
|
|
1227
|
+
function escapeLike(value) {
|
|
1228
|
+
return value.replace(/[\\%_]/g, (c) => `\\${c}`);
|
|
1229
|
+
}
|
|
1230
|
+
/** The SQL a usage report groups by; one group ('all') without a grouping. */
|
|
1231
|
+
function groupExpression(groupBy, columns) {
|
|
1232
|
+
if (!groupBy)
|
|
1233
|
+
return "'all'";
|
|
1234
|
+
if (groupBy === 'day')
|
|
1235
|
+
return `strftime('%Y-%m-%d', ${columns.time} / 1000, 'unixepoch')`;
|
|
1236
|
+
if (groupBy === 'hour')
|
|
1237
|
+
return `strftime('%Y-%m-%dT%H', ${columns.time} / 1000, 'unixepoch')`;
|
|
1238
|
+
return columns[groupBy];
|
|
1239
|
+
}
|
|
1240
|
+
/** Groupings whose groups are UTC time buckets (listed oldest first). */
|
|
1241
|
+
export function isTimeGrouping(groupBy) {
|
|
1242
|
+
return groupBy === 'day' || groupBy === 'hour';
|
|
1243
|
+
}
|
|
1244
|
+
/** Groups by end user past the largest `MAX_USER_GROUPS` (rows come sorted largest first), folded into one. */
|
|
1245
|
+
function capUserGroups(groupBy, rows) {
|
|
1246
|
+
if (groupBy !== 'user' || rows.length <= MAX_USER_GROUPS)
|
|
1247
|
+
return rows;
|
|
1248
|
+
const rest = rows.slice(MAX_USER_GROUPS);
|
|
1249
|
+
const other = { group: OTHER_USERS_GROUP, others: rest.length, ...emptyUsageTotals() };
|
|
1250
|
+
for (const row of rest) {
|
|
1251
|
+
for (const field of Object.keys(emptyUsageTotals()))
|
|
1252
|
+
other[field] += row[field];
|
|
1253
|
+
}
|
|
1254
|
+
return [...rows.slice(0, MAX_USER_GROUPS), other];
|
|
1255
|
+
}
|
|
1256
|
+
function emptyUsageTotals() {
|
|
1257
|
+
return { requests: 0, failedRequests: 0, spentUsdc: 0, inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
1258
|
+
}
|
|
1259
|
+
/** The `spend_totals` / `spend_daily` row key of a budget scope. */
|
|
1260
|
+
function budgetScopeRow(scope) {
|
|
1261
|
+
if ('keyId' in scope)
|
|
1262
|
+
return { kind: 'key', id: scope.keyId };
|
|
1263
|
+
if ('memberId' in scope)
|
|
1264
|
+
return { kind: 'member', id: scope.memberId };
|
|
1265
|
+
return { kind: 'workspace', id: scope.workspaceId };
|
|
232
1266
|
}
|
|
233
1267
|
function toKey(row) {
|
|
234
1268
|
return {
|
|
@@ -236,13 +1270,14 @@ function toKey(row) {
|
|
|
236
1270
|
label: row.label,
|
|
237
1271
|
hint: row.key_hint,
|
|
238
1272
|
buyerIdentity: row.buyer_identity,
|
|
1273
|
+
workspaceId: row.workspace_id ?? DEFAULT_WORKSPACE_ID,
|
|
1274
|
+
ownerMemberId: row.owner_member_id,
|
|
239
1275
|
source: row.source,
|
|
240
1276
|
status: row.status,
|
|
241
|
-
limits:
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
},
|
|
1277
|
+
limits: rowLimits(row),
|
|
1278
|
+
routingPolicy: parsePolicy(row.routing_policy),
|
|
1279
|
+
ownerLimits: rowLimits(row, 'owner_'),
|
|
1280
|
+
ownerRoutingPolicy: parsePolicy(row.owner_routing_policy),
|
|
246
1281
|
topupEnabled: row.topup_enabled === 1,
|
|
247
1282
|
expiresAt: row.expires_at,
|
|
248
1283
|
createdAt: row.created_at,
|
|
@@ -250,4 +1285,91 @@ function toKey(row) {
|
|
|
250
1285
|
lastUsedAt: row.last_used_at,
|
|
251
1286
|
};
|
|
252
1287
|
}
|
|
1288
|
+
function toWorkspace(row) {
|
|
1289
|
+
return {
|
|
1290
|
+
id: row.id,
|
|
1291
|
+
name: row.name,
|
|
1292
|
+
buyerIdentity: row.buyer_identity,
|
|
1293
|
+
isDefault: row.is_default === 1,
|
|
1294
|
+
walletAddress: row.wallet_address,
|
|
1295
|
+
limits: rowLimits(row),
|
|
1296
|
+
routingPolicy: parsePolicy(row.routing_policy),
|
|
1297
|
+
orgRoutingPolicy: parsePolicy(row.org_routing_policy),
|
|
1298
|
+
createdAt: row.created_at,
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
function toMember(row) {
|
|
1302
|
+
return {
|
|
1303
|
+
id: row.id,
|
|
1304
|
+
label: row.label,
|
|
1305
|
+
email: row.email,
|
|
1306
|
+
orgRole: row.org_role,
|
|
1307
|
+
status: row.status,
|
|
1308
|
+
limits: rowLimits(row),
|
|
1309
|
+
routingPolicy: parsePolicy(row.routing_policy),
|
|
1310
|
+
maxKeys: row.max_keys,
|
|
1311
|
+
createdAt: row.created_at,
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
function toInvite(row) {
|
|
1315
|
+
return {
|
|
1316
|
+
id: row.id,
|
|
1317
|
+
memberId: row.member_id,
|
|
1318
|
+
label: row.label,
|
|
1319
|
+
email: row.email,
|
|
1320
|
+
orgRole: row.org_role,
|
|
1321
|
+
workspaces: parseJson(row.workspace_roles, []),
|
|
1322
|
+
createdBy: row.created_by,
|
|
1323
|
+
expiresAt: row.expires_at,
|
|
1324
|
+
usedAt: row.used_at,
|
|
1325
|
+
createdAt: row.created_at,
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function toPeerList(row) {
|
|
1329
|
+
return {
|
|
1330
|
+
id: row['id'],
|
|
1331
|
+
name: row['name'],
|
|
1332
|
+
description: row['description'],
|
|
1333
|
+
peerIds: parseJson(row['peer_ids'], []),
|
|
1334
|
+
createdAt: row['created_at'],
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function toPreset(row) {
|
|
1338
|
+
return {
|
|
1339
|
+
id: row['id'],
|
|
1340
|
+
slug: row['slug'],
|
|
1341
|
+
name: row['name'],
|
|
1342
|
+
workspaceId: row['workspace_id'],
|
|
1343
|
+
model: row['model'],
|
|
1344
|
+
routingPolicy: parsePolicy(row['routing_policy']),
|
|
1345
|
+
systemPrompt: row['system_prompt'],
|
|
1346
|
+
params: parseJson(row['params'], {}),
|
|
1347
|
+
createdAt: row['created_at'],
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
function toAdminToken(row) {
|
|
1351
|
+
return {
|
|
1352
|
+
id: row['id'],
|
|
1353
|
+
label: row['label'],
|
|
1354
|
+
hint: row['hint'],
|
|
1355
|
+
scope: row['scope'],
|
|
1356
|
+
createdBy: row['created_by'],
|
|
1357
|
+
expiresAt: row['expires_at'] ?? null,
|
|
1358
|
+
createdAt: row['created_at'],
|
|
1359
|
+
lastUsedAt: row['last_used_at'],
|
|
1360
|
+
revokedAt: row['revoked_at'],
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
function toAudit(row) {
|
|
1364
|
+
const targetKind = row['target_kind'];
|
|
1365
|
+
return {
|
|
1366
|
+
id: row['id'],
|
|
1367
|
+
at: row['at'],
|
|
1368
|
+
actor: { kind: row['actor_kind'], id: row['actor_id'], label: row['actor_label'] },
|
|
1369
|
+
action: row['action'],
|
|
1370
|
+
target: targetKind ? { kind: targetKind, id: row['target_id'], label: row['target_label'] } : null,
|
|
1371
|
+
details: parseJson(row['details'], {}),
|
|
1372
|
+
ip: row['ip'],
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
253
1375
|
//# sourceMappingURL=store.js.map
|