@antseed/node 0.2.156 → 0.2.158
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/dist/discovery/announcer.d.ts +16 -1
- package/dist/discovery/announcer.d.ts.map +1 -1
- package/dist/discovery/announcer.js +69 -9
- package/dist/discovery/announcer.js.map +1 -1
- package/dist/discovery/http-metadata-resolver.d.ts +1 -1
- package/dist/discovery/http-metadata-resolver.d.ts.map +1 -1
- package/dist/discovery/http-metadata-resolver.js +7 -1
- package/dist/discovery/http-metadata-resolver.js.map +1 -1
- package/dist/discovery/index.d.ts +3 -2
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +2 -1
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/metadata/define-version.d.ts +40 -0
- package/dist/discovery/metadata/define-version.d.ts.map +1 -0
- package/dist/discovery/metadata/define-version.js +104 -0
- package/dist/discovery/metadata/define-version.js.map +1 -0
- package/dist/discovery/metadata/field.d.ts +26 -0
- package/dist/discovery/metadata/field.d.ts.map +1 -0
- package/dist/discovery/metadata/field.js +2 -0
- package/dist/discovery/metadata/field.js.map +1 -0
- package/dist/discovery/metadata/fields/offerings.d.ts +7 -0
- package/dist/discovery/metadata/fields/offerings.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/offerings.js +52 -0
- package/dist/discovery/metadata/fields/offerings.js.map +1 -0
- package/dist/discovery/metadata/fields/on-chain-stats.d.ts +6 -0
- package/dist/discovery/metadata/fields/on-chain-stats.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/on-chain-stats.js +30 -0
- package/dist/discovery/metadata/fields/on-chain-stats.js.map +1 -0
- package/dist/discovery/metadata/fields/peer-capabilities.d.ts +4 -0
- package/dist/discovery/metadata/fields/peer-capabilities.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/peer-capabilities.js +21 -0
- package/dist/discovery/metadata/fields/peer-capabilities.js.map +1 -0
- package/dist/discovery/metadata/fields/peer-profile.d.ts +11 -0
- package/dist/discovery/metadata/fields/peer-profile.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/peer-profile.js +58 -0
- package/dist/discovery/metadata/fields/peer-profile.js.map +1 -0
- package/dist/discovery/metadata/fields/provider-basics.d.ts +12 -0
- package/dist/discovery/metadata/fields/provider-basics.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/provider-basics.js +87 -0
- package/dist/discovery/metadata/fields/provider-basics.js.map +1 -0
- package/dist/discovery/metadata/fields/service-api-protocols.d.ts +4 -0
- package/dist/discovery/metadata/fields/service-api-protocols.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/service-api-protocols.js +40 -0
- package/dist/discovery/metadata/fields/service-api-protocols.js.map +1 -0
- package/dist/discovery/metadata/fields/service-capabilities.d.ts +7 -0
- package/dist/discovery/metadata/fields/service-capabilities.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/service-capabilities.js +229 -0
- package/dist/discovery/metadata/fields/service-capabilities.js.map +1 -0
- package/dist/discovery/metadata/fields/service-categories.d.ts +4 -0
- package/dist/discovery/metadata/fields/service-categories.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/service-categories.js +37 -0
- package/dist/discovery/metadata/fields/service-categories.js.map +1 -0
- package/dist/discovery/metadata/fields/service-health.d.ts +7 -0
- package/dist/discovery/metadata/fields/service-health.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/service-health.js +90 -0
- package/dist/discovery/metadata/fields/service-health.js.map +1 -0
- package/dist/discovery/metadata/fields/service-performance.d.ts +7 -0
- package/dist/discovery/metadata/fields/service-performance.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/service-performance.js +63 -0
- package/dist/discovery/metadata/fields/service-performance.js.map +1 -0
- package/dist/discovery/metadata/fields/service-unit-billing.d.ts +8 -0
- package/dist/discovery/metadata/fields/service-unit-billing.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/service-unit-billing.js +81 -0
- package/dist/discovery/metadata/fields/service-unit-billing.js.map +1 -0
- package/dist/discovery/metadata/fields/verifications.d.ts +7 -0
- package/dist/discovery/metadata/fields/verifications.d.ts.map +1 -0
- package/dist/discovery/metadata/fields/verifications.js +96 -0
- package/dist/discovery/metadata/fields/verifications.js.map +1 -0
- package/dist/discovery/metadata/index.d.ts +27 -0
- package/dist/discovery/metadata/index.d.ts.map +1 -0
- package/dist/discovery/metadata/index.js +56 -0
- package/dist/discovery/metadata/index.js.map +1 -0
- package/dist/discovery/metadata/versions/v10.d.ts +5 -0
- package/dist/discovery/metadata/versions/v10.d.ts.map +1 -0
- package/dist/discovery/metadata/versions/v10.js +32 -0
- package/dist/discovery/metadata/versions/v10.js.map +1 -0
- package/dist/discovery/metadata/versions/v11.d.ts +5 -0
- package/dist/discovery/metadata/versions/v11.d.ts.map +1 -0
- package/dist/discovery/metadata/versions/v11.js +11 -0
- package/dist/discovery/metadata/versions/v11.js.map +1 -0
- package/dist/discovery/metadata/versions/v12.d.ts +5 -0
- package/dist/discovery/metadata/versions/v12.d.ts.map +1 -0
- package/dist/discovery/metadata/versions/v12.js +66 -0
- package/dist/discovery/metadata/versions/v12.js.map +1 -0
- package/dist/discovery/metadata/versions/v13.d.ts +5 -0
- package/dist/discovery/metadata/versions/v13.d.ts.map +1 -0
- package/dist/discovery/metadata/versions/v13.js +16 -0
- package/dist/discovery/metadata/versions/v13.js.map +1 -0
- package/dist/discovery/metadata/versions/v14.d.ts +5 -0
- package/dist/discovery/metadata/versions/v14.d.ts.map +1 -0
- package/dist/discovery/metadata/versions/v14.js +16 -0
- package/dist/discovery/metadata/versions/v14.js.map +1 -0
- package/dist/discovery/metadata/wire.d.ts +54 -0
- package/dist/discovery/metadata/wire.d.ts.map +1 -0
- package/dist/discovery/metadata/wire.js +149 -0
- package/dist/discovery/metadata/wire.js.map +1 -0
- package/dist/discovery/metadata-codec.d.ts +1 -38
- package/dist/discovery/metadata-codec.d.ts.map +1 -1
- package/dist/discovery/metadata-codec.js +3 -1154
- package/dist/discovery/metadata-codec.js.map +1 -1
- package/dist/discovery/metadata-server.d.ts +3 -2
- package/dist/discovery/metadata-server.d.ts.map +1 -1
- package/dist/discovery/metadata-server.js +4 -1
- package/dist/discovery/metadata-server.js.map +1 -1
- package/dist/discovery/metadata-validator.d.ts.map +1 -1
- package/dist/discovery/metadata-validator.js +90 -1
- package/dist/discovery/metadata-validator.js.map +1 -1
- package/dist/discovery/service-catalog.d.ts +16 -2
- package/dist/discovery/service-catalog.d.ts.map +1 -1
- package/dist/discovery/service-catalog.js +8 -1
- package/dist/discovery/service-catalog.js.map +1 -1
- package/dist/health/gas-auto-refuel.d.ts +138 -0
- package/dist/health/gas-auto-refuel.d.ts.map +1 -0
- package/dist/health/gas-auto-refuel.js +216 -0
- package/dist/health/gas-auto-refuel.js.map +1 -0
- package/dist/health/gas-health-monitor.d.ts +4 -0
- package/dist/health/gas-health-monitor.d.ts.map +1 -1
- package/dist/health/gas-health-monitor.js +13 -0
- package/dist/health/gas-health-monitor.js.map +1 -1
- package/dist/health/model-health-checker.d.ts +67 -33
- package/dist/health/model-health-checker.d.ts.map +1 -1
- package/dist/health/model-health-checker.js +273 -121
- package/dist/health/model-health-checker.js.map +1 -1
- package/dist/health/self-health-error.d.ts +20 -0
- package/dist/health/self-health-error.d.ts.map +1 -0
- package/dist/health/self-health-error.js +60 -0
- package/dist/health/self-health-error.js.map +1 -0
- package/dist/health/seller-events.d.ts +1 -1
- package/dist/health/seller-events.d.ts.map +1 -1
- package/dist/health/seller-events.js.map +1 -1
- package/dist/health/service-performance.d.ts +28 -0
- package/dist/health/service-performance.d.ts.map +1 -0
- package/dist/health/service-performance.js +144 -0
- package/dist/health/service-performance.js.map +1 -0
- package/dist/health/stream-outcome.d.ts +19 -0
- package/dist/health/stream-outcome.d.ts.map +1 -0
- package/dist/health/stream-outcome.js +91 -0
- package/dist/health/stream-outcome.js.map +1 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/interfaces/seller-provider.d.ts +11 -5
- package/dist/interfaces/seller-provider.d.ts.map +1 -1
- package/dist/node.d.ts +10 -2
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +43 -89
- package/dist/node.js.map +1 -1
- package/dist/p2p/connection-manager.d.ts +5 -1
- package/dist/p2p/connection-manager.d.ts.map +1 -1
- package/dist/p2p/connection-manager.js +48 -13
- package/dist/p2p/connection-manager.js.map +1 -1
- package/dist/payments/chain-config.d.ts +11 -0
- package/dist/payments/chain-config.d.ts.map +1 -1
- package/dist/payments/chain-config.js +7 -0
- package/dist/payments/chain-config.js.map +1 -1
- package/dist/payments/evm/usdc-eth-swap-client.d.ts +45 -0
- package/dist/payments/evm/usdc-eth-swap-client.d.ts.map +1 -0
- package/dist/payments/evm/usdc-eth-swap-client.js +83 -0
- package/dist/payments/evm/usdc-eth-swap-client.js.map +1 -0
- package/dist/payments/index.d.ts +2 -0
- package/dist/payments/index.d.ts.map +1 -1
- package/dist/payments/index.js +2 -0
- package/dist/payments/index.js.map +1 -1
- package/dist/payments/seller-payment-manager.d.ts +50 -0
- package/dist/payments/seller-payment-manager.d.ts.map +1 -1
- package/dist/payments/seller-payment-manager.js +320 -80
- package/dist/payments/seller-payment-manager.js.map +1 -1
- package/dist/routing/seller-health.d.ts +21 -0
- package/dist/routing/seller-health.d.ts.map +1 -1
- package/dist/routing/seller-health.js +19 -3
- package/dist/routing/seller-health.js.map +1 -1
- package/dist/seller-request-handler.d.ts +14 -0
- package/dist/seller-request-handler.d.ts.map +1 -1
- package/dist/seller-request-handler.js +207 -30
- package/dist/seller-request-handler.js.map +1 -1
- package/dist/types/peer.d.ts +6 -2
- package/dist/types/peer.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -14,12 +14,22 @@ import type { SerializedHttpRequest } from './types/http.js';
|
|
|
14
14
|
import { VerificationMux } from './verification/verification-mux.js';
|
|
15
15
|
import type { UnitBillingContext, UnitBillingModelV1 } from './types/billing.js';
|
|
16
16
|
import type { ResourceOwnershipStore } from './resources/resource-ownership-store.js';
|
|
17
|
+
import type { ServicePerformanceTracker } from './health/service-performance.js';
|
|
17
18
|
type ProviderTokenPricing = import('./interfaces/seller-provider.js').ProviderTokenPricingUsdPerMillion;
|
|
18
19
|
/**
|
|
19
20
|
* Routing protocols whose requests do not name a service (`oneServicePerSeller`) let a seller
|
|
20
21
|
* offer at most one service of that protocol; the request path then addresses it.
|
|
21
22
|
*/
|
|
22
23
|
export declare function assertSingleRoutingService(providers: readonly Provider[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* Upstreams sharing a provider name must announce a service they both list
|
|
26
|
+
* with the same terms: price, API protocols, unit billing models and
|
|
27
|
+
* capabilities. Buyers see one entry per provider name and bill by it, while
|
|
28
|
+
* the seller serves through whichever of those upstreams is healthy. A native
|
|
29
|
+
* video service has one upstream per name: a job stays in the account that
|
|
30
|
+
* accepted it, so its creates and retrieves must reach the same upstream.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertSameNameUpstreamsAgree(providers: readonly Provider[]): void;
|
|
23
33
|
export interface SellerRequestHandlerDeps {
|
|
24
34
|
identity: Identity;
|
|
25
35
|
providers: Provider[];
|
|
@@ -34,6 +44,8 @@ export interface SellerRequestHandlerDeps {
|
|
|
34
44
|
reserveEstimateOverdraftUsdc?: bigint;
|
|
35
45
|
/** Persistent buyer ownership and pending charges for stateful video jobs. Video follow-ups fail closed without it. */
|
|
36
46
|
resourceOwnershipStore?: ResourceOwnershipStore | null;
|
|
47
|
+
/** Records how each service served buyers, for the seller to announce. */
|
|
48
|
+
servicePerformance?: ServicePerformanceTracker | null;
|
|
37
49
|
emit: (event: string, ...args: unknown[]) => boolean;
|
|
38
50
|
}
|
|
39
51
|
/**
|
|
@@ -46,6 +58,8 @@ export interface SellerRequestHandlerDeps {
|
|
|
46
58
|
export declare class SellerRequestHandler {
|
|
47
59
|
private readonly _deps;
|
|
48
60
|
private readonly _providerLoadCounts;
|
|
61
|
+
/** Per channel: the highest spend a catch-up NeedAuth was already sent for, so parked requests ask once. */
|
|
62
|
+
private readonly _catchUpPrompted;
|
|
49
63
|
private readonly _attestRateWindows;
|
|
50
64
|
private _metadataRefreshTimer;
|
|
51
65
|
constructor(deps: SellerRequestHandlerDeps);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seller-request-handler.d.ts","sourceRoot":"","sources":["../src/seller-request-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EACV,QAAQ,EAET,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,KAAK,EAAoB,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAUtG,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,iBAAiB,CAAC;AAgBzB,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"seller-request-handler.d.ts","sourceRoot":"","sources":["../src/seller-request-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EACV,QAAQ,EAET,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,KAAK,EAAoB,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAUtG,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,iBAAiB,CAAC;AAgBzB,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAKrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAA8C,MAAM,oBAAoB,CAAC;AAG7H,OAAO,KAAK,EAAyB,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AAU7G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAIjF,KAAK,oBAAoB,GAAG,OAAO,iCAAiC,EAAE,iCAAiC,CAAC;AASxG;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,IAAI,CAQ/E;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,IAAI,CA4BjF;AAmDD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,oBAAoB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACvD,qBAAqB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACrD,cAAc,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC5C,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,uHAAuH;IACvH,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACvD,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACtD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;CACtD;AAoBD;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA6B;IACjE,4GAA4G;IAC5G,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAC9D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAuD;IAC1F,OAAO,CAAC,qBAAqB,CAA8C;gBAE/D,IAAI,EAAE,wBAAwB;IAI1C,OAAO,CAAC,YAAY;IAoBpB;;;OAGG;IACH,gBAAgB,CACd,IAAI,EAAE,cAAc,EACpB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,eAAe,GAC/B;QAAE,GAAG,EAAE,QAAQ,CAAA;KAAE;IA02BpB,oDAAoD;IACpD,OAAO,CAAC,oBAAoB;IA4B5B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA8C7B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAuC1B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAa5B,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,cAAc;IAiBtB;;;;;OAKG;YACW,qBAAqB;IAiBnC,OAAO,CAAC,oBAAoB;IA4C5B,aAAa,CAAC,OAAO,EAAE,qBAAqB,GAAG,QAAQ,GAAG,SAAS;IA8BnE,sBAAsB,CACpB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB;IAWvB,+BAA+B,CAC7B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,kBAAkB,GAC1B,kBAAkB,GAAG,SAAS;IAMjC,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAY7D,yBAAyB,IAAI,IAAI;IAUjC,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,wBAAwB;IAehC,OAAO,CAAC,yBAAyB;IASjC,OAAO,CAAC,4BAA4B;IAYpC,OAAO,CAAC,+BAA+B;IAevC,OAAO,CAAC,4BAA4B;IAWpC;;;;;OAKG;YACW,gBAAgB;IAmB9B,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,gCAAgC;IA0BxC,OAAO,CAAC,uBAAuB;IAiB/B,OAAO,CAAC,qBAAqB;YAcf,eAAe;IAW7B,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,8BAA8B;IAetC,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,2BAA2B;CA8BpC"}
|
|
@@ -13,11 +13,15 @@ import { debugLog, debugWarn } from './utils/debug.js';
|
|
|
13
13
|
import { CONNECTION_CAPABILITY_RESPONSE_AUTH_V1, DEFAULT_FIRST_SIGN_CAP, DEFAULT_TOP_UP_SETTLED_THRESHOLD_BPS, computeOneOffChannelPlan, PAYMENT_CODE_CHANNEL_EXHAUSTED, PAYMENT_CODE_ONE_OFF_CHANNEL_REQUIRED, } from './types/protocol.js';
|
|
14
14
|
import { createResponseAuthPayload, createStreamingResponseHash } from './verification/response-auth.js';
|
|
15
15
|
import { VIDEO_DOWNLOAD_STREAM_HEADER, VIDEO_DOWNLOAD_STREAM_VERSION } from '@antseed/protocol/http';
|
|
16
|
+
import { isServiceHealthFailing, preferHealthyUpstream, serviceHealthOf } from '@antseed/protocol/peer-metadata';
|
|
16
17
|
import { hasJsonContentType, tryParseJsonObject } from './utils/json-codec.js';
|
|
17
18
|
import { captureUnitBillingContext, computeFinalUnitBilling, estimateUnitRequestCost, isFreeUnitBillingModel } from './billing/unit.js';
|
|
18
19
|
import { nativeVideoAcceptance, nativeVideoDelivered, nativeVideoRoute, requestService } from '@antseed/api-adapter';
|
|
20
|
+
import { isNativeVideoProtocol } from '@antseed/protocol/service-api';
|
|
19
21
|
import { detectRequestServiceApiProtocol, extractRequestBodyFields, selectTargetProtocolForRequest, } from '@antseed/api-adapter';
|
|
20
22
|
import { parseResponseUsage } from './utils/response-usage.js';
|
|
23
|
+
import { buildSelfHealthErrorResponse, isUpstreamFailureStatus } from './health/self-health-error.js';
|
|
24
|
+
import { StreamOutcome } from './health/stream-outcome.js';
|
|
21
25
|
import { routingProtocolForPath, routingProtocolOf, routingRequestOf, routingWireProtocol, ROUTING_PROTOCOLS } from '@antseed/protocol/routing';
|
|
22
26
|
/** A seller's services of one routing protocol. */
|
|
23
27
|
function routingServices(providers, protocol) {
|
|
@@ -39,6 +43,50 @@ export function assertSingleRoutingService(providers) {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Upstreams sharing a provider name must announce a service they both list
|
|
48
|
+
* with the same terms: price, API protocols, unit billing models and
|
|
49
|
+
* capabilities. Buyers see one entry per provider name and bill by it, while
|
|
50
|
+
* the seller serves through whichever of those upstreams is healthy. A native
|
|
51
|
+
* video service has one upstream per name: a job stays in the account that
|
|
52
|
+
* accepted it, so its creates and retrieves must reach the same upstream.
|
|
53
|
+
*/
|
|
54
|
+
export function assertSameNameUpstreamsAgree(providers) {
|
|
55
|
+
const terms = new Map();
|
|
56
|
+
for (const provider of providers) {
|
|
57
|
+
for (const service of provider.services) {
|
|
58
|
+
const video = provider.serviceApiProtocols?.[service]?.some(isNativeVideoProtocol) ?? false;
|
|
59
|
+
const announced = canonicalJson({
|
|
60
|
+
pricing: provider.pricing.services?.[service] ?? provider.pricing.defaults,
|
|
61
|
+
protocols: [...(provider.serviceApiProtocols?.[service] ?? [])].sort(),
|
|
62
|
+
unitBilling: provider.serviceUnitBillingModels?.[service] ?? null,
|
|
63
|
+
capabilities: provider.serviceCapabilities?.[service] ?? null,
|
|
64
|
+
});
|
|
65
|
+
const key = `${provider.name.toLowerCase()}\u0000${service}`;
|
|
66
|
+
const first = terms.get(key);
|
|
67
|
+
if (!first) {
|
|
68
|
+
terms.set(key, { provider, terms: announced });
|
|
69
|
+
}
|
|
70
|
+
else if (video) {
|
|
71
|
+
throw new Error(`Providers sharing the name "${provider.name}" both offer the video service "${service}". `
|
|
72
|
+
+ 'A video job stays with the upstream that accepted it, so offer it from one of them only.');
|
|
73
|
+
}
|
|
74
|
+
else if (first.terms !== announced) {
|
|
75
|
+
throw new Error(`Providers sharing the name "${provider.name}" offer "${service}" with different pricing, API protocols, billing or capabilities. `
|
|
76
|
+
+ 'Buyers see one entry per provider name, so offer it from one of them only, or on the same terms.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** JSON with object keys sorted, so equal values compare equal. */
|
|
82
|
+
function canonicalJson(value) {
|
|
83
|
+
if (Array.isArray(value))
|
|
84
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
85
|
+
if (value && typeof value === 'object') {
|
|
86
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
87
|
+
}
|
|
88
|
+
return JSON.stringify(value) ?? 'null';
|
|
89
|
+
}
|
|
42
90
|
const FREE_TOKEN_PRICING = { inputUsdPerMillion: 0, outputUsdPerMillion: 0 };
|
|
43
91
|
function isZeroTokenPricing(pricing) {
|
|
44
92
|
return pricing.inputUsdPerMillion === 0
|
|
@@ -80,6 +128,10 @@ function takeHeader(headers, name) {
|
|
|
80
128
|
const METADATA_REFRESH_DEBOUNCE_MS = 200;
|
|
81
129
|
/** Time to wait for a catch-up SpendingAuth before returning 402. */
|
|
82
130
|
const DEFAULT_CATCH_UP_WAIT_MS = 5_000;
|
|
131
|
+
/** How long a request waits for a closing channel's close() to land before it is answered. */
|
|
132
|
+
const CLOSING_CHANNEL_WAIT_MS = 15_000;
|
|
133
|
+
/** How long a request at the reserve ceiling waits for a running top-up before a retryable answer. */
|
|
134
|
+
const TOP_UP_WAIT_MS = 15_000;
|
|
83
135
|
/** Per-buyer rate limit for the free attestation route. */
|
|
84
136
|
const ATTEST_RATE_WINDOW_MS = 60_000;
|
|
85
137
|
const ATTEST_RATE_MAX_PER_WINDOW = 10;
|
|
@@ -94,6 +146,8 @@ const ATTEST_RATE_MAX_TRACKED_PEERS = 1024;
|
|
|
94
146
|
export class SellerRequestHandler {
|
|
95
147
|
_deps;
|
|
96
148
|
_providerLoadCounts = new Map();
|
|
149
|
+
/** Per channel: the highest spend a catch-up NeedAuth was already sent for, so parked requests ask once. */
|
|
150
|
+
_catchUpPrompted = new Map();
|
|
97
151
|
_attestRateWindows = new Map();
|
|
98
152
|
_metadataRefreshTimer = null;
|
|
99
153
|
constructor(deps) {
|
|
@@ -470,9 +524,8 @@ export class SellerRequestHandler {
|
|
|
470
524
|
await spm.waitForPendingAuths(buyerPeerId);
|
|
471
525
|
// Re-read after the await — the session may have been evicted (timeout
|
|
472
526
|
// checker, disconnect) while the on-chain top-up was confirming.
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
debugWarn(`[SellerHandler] Session evicted during waitForPendingAuths for ${buyerPeerId.slice(0, 12)}... — returning 402`);
|
|
527
|
+
const sendSessionExpired = (during) => {
|
|
528
|
+
debugWarn(`[SellerHandler] Session evicted during ${during} for ${buyerPeerId.slice(0, 12)}... — returning 402`);
|
|
476
529
|
mux.sendProxyResponse({
|
|
477
530
|
requestId: request.requestId,
|
|
478
531
|
statusCode: 402,
|
|
@@ -482,19 +535,20 @@ export class SellerRequestHandler {
|
|
|
482
535
|
message: 'Session expired, please renegotiate',
|
|
483
536
|
})),
|
|
484
537
|
});
|
|
538
|
+
};
|
|
539
|
+
const session = spm.getChannelByPeer(buyerPeerId);
|
|
540
|
+
if (!session) {
|
|
541
|
+
sendSessionExpired('waitForPendingAuths');
|
|
485
542
|
return;
|
|
486
543
|
}
|
|
487
544
|
let accepted = spm.getAcceptedCumulative(session.sessionId);
|
|
488
|
-
|
|
545
|
+
let spent = spm.getCumulativeSpend(session.sessionId);
|
|
489
546
|
// Serving headroom only includes funds locked on-chain. Pending
|
|
490
547
|
// top-ups are not counted until topUp() succeeds, otherwise a large
|
|
491
548
|
// request could push spend above the current reserve before the extra
|
|
492
549
|
// funds are actually locked.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
// If spend has caught up and there is no headroom left in the reserve,
|
|
496
|
-
// stop serving before accepting any additional request cost.
|
|
497
|
-
const isAtExactSpendLimit = spent > 0n && spent === accepted && reserveMax > 0n && accepted >= reserveMax;
|
|
550
|
+
let reserveMax = spm.getEffectiveReserveMax(session.sessionId);
|
|
551
|
+
let isBlocked = spm.isChannelBlocked(session.sessionId);
|
|
498
552
|
if (spent > 0n && spent > accepted) {
|
|
499
553
|
// Race cover: the buyer's SpendingAuth for the *previous* response's
|
|
500
554
|
// NeedAuth may still be on the wire when this request arrives. The
|
|
@@ -505,21 +559,96 @@ export class SellerRequestHandler {
|
|
|
505
559
|
// operation this wait is a no-op; under pipelined requests (a new
|
|
506
560
|
// request dispatched before the prior NeedAuth → SpendingAuth has
|
|
507
561
|
// completed) it hides the round-trip latency from the buyer.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
562
|
+
// Ask for the catch-up now instead of only after the wait: after a
|
|
563
|
+
// restart (or a lost NeedAuth) nothing else would prompt the buyer,
|
|
564
|
+
// and every parked request would end in a 402. Once per spend level,
|
|
565
|
+
// and only for spend the reserve can cover (beyond it the channel is closed below).
|
|
566
|
+
const signable = reserveMax <= 0n || spent <= reserveMax;
|
|
567
|
+
if (signable && spent > (this._catchUpPrompted.get(session.sessionId) ?? 0n)) {
|
|
568
|
+
this._catchUpPrompted.delete(session.sessionId);
|
|
569
|
+
this._catchUpPrompted.set(session.sessionId, spent);
|
|
570
|
+
if (this._catchUpPrompted.size > 1_024)
|
|
571
|
+
this._catchUpPrompted.delete(this._catchUpPrompted.keys().next().value);
|
|
572
|
+
// A channel-level prompt: no requestId, since it is not about the
|
|
573
|
+
// request that is waiting (its cost is not known yet).
|
|
574
|
+
this._sendNeedAuthBestEffort(paymentMux, {
|
|
575
|
+
channelId: session.sessionId,
|
|
576
|
+
requiredCumulativeAmount: spent.toString(),
|
|
577
|
+
currentAcceptedCumulative: accepted.toString(),
|
|
578
|
+
deposit: session.authMax ?? '0',
|
|
579
|
+
}, buyerPeerId, 'catch-up-before-wait');
|
|
580
|
+
}
|
|
581
|
+
// Other requests can finish while this one is parked: re-read the
|
|
582
|
+
// spend after each catch-up and keep waiting, within one budget,
|
|
583
|
+
// so an auth for the earlier spend never releases newer unpaid work.
|
|
584
|
+
const deadline = Date.now() + DEFAULT_CATCH_UP_WAIT_MS;
|
|
585
|
+
let caughtUp = true;
|
|
586
|
+
while (caughtUp && spent > accepted) {
|
|
587
|
+
caughtUp = await spm.awaitAcceptedAtLeast(session.sessionId, spent, Math.max(0, deadline - Date.now()));
|
|
588
|
+
accepted = spm.getAcceptedCumulative(session.sessionId);
|
|
589
|
+
spent = spm.getCumulativeSpend(session.sessionId);
|
|
590
|
+
}
|
|
591
|
+
// A channel closed or evicted while parked has its counters reset
|
|
592
|
+
// to zero, which would read as caught up: renegotiate instead.
|
|
593
|
+
if (spm.getChannelByPeer(buyerPeerId)?.sessionId !== session.sessionId) {
|
|
594
|
+
sendSessionExpired('the catch-up wait');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
// A top-up can land while parked: judge exhaustion on the current
|
|
598
|
+
// ceiling, or a channel that just grew is closed as exhausted.
|
|
599
|
+
reserveMax = spm.getEffectiveReserveMax(session.sessionId);
|
|
600
|
+
isBlocked = spm.isChannelBlocked(session.sessionId);
|
|
601
|
+
if (spent <= accepted) {
|
|
511
602
|
debugLog(`[SellerHandler] Caught up before 402 for ${buyerPeerId.slice(0, 12)}... (spent=${spent} accepted=${accepted})`);
|
|
512
603
|
}
|
|
513
604
|
}
|
|
514
|
-
|
|
605
|
+
// A stored ceiling can lag the chain (a top-up that confirmed while
|
|
606
|
+
// the seller was down): check it before treating the channel as full.
|
|
515
607
|
const reserveEstimateOverdraft = this._deps.reserveEstimateOverdraftUsdc;
|
|
608
|
+
const estimateExceedsReserve = () => reserveEstimateOverdraft != null
|
|
609
|
+
&& reserveMax > 0n
|
|
610
|
+
&& estimatedRequestCost > 0n
|
|
611
|
+
&& estimatedRequestCost > (reserveMax > spent ? reserveMax - spent : 0n) + reserveEstimateOverdraft;
|
|
612
|
+
const atCeiling = () => reserveMax > 0n && (accepted >= reserveMax || spent > reserveMax || estimateExceedsReserve());
|
|
613
|
+
if (atCeiling()) {
|
|
614
|
+
// A top-up mining in the background may lift the ceiling: wait for
|
|
615
|
+
// it (bounded) rather than close a channel that is being extended.
|
|
616
|
+
let topUpLanded = true;
|
|
617
|
+
if (spm.isTopUpRunning(session.sessionId)) {
|
|
618
|
+
topUpLanded = await spm.waitForTopUp(session.sessionId, TOP_UP_WAIT_MS);
|
|
619
|
+
}
|
|
620
|
+
reserveMax = await spm.refreshReserveMaxFromChain(session.sessionId);
|
|
621
|
+
// The channel may have closed during the read: never admit work
|
|
622
|
+
// on a channel that is gone, and judge the others on fresh counters.
|
|
623
|
+
if (spm.getChannelByPeer(buyerPeerId)?.sessionId !== session.sessionId) {
|
|
624
|
+
sendSessionExpired('the reserve ceiling check');
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
accepted = spm.getAcceptedCumulative(session.sessionId);
|
|
628
|
+
spent = spm.getCumulativeSpend(session.sessionId);
|
|
629
|
+
isBlocked = spm.isChannelBlocked(session.sessionId);
|
|
630
|
+
if (!topUpLanded && atCeiling()) {
|
|
631
|
+
// Still mining: a short retry, never an exhausted (closing) answer.
|
|
632
|
+
mux.sendProxyResponse({
|
|
633
|
+
requestId: request.requestId,
|
|
634
|
+
statusCode: 503,
|
|
635
|
+
headers: { 'content-type': 'application/json', 'retry-after': '2' },
|
|
636
|
+
body: new TextEncoder().encode(JSON.stringify({
|
|
637
|
+
error: 'top_up_pending',
|
|
638
|
+
message: 'The payment channel is being extended on-chain; retry shortly',
|
|
639
|
+
})),
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// If spend has caught up and there is no headroom left in the reserve,
|
|
645
|
+
// stop serving before accepting any additional request cost.
|
|
646
|
+
const isAtExactSpendLimit = spent > 0n && spent === accepted && reserveMax > 0n && accepted >= reserveMax;
|
|
647
|
+
const remainingLockedReserve = reserveMax > spent ? reserveMax - spent : 0n;
|
|
516
648
|
const effectiveEstimateLimit = reserveEstimateOverdraft != null
|
|
517
649
|
? remainingLockedReserve + reserveEstimateOverdraft
|
|
518
650
|
: null;
|
|
519
|
-
const estimatedCostExceedsLockedReserve =
|
|
520
|
-
&& reserveMax > 0n
|
|
521
|
-
&& estimatedRequestCost > 0n
|
|
522
|
-
&& estimatedRequestCost > effectiveEstimateLimit;
|
|
651
|
+
const estimatedCostExceedsLockedReserve = estimateExceedsReserve();
|
|
523
652
|
if (isBlocked || (spent > 0n && (spent > accepted || isAtExactSpendLimit)) || estimatedCostExceedsLockedReserve) {
|
|
524
653
|
const baseRequirements = spm.getPaymentRequirements(request.requestId, buyerPeerId, requestPricing);
|
|
525
654
|
// Tell the buyer exactly how much delivered spend remains unsigned.
|
|
@@ -625,6 +754,8 @@ export class SellerRequestHandler {
|
|
|
625
754
|
let downloadHash;
|
|
626
755
|
let heldDoneChunkData = null;
|
|
627
756
|
let responseStartedAt = startTime;
|
|
757
|
+
// First generated token and in-stream failures of a streamed answer, for the performance this seller announces.
|
|
758
|
+
const streamOutcome = new StreamOutcome();
|
|
628
759
|
let responseForAuth = null;
|
|
629
760
|
let streamAuthStatusCode = 0;
|
|
630
761
|
let streamAuthHeaders = null;
|
|
@@ -636,6 +767,21 @@ export class SellerRequestHandler {
|
|
|
636
767
|
// between serving the request and claiming its cost.
|
|
637
768
|
const isBillable = !isServedFree && !oneOffChannelId && (spm?.hasSession(buyerPeerId) ?? false);
|
|
638
769
|
if (isBillable && spm.hasClosingChannel(buyerPeerId)) {
|
|
770
|
+
// A close takes seconds on-chain (a disconnect, an exhausted channel).
|
|
771
|
+
// Let it finish, then ask for a new channel: the buyer opens one and
|
|
772
|
+
// retries this request, instead of the client getting an error.
|
|
773
|
+
if (await spm.waitForClosingChannel(buyerPeerId, CLOSING_CHANNEL_WAIT_MS) && !spm.hasSession(buyerPeerId)) {
|
|
774
|
+
mux.sendProxyResponse({
|
|
775
|
+
requestId: request.requestId,
|
|
776
|
+
statusCode: 402,
|
|
777
|
+
headers: { 'content-type': 'application/json' },
|
|
778
|
+
body: new TextEncoder().encode(JSON.stringify({
|
|
779
|
+
error: 'payment_required',
|
|
780
|
+
message: 'Session expired, please renegotiate',
|
|
781
|
+
})),
|
|
782
|
+
});
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
639
785
|
mux.sendProxyResponse({
|
|
640
786
|
requestId: request.requestId,
|
|
641
787
|
statusCode: 503,
|
|
@@ -668,6 +814,8 @@ export class SellerRequestHandler {
|
|
|
668
814
|
onResponseChunk: (chunk) => {
|
|
669
815
|
if (!streamedResponseStarted)
|
|
670
816
|
return;
|
|
817
|
+
if (!downloadHash)
|
|
818
|
+
streamOutcome.observe(chunk.data);
|
|
671
819
|
if (downloadHash) {
|
|
672
820
|
downloadHash.update(chunk.data);
|
|
673
821
|
if (!chunk.done)
|
|
@@ -690,13 +838,19 @@ export class SellerRequestHandler {
|
|
|
690
838
|
response = this._acceptVideoCreate(videoRoute, request, response, buyerPeerId, requestedModel, requestBilling, unitBillingModel, oneOffChannelId);
|
|
691
839
|
}
|
|
692
840
|
statusCode = response.statusCode;
|
|
841
|
+
// The upstream's answer for its account and model health (an out-of-credit error is seen here first).
|
|
842
|
+
// Polling or downloading a video already paid for says nothing about the account's credit.
|
|
843
|
+
const upstreamBody = response.body ?? new Uint8Array(0);
|
|
844
|
+
this._deps.emit('provider:response', { provider, service: requestedModel, statusCode, body: statusCode >= 400 ? new TextDecoder().decode(upstreamBody.slice(0, 2_000)) : '', billable: videoRoute?.action !== 'retrieve' });
|
|
845
|
+
const health = serviceHealthOf(provider.serviceHealth, requestedModel);
|
|
846
|
+
if (!streamedResponseStarted && isServiceHealthFailing(health) && isUpstreamFailureStatus(statusCode)) {
|
|
847
|
+
response = buildSelfHealthErrorResponse(request.requestId, requestedModel, health, response);
|
|
848
|
+
statusCode = response.statusCode;
|
|
849
|
+
}
|
|
693
850
|
responseBody = response.body ?? new Uint8Array(0);
|
|
694
851
|
responseForAuth = response;
|
|
695
852
|
if (downloadHash)
|
|
696
853
|
responseForAuth = { ...response, streamedBody: downloadHash.finish() };
|
|
697
|
-
// The upstream's answer for its account health (an out-of-credit error is seen here first).
|
|
698
|
-
// Polling or downloading a video already paid for says nothing about the account's credit.
|
|
699
|
-
this._deps.emit('provider:response', { provider, statusCode, body: statusCode >= 400 ? new TextDecoder().decode(responseBody.slice(0, 2_000)) : '', billable: videoRoute?.action !== 'retrieve' });
|
|
700
854
|
if (statusCode >= 400) {
|
|
701
855
|
const errBody = new TextDecoder().decode(responseBody).slice(0, 200);
|
|
702
856
|
debugWarn(`[SellerHandler] Provider error response: status=${statusCode} provider="${provider.name}" model="${requestedModel}" buyer=${buyerPeerId.slice(0, 12)}... (${Date.now() - startTime}ms) body=${errBody}`);
|
|
@@ -735,6 +889,7 @@ export class SellerRequestHandler {
|
|
|
735
889
|
catch (err) {
|
|
736
890
|
const message = err instanceof Error ? err.message : "Internal error";
|
|
737
891
|
failureMessage = message;
|
|
892
|
+
this._deps.emit('provider:failure', { provider, service: requestedModel });
|
|
738
893
|
debugWarn(`[SellerHandler] Provider exception: provider="${provider.name}" model="${requestedModel}" buyer=${buyerPeerId.slice(0, 12)}... (${Date.now() - startTime}ms) ${message}`);
|
|
739
894
|
if (videoRoute?.action === 'create')
|
|
740
895
|
this._closeOneOffBestEffort(oneOffChannelId, 'video create failed');
|
|
@@ -767,13 +922,17 @@ export class SellerRequestHandler {
|
|
|
767
922
|
}
|
|
768
923
|
}
|
|
769
924
|
else {
|
|
770
|
-
|
|
771
|
-
responseForAuth =
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
925
|
+
const health = serviceHealthOf(provider.serviceHealth, requestedModel);
|
|
926
|
+
responseForAuth = isServiceHealthFailing(health)
|
|
927
|
+
? buildSelfHealthErrorResponse(request.requestId, requestedModel, health, { statusCode: 500, headers: {}, body: responseBody })
|
|
928
|
+
: {
|
|
929
|
+
requestId: request.requestId,
|
|
930
|
+
statusCode: 500,
|
|
931
|
+
headers: { "content-type": "text/plain" },
|
|
932
|
+
body: responseBody,
|
|
933
|
+
};
|
|
934
|
+
statusCode = responseForAuth.statusCode;
|
|
935
|
+
responseBody = responseForAuth.body;
|
|
777
936
|
mux.sendProxyResponse(responseForAuth);
|
|
778
937
|
}
|
|
779
938
|
}
|
|
@@ -784,7 +943,17 @@ export class SellerRequestHandler {
|
|
|
784
943
|
unitCostUsdc = finalBilling.costUsdc;
|
|
785
944
|
}
|
|
786
945
|
// Record metering
|
|
787
|
-
const
|
|
946
|
+
const finishedAt = Date.now();
|
|
947
|
+
const latencyMs = finishedAt - startTime;
|
|
948
|
+
// Video jobs are polled and downloaded on their own schedule: their timing says nothing about the model.
|
|
949
|
+
if (!videoRoute && requestedModel !== 'unknown') {
|
|
950
|
+
const { firstTokenAt } = streamOutcome;
|
|
951
|
+
this._deps.servicePerformance?.record(provider.name, requestedModel, {
|
|
952
|
+
ok: failureMessage === null && !streamOutcome.failed && !isUpstreamFailureStatus(statusCode),
|
|
953
|
+
...(firstTokenAt !== null ? { firstTokenMs: firstTokenAt - startTime, streamMs: finishedAt - firstTokenAt } : {}),
|
|
954
|
+
outputTokens: responseUsage.outputTokens,
|
|
955
|
+
}, finishedAt);
|
|
956
|
+
}
|
|
788
957
|
if (this._deps.sessionTracker) {
|
|
789
958
|
await this._deps.sessionTracker.recordMetering({
|
|
790
959
|
buyerPeerId,
|
|
@@ -1090,12 +1259,20 @@ export class SellerRequestHandler {
|
|
|
1090
1259
|
const videoRoute = nativeVideoRoute(request);
|
|
1091
1260
|
const providers = this._deps.providers;
|
|
1092
1261
|
const matchesService = (provider) => provider.services.includes(requestedService) && (!videoRoute || Boolean(provider.serviceApiProtocols?.[requestedService]?.includes(videoRoute.protocol)));
|
|
1262
|
+
// Several upstreams can share a provider name (with the same terms, see
|
|
1263
|
+
// assertSameNameUpstreamsAgree); buyers merge their announcements with the
|
|
1264
|
+
// same preference (mergeProviderAnnouncements). A native video job lives
|
|
1265
|
+
// in the account of the upstream that accepted it, so video requests keep
|
|
1266
|
+
// to the first matching upstream: a retrieve reaches the job's upstream
|
|
1267
|
+
// even after it starts failing.
|
|
1268
|
+
const pick = (candidates) => (videoRoute ? candidates[0] : preferHealthyUpstream(candidates, requestedService));
|
|
1269
|
+
const serving = providers.filter(matchesService);
|
|
1093
1270
|
let provider;
|
|
1094
1271
|
if (requestedProvider) {
|
|
1095
|
-
provider =
|
|
1272
|
+
provider = pick(serving.filter((candidate) => candidate.name.toLowerCase() === requestedProvider));
|
|
1096
1273
|
}
|
|
1097
1274
|
if (!provider && !(videoRoute && requestedProvider)) {
|
|
1098
|
-
provider =
|
|
1275
|
+
provider = pick(serving);
|
|
1099
1276
|
}
|
|
1100
1277
|
return provider;
|
|
1101
1278
|
}
|