@antseed/cli 0.1.179 → 0.1.180
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 +2 -0
- 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/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 -48
- 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/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/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/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 -0
- 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
|
@@ -4,10 +4,10 @@ import { prepareVideoRequest, recordVideoAcceptance } from './native-video-proxy
|
|
|
4
4
|
import { downloadVideo } from './video-download.js';
|
|
5
5
|
import { createServer } from 'node:http';
|
|
6
6
|
import { randomUUID } from 'node:crypto';
|
|
7
|
-
import { watchFile, unwatchFile } from 'node:fs';
|
|
7
|
+
import { readFileSync, watchFile, unwatchFile } from 'node:fs';
|
|
8
8
|
import { readFile, writeFile, rename, mkdir, readdir, stat, unlink } from 'node:fs/promises';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
-
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, ANTSEED_ATTEST_PATH, adaptPeerFaultErrorResponse, computeTrustScore, decodeSweepRequest, faultAttributionOf, faultCodeOf, isModelRouteEligible, modelRouteTotalPrice, normalizedModelReputationScore, peerSupportsCooperativeClose, DEFAULT_BUYER_IDENTITY, rankModelRoutes, sanitizePeerDisplayName, } from '@antseed/node';
|
|
10
|
+
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, ANTSEED_ATTEST_PATH, adaptPeerFaultErrorResponse, computeTrustScore, decodeSweepRequest, faultAttributionOf, faultCodeOf, isModelRouteCoolingDown, isModelRouteEligible, modelRouteReputationScore, modelRouteTotalPrice, normalizedModelReputationScore, peerSupportsCooperativeClose, DEFAULT_BUYER_IDENTITY, rankModelRoutes, sanitizePeerDisplayName, } from '@antseed/node';
|
|
11
11
|
import { canonicalModelKey } from '@antseed/node/model-identity';
|
|
12
12
|
import { createStreamingAdapter, detectRequestServiceApiProtocol, transformRequest, transformResponse, } from './service-api-adapter.js';
|
|
13
13
|
import { DEBUG, log, extractRequestedService, summarizeRequestShape, summarizeErrorResponse, requestWantsStreaming, parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, overrideRoutedModelInBody, ROUTED_MODEL_ALIAS, SYSTEM_PROXY_SOURCE_HEADER, SYSTEM_ROUTED_MODEL_HEADER, BUYER_IDENTITY_HEADER, normalizePeerId, } from './request-utils.js';
|
|
@@ -17,18 +17,22 @@ import { findMissingRequiredParameters, findUnannouncedRequestParameters, findAd
|
|
|
17
17
|
import { computeResponseTelemetry, attachAntseedTelemetryHeaders, attachStreamingAntseedHeaders, } from './telemetry.js';
|
|
18
18
|
import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
|
|
19
19
|
import { extractConversationIdentity, extractFirstUserSnippet, isCompletionRequestPath, isTitleGenerationRequest, parseRequestBodyObject, } from './conversation-identity.js';
|
|
20
|
-
import { SPEND_ATTRIBUTION_HEADER, SpendAttributionFeed, parseSpendAttributionTag } from './spend-attribution.js';
|
|
20
|
+
import { SPEND_ATTRIBUTION_DB_FILE, SPEND_ATTRIBUTION_HEADER, SpendAttributionFeed, parseSpendAttributionTag } from './spend-attribution.js';
|
|
21
21
|
import { ConversationStore } from './conversation-store.js';
|
|
22
|
+
import { DEPOSIT_WATCH_ACTIVE_TTL_MS } from './deposit-watcher.js';
|
|
22
23
|
import { recordPeerFailureEntry, clearPeerHealthEntry, isCoolingDown, parsePersistedPeerHealth, prunePeerHealth, serializePeerHealth, reasonEscalates, } from './peer-health.js';
|
|
23
24
|
import { PeerAttributionTracker, HEARTBEAT_MS } from './peer-attribution.js';
|
|
24
25
|
import { estimateAnthropicPromptTokens, isCountTokensPath } from './count-tokens.js';
|
|
25
|
-
import { runVerifier, verifierSupportFingerprint } from '../plugins/verifier.js';
|
|
26
|
+
import { runVerifier, selectVerifier, verifierSupportFingerprint } from '../plugins/verifier.js';
|
|
26
27
|
import { TEE_VERIFIER_ID } from '@antseed/node/tee-status';
|
|
27
28
|
import { parseVerifierCapabilities } from '@antseed/node/verifier-capabilities';
|
|
28
29
|
import { TeeVerification } from './tee-verification.js';
|
|
29
30
|
import { TeeControl } from './tee-control.js';
|
|
31
|
+
import { ChainReadCache } from './chain-read-cache.js';
|
|
30
32
|
import { isClientCredentialHeader } from './credential-headers.js';
|
|
31
33
|
import { loadConfig } from '../config/loader.js';
|
|
34
|
+
import { GATEWAY_CONTROL_HEADER, GATEWAY_CONTROL_SECRET_ENV, GATEWAY_CONTROL_SECRET_FILE, ROUTING_POLICY_HEADER, decodePolicyHeader, findModelRoute, meaningfulPolicy, narrowPolicy, normalizePeerId as normalizePolicyPeerId, policyAllowsModel, policyModelKey, } from '../routing-policy/policy.js';
|
|
35
|
+
import { buyerConfigReason, buyerHardPolicy, buyerPreferencePolicy, orderByPolicy, pinnedPeerExclusionReasons, policyExclusionReasons, preferenceExclusionReasons, rankingPreferences, secretsMatch, } from './route-policy.js';
|
|
32
36
|
// Re-export for backward compatibility (used by tests and other consumers)
|
|
33
37
|
export { selectCandidatePeersForRouting } from './routing.js';
|
|
34
38
|
export { parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, ROUTED_MODEL_ALIAS } from './request-utils.js';
|
|
@@ -36,7 +40,34 @@ const WATCHER_ABSENCE_ERRORS = {
|
|
|
36
40
|
'payments-disabled': 'Deposit watcher unavailable — payments are disabled on this buyer.',
|
|
37
41
|
'no-deposit-relay': 'Deposit watcher unavailable — this chain has no deposit relay.',
|
|
38
42
|
'external-daemon': 'Deposit watcher unavailable — another daemon owns the proxy port and runs the watcher.',
|
|
43
|
+
'identity-not-watched': 'Deposit watcher unavailable — this buyer identity has no deposit watcher.',
|
|
39
44
|
};
|
|
45
|
+
/** Exit code `POST /_antseed/restart` ends the process with, so a supervisor restarts it. */
|
|
46
|
+
export const BUYER_RESTART_EXIT_CODE = 75;
|
|
47
|
+
const MIN_RESTART_INTERVAL_MS = 30_000;
|
|
48
|
+
const BALANCE_CACHE_TTL_MS = 15_000;
|
|
49
|
+
/** A balance up to this much past its TTL is answered at once while it refreshes. */
|
|
50
|
+
const BALANCE_REVALIDATE_MS = 45_000;
|
|
51
|
+
/** `fresh=1` re-reads the chain at most this often per address. */
|
|
52
|
+
const BALANCE_FRESH_FLOOR_MS = 5_000;
|
|
53
|
+
const CONTROL_SECRET_RECHECK_MS = 1_000;
|
|
54
|
+
/** Floor between forced re-reads on a secret mismatch, so a stream of bad secrets can't hammer the disk. */
|
|
55
|
+
const CONTROL_SECRET_FORCED_RECHECK_MS = 100;
|
|
56
|
+
const LATENCY_EMA_ALPHA = 0.3;
|
|
57
|
+
const MAX_LATENCY_ENTRIES = 4096;
|
|
58
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
59
|
+
/** Exact USDC decimal string from base units, e.g. 1500000n -> "1.500000". */
|
|
60
|
+
function formatUsdcBaseUnits(amount) {
|
|
61
|
+
const sign = amount < 0n ? '-' : '';
|
|
62
|
+
const abs = amount < 0n ? -amount : amount;
|
|
63
|
+
return `${sign}${abs / 1000000n}.${(abs % 1000000n).toString().padStart(6, '0')}`;
|
|
64
|
+
}
|
|
65
|
+
function isLoopbackAddress(address) {
|
|
66
|
+
// A destroyed socket has no address: never treat it as local.
|
|
67
|
+
if (address === undefined)
|
|
68
|
+
return false;
|
|
69
|
+
return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1' || address.startsWith('127.');
|
|
70
|
+
}
|
|
40
71
|
// 401/403 are included: sellers relay upstream auth failures (revoked or
|
|
41
72
|
// expired key, region/WAF block) that are specific to that seller's upstream
|
|
42
73
|
// account, so another peer serving the same model can usually complete the
|
|
@@ -44,6 +75,24 @@ const WATCHER_ABSENCE_ERRORS = {
|
|
|
44
75
|
// because model_not_found already has dedicated unadvertise handling while a
|
|
45
76
|
// generic 404 would repeat identically on every peer.
|
|
46
77
|
const RETRYABLE_STATUS_CODES = new Set([401, 403, 408, 429, 500, 502, 503, 504]);
|
|
78
|
+
/**
|
|
79
|
+
* Gateway-policy requests on automatic routing also move on after a 402: the
|
|
80
|
+
* buyer could not fund that seller (no deposits for its reserve), but a free
|
|
81
|
+
* seller needs none. Other requests keep the 402, as they always have.
|
|
82
|
+
*/
|
|
83
|
+
const GATEWAY_AUTO_ROUTE_RETRYABLE_STATUS_CODES = new Set([...RETRYABLE_STATUS_CODES, 402]);
|
|
84
|
+
function retryableStatusCodesFor(gatewayAutoRoute) {
|
|
85
|
+
return gatewayAutoRoute ? GATEWAY_AUTO_ROUTE_RETRYABLE_STATUS_CODES : RETRYABLE_STATUS_CODES;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Whether the routing loop should try the next seller. On automatic routing a
|
|
89
|
+
* seller's model_not_found also moves on: its cached catalog was stale (the
|
|
90
|
+
* seller stopped serving the model), and another seller may still serve it.
|
|
91
|
+
* A pinned request keeps the pinned seller's answer.
|
|
92
|
+
*/
|
|
93
|
+
function shouldTryNextPeer(statusCode, modelNotFound, pinned, gatewayAutoRoute) {
|
|
94
|
+
return retryableStatusCodesFor(gatewayAutoRoute).has(statusCode) || (modelNotFound && !pinned);
|
|
95
|
+
}
|
|
47
96
|
const MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER = 3;
|
|
48
97
|
const MODEL_RATE_LIMIT_RETRY_DELAYS_MS = [250, 750];
|
|
49
98
|
const MODEL_RATE_LIMIT_MAX_RETRY_AFTER_MS = 2_000;
|
|
@@ -96,11 +145,34 @@ function peerAllowedByPolicy(policyRouter, request, peer) {
|
|
|
96
145
|
return policyRouter.allowsPeerForPricing(request, peer);
|
|
97
146
|
return true;
|
|
98
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Appends the buyer-level exclusion reasons, labelled as buyer config, that
|
|
150
|
+
* the gateway level did not already report (in either form).
|
|
151
|
+
*/
|
|
152
|
+
function addBuyerConfigReasons(reasons, buyerReasons) {
|
|
153
|
+
for (const reason of buyerReasons) {
|
|
154
|
+
const labelled = buyerConfigReason(reason);
|
|
155
|
+
if (!reasons.includes(reason) && !reasons.includes(labelled))
|
|
156
|
+
reasons.push(labelled);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Why a route candidate's effective trust is below its seller's score: the
|
|
161
|
+
* only penalty (`effectiveModelReputationScore`) halves a paid seller that
|
|
162
|
+
* lists no cached-input price for a model other sellers price cache reads on.
|
|
163
|
+
*/
|
|
164
|
+
function trustPenalty(candidate) {
|
|
165
|
+
const effective = candidate.effectiveReputationScore;
|
|
166
|
+
if (candidate.reputation < 0 || effective === null || effective >= candidate.reputation)
|
|
167
|
+
return null;
|
|
168
|
+
return `halved from ${Math.round(candidate.reputation)}: no cached-input price listed`;
|
|
169
|
+
}
|
|
99
170
|
function isControlPlaneServicesPath(path) {
|
|
100
171
|
return path.toLowerCase().startsWith('/v1/models');
|
|
101
172
|
}
|
|
102
173
|
function isRouterSuccess(statusCode, path, retryableStatusCodes) {
|
|
103
|
-
|
|
174
|
+
// A 402 is the buyer's own funding gap, never the seller's failure.
|
|
175
|
+
return isControlPlaneServicesPath(path) || statusCode === 402 || !retryableStatusCodes.has(statusCode);
|
|
104
176
|
}
|
|
105
177
|
/**
|
|
106
178
|
* Detect a "model not served" rejection: the seller's own pre-payment 400
|
|
@@ -610,9 +682,16 @@ export async function mergeJsonStateFile(stateDir, stateFile, patch) {
|
|
|
610
682
|
throw err;
|
|
611
683
|
}
|
|
612
684
|
}
|
|
685
|
+
function sendJson(res, status, body) {
|
|
686
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
687
|
+
res.end(JSON.stringify(body));
|
|
688
|
+
}
|
|
689
|
+
/** A requested buyer identity name as stored ('' when none is named). */
|
|
690
|
+
function buyerIdentityName(value) {
|
|
691
|
+
return value?.trim().toLowerCase() ?? '';
|
|
692
|
+
}
|
|
613
693
|
function sendUnknownBuyerIdentity(res) {
|
|
614
|
-
res
|
|
615
|
-
res.end(JSON.stringify({ ok: false, error: 'Unknown buyer identity' }));
|
|
694
|
+
sendJson(res, 404, { ok: false, error: 'Unknown buyer identity' });
|
|
616
695
|
}
|
|
617
696
|
export class BuyerProxy {
|
|
618
697
|
_server;
|
|
@@ -681,6 +760,8 @@ export class BuyerProxy {
|
|
|
681
760
|
_depositWatcher = null;
|
|
682
761
|
/** Why no watcher is attached, so UIs can show the actual cause. */
|
|
683
762
|
_depositWatcherAbsence = null;
|
|
763
|
+
/** Watchers for named buyer identities (each workspace wallet), keyed by identity name. */
|
|
764
|
+
_identityDepositWatchers = new Map();
|
|
684
765
|
/**
|
|
685
766
|
* requestId -> the conversation that issued it, so the node's per-request
|
|
686
767
|
* spend events can be attributed to a chat. Only the proxy knows this
|
|
@@ -691,8 +772,32 @@ export class BuyerProxy {
|
|
|
691
772
|
*/
|
|
692
773
|
_requestConversations = new Map();
|
|
693
774
|
/** Signed spend for requests a local gateway tagged, polled over the control plane. */
|
|
694
|
-
_spendAttribution
|
|
775
|
+
_spendAttribution;
|
|
776
|
+
_minPeerReputation;
|
|
777
|
+
/** Gateway control secret, read lazily (the gateway may create it after the buyer starts). */
|
|
778
|
+
_controlSecret = null;
|
|
779
|
+
_controlSecretCheckedAt = 0;
|
|
780
|
+
_controlSecretForcedAt = 0;
|
|
781
|
+
_loggedUnauthenticatedPolicy = false;
|
|
782
|
+
/**
|
|
783
|
+
* Per-seller latency EMA (ms) measured by this proxy: time to the first
|
|
784
|
+
* response frame for streamed requests, full round trip otherwise. Feeds
|
|
785
|
+
* `sort: 'latency'`; sellers without a measurement rank after measured ones.
|
|
786
|
+
*/
|
|
787
|
+
_peerLatencyMs = new Map();
|
|
788
|
+
_balanceReader;
|
|
789
|
+
_balanceCache = new ChainReadCache();
|
|
790
|
+
_restartHandler;
|
|
791
|
+
_settingOverrides;
|
|
792
|
+
_restartRequested = false;
|
|
793
|
+
/** A restarted buyer is a new process, so its start is the last accepted restart. */
|
|
794
|
+
_restartAllowedAt;
|
|
695
795
|
constructor(config) {
|
|
796
|
+
this._minPeerReputation = config.minPeerReputation ?? null;
|
|
797
|
+
this._balanceReader = config.balanceReader ?? null;
|
|
798
|
+
this._restartHandler = config.onRestartRequested ?? null;
|
|
799
|
+
this._restartAllowedAt = Date.now() + (config.minRestartIntervalMs ?? MIN_RESTART_INTERVAL_MS);
|
|
800
|
+
this._settingOverrides = [...(config.settingOverrides ?? [])];
|
|
696
801
|
this._node = config.node;
|
|
697
802
|
this._verifier = config.verifier;
|
|
698
803
|
this._buyerIdentities = config.buyerIdentities ?? new BuyerIdentityLoader(config.node, config.dataDir);
|
|
@@ -703,6 +808,11 @@ export class BuyerProxy {
|
|
|
703
808
|
this._peerCacheTtlMs = Math.max(0, config.peerCacheTtlMs ?? Math.max(6 * 60_000, this._bgRefreshIntervalMs + 60_000));
|
|
704
809
|
this._stateDir = config.dataDir;
|
|
705
810
|
this._stateFile = join(config.dataDir, 'buyer.state.json');
|
|
811
|
+
// Kept on disk so spend signed just before a restart still reaches the gateway.
|
|
812
|
+
this._spendAttribution = new SpendAttributionFeed(undefined, {
|
|
813
|
+
path: join(config.dataDir, SPEND_ATTRIBUTION_DB_FILE),
|
|
814
|
+
onLog: (message) => console.warn(`[proxy] ${message}`),
|
|
815
|
+
});
|
|
706
816
|
this._configPath = config.configPath ?? null;
|
|
707
817
|
this._conversations = new ConversationStore(config.dataDir);
|
|
708
818
|
this._pinnedPeer = config.pinnedPeerId?.toLowerCase() ?? null;
|
|
@@ -771,7 +881,7 @@ export class BuyerProxy {
|
|
|
771
881
|
* (or can be) loaded, undefined when no stored identity has that name.
|
|
772
882
|
*/
|
|
773
883
|
async _resolveBuyerIdentity(header) {
|
|
774
|
-
const name = header
|
|
884
|
+
const name = buyerIdentityName(header);
|
|
775
885
|
if (!name || name === DEFAULT_BUYER_IDENTITY)
|
|
776
886
|
return null;
|
|
777
887
|
const loaded = await this._buyerIdentities.ensure(name).catch((err) => {
|
|
@@ -780,10 +890,531 @@ export class BuyerProxy {
|
|
|
780
890
|
});
|
|
781
891
|
return loaded ? name : undefined;
|
|
782
892
|
}
|
|
783
|
-
/**
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
893
|
+
/**
|
|
894
|
+
* Whether a request may act as this buyer identity. The default wallet is
|
|
895
|
+
* always allowed; any other is a workspace wallet, so once a control secret
|
|
896
|
+
* exists only the gateway may name it (loopback callers otherwise, as for
|
|
897
|
+
* the attributed-spend feed).
|
|
898
|
+
*/
|
|
899
|
+
_mayUseBuyerIdentity(req, value) {
|
|
900
|
+
const name = buyerIdentityName(value);
|
|
901
|
+
if (!name || name === DEFAULT_BUYER_IDENTITY)
|
|
902
|
+
return true;
|
|
903
|
+
if (!isLoopbackAddress(req.socket?.remoteAddress))
|
|
904
|
+
return false;
|
|
905
|
+
return this._readControlSecret() === null || this._isGatewayAuthenticated(req.headers);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* `?identity=` on control-plane requests (a body's `identity`, when given,
|
|
909
|
+
* wins); undefined when it names no stored identity.
|
|
910
|
+
*/
|
|
911
|
+
async _controlPlaneIdentity(path, bodyIdentity) {
|
|
912
|
+
const param = bodyIdentity ?? new URL(path, 'http://localhost').searchParams.get('identity') ?? undefined;
|
|
913
|
+
return this._resolveBuyerIdentity(param);
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* The gateway↔buyer control secret: env var first, else the file the
|
|
917
|
+
* gateway creates under the data dir. A missing file is re-checked at most
|
|
918
|
+
* once a second so a gateway started after the buyer is picked up. A
|
|
919
|
+
* mismatch forces an immediate re-read (bypassing that throttle, at most
|
|
920
|
+
* every 100 ms) in case the secret was just created or rotated.
|
|
921
|
+
*/
|
|
922
|
+
_readControlSecret(forceReread = false) {
|
|
923
|
+
const fromEnv = process.env[GATEWAY_CONTROL_SECRET_ENV]?.trim();
|
|
924
|
+
if (fromEnv)
|
|
925
|
+
return fromEnv;
|
|
926
|
+
const now = Date.now();
|
|
927
|
+
if (forceReread) {
|
|
928
|
+
if (now - this._controlSecretForcedAt < CONTROL_SECRET_FORCED_RECHECK_MS)
|
|
929
|
+
return this._controlSecret;
|
|
930
|
+
this._controlSecretForcedAt = now;
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
if (this._controlSecret)
|
|
934
|
+
return this._controlSecret;
|
|
935
|
+
if (now - this._controlSecretCheckedAt < CONTROL_SECRET_RECHECK_MS)
|
|
936
|
+
return this._controlSecret;
|
|
937
|
+
}
|
|
938
|
+
this._controlSecretCheckedAt = now;
|
|
939
|
+
try {
|
|
940
|
+
const value = readFileSync(join(this._stateDir, GATEWAY_CONTROL_SECRET_FILE), 'utf8').trim();
|
|
941
|
+
this._controlSecret = value.length > 0 ? value : null;
|
|
942
|
+
}
|
|
943
|
+
catch {
|
|
944
|
+
// Not created yet (no gateway): keep whatever we had.
|
|
945
|
+
}
|
|
946
|
+
return this._controlSecret;
|
|
947
|
+
}
|
|
948
|
+
/** Whether the request carries the local gateway's control secret. */
|
|
949
|
+
_isGatewayAuthenticated(headers) {
|
|
950
|
+
const raw = headers[GATEWAY_CONTROL_HEADER];
|
|
951
|
+
const provided = Array.isArray(raw) ? raw[0] : raw;
|
|
952
|
+
if (!provided)
|
|
953
|
+
return false;
|
|
954
|
+
const secret = this._readControlSecret();
|
|
955
|
+
if (secret && secretsMatch(provided, secret))
|
|
956
|
+
return true;
|
|
957
|
+
const reread = this._readControlSecret(true);
|
|
958
|
+
return reread !== null && reread !== secret && secretsMatch(provided, reread);
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Reads and strips the gateway routing-policy headers. A control header
|
|
962
|
+
* that is present but wrong fails closed (the gateway meant to restrict
|
|
963
|
+
* this request); a policy sent with no control header at all is dropped
|
|
964
|
+
* and ignored. An authenticated but malformed policy is an error, never
|
|
965
|
+
* unrestricted routing.
|
|
966
|
+
*/
|
|
967
|
+
_takeRequestPolicy(headers) {
|
|
968
|
+
const hasAuth = headers[GATEWAY_CONTROL_HEADER] !== undefined;
|
|
969
|
+
const encoded = headers[ROUTING_POLICY_HEADER];
|
|
970
|
+
const authenticated = hasAuth && this._isGatewayAuthenticated(headers);
|
|
971
|
+
delete headers[GATEWAY_CONTROL_HEADER];
|
|
972
|
+
delete headers[ROUTING_POLICY_HEADER];
|
|
973
|
+
if (hasAuth && !authenticated)
|
|
974
|
+
return { kind: 'auth_invalid' };
|
|
975
|
+
if (encoded === undefined)
|
|
976
|
+
return { kind: 'none' };
|
|
977
|
+
if (!authenticated) {
|
|
978
|
+
if (!this._loggedUnauthenticatedPolicy) {
|
|
979
|
+
this._loggedUnauthenticatedPolicy = true;
|
|
980
|
+
console.warn(`[proxy] Ignoring ${ROUTING_POLICY_HEADER} without a valid ${GATEWAY_CONTROL_HEADER} (logged once).`);
|
|
981
|
+
}
|
|
982
|
+
return { kind: 'none' };
|
|
983
|
+
}
|
|
984
|
+
const policy = decodePolicyHeader(encoded);
|
|
985
|
+
if (!policy)
|
|
986
|
+
return { kind: 'invalid' };
|
|
987
|
+
// A policy that changes nothing routes exactly like no policy (as on a
|
|
988
|
+
// buyer without a gateway), down to the error messages.
|
|
989
|
+
const meaningful = meaningfulPolicy(policy);
|
|
990
|
+
return Object.keys(meaningful).length > 0 ? { kind: 'policy', policy: meaningful } : { kind: 'none' };
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* The buyer's hard limits (min reputation, required verifier), which judge
|
|
994
|
+
* every request, pinned ones included.
|
|
995
|
+
*/
|
|
996
|
+
_buyerHardPolicy() {
|
|
997
|
+
return buyerHardPolicy({
|
|
998
|
+
minPeerReputation: this._minPeerReputation,
|
|
999
|
+
verifierRequired: this._verifier?.require === true,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
/** `buyer.routingPreferences` as a policy level (empty without preferences). */
|
|
1003
|
+
_buyerPreferencePolicy() {
|
|
1004
|
+
return this._routingPreferences ? buyerPreferencePolicy(this._routingPreferences) : {};
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* The buyer's whole config as the top policy level for automatic routing:
|
|
1008
|
+
* hard limits plus routing preferences. Reported to the gateway as the
|
|
1009
|
+
* route preview's `buyer` level.
|
|
1010
|
+
*/
|
|
1011
|
+
_buyerPolicy() {
|
|
1012
|
+
return narrowPolicy(this._buyerHardPolicy(), this._buyerPreferencePolicy());
|
|
1013
|
+
}
|
|
1014
|
+
/** A gateway policy narrowed under the buyer's hard limits; null when the request carries none. Judges pins. */
|
|
1015
|
+
_effectivePolicy(requestPolicy) {
|
|
1016
|
+
if (!requestPolicy)
|
|
1017
|
+
return null;
|
|
1018
|
+
return narrowPolicy(this._buyerHardPolicy(), requestPolicy);
|
|
1019
|
+
}
|
|
1020
|
+
/** Null when the seller can be verified with this buyer's verifier, else why not. */
|
|
1021
|
+
_unverifiedReason(peer) {
|
|
1022
|
+
if (!this._verifier)
|
|
1023
|
+
return 'not verified (verification disabled on this buyer)';
|
|
1024
|
+
const supported = parseVerifierCapabilities(peer.capabilities);
|
|
1025
|
+
return selectVerifier({ ...this._verifier, require: true }, supported) === null
|
|
1026
|
+
? 'not verified (no supported verifier)'
|
|
1027
|
+
: null;
|
|
1028
|
+
}
|
|
1029
|
+
_policyFacts(peer, offer) {
|
|
1030
|
+
const score = normalizedModelReputationScore(peer);
|
|
1031
|
+
return {
|
|
1032
|
+
peerId: peer.peerId,
|
|
1033
|
+
trustScore: score,
|
|
1034
|
+
reputation: score,
|
|
1035
|
+
inputUsdPerMillion: offer?.inputUsdPerMillion ?? null,
|
|
1036
|
+
outputUsdPerMillion: offer?.outputUsdPerMillion ?? null,
|
|
1037
|
+
cachedInputUsdPerMillion: offer?.cachedInputUsdPerMillion ?? null,
|
|
1038
|
+
imageUsdPerImage: offer?.maxImageUsdPerImage ?? offer?.minImageUsdPerImage ?? null,
|
|
1039
|
+
unverifiedReason: this._unverifiedReason(peer),
|
|
1040
|
+
teeCapable: parseVerifierCapabilities(peer.capabilities).supported.includes(TEE_VERIFIER_ID),
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
/** Latency is tracked per seller and model: one seller can serve a small model fast and a large one slowly. */
|
|
1044
|
+
_latencyKey(peerId, serviceId) {
|
|
1045
|
+
return `${peerId.toLowerCase()}|${serviceId ? policyModelKey(serviceId) : ''}`;
|
|
1046
|
+
}
|
|
1047
|
+
_peerLatency(peerId, serviceId) {
|
|
1048
|
+
return this._peerLatencyMs.get(this._latencyKey(peerId, serviceId)) ?? null;
|
|
1049
|
+
}
|
|
1050
|
+
_recordPeerLatency(peerId, serviceId, latencyMs) {
|
|
1051
|
+
if (!Number.isFinite(latencyMs) || latencyMs < 0)
|
|
1052
|
+
return;
|
|
1053
|
+
const key = this._latencyKey(peerId, serviceId);
|
|
1054
|
+
const previous = this._peerLatencyMs.get(key);
|
|
1055
|
+
// Re-inserted so the map's order is least recently measured first.
|
|
1056
|
+
this._peerLatencyMs.delete(key);
|
|
1057
|
+
this._peerLatencyMs.set(key, previous === undefined
|
|
1058
|
+
? latencyMs
|
|
1059
|
+
: previous + LATENCY_EMA_ALPHA * (latencyMs - previous));
|
|
1060
|
+
if (this._peerLatencyMs.size > MAX_LATENCY_ENTRIES) {
|
|
1061
|
+
const oldest = this._peerLatencyMs.keys().next().value;
|
|
1062
|
+
if (oldest !== undefined)
|
|
1063
|
+
this._peerLatencyMs.delete(oldest);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Candidate selection and ranking for a model-only request. The real
|
|
1068
|
+
* routing path and `GET /_antseed/route-preview` both call this, so the
|
|
1069
|
+
* preview shows exactly what routing would do. `verdicts` lists every
|
|
1070
|
+
* seller advertising the model with why it is (in)eligible; `candidates`
|
|
1071
|
+
* are the eligible ones in dispatch order.
|
|
1072
|
+
*/
|
|
1073
|
+
_planModelRoutes(input) {
|
|
1074
|
+
const { modelPeers, modelPlans, request, requestProtocol, requestedService, explicitProvider, requiredParameters } = input;
|
|
1075
|
+
const policyRouter = this._node.router;
|
|
1076
|
+
const requestPolicy = input.requestPolicy;
|
|
1077
|
+
// Under a gateway policy the buyer's config is the top level of the
|
|
1078
|
+
// chain: the gateway narrows it, never replaces it.
|
|
1079
|
+
const effectivePolicy = requestPolicy ? narrowPolicy(this._buyerPolicy(), requestPolicy) : null;
|
|
1080
|
+
const verdicts = new Map();
|
|
1081
|
+
const routeCandidates = [];
|
|
1082
|
+
for (const peer of modelPeers) {
|
|
1083
|
+
const plan = modelPlans.get(peer.peerId)
|
|
1084
|
+
?? resolvePeerRoutePlan(peer, requestProtocol, requestedService, explicitProvider, 'strict');
|
|
1085
|
+
if (!plan?.serviceId)
|
|
1086
|
+
continue;
|
|
1087
|
+
const offer = findAdvertisedServiceOffer(peer, plan.provider, plan.serviceId);
|
|
1088
|
+
if (!offer)
|
|
1089
|
+
continue;
|
|
1090
|
+
const verdict = { peer, serviceId: plan.serviceId, offer, eligible: false, rank: null, reasons: [] };
|
|
1091
|
+
verdicts.set(peer.peerId, verdict);
|
|
1092
|
+
const missingRequired = plan.selection?.requiresTransform
|
|
1093
|
+
? [...requiredParameters]
|
|
1094
|
+
: findMissingRequiredParameters(peer, plan.provider, plan.serviceId, requiredParameters);
|
|
1095
|
+
if (missingRequired.length > 0) {
|
|
1096
|
+
log(`Capability filter: peer ${peer.peerId.slice(0, 12)}... service="${plan.serviceId}" `
|
|
1097
|
+
+ `missing required parameter(s): ${missingRequired.join(', ')}`);
|
|
1098
|
+
verdict.reasons.push(`missing required parameter(s): ${missingRequired.join(', ')}`);
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
const requestForPolicy = withRoutedModel(request, plan.serviceId);
|
|
1102
|
+
if (!peerAllowedByPolicy(policyRouter, requestForPolicy, peer)) {
|
|
1103
|
+
verdict.reasons.push(buyerConfigReason(policyRouter?.explainPolicyRejection?.(requestForPolicy, peer) ?? 'outside buyer pricing/reputation limits'));
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
routeCandidates.push({
|
|
1107
|
+
offer,
|
|
1108
|
+
peer,
|
|
1109
|
+
peerId: peer.peerId,
|
|
1110
|
+
serviceId: plan.serviceId,
|
|
1111
|
+
request: requestForPolicy,
|
|
1112
|
+
reputation: normalizedModelReputationScore(peer) ?? -1,
|
|
1113
|
+
hasCachedInputPricing: offer.cachedInputUsdPerMillion !== undefined,
|
|
1114
|
+
inputUsdPerMillion: offer.inputUsdPerMillion ?? null,
|
|
1115
|
+
outputUsdPerMillion: offer.outputUsdPerMillion ?? null,
|
|
1116
|
+
minImageUsdPerImage: offer.minImageUsdPerImage ?? null,
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
const now = this._now();
|
|
1120
|
+
const preferCachedPricing = routeCandidates.some((candidate) => candidate.hasCachedInputPricing);
|
|
1121
|
+
const ranked = routeCandidates.map((candidate) => {
|
|
1122
|
+
const health = this._peerHealth.get(candidate.peer.peerId);
|
|
1123
|
+
return {
|
|
1124
|
+
...candidate,
|
|
1125
|
+
effectiveReputationScore: effectiveModelReputationScore(candidate.reputation >= 0 ? candidate.reputation : null, candidate.hasCachedInputPricing, preferCachedPricing, modelRouteTotalPrice(candidate) === 0),
|
|
1126
|
+
peerCooldownUntil: health?.cooldownUntil ?? null,
|
|
1127
|
+
peerFailureStreak: health?.failureStreak ?? 0,
|
|
1128
|
+
};
|
|
1129
|
+
});
|
|
1130
|
+
let candidates;
|
|
1131
|
+
const routingPreferences = this._routingPreferences;
|
|
1132
|
+
if (requestPolicy && effectivePolicy) {
|
|
1133
|
+
// Exclusions from the merged chain, reported per level: the gateway's
|
|
1134
|
+
// policy on raw trust, the buyer's preferences on the effective trust
|
|
1135
|
+
// its own automatic routing uses. (The buyer's hard limits were
|
|
1136
|
+
// already applied by the router plugin above.) Excluded by the merged
|
|
1137
|
+
// policy exactly when excluded by either level.
|
|
1138
|
+
const buyerPreferences = this._buyerPreferencePolicy();
|
|
1139
|
+
const allowed = ranked.filter((candidate) => {
|
|
1140
|
+
const facts = this._policyFacts(candidate.peer, candidate.offer);
|
|
1141
|
+
const reasons = policyExclusionReasons(requestPolicy, facts);
|
|
1142
|
+
addBuyerConfigReasons(reasons, policyExclusionReasons(buyerPreferences, {
|
|
1143
|
+
...facts, trustScore: modelRouteReputationScore(candidate), trustPenalty: trustPenalty(candidate),
|
|
1144
|
+
}));
|
|
1145
|
+
if (reasons.length === 0)
|
|
1146
|
+
return true;
|
|
1147
|
+
verdicts.get(candidate.peerId)?.reasons.push(...reasons);
|
|
1148
|
+
return false;
|
|
1149
|
+
});
|
|
1150
|
+
candidates = routingPreferences
|
|
1151
|
+
? rankModelRoutes(allowed, rankingPreferences(routingPreferences, effectivePolicy), now)
|
|
1152
|
+
: this._readyFirst(allowed, verdicts, now);
|
|
1153
|
+
}
|
|
1154
|
+
else if (routingPreferences) {
|
|
1155
|
+
candidates = [];
|
|
1156
|
+
for (const candidate of rankModelRoutes(ranked, routingPreferences, now)) {
|
|
1157
|
+
if (isModelRouteEligible(candidate, routingPreferences)) {
|
|
1158
|
+
candidates.push(candidate);
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
verdicts.get(candidate.peerId)?.reasons.push(...preferenceExclusionReasons(routingPreferences, candidate.peerId, modelRouteReputationScore(candidate), trustPenalty(candidate)));
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
candidates = this._readyFirst(ranked, verdicts, now);
|
|
1167
|
+
}
|
|
1168
|
+
const isCooling = (candidate) => isModelRouteCoolingDown(candidate, now);
|
|
1169
|
+
let chainPosition = null;
|
|
1170
|
+
if (effectivePolicy) {
|
|
1171
|
+
const ordering = orderByPolicy(candidates, effectivePolicy, requestedService, {
|
|
1172
|
+
peerId: (candidate) => candidate.peerId,
|
|
1173
|
+
totalPrice: (candidate) => modelRouteTotalPrice(candidate),
|
|
1174
|
+
trustScore: (candidate) => (candidate.reputation >= 0 ? candidate.reputation : null),
|
|
1175
|
+
latencyMs: (candidate) => this._peerLatency(candidate.peerId, candidate.serviceId),
|
|
1176
|
+
coolingDown: isCooling,
|
|
1177
|
+
});
|
|
1178
|
+
for (const candidate of candidates) {
|
|
1179
|
+
const reason = ordering.excluded.get(normalizePolicyPeerId(candidate.peerId));
|
|
1180
|
+
if (reason)
|
|
1181
|
+
verdicts.get(candidate.peerId)?.reasons.push(reason);
|
|
1182
|
+
}
|
|
1183
|
+
candidates = ordering.ordered;
|
|
1184
|
+
chainPosition = ordering.chainPosition;
|
|
1185
|
+
}
|
|
1186
|
+
// Conversation affinity is a soft preference: it only reorders sellers the
|
|
1187
|
+
// policy already allows, and yields to an explicit per-model route.
|
|
1188
|
+
const preferredPeerId = input.preferredPeerId;
|
|
1189
|
+
if (preferredPeerId && !findModelRoute(effectivePolicy, requestedService)) {
|
|
1190
|
+
const preferredIndex = candidates.findIndex((candidate) => (candidate.peer.peerId.toLowerCase() === preferredPeerId
|
|
1191
|
+
&& !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now)));
|
|
1192
|
+
if (preferredIndex > 0) {
|
|
1193
|
+
const [preferred] = candidates.splice(preferredIndex, 1);
|
|
1194
|
+
if (preferred)
|
|
1195
|
+
candidates.unshift(preferred);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
candidates.forEach((candidate, index) => {
|
|
1199
|
+
const verdict = verdicts.get(candidate.peerId);
|
|
1200
|
+
if (!verdict)
|
|
1201
|
+
return;
|
|
1202
|
+
verdict.eligible = true;
|
|
1203
|
+
verdict.rank = index + 1;
|
|
1204
|
+
const position = chainPosition?.get(normalizePolicyPeerId(candidate.peerId));
|
|
1205
|
+
if (position !== undefined)
|
|
1206
|
+
verdict.reasons.push(`route chain #${position + 1}`);
|
|
1207
|
+
if (isCooling(candidate))
|
|
1208
|
+
verdict.reasons.push('cooling down (tried last)');
|
|
1209
|
+
if (modelRouteTotalPrice(candidate) === 0)
|
|
1210
|
+
verdict.reasons.push('free');
|
|
1211
|
+
const latency = this._peerLatency(candidate.peerId, candidate.serviceId);
|
|
1212
|
+
if (latency !== null)
|
|
1213
|
+
verdict.reasons.push(`latency ~${Math.round(latency)}ms`);
|
|
1214
|
+
});
|
|
1215
|
+
return { candidates, verdicts: [...verdicts.values()], effectivePolicy };
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Without routing preferences: best effective reputation first, and only
|
|
1219
|
+
* sellers not cooling down while any are ready.
|
|
1220
|
+
*/
|
|
1221
|
+
_readyFirst(ranked, verdicts, now) {
|
|
1222
|
+
ranked.sort((a, b) => (b.effectiveReputationScore ?? -1) - (a.effectiveReputationScore ?? -1)
|
|
1223
|
+
|| a.peer.peerId.localeCompare(b.peer.peerId));
|
|
1224
|
+
const ready = [];
|
|
1225
|
+
const cooling = [];
|
|
1226
|
+
for (const candidate of ranked) {
|
|
1227
|
+
if (isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now))
|
|
1228
|
+
cooling.push(candidate);
|
|
1229
|
+
else
|
|
1230
|
+
ready.push(candidate);
|
|
1231
|
+
}
|
|
1232
|
+
if (ready.length === 0)
|
|
1233
|
+
return ranked;
|
|
1234
|
+
for (const candidate of cooling)
|
|
1235
|
+
verdicts.get(candidate.peerId)?.reasons.push('cooling down');
|
|
1236
|
+
return ready;
|
|
1237
|
+
}
|
|
1238
|
+
/** `GET /_antseed/route-preview`: what routing would do for a model-only request, ineligible sellers included. */
|
|
1239
|
+
async _routePreview(model, requestPolicy) {
|
|
1240
|
+
const peers = await this._getPeers();
|
|
1241
|
+
const request = {
|
|
1242
|
+
requestId: randomUUID(),
|
|
1243
|
+
method: 'POST',
|
|
1244
|
+
path: '/v1/chat/completions',
|
|
1245
|
+
headers: { 'content-type': 'application/json' },
|
|
1246
|
+
body: new TextEncoder().encode(JSON.stringify({ model })),
|
|
1247
|
+
};
|
|
1248
|
+
const { candidatePeers, routePlanByPeerId } = selectCandidatePeersForRouting(peers, null, model, null, 'strict');
|
|
1249
|
+
const plan = this._planModelRoutes({
|
|
1250
|
+
modelPeers: candidatePeers,
|
|
1251
|
+
modelPlans: routePlanByPeerId,
|
|
1252
|
+
request,
|
|
1253
|
+
requestProtocol: null,
|
|
1254
|
+
requestedService: model,
|
|
1255
|
+
explicitProvider: null,
|
|
1256
|
+
requiredParameters: [],
|
|
1257
|
+
requestPolicy,
|
|
1258
|
+
preferredPeerId: null,
|
|
1259
|
+
});
|
|
1260
|
+
const effective = plan.effectivePolicy;
|
|
1261
|
+
const modelAllowed = policyAllowsModel(effective, model);
|
|
1262
|
+
return plan.verdicts
|
|
1263
|
+
.map((verdict) => ({
|
|
1264
|
+
peerId: verdict.peer.peerId,
|
|
1265
|
+
displayName: verdict.peer.displayName ?? null,
|
|
1266
|
+
rank: modelAllowed ? verdict.rank : null,
|
|
1267
|
+
eligible: modelAllowed && verdict.eligible,
|
|
1268
|
+
reasons: modelAllowed ? verdict.reasons : ['model not allowed', ...verdict.reasons],
|
|
1269
|
+
inputUsdPerMillion: verdict.offer.inputUsdPerMillion ?? null,
|
|
1270
|
+
outputUsdPerMillion: verdict.offer.outputUsdPerMillion ?? null,
|
|
1271
|
+
trustScore: normalizedModelReputationScore(verdict.peer),
|
|
1272
|
+
}))
|
|
1273
|
+
.sort((a, b) => (a.rank ?? Number.POSITIVE_INFINITY) - (b.rank ?? Number.POSITIVE_INFINITY)
|
|
1274
|
+
|| a.peerId.localeCompare(b.peerId));
|
|
1275
|
+
}
|
|
1276
|
+
/** Forget cached balances of an address, e.g. once a deposit sweep is credited. */
|
|
1277
|
+
invalidateBalances(address) {
|
|
1278
|
+
this._balanceCache.invalidate(address.toLowerCase());
|
|
1279
|
+
this._balanceReader?.invalidate?.(address);
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* On-chain balances for a buyer identity, cached ~15 s per address and
|
|
1283
|
+
* answered from the cache (refreshing in the background) for a while
|
|
1284
|
+
* after that. When the chain cannot be read the last values come back
|
|
1285
|
+
* with `stale: true`. `fresh` re-reads, at most every few seconds.
|
|
1286
|
+
*/
|
|
1287
|
+
async _readBalances(address, fresh = false) {
|
|
1288
|
+
const reader = this._balanceReader;
|
|
1289
|
+
const key = address.toLowerCase();
|
|
1290
|
+
const cached = this._balanceCache.peek(key);
|
|
1291
|
+
const force = fresh && (!cached || Date.now() - cached.fetchedAt >= BALANCE_FRESH_FLOOR_MS);
|
|
1292
|
+
if (force)
|
|
1293
|
+
reader.invalidate?.(address);
|
|
1294
|
+
const result = await this._balanceCache.read(key, { ttlMs: BALANCE_CACHE_TTL_MS, staleWhileRevalidateMs: BALANCE_REVALIDATE_MS, force }, async () => {
|
|
1295
|
+
const [balance, wallet, creditLimit, operator] = await Promise.allSettled([
|
|
1296
|
+
reader.getBuyerBalance(address),
|
|
1297
|
+
reader.getUSDCBalance(address),
|
|
1298
|
+
reader.getBuyerCreditLimit(address),
|
|
1299
|
+
reader.getOperator(address),
|
|
1300
|
+
]);
|
|
1301
|
+
if (balance.status === 'rejected')
|
|
1302
|
+
throw balance.reason;
|
|
1303
|
+
return {
|
|
1304
|
+
address,
|
|
1305
|
+
available: formatUsdcBaseUnits(balance.value.available),
|
|
1306
|
+
reserved: formatUsdcBaseUnits(balance.value.reserved),
|
|
1307
|
+
walletUsdc: wallet.status === 'fulfilled' ? formatUsdcBaseUnits(wallet.value) : null,
|
|
1308
|
+
creditLimit: creditLimit.status === 'fulfilled' ? formatUsdcBaseUnits(creditLimit.value) : null,
|
|
1309
|
+
operator: operator.status === 'fulfilled' && operator.value && operator.value.toLowerCase() !== ZERO_ADDRESS
|
|
1310
|
+
? operator.value
|
|
1311
|
+
: null,
|
|
1312
|
+
...(reader.isStale?.(address) ? { stale: true } : {}),
|
|
1313
|
+
};
|
|
1314
|
+
});
|
|
1315
|
+
return result.stale ? { ...result.value, stale: true } : result.value;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Endpoints the local gateway calls with the control secret. Returns false
|
|
1319
|
+
* when the path is not one of them, so the regular control plane handles it.
|
|
1320
|
+
*/
|
|
1321
|
+
async _handleGatewayControl(req, res, method, path) {
|
|
1322
|
+
const url = new URL(path, 'http://localhost');
|
|
1323
|
+
const handlers = {
|
|
1324
|
+
'GET /_antseed/route-preview': () => this._handleRoutePreview(req, res, url),
|
|
1325
|
+
'GET /_antseed/balances': () => this._handleBalances(res, url),
|
|
1326
|
+
// Routing preferences are reloaded from the config file, so they are never overridden.
|
|
1327
|
+
'GET /_antseed/setting-overrides': () => sendJson(res, 200, { overrides: this._settingOverrides }),
|
|
1328
|
+
'POST /_antseed/restart': () => this._handleRestart(res),
|
|
1329
|
+
};
|
|
1330
|
+
const handler = handlers[`${method} ${url.pathname}`];
|
|
1331
|
+
if (!handler)
|
|
1332
|
+
return false;
|
|
1333
|
+
if (!isLoopbackAddress(req.socket?.remoteAddress) || !this._isGatewayAuthenticated(req.headers)) {
|
|
1334
|
+
sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
await handler();
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1340
|
+
/** `GET /_antseed/route-preview?model=<id>`, optionally under a policy header. */
|
|
1341
|
+
async _handleRoutePreview(req, res, url) {
|
|
1342
|
+
const model = url.searchParams.get('model')?.trim() ?? '';
|
|
1343
|
+
if (!model) {
|
|
1344
|
+
sendJson(res, 400, { ok: false, error: 'model is required' });
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const encoded = req.headers[ROUTING_POLICY_HEADER];
|
|
1348
|
+
const raw = Array.isArray(encoded) ? encoded[0] : encoded;
|
|
1349
|
+
let policy = null;
|
|
1350
|
+
if (raw !== undefined) {
|
|
1351
|
+
policy = decodePolicyHeader(raw);
|
|
1352
|
+
if (!policy) {
|
|
1353
|
+
sendJson(res, 400, { ok: false, error: 'invalid_routing_policy' });
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
if (Object.keys(meaningfulPolicy(policy)).length === 0)
|
|
1357
|
+
policy = null;
|
|
1358
|
+
}
|
|
1359
|
+
// `buyer`: the top level of the chain (hard limits plus routing
|
|
1360
|
+
// preferences), so the console can show it (max pricing lives in the
|
|
1361
|
+
// router plugin's config).
|
|
1362
|
+
sendJson(res, 200, { model, candidates: await this._routePreview(model, policy), buyer: this._buyerPolicy() });
|
|
1363
|
+
}
|
|
1364
|
+
/** `GET /_antseed/balances?identity=<name>`: on-chain balances of a buyer identity. */
|
|
1365
|
+
async _handleBalances(res, url) {
|
|
1366
|
+
const identity = await this._resolveBuyerIdentity(url.searchParams.get('identity') ?? undefined);
|
|
1367
|
+
if (identity === undefined) {
|
|
1368
|
+
sendUnknownBuyerIdentity(res);
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
if (!this._balanceReader) {
|
|
1372
|
+
sendJson(res, 503, { ok: false, error: 'payments_disabled' });
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const name = identity ?? DEFAULT_BUYER_IDENTITY;
|
|
1376
|
+
const address = this._node.buyerIdentities?.().find((entry) => entry.name === name)?.address;
|
|
1377
|
+
if (!address) {
|
|
1378
|
+
sendUnknownBuyerIdentity(res);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
try {
|
|
1382
|
+
sendJson(res, 200, await this._readBalances(address, url.searchParams.get('fresh') === '1'));
|
|
1383
|
+
}
|
|
1384
|
+
catch (err) {
|
|
1385
|
+
sendJson(res, 502, { ok: false, error: `Balance read failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* `POST /_antseed/restart`. Only offered under a supervisor that brings the
|
|
1390
|
+
* buyer back (see `buyer start`); otherwise the console asks the operator
|
|
1391
|
+
* to restart it.
|
|
1392
|
+
*/
|
|
1393
|
+
_handleRestart(res) {
|
|
1394
|
+
const restartHandler = this._restartHandler;
|
|
1395
|
+
if (!restartHandler) {
|
|
1396
|
+
sendJson(res, 503, { ok: false, error: 'restart_unsupported' });
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const waitMs = this._restartAllowedAt - Date.now();
|
|
1400
|
+
if (!this._restartRequested && waitMs > 0) {
|
|
1401
|
+
res.setHeader('retry-after', String(Math.ceil(waitMs / 1000)));
|
|
1402
|
+
sendJson(res, 429, { ok: false, error: 'restart_too_soon' });
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (!this._restartRequested) {
|
|
1406
|
+
this._restartRequested = true;
|
|
1407
|
+
log('Restart requested by the local gateway; shutting down gracefully.');
|
|
1408
|
+
res.once('finish', () => {
|
|
1409
|
+
setImmediate(() => {
|
|
1410
|
+
void Promise.resolve().then(restartHandler).catch((err) => {
|
|
1411
|
+
console.error('[proxy] restart failed:', err instanceof Error ? err.message : String(err));
|
|
1412
|
+
});
|
|
1413
|
+
});
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
res.writeHead(202, { 'content-type': 'application/json', connection: 'close' });
|
|
1417
|
+
res.end(JSON.stringify({ ok: true, restarting: true }));
|
|
787
1418
|
}
|
|
788
1419
|
/**
|
|
789
1420
|
* Remember which chat a request belongs to for the life of the request plus
|
|
@@ -807,6 +1438,22 @@ export class BuyerProxy {
|
|
|
807
1438
|
this._depositWatcher = watcher;
|
|
808
1439
|
this._depositWatcherAbsence = watcher ? null : absenceReason;
|
|
809
1440
|
}
|
|
1441
|
+
/** Attach (or detach with null) the deposit watcher of a named buyer identity. */
|
|
1442
|
+
setIdentityDepositWatcher(identity, watcher) {
|
|
1443
|
+
if (watcher)
|
|
1444
|
+
this._identityDepositWatchers.set(identity, watcher);
|
|
1445
|
+
else
|
|
1446
|
+
this._identityDepositWatchers.delete(identity);
|
|
1447
|
+
}
|
|
1448
|
+
/** Watcher for an identity (null = default) and, when there is none, why. */
|
|
1449
|
+
_depositWatcherFor(identity) {
|
|
1450
|
+
const watcher = identity ? this._identityDepositWatchers.get(identity) ?? null : this._depositWatcher;
|
|
1451
|
+
if (watcher)
|
|
1452
|
+
return { watcher, reason: null };
|
|
1453
|
+
// A named identity without a watcher shares the default's cause; if the
|
|
1454
|
+
// default has one, the identity simply has not been given one yet.
|
|
1455
|
+
return { watcher: null, reason: this._depositWatcherAbsence ?? (identity ? 'identity-not-watched' : null) };
|
|
1456
|
+
}
|
|
810
1457
|
/** Latest relayer receipt for a sweep authNonce, if one has arrived. */
|
|
811
1458
|
getSweepReceipt(authNonce) {
|
|
812
1459
|
return this._sweepReceipts.get(authNonce.toLowerCase()) ?? null;
|
|
@@ -989,12 +1636,18 @@ export class BuyerProxy {
|
|
|
989
1636
|
return;
|
|
990
1637
|
try {
|
|
991
1638
|
const config = await loadConfig(this._configPath);
|
|
1639
|
+
// Read straight from the file: no CLI flag or env var overrides routing
|
|
1640
|
+
// preferences, and resolving the full effective buyer config would let
|
|
1641
|
+
// an unrelated invalid field block this reload.
|
|
992
1642
|
const next = config.buyer.routingPreferences;
|
|
993
1643
|
this._routingPreferences = {
|
|
994
1644
|
...next,
|
|
995
1645
|
allowedPeerIds: [...next.allowedPeerIds],
|
|
996
1646
|
blockedPeerIds: [...next.blockedPeerIds],
|
|
997
1647
|
};
|
|
1648
|
+
// `minPeerReputation` (like max pricing) is not reloaded: the router
|
|
1649
|
+
// plugin enforces it with the value it started with, so it applies on
|
|
1650
|
+
// restart only, and the proxy keeps the same value to stay in step.
|
|
998
1651
|
log(`Routing preferences reloaded: minTrust=${next.minTrustScore} maxInput=${next.maxInputUsdPerMillion} `
|
|
999
1652
|
+ `preferFree=${next.preferFreePeers} allow=${next.allowedPeerIds.length} block=${next.blockedPeerIds.length}`);
|
|
1000
1653
|
}
|
|
@@ -1434,9 +2087,11 @@ export class BuyerProxy {
|
|
|
1434
2087
|
const outcome = await this._teeVerification.verifyForDisplay(peer, () => runVerifier({ require: false, prefer: [TEE_VERIFIER_ID] }, peer.peerId, peer.capabilities, (chosen) => makeVerifierReach(this._node, peer, chosen, signal), signal));
|
|
1435
2088
|
if (outcome.code === 'busy')
|
|
1436
2089
|
throw new Error(outcome.reason);
|
|
1437
|
-
});
|
|
2090
|
+
}, this._isGatewayAuthenticated(req.headers));
|
|
1438
2091
|
return;
|
|
1439
2092
|
}
|
|
2093
|
+
if (await this._handleGatewayControl(req, res, method, path))
|
|
2094
|
+
return;
|
|
1440
2095
|
const origin = req.headers.origin ?? '';
|
|
1441
2096
|
const isLocal = origin.startsWith('http://127.0.0.1') || origin.startsWith('http://localhost') || origin === 'file://';
|
|
1442
2097
|
if (isLocal)
|
|
@@ -1499,6 +2154,12 @@ export class BuyerProxy {
|
|
|
1499
2154
|
onChainUsageLastEpochUsdcMicros: p.onChainUsageLastEpochUsdcMicros ?? null,
|
|
1500
2155
|
onChainWashFlagged: p.onChainWashFlagged ?? null,
|
|
1501
2156
|
onChainWashShareBps: p.onChainWashShareBps ?? null,
|
|
2157
|
+
maxConcurrency: p.maxConcurrency ?? null,
|
|
2158
|
+
currentLoad: p.currentLoad ?? null,
|
|
2159
|
+
onChainChannelCount: p.onChainChannelCount ?? null,
|
|
2160
|
+
onChainTotalVolumeUsdcMicros: p.onChainTotalVolumeUsdcMicros ?? null,
|
|
2161
|
+
onChainLastSettledAtSec: p.onChainLastSettledAtSec ?? null,
|
|
2162
|
+
onChainSybilRisk: p.onChainSybilRisk ?? null,
|
|
1502
2163
|
verificationResults: p.verificationResults,
|
|
1503
2164
|
lastSeen: p.lastSeen,
|
|
1504
2165
|
}));
|
|
@@ -1741,8 +2402,7 @@ export class BuyerProxy {
|
|
|
1741
2402
|
}
|
|
1742
2403
|
if (path.startsWith('/_antseed/buyer-identities') && method === 'GET') {
|
|
1743
2404
|
await this._buyerIdentities.loadAll();
|
|
1744
|
-
res
|
|
1745
|
-
res.end(JSON.stringify({ ok: true, identities: this._node.buyerIdentities() }));
|
|
2405
|
+
sendJson(res, 200, { ok: true, identities: this._node.buyerIdentities() });
|
|
1746
2406
|
return;
|
|
1747
2407
|
}
|
|
1748
2408
|
if (path.startsWith('/_antseed/channels') && method === 'GET') {
|
|
@@ -1767,12 +2427,24 @@ export class BuyerProxy {
|
|
|
1767
2427
|
res.end(JSON.stringify({ ok: true, channels: channelsWithCapabilities }));
|
|
1768
2428
|
return;
|
|
1769
2429
|
}
|
|
1770
|
-
//
|
|
1771
|
-
//
|
|
2430
|
+
// Gateway only: the feed holds up to 7 days of per-seller spend and tags.
|
|
2431
|
+
// Once a control secret exists, reading it takes that secret and every
|
|
2432
|
+
// read acknowledges the events before its cursor (lets them be pruned).
|
|
2433
|
+
// Without one (no gateway on this data dir, e.g. a gateway started with
|
|
2434
|
+
// its own --data-dir), loopback callers read it unacknowledged, as before
|
|
2435
|
+
// the secret existed, so such a gateway keeps tracking spend.
|
|
1772
2436
|
if (path.startsWith('/_antseed/attributed-spend') && method === 'GET') {
|
|
2437
|
+
if (!isLoopbackAddress(req.socket?.remoteAddress)) {
|
|
2438
|
+
sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
const hasSecret = this._readControlSecret() !== null;
|
|
2442
|
+
if (hasSecret && !this._isGatewayAuthenticated(req.headers)) {
|
|
2443
|
+
sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
1773
2446
|
const after = Number(new URL(path, 'http://localhost').searchParams.get('after') ?? '0');
|
|
1774
|
-
res.
|
|
1775
|
-
res.end(JSON.stringify({ ok: true, ...this._spendAttribution.page(Number.isFinite(after) ? after : 0) }));
|
|
2447
|
+
sendJson(res, 200, { ok: true, ...this._spendAttribution.page(Number.isFinite(after) ? after : 0, { ack: hasSecret }) });
|
|
1776
2448
|
return;
|
|
1777
2449
|
}
|
|
1778
2450
|
if (path.startsWith('/_antseed/buyer-usage') && method === 'GET') {
|
|
@@ -1863,6 +2535,11 @@ export class BuyerProxy {
|
|
|
1863
2535
|
res.end(JSON.stringify({ ok: false, error: 'Missing peerId' }));
|
|
1864
2536
|
return;
|
|
1865
2537
|
}
|
|
2538
|
+
// Closing hands the seller the identity's latest spending authorization.
|
|
2539
|
+
if (!this._mayUseBuyerIdentity(req, identityParam)) {
|
|
2540
|
+
sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
1866
2543
|
try {
|
|
1867
2544
|
const identity = await this._resolveBuyerIdentity(identityParam);
|
|
1868
2545
|
if (identity === undefined)
|
|
@@ -1883,23 +2560,29 @@ export class BuyerProxy {
|
|
|
1883
2560
|
// Hot-wallet deposit watcher (auto-sweep). The desktop and `antseed
|
|
1884
2561
|
// deposit` drive the daemon's single watcher through these instead of
|
|
1885
2562
|
// running a second signer against the same wallet.
|
|
1886
|
-
|
|
2563
|
+
const controlPathname = path.split('?')[0];
|
|
2564
|
+
if (controlPathname === '/_antseed/deposits/status' && method === 'GET') {
|
|
2565
|
+
const identity = await this._controlPlaneIdentity(path);
|
|
2566
|
+
if (identity === undefined)
|
|
2567
|
+
return sendUnknownBuyerIdentity(res);
|
|
2568
|
+
const { watcher, reason } = this._depositWatcherFor(identity);
|
|
1887
2569
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
1888
2570
|
res.end(JSON.stringify({
|
|
1889
2571
|
ok: true,
|
|
1890
|
-
|
|
2572
|
+
...(identity ? { identity } : {}),
|
|
2573
|
+
watcher: watcher !== null,
|
|
1891
2574
|
// Why no watcher runs (null while one is attached) — lets UIs say
|
|
1892
2575
|
// "payments are disabled" vs "this chain has no deposit relay".
|
|
1893
|
-
reason
|
|
2576
|
+
reason,
|
|
1894
2577
|
// Live payments health: configured/active flags + chain-RPC
|
|
1895
2578
|
// reachability from the node's background monitor. Optional call —
|
|
1896
2579
|
// tolerate an older @antseed/node without it.
|
|
1897
2580
|
payments: this._node.getPaymentsStatus?.() ?? null,
|
|
1898
|
-
status:
|
|
2581
|
+
status: watcher?.status() ?? null,
|
|
1899
2582
|
}));
|
|
1900
2583
|
return;
|
|
1901
2584
|
}
|
|
1902
|
-
if (
|
|
2585
|
+
if (controlPathname === '/_antseed/deposits/watch' && method === 'POST') {
|
|
1903
2586
|
const chunks = [];
|
|
1904
2587
|
let totalSize = 0;
|
|
1905
2588
|
for await (const chunk of req) {
|
|
@@ -1912,9 +2595,12 @@ export class BuyerProxy {
|
|
|
1912
2595
|
chunks.push(chunk);
|
|
1913
2596
|
}
|
|
1914
2597
|
let mode;
|
|
2598
|
+
let bodyIdentity;
|
|
1915
2599
|
try {
|
|
1916
2600
|
const body = JSON.parse(Buffer.concat(chunks).toString() || '{}');
|
|
1917
2601
|
mode = String(body.mode ?? 'active');
|
|
2602
|
+
if (typeof body.identity === 'string')
|
|
2603
|
+
bodyIdentity = body.identity;
|
|
1918
2604
|
}
|
|
1919
2605
|
catch {
|
|
1920
2606
|
res.writeHead(400, { 'content-type': 'application/json' });
|
|
@@ -1926,18 +2612,26 @@ export class BuyerProxy {
|
|
|
1926
2612
|
res.end(JSON.stringify({ ok: false, error: 'mode must be "active" or "background"' }));
|
|
1927
2613
|
return;
|
|
1928
2614
|
}
|
|
1929
|
-
const
|
|
2615
|
+
const identity = await this._controlPlaneIdentity(path, bodyIdentity);
|
|
2616
|
+
if (identity === undefined)
|
|
2617
|
+
return sendUnknownBuyerIdentity(res);
|
|
2618
|
+
const { watcher, reason } = this._depositWatcherFor(identity);
|
|
1930
2619
|
if (!watcher) {
|
|
1931
|
-
const reason = this._depositWatcherAbsence;
|
|
1932
2620
|
const error = (reason && WATCHER_ABSENCE_ERRORS[reason]) ?? 'Deposit watcher unavailable.';
|
|
1933
2621
|
res.writeHead(503, { 'content-type': 'application/json' });
|
|
1934
2622
|
res.end(JSON.stringify({ ok: false, reason, error }));
|
|
1935
2623
|
return;
|
|
1936
2624
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
2625
|
+
// Only the gateway console's watch lapses on its own (it re-requests it
|
|
2626
|
+
// while its page is open); the desktop and `antseed deposit` demote
|
|
2627
|
+
// explicitly, as they always have.
|
|
2628
|
+
if (mode === 'active') {
|
|
2629
|
+
const fromGateway = isLoopbackAddress(req.socket?.remoteAddress) && this._isGatewayAuthenticated(req.headers);
|
|
2630
|
+
watcher.promote(fromGateway ? DEPOSIT_WATCH_ACTIVE_TTL_MS : null);
|
|
2631
|
+
}
|
|
2632
|
+
else {
|
|
1940
2633
|
watcher.demote();
|
|
2634
|
+
}
|
|
1941
2635
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
1942
2636
|
res.end(JSON.stringify({ ok: true, status: watcher.status() }));
|
|
1943
2637
|
return;
|
|
@@ -1958,13 +2652,17 @@ export class BuyerProxy {
|
|
|
1958
2652
|
* `x-antseed-request-id` response header keeps the port-reuse probe in
|
|
1959
2653
|
* `buyer start` recognizing this as an AntSeed proxy.
|
|
1960
2654
|
*/
|
|
1961
|
-
async _handleNetworkModels(res, rawPath) {
|
|
2655
|
+
async _handleNetworkModels(res, rawPath, requestPolicy) {
|
|
1962
2656
|
const url = new URL(rawPath, 'http://localhost');
|
|
1963
2657
|
const responseHeaders = { 'content-type': 'application/json', 'x-antseed-request-id': randomUUID() };
|
|
1964
2658
|
const peers = await this._getPeers();
|
|
2659
|
+
const peerById = new Map(peers.map((peer) => [peer.peerId, peer]));
|
|
1965
2660
|
const models = buildNetworkModels(peers, this._now(), {
|
|
1966
2661
|
routingPreferences: this._routingPreferences,
|
|
1967
2662
|
peerHealth: this._peerHealth,
|
|
2663
|
+
...(requestPolicy
|
|
2664
|
+
? { offerFilter: (offer) => this._policyAllowsOffer(requestPolicy, peerById.get(offer.peerId), offer) }
|
|
2665
|
+
: {}),
|
|
1968
2666
|
});
|
|
1969
2667
|
const modelIdRaw = url.pathname.replace(/^\/v1\/models\/?/i, '');
|
|
1970
2668
|
if (modelIdRaw.length > 0) {
|
|
@@ -2009,6 +2707,18 @@ export class BuyerProxy {
|
|
|
2009
2707
|
res.writeHead(200, responseHeaders);
|
|
2010
2708
|
res.end(JSON.stringify({ object: 'list', data }));
|
|
2011
2709
|
}
|
|
2710
|
+
/**
|
|
2711
|
+
* Whether a request under the gateway's policy could use this offer, by
|
|
2712
|
+
* the checks that judge a pinned `peer@model`: the model allow list, the
|
|
2713
|
+
* seller-level policy, a strict model route and the buyer's hard limits.
|
|
2714
|
+
*/
|
|
2715
|
+
_policyAllowsOffer(requestPolicy, peer, offer) {
|
|
2716
|
+
if (!peer || !policyAllowsModel(this._effectivePolicy(requestPolicy), offer.serviceId))
|
|
2717
|
+
return false;
|
|
2718
|
+
const facts = this._policyFacts(peer, offer);
|
|
2719
|
+
return pinnedPeerExclusionReasons(requestPolicy, facts, offer.serviceId).length === 0
|
|
2720
|
+
&& policyExclusionReasons(this._buyerHardPolicy(), facts).length === 0;
|
|
2721
|
+
}
|
|
2012
2722
|
async _handleRequest(req, res) {
|
|
2013
2723
|
const method = req.method ?? 'GET';
|
|
2014
2724
|
const path = req.url ?? '/';
|
|
@@ -2033,13 +2743,6 @@ export class BuyerProxy {
|
|
|
2033
2743
|
res.end(JSON.stringify({ error: { message: 'Not found', type: 'invalid_request_error' } }));
|
|
2034
2744
|
return;
|
|
2035
2745
|
}
|
|
2036
|
-
// `/v1/models` is answered locally from the discovered-peer cache: one
|
|
2037
|
-
// entry per model across the whole network, with the peers serving it.
|
|
2038
|
-
// Routed to a single pinned seller it would only cover that seller's
|
|
2039
|
-
// services — and would require a pin just to browse the network.
|
|
2040
|
-
if (method === 'GET' && normalizedPath.startsWith('/v1/models')) {
|
|
2041
|
-
return this._handleNetworkModels(res, path);
|
|
2042
|
-
}
|
|
2043
2746
|
// Collect request body
|
|
2044
2747
|
const chunks = [];
|
|
2045
2748
|
for await (const chunk of req) {
|
|
@@ -2073,6 +2776,41 @@ export class BuyerProxy {
|
|
|
2073
2776
|
}
|
|
2074
2777
|
// Remove host header (points to localhost, not the seller)
|
|
2075
2778
|
delete headers['host'];
|
|
2779
|
+
// Routing policy from the local gateway. Both gateway headers are always
|
|
2780
|
+
// stripped here so they never reach a seller.
|
|
2781
|
+
const requestPolicyResult = this._takeRequestPolicy(headers);
|
|
2782
|
+
if (requestPolicyResult.kind === 'auth_invalid') {
|
|
2783
|
+
log('Request rejected: invalid gateway control secret');
|
|
2784
|
+
sendJson(res, 401, {
|
|
2785
|
+
error: {
|
|
2786
|
+
type: 'authentication_error',
|
|
2787
|
+
code: 'gateway_auth_invalid',
|
|
2788
|
+
message: `${GATEWAY_CONTROL_HEADER} does not match this buyer's gateway control secret.`,
|
|
2789
|
+
param: GATEWAY_CONTROL_HEADER,
|
|
2790
|
+
},
|
|
2791
|
+
});
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
if (requestPolicyResult.kind === 'invalid') {
|
|
2795
|
+
sendJson(res, 400, {
|
|
2796
|
+
error: {
|
|
2797
|
+
type: 'invalid_request_error',
|
|
2798
|
+
code: 'invalid_routing_policy',
|
|
2799
|
+
message: `${ROUTING_POLICY_HEADER} is not a valid routing policy.`,
|
|
2800
|
+
param: ROUTING_POLICY_HEADER,
|
|
2801
|
+
},
|
|
2802
|
+
});
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
const requestPolicy = requestPolicyResult.kind === 'policy' ? requestPolicyResult.policy : null;
|
|
2806
|
+
// `/v1/models` is answered locally from the discovered-peer cache: one
|
|
2807
|
+
// entry per model across the whole network, with the peers serving it.
|
|
2808
|
+
// Routed to a single pinned seller it would only cover that seller's
|
|
2809
|
+
// services — and would require a pin just to browse the network. Under a
|
|
2810
|
+
// gateway policy it lists only what the policy lets the caller use.
|
|
2811
|
+
if (method === 'GET' && normalizedPath.startsWith('/v1/models')) {
|
|
2812
|
+
return this._handleNetworkModels(res, path, requestPolicy);
|
|
2813
|
+
}
|
|
2076
2814
|
// Node already answered curl's `Expect: 100-continue` for large bodies;
|
|
2077
2815
|
// forwarded, it makes the seller's fetch fail.
|
|
2078
2816
|
delete headers['expect'];
|
|
@@ -2087,6 +2825,18 @@ export class BuyerProxy {
|
|
|
2087
2825
|
delete headers[SPEND_ATTRIBUTION_HEADER];
|
|
2088
2826
|
// The paying identity rides on the internal headers through routing, like
|
|
2089
2827
|
// a peer pin, and is stripped when the request is built for the seller.
|
|
2828
|
+
if (!this._mayUseBuyerIdentity(req, headers[BUYER_IDENTITY_HEADER])) {
|
|
2829
|
+
log('Request rejected: buyer identity named without the gateway control secret');
|
|
2830
|
+
sendJson(res, 401, {
|
|
2831
|
+
error: {
|
|
2832
|
+
type: 'authentication_error',
|
|
2833
|
+
code: 'gateway_auth_required',
|
|
2834
|
+
message: `Paying as a buyer identity other than the default requires ${GATEWAY_CONTROL_HEADER}.`,
|
|
2835
|
+
param: GATEWAY_CONTROL_HEADER,
|
|
2836
|
+
},
|
|
2837
|
+
});
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2090
2840
|
const buyerIdentity = await this._resolveBuyerIdentity(headers[BUYER_IDENTITY_HEADER]);
|
|
2091
2841
|
if (buyerIdentity === undefined) {
|
|
2092
2842
|
res.writeHead(400, { 'content-type': 'application/json' });
|
|
@@ -2310,6 +3060,17 @@ export class BuyerProxy {
|
|
|
2310
3060
|
}));
|
|
2311
3061
|
return;
|
|
2312
3062
|
}
|
|
3063
|
+
if (requestPolicy && !policyAllowsModel(this._effectivePolicy(requestPolicy), requestedService)) {
|
|
3064
|
+
sendJson(res, 403, {
|
|
3065
|
+
error: {
|
|
3066
|
+
type: 'permission_error',
|
|
3067
|
+
code: 'model_not_allowed',
|
|
3068
|
+
message: `Model "${requestedService ?? 'unknown'}" is not allowed by the routing policy.`,
|
|
3069
|
+
param: 'model',
|
|
3070
|
+
},
|
|
3071
|
+
});
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
2313
3074
|
// Discover peers
|
|
2314
3075
|
const peers = await this._getPeers();
|
|
2315
3076
|
if (peers.length === 0) {
|
|
@@ -2328,72 +3089,18 @@ export class BuyerProxy {
|
|
|
2328
3089
|
({ candidatePeers: modelPeers, routePlanByPeerId: modelPlans } = selectModelPeers(discoveredPeers));
|
|
2329
3090
|
}
|
|
2330
3091
|
const router = this._node.router;
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
const missingRequired = plan.selection?.requiresTransform
|
|
2342
|
-
? requiredParameters
|
|
2343
|
-
: findMissingRequiredParameters(peer, plan.provider, plan.serviceId, requiredParameters);
|
|
2344
|
-
if (missingRequired.length > 0) {
|
|
2345
|
-
log(`Capability filter: peer ${peer.peerId.slice(0, 12)}... service="${plan.serviceId}" `
|
|
2346
|
-
+ `missing required parameter(s): ${missingRequired.join(', ')}`);
|
|
2347
|
-
return null;
|
|
2348
|
-
}
|
|
2349
|
-
const requestForPolicy = withRoutedModel(serializedReq, plan.serviceId);
|
|
2350
|
-
if (!peerAllowedByPolicy(policyRouter, requestForPolicy, peer))
|
|
2351
|
-
return null;
|
|
2352
|
-
return {
|
|
2353
|
-
peer,
|
|
2354
|
-
peerId: peer.peerId,
|
|
2355
|
-
serviceId: plan.serviceId,
|
|
2356
|
-
request: requestForPolicy,
|
|
2357
|
-
reputation: normalizedModelReputationScore(peer) ?? -1,
|
|
2358
|
-
hasCachedInputPricing: offer.cachedInputUsdPerMillion !== undefined,
|
|
2359
|
-
inputUsdPerMillion: offer.inputUsdPerMillion ?? null,
|
|
2360
|
-
outputUsdPerMillion: offer.outputUsdPerMillion ?? null,
|
|
2361
|
-
minImageUsdPerImage: offer.minImageUsdPerImage ?? null,
|
|
2362
|
-
};
|
|
2363
|
-
})
|
|
2364
|
-
.filter((candidate) => candidate !== null);
|
|
2365
|
-
const now = this._now();
|
|
2366
|
-
const preferCachedPricing = routeCandidates.some((candidate) => candidate.hasCachedInputPricing);
|
|
2367
|
-
const ranked = routeCandidates.map((candidate) => {
|
|
2368
|
-
const health = this._peerHealth.get(candidate.peer.peerId);
|
|
2369
|
-
return {
|
|
2370
|
-
...candidate,
|
|
2371
|
-
effectiveReputationScore: effectiveModelReputationScore(candidate.reputation >= 0 ? candidate.reputation : null, candidate.hasCachedInputPricing, preferCachedPricing, modelRouteTotalPrice(candidate) === 0),
|
|
2372
|
-
peerCooldownUntil: health?.cooldownUntil ?? null,
|
|
2373
|
-
peerFailureStreak: health?.failureStreak ?? 0,
|
|
2374
|
-
};
|
|
3092
|
+
const { candidates, effectivePolicy } = this._planModelRoutes({
|
|
3093
|
+
modelPeers,
|
|
3094
|
+
modelPlans,
|
|
3095
|
+
request: serializedReq,
|
|
3096
|
+
requestProtocol,
|
|
3097
|
+
requestedService,
|
|
3098
|
+
explicitProvider,
|
|
3099
|
+
requiredParameters,
|
|
3100
|
+
requestPolicy,
|
|
3101
|
+
preferredPeerId: preferredConversationPeerId,
|
|
2375
3102
|
});
|
|
2376
|
-
|
|
2377
|
-
const routingPreferences = this._routingPreferences;
|
|
2378
|
-
if (routingPreferences) {
|
|
2379
|
-
candidates = rankModelRoutes(ranked, routingPreferences, now)
|
|
2380
|
-
.filter((candidate) => isModelRouteEligible(candidate, routingPreferences));
|
|
2381
|
-
}
|
|
2382
|
-
else {
|
|
2383
|
-
ranked.sort((a, b) => (b.effectiveReputationScore ?? -1) - (a.effectiveReputationScore ?? -1)
|
|
2384
|
-
|| a.peer.peerId.localeCompare(b.peer.peerId));
|
|
2385
|
-
const ready = ranked.filter((candidate) => !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now));
|
|
2386
|
-
candidates = ready.length > 0 ? ready : ranked;
|
|
2387
|
-
}
|
|
2388
|
-
if (preferredConversationPeerId) {
|
|
2389
|
-
const preferredIndex = candidates.findIndex((candidate) => (candidate.peer.peerId.toLowerCase() === preferredConversationPeerId
|
|
2390
|
-
&& !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now)));
|
|
2391
|
-
if (preferredIndex > 0) {
|
|
2392
|
-
const [preferred] = candidates.splice(preferredIndex, 1);
|
|
2393
|
-
if (preferred)
|
|
2394
|
-
candidates.unshift(preferred);
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
3103
|
+
const requireVerified = effectivePolicy?.requireVerified === true;
|
|
2397
3104
|
if (candidates.length === 0) {
|
|
2398
3105
|
const capabilityRequired = requiredParameters.length > 0;
|
|
2399
3106
|
// The model exists on the network but only behind an API this
|
|
@@ -2437,10 +3144,16 @@ export class BuyerProxy {
|
|
|
2437
3144
|
}
|
|
2438
3145
|
let lastRetry = null;
|
|
2439
3146
|
let lastVerificationError = null;
|
|
3147
|
+
// Set once a seller refused for lack of deposits: every other paid
|
|
3148
|
+
// seller would too, so only free ones are still worth trying.
|
|
3149
|
+
let unfunded = false;
|
|
3150
|
+
const isFree = (candidate) => modelRouteTotalPrice(candidate) === 0;
|
|
2440
3151
|
for (const [index, selected] of candidates.entries()) {
|
|
3152
|
+
if (unfunded && !isFree(selected))
|
|
3153
|
+
continue;
|
|
2441
3154
|
if (this._verifier) {
|
|
2442
3155
|
const makeReach = (chosenId) => makeVerifierReach(this._node, selected.peer, chosenId, clientAbortController.signal);
|
|
2443
|
-
const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal);
|
|
3156
|
+
const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal, requireVerified);
|
|
2444
3157
|
if (!outcome.ok) {
|
|
2445
3158
|
lastVerificationError = `Peer ${selected.peer.peerId.slice(0, 12)}... failed required verification (${outcome.reason ?? 'failed'}).`;
|
|
2446
3159
|
log(`${lastVerificationError} Trying the next model peer.`);
|
|
@@ -2452,9 +3165,11 @@ export class BuyerProxy {
|
|
|
2452
3165
|
+ `service="${selected.serviceId}" reputation=${selected.reputation} `
|
|
2453
3166
|
+ `effective=${selected.effectiveReputationScore ?? 'unknown'} peer=${index + 1}/${candidates.length} `
|
|
2454
3167
|
+ `attempt=${peerAttempt + 1}/${MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER}`);
|
|
2455
|
-
const result = await this._dispatchToPeer(res, selected.request, selected.peer, modelPlans, requestProtocol, selected.serviceId, explicitProvider, router,
|
|
3168
|
+
const result = await this._dispatchToPeer(res, selected.request, selected.peer, modelPlans, requestProtocol, selected.serviceId, explicitProvider, router, false, clientAbortController.signal, requestPolicy !== null);
|
|
2456
3169
|
if (result.done) {
|
|
2457
|
-
|
|
3170
|
+
// Only a seller that answered becomes the chat's route: an error
|
|
3171
|
+
// must not pin later turns to it.
|
|
3172
|
+
if (trackedConversationId && res.statusCode < 400) {
|
|
2458
3173
|
this._conversations.recordRoutedModel(trackedConversationId, `${selected.peer.peerId}@${selected.serviceId}`, conversationPinAtRouting);
|
|
2459
3174
|
}
|
|
2460
3175
|
return;
|
|
@@ -2468,6 +3183,12 @@ export class BuyerProxy {
|
|
|
2468
3183
|
}
|
|
2469
3184
|
lastRetry = result;
|
|
2470
3185
|
if (result.responseHeaders[ANTSEED_FAULT_ATTRIBUTION_HEADER]?.toLowerCase() === 'buyer') {
|
|
3186
|
+
const freeLeft = candidates.slice(index + 1).some(isFree);
|
|
3187
|
+
if (result.statusCode === 402 && freeLeft) {
|
|
3188
|
+
unfunded = true;
|
|
3189
|
+
log(`No deposits to pay peer ${selected.peer.peerId.slice(0, 12)}...; trying free sellers only.`);
|
|
3190
|
+
break;
|
|
3191
|
+
}
|
|
2471
3192
|
res.writeHead(result.statusCode, result.responseHeaders);
|
|
2472
3193
|
res.end(result.responseBody);
|
|
2473
3194
|
return;
|
|
@@ -2614,6 +3335,31 @@ export class BuyerProxy {
|
|
|
2614
3335
|
return;
|
|
2615
3336
|
}
|
|
2616
3337
|
const pinnedRequest = pinnedServiceId ? withRoutedModel(serializedReq, pinnedServiceId) : serializedReq;
|
|
3338
|
+
// A hard pin cannot escape the gateway's policy: an excluded seller is
|
|
3339
|
+
// refused rather than silently swapped for another. Only the gateway
|
|
3340
|
+
// policy and the buyer's hard limits judge a pin, never the buyer's
|
|
3341
|
+
// routing preferences (they are automatic-routing defaults, as without
|
|
3342
|
+
// a gateway).
|
|
3343
|
+
const pinnedPolicy = this._effectivePolicy(requestPolicy);
|
|
3344
|
+
if (requestPolicy) {
|
|
3345
|
+
const offer = selectedPlan && pinnedServiceId
|
|
3346
|
+
? findAdvertisedServiceOffer(selectedPeer, selectedPlan.provider, pinnedServiceId)
|
|
3347
|
+
: null;
|
|
3348
|
+
const facts = this._policyFacts(selectedPeer, offer);
|
|
3349
|
+
const reasons = pinnedPeerExclusionReasons(requestPolicy, facts, requestedService);
|
|
3350
|
+
addBuyerConfigReasons(reasons, policyExclusionReasons(this._buyerHardPolicy(), facts));
|
|
3351
|
+
if (reasons.length > 0) {
|
|
3352
|
+
log(`Pinned peer ${selectedPeer.peerId.slice(0, 12)}... refused by routing policy: ${reasons.join('; ')}`);
|
|
3353
|
+
sendJson(res, 403, {
|
|
3354
|
+
error: {
|
|
3355
|
+
type: 'permission_error',
|
|
3356
|
+
code: 'peer_not_allowed',
|
|
3357
|
+
message: `Seller ${selectedPeer.peerId.slice(0, 12)}... is not allowed by the routing policy (${reasons.join('; ')}).`,
|
|
3358
|
+
},
|
|
3359
|
+
});
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
2617
3363
|
if (!peerAllowedByPolicy(policyRouter, pinnedRequest, selectedPeer)) {
|
|
2618
3364
|
log(`Pinned peer ${selectedPeer.peerId.slice(0, 12)}... filtered out by buyer routing policy`);
|
|
2619
3365
|
res.writeHead(502, { 'content-type': 'text/plain' });
|
|
@@ -2623,7 +3369,7 @@ export class BuyerProxy {
|
|
|
2623
3369
|
}
|
|
2624
3370
|
if (this._verifier) {
|
|
2625
3371
|
const makeReach = (chosenId) => makeVerifierReach(this._node, selectedPeer, chosenId, clientAbortController.signal);
|
|
2626
|
-
const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal);
|
|
3372
|
+
const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal, pinnedPolicy?.requireVerified === true);
|
|
2627
3373
|
const short = selectedPeer.peerId.slice(0, 12);
|
|
2628
3374
|
if (outcome.verified) {
|
|
2629
3375
|
log(`Verified ${short}... via ${outcome.sdk}`);
|
|
@@ -2639,7 +3385,7 @@ export class BuyerProxy {
|
|
|
2639
3385
|
}
|
|
2640
3386
|
}
|
|
2641
3387
|
log(`Using pinned peer ${selectedPeer.peerId.slice(0, 12)}...`);
|
|
2642
|
-
const result = await this._dispatchToPeer(res, pinnedRequest, selectedPeer, routingPlans, requestProtocol, requestedService, explicitProvider, router,
|
|
3388
|
+
const result = await this._dispatchToPeer(res, pinnedRequest, selectedPeer, routingPlans, requestProtocol, requestedService, explicitProvider, router, true, clientAbortController.signal);
|
|
2643
3389
|
if (result.done && trackedConversationId && pinnedServiceId) {
|
|
2644
3390
|
this._conversations.recordRoutedModel(trackedConversationId, `${selectedPeer.peerId}@${pinnedServiceId}`, conversationPinAtRouting);
|
|
2645
3391
|
}
|
|
@@ -2650,10 +3396,12 @@ export class BuyerProxy {
|
|
|
2650
3396
|
res.end(result.responseBody);
|
|
2651
3397
|
}
|
|
2652
3398
|
}
|
|
2653
|
-
async _verifyPeer(peer, makeReach, signal) {
|
|
2654
|
-
const
|
|
2655
|
-
if (!
|
|
3399
|
+
async _verifyPeer(peer, makeReach, signal, requireVerified = false) {
|
|
3400
|
+
const configured = this._verifier;
|
|
3401
|
+
if (!configured)
|
|
2656
3402
|
return { ok: true, verified: false };
|
|
3403
|
+
// A routing policy's `requireVerified` makes the buyer's verifier mandatory for this request.
|
|
3404
|
+
const policy = requireVerified && !configured.require ? { ...configured, require: true } : configured;
|
|
2657
3405
|
const fingerprint = verifierSupportFingerprint(peer.capabilities);
|
|
2658
3406
|
const outcome = await this._teeVerification.verify(peer, policy, () => runVerifier(policy, peer.peerId, peer.capabilities, makeReach, signal));
|
|
2659
3407
|
const current = this._cachedPeers.find((candidate) => candidate.peerId === peer.peerId);
|
|
@@ -2731,7 +3479,8 @@ export class BuyerProxy {
|
|
|
2731
3479
|
* was sent to the client (success or non-retryable error), or retry info if the
|
|
2732
3480
|
* caller should try another peer.
|
|
2733
3481
|
*/
|
|
2734
|
-
async _dispatchToPeer(res, serializedReq, selectedPeer, routePlanByPeerId, requestProtocol, requestedService, explicitProvider, router,
|
|
3482
|
+
async _dispatchToPeer(res, serializedReq, selectedPeer, routePlanByPeerId, requestProtocol, requestedService, explicitProvider, router, pinned, requestSignal, gatewayRouted = false) {
|
|
3483
|
+
const gatewayAutoRoute = gatewayRouted && !pinned;
|
|
2735
3484
|
const selectedRoutePlan = routePlanByPeerId.get(selectedPeer.peerId)
|
|
2736
3485
|
?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedService, explicitProvider, 'lenient');
|
|
2737
3486
|
if (!selectedRoutePlan) {
|
|
@@ -2757,6 +3506,7 @@ export class BuyerProxy {
|
|
|
2757
3506
|
...(requestedService ? { 'x-antseed-service': requestedService } : {}),
|
|
2758
3507
|
},
|
|
2759
3508
|
};
|
|
3509
|
+
const latencyServiceId = selectedRoutePlan.serviceId ?? requestedService;
|
|
2760
3510
|
if (selectedRoutePlan.serviceId) {
|
|
2761
3511
|
requestForPeer = withRoutedModel(requestForPeer, selectedRoutePlan.serviceId);
|
|
2762
3512
|
}
|
|
@@ -2832,10 +3582,12 @@ export class BuyerProxy {
|
|
|
2832
3582
|
if (!metadata.streaming)
|
|
2833
3583
|
return;
|
|
2834
3584
|
streamed = true;
|
|
3585
|
+
if (startResponse.statusCode < 400)
|
|
3586
|
+
this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, Date.now() - startTime);
|
|
2835
3587
|
const adaptedStartResponse = streamResponseAdapter
|
|
2836
3588
|
? streamResponseAdapter.adaptStart(startResponse)
|
|
2837
3589
|
: startResponse;
|
|
2838
|
-
const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer
|
|
3590
|
+
const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer);
|
|
2839
3591
|
// Ensure content-type is set for SSE — some upstream APIs (e.g. Codex)
|
|
2840
3592
|
// omit it, which can cause the client's fetch body reader to not
|
|
2841
3593
|
// detect end-of-stream properly.
|
|
@@ -2874,6 +3626,8 @@ export class BuyerProxy {
|
|
|
2874
3626
|
responseForClient = inject402PeerId(responseForClient, selectedPeer.peerId);
|
|
2875
3627
|
}
|
|
2876
3628
|
const latencyMs = Date.now() - startTime;
|
|
3629
|
+
if (!streamed && responseForClient.statusCode < 400)
|
|
3630
|
+
this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
|
|
2877
3631
|
log(`Response: ${responseForClient.statusCode} (${latencyMs}ms, ${responseForClient.body.length} bytes)`);
|
|
2878
3632
|
if (responseForClient.statusCode >= 400) {
|
|
2879
3633
|
const prefix = adaptResponse && !streamed ? 'Upstream adapted error detail' : 'Upstream error detail';
|
|
@@ -2890,7 +3644,7 @@ export class BuyerProxy {
|
|
|
2890
3644
|
if (router && responseFault !== 'buyer') {
|
|
2891
3645
|
router.onResult(selectedPeer, {
|
|
2892
3646
|
success: !modelNotFound
|
|
2893
|
-
&& isRouterSuccess(responseForClient.statusCode, requestForPeer.path,
|
|
3647
|
+
&& isRouterSuccess(responseForClient.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
|
|
2894
3648
|
latencyMs,
|
|
2895
3649
|
tokens: telemetry.usage.totalTokens,
|
|
2896
3650
|
});
|
|
@@ -2910,7 +3664,7 @@ export class BuyerProxy {
|
|
|
2910
3664
|
}
|
|
2911
3665
|
// Non-streamed response — check if retryable
|
|
2912
3666
|
const responseHeaders = attachAntseedTelemetryHeaders(responseForClient.headers, selectedPeer, telemetry, requestForPeer.requestId, latencyMs);
|
|
2913
|
-
if (
|
|
3667
|
+
if (shouldTryNextPeer(responseForClient.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
|
|
2914
3668
|
return {
|
|
2915
3669
|
done: false,
|
|
2916
3670
|
statusCode: responseForClient.statusCode,
|
|
@@ -2951,6 +3705,8 @@ export class BuyerProxy {
|
|
|
2951
3705
|
}
|
|
2952
3706
|
const latencyMs = Date.now() - startTime;
|
|
2953
3707
|
this._markModelActivity();
|
|
3708
|
+
if (response.statusCode < 400)
|
|
3709
|
+
this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
|
|
2954
3710
|
log(`Response: ${response.statusCode} (${latencyMs}ms, ${response.body.length} bytes)`);
|
|
2955
3711
|
if (response.statusCode >= 400) {
|
|
2956
3712
|
const prefix = adaptResponse ? 'Upstream adapted error detail' : 'Upstream error detail';
|
|
@@ -2968,7 +3724,7 @@ export class BuyerProxy {
|
|
|
2968
3724
|
if (router && responseFault !== 'buyer') {
|
|
2969
3725
|
router.onResult(selectedPeer, {
|
|
2970
3726
|
success: !modelNotFound
|
|
2971
|
-
&& isRouterSuccess(response.statusCode, requestForPeer.path,
|
|
3727
|
+
&& isRouterSuccess(response.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
|
|
2972
3728
|
latencyMs,
|
|
2973
3729
|
tokens: telemetry.usage.totalTokens,
|
|
2974
3730
|
});
|
|
@@ -2980,7 +3736,7 @@ export class BuyerProxy {
|
|
|
2980
3736
|
this._recordPeerResponseHealth(selectedPeer.peerId, response.statusCode, requestForPeer.path);
|
|
2981
3737
|
}
|
|
2982
3738
|
// Check if retryable
|
|
2983
|
-
if (
|
|
3739
|
+
if (shouldTryNextPeer(response.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
|
|
2984
3740
|
return { done: false, statusCode: response.statusCode, responseBody: Buffer.from(response.body), responseHeaders, errorMessage: null };
|
|
2985
3741
|
}
|
|
2986
3742
|
// Forward response headers and body to the HTTP client
|