@antseed/cli 0.1.178 → 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 +5 -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/apps/index.d.ts +42 -0
- package/dist/cli/commands/apps/index.d.ts.map +1 -0
- package/dist/cli/commands/apps/index.js +224 -0
- package/dist/cli/commands/apps/index.js.map +1 -0
- package/dist/cli/commands/apps/index.test.d.ts +2 -0
- package/dist/cli/commands/apps/index.test.d.ts.map +1 -0
- package/dist/cli/commands/apps/index.test.js +206 -0
- package/dist/cli/commands/apps/index.test.js.map +1 -0
- package/dist/cli/commands/buyer/deposit.d.ts.map +1 -1
- package/dist/cli/commands/buyer/deposit.js +9 -1
- package/dist/cli/commands/buyer/deposit.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 -42
- 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 +77 -24
- 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 +533 -114
- 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 +882 -118
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +108 -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/credential-headers.d.ts +17 -0
- package/dist/proxy/credential-headers.d.ts.map +1 -0
- package/dist/proxy/credential-headers.js +29 -0
- package/dist/proxy/credential-headers.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 +63 -4
- package/dist/proxy/deposit-watcher.d.ts.map +1 -1
- package/dist/proxy/deposit-watcher.js +120 -19
- package/dist/proxy/deposit-watcher.js.map +1 -1
- package/dist/proxy/deposit-watcher.test.js +81 -5
- 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/dist/system-proxy/proxy-server.js +2 -2
- package/dist/system-proxy/proxy-server.js.map +1 -1
- package/dist/system-proxy/proxy-server.test.js +40 -0
- package/dist/system-proxy/proxy-server.test.js.map +1 -1
- package/package.json +16 -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,17 +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';
|
|
32
|
+
import { isClientCredentialHeader } from './credential-headers.js';
|
|
30
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';
|
|
31
36
|
// Re-export for backward compatibility (used by tests and other consumers)
|
|
32
37
|
export { selectCandidatePeersForRouting } from './routing.js';
|
|
33
38
|
export { parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, ROUTED_MODEL_ALIAS } from './request-utils.js';
|
|
@@ -35,7 +40,34 @@ const WATCHER_ABSENCE_ERRORS = {
|
|
|
35
40
|
'payments-disabled': 'Deposit watcher unavailable — payments are disabled on this buyer.',
|
|
36
41
|
'no-deposit-relay': 'Deposit watcher unavailable — this chain has no deposit relay.',
|
|
37
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.',
|
|
38
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
|
+
}
|
|
39
71
|
// 401/403 are included: sellers relay upstream auth failures (revoked or
|
|
40
72
|
// expired key, region/WAF block) that are specific to that seller's upstream
|
|
41
73
|
// account, so another peer serving the same model can usually complete the
|
|
@@ -43,6 +75,24 @@ const WATCHER_ABSENCE_ERRORS = {
|
|
|
43
75
|
// because model_not_found already has dedicated unadvertise handling while a
|
|
44
76
|
// generic 404 would repeat identically on every peer.
|
|
45
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
|
+
}
|
|
46
96
|
const MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER = 3;
|
|
47
97
|
const MODEL_RATE_LIMIT_RETRY_DELAYS_MS = [250, 750];
|
|
48
98
|
const MODEL_RATE_LIMIT_MAX_RETRY_AFTER_MS = 2_000;
|
|
@@ -95,11 +145,34 @@ function peerAllowedByPolicy(policyRouter, request, peer) {
|
|
|
95
145
|
return policyRouter.allowsPeerForPricing(request, peer);
|
|
96
146
|
return true;
|
|
97
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
|
+
}
|
|
98
170
|
function isControlPlaneServicesPath(path) {
|
|
99
171
|
return path.toLowerCase().startsWith('/v1/models');
|
|
100
172
|
}
|
|
101
173
|
function isRouterSuccess(statusCode, path, retryableStatusCodes) {
|
|
102
|
-
|
|
174
|
+
// A 402 is the buyer's own funding gap, never the seller's failure.
|
|
175
|
+
return isControlPlaneServicesPath(path) || statusCode === 402 || !retryableStatusCodes.has(statusCode);
|
|
103
176
|
}
|
|
104
177
|
/**
|
|
105
178
|
* Detect a "model not served" rejection: the seller's own pre-payment 400
|
|
@@ -609,9 +682,16 @@ export async function mergeJsonStateFile(stateDir, stateFile, patch) {
|
|
|
609
682
|
throw err;
|
|
610
683
|
}
|
|
611
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
|
+
}
|
|
612
693
|
function sendUnknownBuyerIdentity(res) {
|
|
613
|
-
res
|
|
614
|
-
res.end(JSON.stringify({ ok: false, error: 'Unknown buyer identity' }));
|
|
694
|
+
sendJson(res, 404, { ok: false, error: 'Unknown buyer identity' });
|
|
615
695
|
}
|
|
616
696
|
export class BuyerProxy {
|
|
617
697
|
_server;
|
|
@@ -680,6 +760,8 @@ export class BuyerProxy {
|
|
|
680
760
|
_depositWatcher = null;
|
|
681
761
|
/** Why no watcher is attached, so UIs can show the actual cause. */
|
|
682
762
|
_depositWatcherAbsence = null;
|
|
763
|
+
/** Watchers for named buyer identities (each workspace wallet), keyed by identity name. */
|
|
764
|
+
_identityDepositWatchers = new Map();
|
|
683
765
|
/**
|
|
684
766
|
* requestId -> the conversation that issued it, so the node's per-request
|
|
685
767
|
* spend events can be attributed to a chat. Only the proxy knows this
|
|
@@ -690,8 +772,32 @@ export class BuyerProxy {
|
|
|
690
772
|
*/
|
|
691
773
|
_requestConversations = new Map();
|
|
692
774
|
/** Signed spend for requests a local gateway tagged, polled over the control plane. */
|
|
693
|
-
_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;
|
|
694
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 ?? [])];
|
|
695
801
|
this._node = config.node;
|
|
696
802
|
this._verifier = config.verifier;
|
|
697
803
|
this._buyerIdentities = config.buyerIdentities ?? new BuyerIdentityLoader(config.node, config.dataDir);
|
|
@@ -702,6 +808,11 @@ export class BuyerProxy {
|
|
|
702
808
|
this._peerCacheTtlMs = Math.max(0, config.peerCacheTtlMs ?? Math.max(6 * 60_000, this._bgRefreshIntervalMs + 60_000));
|
|
703
809
|
this._stateDir = config.dataDir;
|
|
704
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
|
+
});
|
|
705
816
|
this._configPath = config.configPath ?? null;
|
|
706
817
|
this._conversations = new ConversationStore(config.dataDir);
|
|
707
818
|
this._pinnedPeer = config.pinnedPeerId?.toLowerCase() ?? null;
|
|
@@ -770,7 +881,7 @@ export class BuyerProxy {
|
|
|
770
881
|
* (or can be) loaded, undefined when no stored identity has that name.
|
|
771
882
|
*/
|
|
772
883
|
async _resolveBuyerIdentity(header) {
|
|
773
|
-
const name = header
|
|
884
|
+
const name = buyerIdentityName(header);
|
|
774
885
|
if (!name || name === DEFAULT_BUYER_IDENTITY)
|
|
775
886
|
return null;
|
|
776
887
|
const loaded = await this._buyerIdentities.ensure(name).catch((err) => {
|
|
@@ -779,10 +890,531 @@ export class BuyerProxy {
|
|
|
779
890
|
});
|
|
780
891
|
return loaded ? name : undefined;
|
|
781
892
|
}
|
|
782
|
-
/**
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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 }));
|
|
786
1418
|
}
|
|
787
1419
|
/**
|
|
788
1420
|
* Remember which chat a request belongs to for the life of the request plus
|
|
@@ -806,6 +1438,22 @@ export class BuyerProxy {
|
|
|
806
1438
|
this._depositWatcher = watcher;
|
|
807
1439
|
this._depositWatcherAbsence = watcher ? null : absenceReason;
|
|
808
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
|
+
}
|
|
809
1457
|
/** Latest relayer receipt for a sweep authNonce, if one has arrived. */
|
|
810
1458
|
getSweepReceipt(authNonce) {
|
|
811
1459
|
return this._sweepReceipts.get(authNonce.toLowerCase()) ?? null;
|
|
@@ -988,12 +1636,18 @@ export class BuyerProxy {
|
|
|
988
1636
|
return;
|
|
989
1637
|
try {
|
|
990
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.
|
|
991
1642
|
const next = config.buyer.routingPreferences;
|
|
992
1643
|
this._routingPreferences = {
|
|
993
1644
|
...next,
|
|
994
1645
|
allowedPeerIds: [...next.allowedPeerIds],
|
|
995
1646
|
blockedPeerIds: [...next.blockedPeerIds],
|
|
996
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.
|
|
997
1651
|
log(`Routing preferences reloaded: minTrust=${next.minTrustScore} maxInput=${next.maxInputUsdPerMillion} `
|
|
998
1652
|
+ `preferFree=${next.preferFreePeers} allow=${next.allowedPeerIds.length} block=${next.blockedPeerIds.length}`);
|
|
999
1653
|
}
|
|
@@ -1433,9 +2087,11 @@ export class BuyerProxy {
|
|
|
1433
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));
|
|
1434
2088
|
if (outcome.code === 'busy')
|
|
1435
2089
|
throw new Error(outcome.reason);
|
|
1436
|
-
});
|
|
2090
|
+
}, this._isGatewayAuthenticated(req.headers));
|
|
1437
2091
|
return;
|
|
1438
2092
|
}
|
|
2093
|
+
if (await this._handleGatewayControl(req, res, method, path))
|
|
2094
|
+
return;
|
|
1439
2095
|
const origin = req.headers.origin ?? '';
|
|
1440
2096
|
const isLocal = origin.startsWith('http://127.0.0.1') || origin.startsWith('http://localhost') || origin === 'file://';
|
|
1441
2097
|
if (isLocal)
|
|
@@ -1498,6 +2154,12 @@ export class BuyerProxy {
|
|
|
1498
2154
|
onChainUsageLastEpochUsdcMicros: p.onChainUsageLastEpochUsdcMicros ?? null,
|
|
1499
2155
|
onChainWashFlagged: p.onChainWashFlagged ?? null,
|
|
1500
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,
|
|
1501
2163
|
verificationResults: p.verificationResults,
|
|
1502
2164
|
lastSeen: p.lastSeen,
|
|
1503
2165
|
}));
|
|
@@ -1740,8 +2402,7 @@ export class BuyerProxy {
|
|
|
1740
2402
|
}
|
|
1741
2403
|
if (path.startsWith('/_antseed/buyer-identities') && method === 'GET') {
|
|
1742
2404
|
await this._buyerIdentities.loadAll();
|
|
1743
|
-
res
|
|
1744
|
-
res.end(JSON.stringify({ ok: true, identities: this._node.buyerIdentities() }));
|
|
2405
|
+
sendJson(res, 200, { ok: true, identities: this._node.buyerIdentities() });
|
|
1745
2406
|
return;
|
|
1746
2407
|
}
|
|
1747
2408
|
if (path.startsWith('/_antseed/channels') && method === 'GET') {
|
|
@@ -1766,12 +2427,24 @@ export class BuyerProxy {
|
|
|
1766
2427
|
res.end(JSON.stringify({ ok: true, channels: channelsWithCapabilities }));
|
|
1767
2428
|
return;
|
|
1768
2429
|
}
|
|
1769
|
-
//
|
|
1770
|
-
//
|
|
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.
|
|
1771
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
|
+
}
|
|
1772
2446
|
const after = Number(new URL(path, 'http://localhost').searchParams.get('after') ?? '0');
|
|
1773
|
-
res.
|
|
1774
|
-
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 }) });
|
|
1775
2448
|
return;
|
|
1776
2449
|
}
|
|
1777
2450
|
if (path.startsWith('/_antseed/buyer-usage') && method === 'GET') {
|
|
@@ -1862,6 +2535,11 @@ export class BuyerProxy {
|
|
|
1862
2535
|
res.end(JSON.stringify({ ok: false, error: 'Missing peerId' }));
|
|
1863
2536
|
return;
|
|
1864
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
|
+
}
|
|
1865
2543
|
try {
|
|
1866
2544
|
const identity = await this._resolveBuyerIdentity(identityParam);
|
|
1867
2545
|
if (identity === undefined)
|
|
@@ -1882,23 +2560,29 @@ export class BuyerProxy {
|
|
|
1882
2560
|
// Hot-wallet deposit watcher (auto-sweep). The desktop and `antseed
|
|
1883
2561
|
// deposit` drive the daemon's single watcher through these instead of
|
|
1884
2562
|
// running a second signer against the same wallet.
|
|
1885
|
-
|
|
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);
|
|
1886
2569
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
1887
2570
|
res.end(JSON.stringify({
|
|
1888
2571
|
ok: true,
|
|
1889
|
-
|
|
2572
|
+
...(identity ? { identity } : {}),
|
|
2573
|
+
watcher: watcher !== null,
|
|
1890
2574
|
// Why no watcher runs (null while one is attached) — lets UIs say
|
|
1891
2575
|
// "payments are disabled" vs "this chain has no deposit relay".
|
|
1892
|
-
reason
|
|
2576
|
+
reason,
|
|
1893
2577
|
// Live payments health: configured/active flags + chain-RPC
|
|
1894
2578
|
// reachability from the node's background monitor. Optional call —
|
|
1895
2579
|
// tolerate an older @antseed/node without it.
|
|
1896
2580
|
payments: this._node.getPaymentsStatus?.() ?? null,
|
|
1897
|
-
status:
|
|
2581
|
+
status: watcher?.status() ?? null,
|
|
1898
2582
|
}));
|
|
1899
2583
|
return;
|
|
1900
2584
|
}
|
|
1901
|
-
if (
|
|
2585
|
+
if (controlPathname === '/_antseed/deposits/watch' && method === 'POST') {
|
|
1902
2586
|
const chunks = [];
|
|
1903
2587
|
let totalSize = 0;
|
|
1904
2588
|
for await (const chunk of req) {
|
|
@@ -1911,9 +2595,12 @@ export class BuyerProxy {
|
|
|
1911
2595
|
chunks.push(chunk);
|
|
1912
2596
|
}
|
|
1913
2597
|
let mode;
|
|
2598
|
+
let bodyIdentity;
|
|
1914
2599
|
try {
|
|
1915
2600
|
const body = JSON.parse(Buffer.concat(chunks).toString() || '{}');
|
|
1916
2601
|
mode = String(body.mode ?? 'active');
|
|
2602
|
+
if (typeof body.identity === 'string')
|
|
2603
|
+
bodyIdentity = body.identity;
|
|
1917
2604
|
}
|
|
1918
2605
|
catch {
|
|
1919
2606
|
res.writeHead(400, { 'content-type': 'application/json' });
|
|
@@ -1925,18 +2612,26 @@ export class BuyerProxy {
|
|
|
1925
2612
|
res.end(JSON.stringify({ ok: false, error: 'mode must be "active" or "background"' }));
|
|
1926
2613
|
return;
|
|
1927
2614
|
}
|
|
1928
|
-
const
|
|
2615
|
+
const identity = await this._controlPlaneIdentity(path, bodyIdentity);
|
|
2616
|
+
if (identity === undefined)
|
|
2617
|
+
return sendUnknownBuyerIdentity(res);
|
|
2618
|
+
const { watcher, reason } = this._depositWatcherFor(identity);
|
|
1929
2619
|
if (!watcher) {
|
|
1930
|
-
const reason = this._depositWatcherAbsence;
|
|
1931
2620
|
const error = (reason && WATCHER_ABSENCE_ERRORS[reason]) ?? 'Deposit watcher unavailable.';
|
|
1932
2621
|
res.writeHead(503, { 'content-type': 'application/json' });
|
|
1933
2622
|
res.end(JSON.stringify({ ok: false, reason, error }));
|
|
1934
2623
|
return;
|
|
1935
2624
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
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 {
|
|
1939
2633
|
watcher.demote();
|
|
2634
|
+
}
|
|
1940
2635
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
1941
2636
|
res.end(JSON.stringify({ ok: true, status: watcher.status() }));
|
|
1942
2637
|
return;
|
|
@@ -1957,13 +2652,17 @@ export class BuyerProxy {
|
|
|
1957
2652
|
* `x-antseed-request-id` response header keeps the port-reuse probe in
|
|
1958
2653
|
* `buyer start` recognizing this as an AntSeed proxy.
|
|
1959
2654
|
*/
|
|
1960
|
-
async _handleNetworkModels(res, rawPath) {
|
|
2655
|
+
async _handleNetworkModels(res, rawPath, requestPolicy) {
|
|
1961
2656
|
const url = new URL(rawPath, 'http://localhost');
|
|
1962
2657
|
const responseHeaders = { 'content-type': 'application/json', 'x-antseed-request-id': randomUUID() };
|
|
1963
2658
|
const peers = await this._getPeers();
|
|
2659
|
+
const peerById = new Map(peers.map((peer) => [peer.peerId, peer]));
|
|
1964
2660
|
const models = buildNetworkModels(peers, this._now(), {
|
|
1965
2661
|
routingPreferences: this._routingPreferences,
|
|
1966
2662
|
peerHealth: this._peerHealth,
|
|
2663
|
+
...(requestPolicy
|
|
2664
|
+
? { offerFilter: (offer) => this._policyAllowsOffer(requestPolicy, peerById.get(offer.peerId), offer) }
|
|
2665
|
+
: {}),
|
|
1967
2666
|
});
|
|
1968
2667
|
const modelIdRaw = url.pathname.replace(/^\/v1\/models\/?/i, '');
|
|
1969
2668
|
if (modelIdRaw.length > 0) {
|
|
@@ -2008,6 +2707,18 @@ export class BuyerProxy {
|
|
|
2008
2707
|
res.writeHead(200, responseHeaders);
|
|
2009
2708
|
res.end(JSON.stringify({ object: 'list', data }));
|
|
2010
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
|
+
}
|
|
2011
2722
|
async _handleRequest(req, res) {
|
|
2012
2723
|
const method = req.method ?? 'GET';
|
|
2013
2724
|
const path = req.url ?? '/';
|
|
@@ -2032,13 +2743,6 @@ export class BuyerProxy {
|
|
|
2032
2743
|
res.end(JSON.stringify({ error: { message: 'Not found', type: 'invalid_request_error' } }));
|
|
2033
2744
|
return;
|
|
2034
2745
|
}
|
|
2035
|
-
// `/v1/models` is answered locally from the discovered-peer cache: one
|
|
2036
|
-
// entry per model across the whole network, with the peers serving it.
|
|
2037
|
-
// Routed to a single pinned seller it would only cover that seller's
|
|
2038
|
-
// services — and would require a pin just to browse the network.
|
|
2039
|
-
if (method === 'GET' && normalizedPath.startsWith('/v1/models')) {
|
|
2040
|
-
return this._handleNetworkModels(res, path);
|
|
2041
|
-
}
|
|
2042
2746
|
// Collect request body
|
|
2043
2747
|
const chunks = [];
|
|
2044
2748
|
for await (const chunk of req) {
|
|
@@ -2056,9 +2760,13 @@ export class BuyerProxy {
|
|
|
2056
2760
|
res.end(JSON.stringify({ input_tokens: inputTokens }));
|
|
2057
2761
|
return;
|
|
2058
2762
|
}
|
|
2059
|
-
// Build serialized request
|
|
2763
|
+
// Build serialized request. Client credentials (provider keys, cookies)
|
|
2764
|
+
// never leave the machine — see credential-headers.ts for why no seller
|
|
2765
|
+
// needs them.
|
|
2060
2766
|
const headers = {};
|
|
2061
2767
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
2768
|
+
if (isClientCredentialHeader(key))
|
|
2769
|
+
continue;
|
|
2062
2770
|
if (typeof value === 'string') {
|
|
2063
2771
|
headers[key] = value;
|
|
2064
2772
|
}
|
|
@@ -2068,6 +2776,44 @@ export class BuyerProxy {
|
|
|
2068
2776
|
}
|
|
2069
2777
|
// Remove host header (points to localhost, not the seller)
|
|
2070
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
|
+
}
|
|
2814
|
+
// Node already answered curl's `Expect: 100-continue` for large bodies;
|
|
2815
|
+
// forwarded, it makes the seller's fetch fail.
|
|
2816
|
+
delete headers['expect'];
|
|
2071
2817
|
// Internal marker from the system proxy: the body's model was assigned
|
|
2072
2818
|
// by the proxy's route rewrite, not chosen by the tool. Stripped here so
|
|
2073
2819
|
// it never reaches a seller.
|
|
@@ -2079,6 +2825,18 @@ export class BuyerProxy {
|
|
|
2079
2825
|
delete headers[SPEND_ATTRIBUTION_HEADER];
|
|
2080
2826
|
// The paying identity rides on the internal headers through routing, like
|
|
2081
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
|
+
}
|
|
2082
2840
|
const buyerIdentity = await this._resolveBuyerIdentity(headers[BUYER_IDENTITY_HEADER]);
|
|
2083
2841
|
if (buyerIdentity === undefined) {
|
|
2084
2842
|
res.writeHead(400, { 'content-type': 'application/json' });
|
|
@@ -2302,6 +3060,17 @@ export class BuyerProxy {
|
|
|
2302
3060
|
}));
|
|
2303
3061
|
return;
|
|
2304
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
|
+
}
|
|
2305
3074
|
// Discover peers
|
|
2306
3075
|
const peers = await this._getPeers();
|
|
2307
3076
|
if (peers.length === 0) {
|
|
@@ -2320,72 +3089,18 @@ export class BuyerProxy {
|
|
|
2320
3089
|
({ candidatePeers: modelPeers, routePlanByPeerId: modelPlans } = selectModelPeers(discoveredPeers));
|
|
2321
3090
|
}
|
|
2322
3091
|
const router = this._node.router;
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
const missingRequired = plan.selection?.requiresTransform
|
|
2334
|
-
? requiredParameters
|
|
2335
|
-
: findMissingRequiredParameters(peer, plan.provider, plan.serviceId, requiredParameters);
|
|
2336
|
-
if (missingRequired.length > 0) {
|
|
2337
|
-
log(`Capability filter: peer ${peer.peerId.slice(0, 12)}... service="${plan.serviceId}" `
|
|
2338
|
-
+ `missing required parameter(s): ${missingRequired.join(', ')}`);
|
|
2339
|
-
return null;
|
|
2340
|
-
}
|
|
2341
|
-
const requestForPolicy = withRoutedModel(serializedReq, plan.serviceId);
|
|
2342
|
-
if (!peerAllowedByPolicy(policyRouter, requestForPolicy, peer))
|
|
2343
|
-
return null;
|
|
2344
|
-
return {
|
|
2345
|
-
peer,
|
|
2346
|
-
peerId: peer.peerId,
|
|
2347
|
-
serviceId: plan.serviceId,
|
|
2348
|
-
request: requestForPolicy,
|
|
2349
|
-
reputation: normalizedModelReputationScore(peer) ?? -1,
|
|
2350
|
-
hasCachedInputPricing: offer.cachedInputUsdPerMillion !== undefined,
|
|
2351
|
-
inputUsdPerMillion: offer.inputUsdPerMillion ?? null,
|
|
2352
|
-
outputUsdPerMillion: offer.outputUsdPerMillion ?? null,
|
|
2353
|
-
minImageUsdPerImage: offer.minImageUsdPerImage ?? null,
|
|
2354
|
-
};
|
|
2355
|
-
})
|
|
2356
|
-
.filter((candidate) => candidate !== null);
|
|
2357
|
-
const now = this._now();
|
|
2358
|
-
const preferCachedPricing = routeCandidates.some((candidate) => candidate.hasCachedInputPricing);
|
|
2359
|
-
const ranked = routeCandidates.map((candidate) => {
|
|
2360
|
-
const health = this._peerHealth.get(candidate.peer.peerId);
|
|
2361
|
-
return {
|
|
2362
|
-
...candidate,
|
|
2363
|
-
effectiveReputationScore: effectiveModelReputationScore(candidate.reputation >= 0 ? candidate.reputation : null, candidate.hasCachedInputPricing, preferCachedPricing, modelRouteTotalPrice(candidate) === 0),
|
|
2364
|
-
peerCooldownUntil: health?.cooldownUntil ?? null,
|
|
2365
|
-
peerFailureStreak: health?.failureStreak ?? 0,
|
|
2366
|
-
};
|
|
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,
|
|
2367
3102
|
});
|
|
2368
|
-
|
|
2369
|
-
const routingPreferences = this._routingPreferences;
|
|
2370
|
-
if (routingPreferences) {
|
|
2371
|
-
candidates = rankModelRoutes(ranked, routingPreferences, now)
|
|
2372
|
-
.filter((candidate) => isModelRouteEligible(candidate, routingPreferences));
|
|
2373
|
-
}
|
|
2374
|
-
else {
|
|
2375
|
-
ranked.sort((a, b) => (b.effectiveReputationScore ?? -1) - (a.effectiveReputationScore ?? -1)
|
|
2376
|
-
|| a.peer.peerId.localeCompare(b.peer.peerId));
|
|
2377
|
-
const ready = ranked.filter((candidate) => !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now));
|
|
2378
|
-
candidates = ready.length > 0 ? ready : ranked;
|
|
2379
|
-
}
|
|
2380
|
-
if (preferredConversationPeerId) {
|
|
2381
|
-
const preferredIndex = candidates.findIndex((candidate) => (candidate.peer.peerId.toLowerCase() === preferredConversationPeerId
|
|
2382
|
-
&& !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now)));
|
|
2383
|
-
if (preferredIndex > 0) {
|
|
2384
|
-
const [preferred] = candidates.splice(preferredIndex, 1);
|
|
2385
|
-
if (preferred)
|
|
2386
|
-
candidates.unshift(preferred);
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
3103
|
+
const requireVerified = effectivePolicy?.requireVerified === true;
|
|
2389
3104
|
if (candidates.length === 0) {
|
|
2390
3105
|
const capabilityRequired = requiredParameters.length > 0;
|
|
2391
3106
|
// The model exists on the network but only behind an API this
|
|
@@ -2429,10 +3144,16 @@ export class BuyerProxy {
|
|
|
2429
3144
|
}
|
|
2430
3145
|
let lastRetry = null;
|
|
2431
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;
|
|
2432
3151
|
for (const [index, selected] of candidates.entries()) {
|
|
3152
|
+
if (unfunded && !isFree(selected))
|
|
3153
|
+
continue;
|
|
2433
3154
|
if (this._verifier) {
|
|
2434
3155
|
const makeReach = (chosenId) => makeVerifierReach(this._node, selected.peer, chosenId, clientAbortController.signal);
|
|
2435
|
-
const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal);
|
|
3156
|
+
const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal, requireVerified);
|
|
2436
3157
|
if (!outcome.ok) {
|
|
2437
3158
|
lastVerificationError = `Peer ${selected.peer.peerId.slice(0, 12)}... failed required verification (${outcome.reason ?? 'failed'}).`;
|
|
2438
3159
|
log(`${lastVerificationError} Trying the next model peer.`);
|
|
@@ -2444,9 +3165,11 @@ export class BuyerProxy {
|
|
|
2444
3165
|
+ `service="${selected.serviceId}" reputation=${selected.reputation} `
|
|
2445
3166
|
+ `effective=${selected.effectiveReputationScore ?? 'unknown'} peer=${index + 1}/${candidates.length} `
|
|
2446
3167
|
+ `attempt=${peerAttempt + 1}/${MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER}`);
|
|
2447
|
-
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);
|
|
2448
3169
|
if (result.done) {
|
|
2449
|
-
|
|
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) {
|
|
2450
3173
|
this._conversations.recordRoutedModel(trackedConversationId, `${selected.peer.peerId}@${selected.serviceId}`, conversationPinAtRouting);
|
|
2451
3174
|
}
|
|
2452
3175
|
return;
|
|
@@ -2460,6 +3183,12 @@ export class BuyerProxy {
|
|
|
2460
3183
|
}
|
|
2461
3184
|
lastRetry = result;
|
|
2462
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
|
+
}
|
|
2463
3192
|
res.writeHead(result.statusCode, result.responseHeaders);
|
|
2464
3193
|
res.end(result.responseBody);
|
|
2465
3194
|
return;
|
|
@@ -2606,6 +3335,31 @@ export class BuyerProxy {
|
|
|
2606
3335
|
return;
|
|
2607
3336
|
}
|
|
2608
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
|
+
}
|
|
2609
3363
|
if (!peerAllowedByPolicy(policyRouter, pinnedRequest, selectedPeer)) {
|
|
2610
3364
|
log(`Pinned peer ${selectedPeer.peerId.slice(0, 12)}... filtered out by buyer routing policy`);
|
|
2611
3365
|
res.writeHead(502, { 'content-type': 'text/plain' });
|
|
@@ -2615,7 +3369,7 @@ export class BuyerProxy {
|
|
|
2615
3369
|
}
|
|
2616
3370
|
if (this._verifier) {
|
|
2617
3371
|
const makeReach = (chosenId) => makeVerifierReach(this._node, selectedPeer, chosenId, clientAbortController.signal);
|
|
2618
|
-
const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal);
|
|
3372
|
+
const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal, pinnedPolicy?.requireVerified === true);
|
|
2619
3373
|
const short = selectedPeer.peerId.slice(0, 12);
|
|
2620
3374
|
if (outcome.verified) {
|
|
2621
3375
|
log(`Verified ${short}... via ${outcome.sdk}`);
|
|
@@ -2631,7 +3385,7 @@ export class BuyerProxy {
|
|
|
2631
3385
|
}
|
|
2632
3386
|
}
|
|
2633
3387
|
log(`Using pinned peer ${selectedPeer.peerId.slice(0, 12)}...`);
|
|
2634
|
-
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);
|
|
2635
3389
|
if (result.done && trackedConversationId && pinnedServiceId) {
|
|
2636
3390
|
this._conversations.recordRoutedModel(trackedConversationId, `${selectedPeer.peerId}@${pinnedServiceId}`, conversationPinAtRouting);
|
|
2637
3391
|
}
|
|
@@ -2642,10 +3396,12 @@ export class BuyerProxy {
|
|
|
2642
3396
|
res.end(result.responseBody);
|
|
2643
3397
|
}
|
|
2644
3398
|
}
|
|
2645
|
-
async _verifyPeer(peer, makeReach, signal) {
|
|
2646
|
-
const
|
|
2647
|
-
if (!
|
|
3399
|
+
async _verifyPeer(peer, makeReach, signal, requireVerified = false) {
|
|
3400
|
+
const configured = this._verifier;
|
|
3401
|
+
if (!configured)
|
|
2648
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;
|
|
2649
3405
|
const fingerprint = verifierSupportFingerprint(peer.capabilities);
|
|
2650
3406
|
const outcome = await this._teeVerification.verify(peer, policy, () => runVerifier(policy, peer.peerId, peer.capabilities, makeReach, signal));
|
|
2651
3407
|
const current = this._cachedPeers.find((candidate) => candidate.peerId === peer.peerId);
|
|
@@ -2723,7 +3479,8 @@ export class BuyerProxy {
|
|
|
2723
3479
|
* was sent to the client (success or non-retryable error), or retry info if the
|
|
2724
3480
|
* caller should try another peer.
|
|
2725
3481
|
*/
|
|
2726
|
-
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;
|
|
2727
3484
|
const selectedRoutePlan = routePlanByPeerId.get(selectedPeer.peerId)
|
|
2728
3485
|
?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedService, explicitProvider, 'lenient');
|
|
2729
3486
|
if (!selectedRoutePlan) {
|
|
@@ -2749,6 +3506,7 @@ export class BuyerProxy {
|
|
|
2749
3506
|
...(requestedService ? { 'x-antseed-service': requestedService } : {}),
|
|
2750
3507
|
},
|
|
2751
3508
|
};
|
|
3509
|
+
const latencyServiceId = selectedRoutePlan.serviceId ?? requestedService;
|
|
2752
3510
|
if (selectedRoutePlan.serviceId) {
|
|
2753
3511
|
requestForPeer = withRoutedModel(requestForPeer, selectedRoutePlan.serviceId);
|
|
2754
3512
|
}
|
|
@@ -2824,10 +3582,12 @@ export class BuyerProxy {
|
|
|
2824
3582
|
if (!metadata.streaming)
|
|
2825
3583
|
return;
|
|
2826
3584
|
streamed = true;
|
|
3585
|
+
if (startResponse.statusCode < 400)
|
|
3586
|
+
this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, Date.now() - startTime);
|
|
2827
3587
|
const adaptedStartResponse = streamResponseAdapter
|
|
2828
3588
|
? streamResponseAdapter.adaptStart(startResponse)
|
|
2829
3589
|
: startResponse;
|
|
2830
|
-
const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer
|
|
3590
|
+
const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer);
|
|
2831
3591
|
// Ensure content-type is set for SSE — some upstream APIs (e.g. Codex)
|
|
2832
3592
|
// omit it, which can cause the client's fetch body reader to not
|
|
2833
3593
|
// detect end-of-stream properly.
|
|
@@ -2866,6 +3626,8 @@ export class BuyerProxy {
|
|
|
2866
3626
|
responseForClient = inject402PeerId(responseForClient, selectedPeer.peerId);
|
|
2867
3627
|
}
|
|
2868
3628
|
const latencyMs = Date.now() - startTime;
|
|
3629
|
+
if (!streamed && responseForClient.statusCode < 400)
|
|
3630
|
+
this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
|
|
2869
3631
|
log(`Response: ${responseForClient.statusCode} (${latencyMs}ms, ${responseForClient.body.length} bytes)`);
|
|
2870
3632
|
if (responseForClient.statusCode >= 400) {
|
|
2871
3633
|
const prefix = adaptResponse && !streamed ? 'Upstream adapted error detail' : 'Upstream error detail';
|
|
@@ -2882,7 +3644,7 @@ export class BuyerProxy {
|
|
|
2882
3644
|
if (router && responseFault !== 'buyer') {
|
|
2883
3645
|
router.onResult(selectedPeer, {
|
|
2884
3646
|
success: !modelNotFound
|
|
2885
|
-
&& isRouterSuccess(responseForClient.statusCode, requestForPeer.path,
|
|
3647
|
+
&& isRouterSuccess(responseForClient.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
|
|
2886
3648
|
latencyMs,
|
|
2887
3649
|
tokens: telemetry.usage.totalTokens,
|
|
2888
3650
|
});
|
|
@@ -2902,7 +3664,7 @@ export class BuyerProxy {
|
|
|
2902
3664
|
}
|
|
2903
3665
|
// Non-streamed response — check if retryable
|
|
2904
3666
|
const responseHeaders = attachAntseedTelemetryHeaders(responseForClient.headers, selectedPeer, telemetry, requestForPeer.requestId, latencyMs);
|
|
2905
|
-
if (
|
|
3667
|
+
if (shouldTryNextPeer(responseForClient.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
|
|
2906
3668
|
return {
|
|
2907
3669
|
done: false,
|
|
2908
3670
|
statusCode: responseForClient.statusCode,
|
|
@@ -2943,6 +3705,8 @@ export class BuyerProxy {
|
|
|
2943
3705
|
}
|
|
2944
3706
|
const latencyMs = Date.now() - startTime;
|
|
2945
3707
|
this._markModelActivity();
|
|
3708
|
+
if (response.statusCode < 400)
|
|
3709
|
+
this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
|
|
2946
3710
|
log(`Response: ${response.statusCode} (${latencyMs}ms, ${response.body.length} bytes)`);
|
|
2947
3711
|
if (response.statusCode >= 400) {
|
|
2948
3712
|
const prefix = adaptResponse ? 'Upstream adapted error detail' : 'Upstream error detail';
|
|
@@ -2960,7 +3724,7 @@ export class BuyerProxy {
|
|
|
2960
3724
|
if (router && responseFault !== 'buyer') {
|
|
2961
3725
|
router.onResult(selectedPeer, {
|
|
2962
3726
|
success: !modelNotFound
|
|
2963
|
-
&& isRouterSuccess(response.statusCode, requestForPeer.path,
|
|
3727
|
+
&& isRouterSuccess(response.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
|
|
2964
3728
|
latencyMs,
|
|
2965
3729
|
tokens: telemetry.usage.totalTokens,
|
|
2966
3730
|
});
|
|
@@ -2972,7 +3736,7 @@ export class BuyerProxy {
|
|
|
2972
3736
|
this._recordPeerResponseHealth(selectedPeer.peerId, response.statusCode, requestForPeer.path);
|
|
2973
3737
|
}
|
|
2974
3738
|
// Check if retryable
|
|
2975
|
-
if (
|
|
3739
|
+
if (shouldTryNextPeer(response.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
|
|
2976
3740
|
return { done: false, statusCode: response.statusCode, responseBody: Buffer.from(response.body), responseHeaders, errorMessage: null };
|
|
2977
3741
|
}
|
|
2978
3742
|
// Forward response headers and body to the HTTP client
|