@antseed/cli 0.1.195 → 0.1.197
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/cli/commands/buyer/start.d.ts.map +1 -1
- package/dist/cli/commands/buyer/start.js +1 -0
- package/dist/cli/commands/buyer/start.js.map +1 -1
- package/dist/cli/commands/gateway/activity.d.ts.map +1 -1
- package/dist/cli/commands/gateway/activity.js +26 -5
- package/dist/cli/commands/gateway/activity.js.map +1 -1
- package/dist/cli/commands/seller/start.d.ts.map +1 -1
- package/dist/cli/commands/seller/start.js +5 -3
- package/dist/cli/commands/seller/start.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/gateway/accounting.d.ts +118 -6
- package/dist/gateway/accounting.d.ts.map +1 -1
- package/dist/gateway/accounting.js +319 -21
- package/dist/gateway/accounting.js.map +1 -1
- package/dist/gateway/console-api/handlers/favorites.d.ts +9 -0
- package/dist/gateway/console-api/handlers/favorites.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/favorites.js +44 -0
- package/dist/gateway/console-api/handlers/favorites.js.map +1 -0
- package/dist/gateway/console-api/handlers/favorites.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/favorites.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/favorites.test.js +41 -0
- package/dist/gateway/console-api/handlers/favorites.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/index.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/index.js +2 -0
- package/dist/gateway/console-api/handlers/index.js.map +1 -1
- package/dist/gateway/console-api/handlers/network-mapping.d.ts +7 -1
- package/dist/gateway/console-api/handlers/network-mapping.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/network-mapping.js +44 -0
- package/dist/gateway/console-api/handlers/network-mapping.js.map +1 -1
- package/dist/gateway/console-api/handlers/network.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/network.js +3 -1
- package/dist/gateway/console-api/handlers/network.js.map +1 -1
- package/dist/gateway/console-api/handlers/network.test.js +39 -4
- package/dist/gateway/console-api/handlers/network.test.js.map +1 -1
- package/dist/gateway/console-api/handlers/usage.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/usage.js +2 -1
- package/dist/gateway/console-api/handlers/usage.js.map +1 -1
- package/dist/gateway/console-api/types.d.ts +69 -1
- package/dist/gateway/console-api/types.d.ts.map +1 -1
- package/dist/gateway/console-api/types.js.map +1 -1
- package/dist/gateway/gateway.test.js +829 -3
- package/dist/gateway/gateway.test.js.map +1 -1
- package/dist/gateway/request-errors.test.js +30 -0
- package/dist/gateway/request-errors.test.js.map +1 -1
- package/dist/gateway/routing-through-gateway.test.js +86 -4
- package/dist/gateway/routing-through-gateway.test.js.map +1 -1
- package/dist/gateway/runtime.d.ts.map +1 -1
- package/dist/gateway/runtime.js +4 -0
- package/dist/gateway/runtime.js.map +1 -1
- package/dist/gateway/server.d.ts +20 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +178 -25
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/services/network.d.ts +6 -1
- package/dist/gateway/services/network.d.ts.map +1 -1
- package/dist/gateway/services/network.js +12 -1
- package/dist/gateway/services/network.js.map +1 -1
- package/dist/gateway/services/usage.d.ts +2 -0
- package/dist/gateway/services/usage.d.ts.map +1 -1
- package/dist/gateway/services/usage.js +18 -4
- package/dist/gateway/services/usage.js.map +1 -1
- package/dist/gateway/spend-feed.d.ts +4 -1
- package/dist/gateway/spend-feed.d.ts.map +1 -1
- package/dist/gateway/spend-feed.js +7 -0
- package/dist/gateway/spend-feed.js.map +1 -1
- package/dist/gateway/store-data.test.js +1 -1
- package/dist/gateway/store-data.test.js.map +1 -1
- package/dist/gateway/store-migrations.d.ts.map +1 -1
- package/dist/gateway/store-migrations.js +35 -0
- package/dist/gateway/store-migrations.js.map +1 -1
- package/dist/gateway/store.d.ts +59 -1
- package/dist/gateway/store.d.ts.map +1 -1
- package/dist/gateway/store.js +124 -19
- package/dist/gateway/store.js.map +1 -1
- package/dist/gateway/video-jobs.d.ts +12 -0
- package/dist/gateway/video-jobs.d.ts.map +1 -0
- package/dist/gateway/video-jobs.js +24 -0
- package/dist/gateway/video-jobs.js.map +1 -0
- package/dist/proxy/buyer-proxy.d.ts +15 -0
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +111 -13
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +174 -1
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/request-utils.d.ts +4 -0
- package/dist/proxy/request-utils.d.ts.map +1 -1
- package/dist/proxy/request-utils.js +4 -0
- package/dist/proxy/request-utils.js.map +1 -1
- package/dist/proxy/resource-routes.d.ts +2 -0
- package/dist/proxy/resource-routes.d.ts.map +1 -1
- package/dist/proxy/resource-routes.js +4 -0
- package/dist/proxy/resource-routes.js.map +1 -1
- package/dist/proxy/routing-parameters.test.js +10 -0
- package/dist/proxy/routing-parameters.test.js.map +1 -1
- package/dist/proxy/routing.d.ts.map +1 -1
- package/dist/proxy/routing.js +2 -0
- package/dist/proxy/routing.js.map +1 -1
- package/dist/proxy/spend-attribution.d.ts +17 -1
- package/dist/proxy/spend-attribution.d.ts.map +1 -1
- package/dist/proxy/spend-attribution.js +45 -10
- package/dist/proxy/spend-attribution.js.map +1 -1
- package/dist/proxy/spend-attribution.test.d.ts +2 -0
- package/dist/proxy/spend-attribution.test.d.ts.map +1 -0
- package/dist/proxy/spend-attribution.test.js +63 -0
- package/dist/proxy/spend-attribution.test.js.map +1 -0
- package/dist/proxy/telemetry.d.ts +14 -0
- package/dist/proxy/telemetry.d.ts.map +1 -1
- package/dist/proxy/telemetry.js +23 -3
- package/dist/proxy/telemetry.js.map +1 -1
- package/dist/proxy/video-download.d.ts +18 -1
- package/dist/proxy/video-download.d.ts.map +1 -1
- package/dist/proxy/video-download.js +100 -12
- package/dist/proxy/video-download.js.map +1 -1
- package/dist/proxy/video-download.test.js +106 -13
- package/dist/proxy/video-download.test.js.map +1 -1
- package/package.json +8 -8
|
@@ -8,14 +8,14 @@ import { buildRoutingServices, findPinnedRoutingService } from './routing-servic
|
|
|
8
8
|
import { readFileSync, watchFile, unwatchFile } from 'node:fs';
|
|
9
9
|
import { readFile, writeFile, rename, mkdir, readdir, stat, unlink } from 'node:fs/promises';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
-
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, ANTSEED_ATTEST_PATH, adaptPeerFaultErrorResponse, computeTrustScore, decodeSweepRequest, faultAttributionOf, faultCodeOf, isModelRouteCoolingDown, isModelRouteEligible, modelRouteReputationScore, modelRouteTotalPrice, normalizedModelReputationScore, peerSupportsCooperativeClose, isRoutingSelection, DEFAULT_BUYER_IDENTITY, rankModelRoutes, SellerHealthBook, orderAdaptiveRoutes, adaptiveFirstTokenTimeoutMs, sanitizePeerDisplayName, } from '@antseed/node';
|
|
11
|
+
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, ANTSEED_ATTEST_PATH, adaptPeerFaultErrorResponse, computeTrustScore, decodeSweepRequest, faultAttributionOf, faultCodeOf, isModelRouteCoolingDown, isModelRouteEligible, modelRouteReputationScore, modelRouteTotalPrice, normalizedModelReputationScore, peerSupportsCooperativeClose, isRoutingSelection, DEFAULT_BUYER_IDENTITY, rankModelRoutes, SellerHealthBook, orderAdaptiveRoutes, adaptiveFirstTokenTimeoutMs, sanitizePeerDisplayName, VIDEO_JOB_RETIRED_HEADER, } from '@antseed/node';
|
|
12
12
|
import { canonicalModelKey } from '@antseed/node/model-identity';
|
|
13
13
|
import { createStreamingAdapter, detectRequestServiceApiProtocol, extractRequestBodyFields, transformRequest, transformResponse, } from './service-api-adapter.js';
|
|
14
|
-
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, takeRoutingOptions, } from './request-utils.js';
|
|
14
|
+
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, MAX_VIDEO_COST_HEADER, normalizePeerId, takeRoutingOptions, } from './request-utils.js';
|
|
15
15
|
import { BuyerIdentityLoader } from '../buyer-identities/loader.js';
|
|
16
16
|
import { buildNetworkModels, effectiveModelReputationScore, parseModelTypeFilter, } from './network-models.js';
|
|
17
17
|
import { findMissingRequiredParameters, findUnannouncedRequestParameters, findAdvertisedServiceOffer, findAdvertisedServiceProtocols, getExplicitProviderOverride, getExplicitPeerIdOverride, parseRequiredParametersHeader, REQUIRED_PARAMETERS_HEADER, resolvePeerRoutePlan, selectCandidatePeersForRouting, } from './routing.js';
|
|
18
|
-
import { computeResponseTelemetry, attachAntseedTelemetryHeaders, attachStreamingAntseedHeaders, } from './telemetry.js';
|
|
18
|
+
import { computeResponseTelemetry, attachAntseedTelemetryHeaders, ROUTER_PEER_ID_HEADER, ROUTER_RANK_HEADER, ROUTER_SERVICE_HEADER, withoutRouterHeaders, attachStreamingAntseedHeaders, } from './telemetry.js';
|
|
19
19
|
import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
|
|
20
20
|
import { extractConversationIdentity, extractFirstUserSnippet, isCompletionRequestPath, isTitleGenerationRequest, parseRequestBodyObject, } from './conversation-identity.js';
|
|
21
21
|
import { ConversationStore } from './conversation-store.js';
|
|
@@ -255,6 +255,18 @@ export function isModelNotFoundResponse(response) {
|
|
|
255
255
|
* liveness (`lastReachedAt`) even if the DHT record is older.
|
|
256
256
|
*/
|
|
257
257
|
const CARRY_FORWARD_TTL_MS = 2 * 60 * 60_000;
|
|
258
|
+
/**
|
|
259
|
+
* Seller-announced catalog fields. Explicit tracking ensures stale catalog
|
|
260
|
+
* (for example after health checks hide all models) is cleared on the next
|
|
261
|
+
* peer refresh.
|
|
262
|
+
*/
|
|
263
|
+
const PEER_CATALOG_FIELDS = [
|
|
264
|
+
'providerPricing',
|
|
265
|
+
'providerServiceCategories',
|
|
266
|
+
'providerServiceApiProtocols',
|
|
267
|
+
'providerServiceUnitBillingModels',
|
|
268
|
+
'providerServiceCapabilities',
|
|
269
|
+
];
|
|
258
270
|
/**
|
|
259
271
|
* Requests kept in the spend-attribution map. Entries outlive their request on
|
|
260
272
|
* purpose (a seller-initiated auth can land after the response), so this is
|
|
@@ -298,7 +310,8 @@ function dispatchFailure(statusCode, responseHeaders, body) {
|
|
|
298
310
|
function writeDispatchFailure(res, result) {
|
|
299
311
|
if (res.headersSent || res.writableEnded)
|
|
300
312
|
return;
|
|
301
|
-
|
|
313
|
+
// A refusal can carry the seller's own headers: never let it name a router.
|
|
314
|
+
res.writeHead(result.statusCode, withoutRouterHeaders(result.responseHeaders));
|
|
302
315
|
res.end(result.responseBody);
|
|
303
316
|
}
|
|
304
317
|
function isBuyerFault(result) {
|
|
@@ -422,6 +435,10 @@ export function sanitizePeerBuyerFaultMarker(response) {
|
|
|
422
435
|
? { ...response, body: Buffer.from(JSON.stringify(parsed)) }
|
|
423
436
|
: response;
|
|
424
437
|
}
|
|
438
|
+
/** A non-negative integer amount of USDC base units, or null when absent or malformed. */
|
|
439
|
+
function parseUsdcBaseUnits(value) {
|
|
440
|
+
return value && /^\d{1,15}$/.test(value) ? BigInt(value) : null;
|
|
441
|
+
}
|
|
425
442
|
function buyerFaultStatusCode(faultCode) {
|
|
426
443
|
switch (faultCode) {
|
|
427
444
|
case 'invalid-request': return 400;
|
|
@@ -798,6 +815,12 @@ export class BuyerProxy {
|
|
|
798
815
|
_routingPreferences;
|
|
799
816
|
_stateWriteChain = Promise.resolve();
|
|
800
817
|
_resourceRoutes = new ResourceRoutes();
|
|
818
|
+
/** Retrieves of one video job run one at a time, so a job retired as failed cannot be delivered by a retrieve already under way. */
|
|
819
|
+
_videoJobQueues = new Map();
|
|
820
|
+
/** Jobs whose retirement is being saved: left out of the saved routes, still routable in memory until the save succeeds. */
|
|
821
|
+
/** The saved video routes existed but could not be read at start (see `start`). */
|
|
822
|
+
_resourceRoutesUnread = false;
|
|
823
|
+
_retiringVideoJobs = new Set();
|
|
801
824
|
_cachedPeers = [];
|
|
802
825
|
_cacheLastUpdatedAtMs = 0;
|
|
803
826
|
_cacheMutationEpoch = 0;
|
|
@@ -1636,7 +1659,13 @@ export class BuyerProxy {
|
|
|
1636
1659
|
const state = JSON.parse(await readFile(this._stateFile, 'utf8'));
|
|
1637
1660
|
this._resourceRoutes.hydrate(state.resourceRoutes);
|
|
1638
1661
|
}
|
|
1639
|
-
catch {
|
|
1662
|
+
catch (error) {
|
|
1663
|
+
// Routes that could not be read may come back on a later start: until then no job is said to be retired.
|
|
1664
|
+
if (error.code !== 'ENOENT') {
|
|
1665
|
+
this._resourceRoutesUnread = true;
|
|
1666
|
+
console.warn(`[buyer-proxy] Could not read saved video routes: ${error instanceof Error ? error.message : String(error)}`);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1640
1669
|
// Adopt persisted session overrides (peer pin, default routed model) so
|
|
1641
1670
|
// they survive daemon restart. A --peer CLI flag beats the persisted pin
|
|
1642
1671
|
// at startup; runtime `connection set` writes still take over via the
|
|
@@ -1897,8 +1926,50 @@ export class BuyerProxy {
|
|
|
1897
1926
|
}).catch(() => { });
|
|
1898
1927
|
return this._stateWriteChain;
|
|
1899
1928
|
}
|
|
1929
|
+
async _serializeVideoJob(job, run) {
|
|
1930
|
+
const previous = this._videoJobQueues.get(job) ?? Promise.resolve();
|
|
1931
|
+
const current = previous.then(run, run);
|
|
1932
|
+
const tail = current.catch(() => { });
|
|
1933
|
+
this._videoJobQueues.set(job, tail);
|
|
1934
|
+
try {
|
|
1935
|
+
await current;
|
|
1936
|
+
}
|
|
1937
|
+
finally {
|
|
1938
|
+
if (this._videoJobQueues.get(job) === tail)
|
|
1939
|
+
this._videoJobQueues.delete(job);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Retires a video job that failed or that its seller no longer has: later
|
|
1944
|
+
* retrieves get `video_route_not_found` and never reach the seller, so it can
|
|
1945
|
+
* never be delivered or charged, and a gateway may release the price it held
|
|
1946
|
+
* for it. False (route kept) when the retirement could not be saved, so a
|
|
1947
|
+
* restart cannot bring the job back.
|
|
1948
|
+
*/
|
|
1949
|
+
async _retireVideoJob(protocol, resourceId) {
|
|
1950
|
+
if (!this._resourceRoutes.resolve(protocol, resourceId))
|
|
1951
|
+
return true;
|
|
1952
|
+
// The route stays usable until the retirement is saved: a retrieve arriving meanwhile waits its turn
|
|
1953
|
+
// (retrieves of one job run one at a time) instead of seeing the job as gone before that is durable.
|
|
1954
|
+
const key = `${protocol}\n${resourceId}`;
|
|
1955
|
+
this._retiringVideoJobs.add(key);
|
|
1956
|
+
try {
|
|
1957
|
+
await this._persistResourceRoutes();
|
|
1958
|
+
this._resourceRoutes.forget(protocol, resourceId);
|
|
1959
|
+
return true;
|
|
1960
|
+
}
|
|
1961
|
+
catch (error) {
|
|
1962
|
+
console.error('[proxy] Retired video route was not saved; the job is kept:', error);
|
|
1963
|
+
return false;
|
|
1964
|
+
}
|
|
1965
|
+
finally {
|
|
1966
|
+
this._retiringVideoJobs.delete(key);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1900
1969
|
async _persistResourceRoutes() {
|
|
1901
|
-
const write = this._stateWriteChain.then(() => mergeJsonStateFile(this._stateDir, this._stateFile, {
|
|
1970
|
+
const write = this._stateWriteChain.then(() => mergeJsonStateFile(this._stateDir, this._stateFile, {
|
|
1971
|
+
resourceRoutes: this._resourceRoutes.snapshot().filter((route) => !this._retiringVideoJobs.has(`${route.protocol}\n${route.resourceId}`)),
|
|
1972
|
+
}));
|
|
1902
1973
|
this._stateWriteChain = write.catch(() => { });
|
|
1903
1974
|
return write;
|
|
1904
1975
|
}
|
|
@@ -1946,6 +2017,13 @@ export class BuyerProxy {
|
|
|
1946
2017
|
...peer,
|
|
1947
2018
|
...(metadata ? { metadata } : {}),
|
|
1948
2019
|
};
|
|
2020
|
+
// The seller's announced catalog comes only from this scan: a seller
|
|
2021
|
+
// that stopped advertising a service (for example after failed health
|
|
2022
|
+
// checks) leaves these fields out, and the cached copy must not survive.
|
|
2023
|
+
for (const field of PEER_CATALOG_FIELDS) {
|
|
2024
|
+
if (peer[field] === undefined)
|
|
2025
|
+
delete mergedPeer[field];
|
|
2026
|
+
}
|
|
1949
2027
|
if (prev.lastReachedAt && (!peer.lastReachedAt || prev.lastReachedAt > peer.lastReachedAt)) {
|
|
1950
2028
|
mergedPeer.lastReachedAt = prev.lastReachedAt;
|
|
1951
2029
|
}
|
|
@@ -3228,7 +3306,10 @@ export class BuyerProxy {
|
|
|
3228
3306
|
// Re-classify with the body: some APIs (Venice) carry the job ID in it.
|
|
3229
3307
|
const prepared = prepareVideoRequest(nativeVideoRoute(serializedReq) ?? nativeVideo, serializedReq.headers, this._resourceRoutes);
|
|
3230
3308
|
if ('error' in prepared) {
|
|
3231
|
-
|
|
3309
|
+
// No route for the job: this buyer can never deliver or charge it. Unless its saved routes could not be
|
|
3310
|
+
// read at start, when the job may still be there on the next one.
|
|
3311
|
+
const retired = prepared.error.body.error.code === 'video_route_not_found' && !this._resourceRoutesUnread ? { [VIDEO_JOB_RETIRED_HEADER]: '1' } : {};
|
|
3312
|
+
res.writeHead(prepared.error.statusCode, { 'content-type': 'application/json', ...retired });
|
|
3232
3313
|
res.end(JSON.stringify(prepared.error.body));
|
|
3233
3314
|
return;
|
|
3234
3315
|
}
|
|
@@ -3367,6 +3448,11 @@ export class BuyerProxy {
|
|
|
3367
3448
|
const conversationId = this._trackRouterConversation(context);
|
|
3368
3449
|
if (conversationId && selection.service)
|
|
3369
3450
|
this._conversations.setLastRouter(conversationId, `${selection.service.peerId}@${selection.service.serviceId}`);
|
|
3451
|
+
// Merged into whatever answer this request gets, so a gateway can tell routed requests (and failed rankings) apart.
|
|
3452
|
+
if (selection.service) {
|
|
3453
|
+
res.setHeader(ROUTER_PEER_ID_HEADER, selection.service.peerId.toLowerCase());
|
|
3454
|
+
res.setHeader(ROUTER_SERVICE_HEADER, selection.service.serviceId);
|
|
3455
|
+
}
|
|
3370
3456
|
let recommendations;
|
|
3371
3457
|
try {
|
|
3372
3458
|
const client = this._modelRoutingClient;
|
|
@@ -3384,7 +3470,7 @@ export class BuyerProxy {
|
|
|
3384
3470
|
signal: AbortSignal.any([signal, AbortSignal.timeout(120_000)]),
|
|
3385
3471
|
onRoutingRequest: requestId => {
|
|
3386
3472
|
if (spendAttributionTag)
|
|
3387
|
-
this._spendAttribution.track(requestId, spendAttributionTag);
|
|
3473
|
+
this._spendAttribution.track(requestId, spendAttributionTag, 'routing');
|
|
3388
3474
|
if (conversationId)
|
|
3389
3475
|
this._trackRequestConversation(requestId, conversationId, undefined, 'routing');
|
|
3390
3476
|
},
|
|
@@ -3407,10 +3493,11 @@ export class BuyerProxy {
|
|
|
3407
3493
|
return;
|
|
3408
3494
|
}
|
|
3409
3495
|
let lastFailure = null;
|
|
3410
|
-
for (const recommendation of recommendations) {
|
|
3496
|
+
for (const [index, recommendation] of recommendations.entries()) {
|
|
3411
3497
|
// Never switch sellers once the user has disconnected or a response has started.
|
|
3412
3498
|
if (signal.aborted || res.headersSent || res.writableEnded)
|
|
3413
3499
|
return;
|
|
3500
|
+
res.setHeader(ROUTER_RANK_HEADER, String(index + 1));
|
|
3414
3501
|
// Each attempt has its own billing ID, but all attempts count as one chat turn.
|
|
3415
3502
|
const attempt = requestForRecommendation(request, recommendation, randomUUID());
|
|
3416
3503
|
if (spendAttributionTag)
|
|
@@ -4120,7 +4207,7 @@ export class BuyerProxy {
|
|
|
4120
4207
|
+ `did not announce for this service: ${unannounced.join(', ')} — the upstream may ignore or reject them`);
|
|
4121
4208
|
}
|
|
4122
4209
|
}
|
|
4123
|
-
const { 'x-antseed-pin-peer': _pinPeer, 'x-antseed-prefer-peer': _preferPeer, [BUYER_IDENTITY_HEADER]: buyerIdentity, [REQUIRED_PARAMETERS_HEADER]: _requiredParameters, 'x-vpr-session-id': _vprSession,
|
|
4210
|
+
const { 'x-antseed-pin-peer': _pinPeer, 'x-antseed-prefer-peer': _preferPeer, [BUYER_IDENTITY_HEADER]: buyerIdentity, [MAX_VIDEO_COST_HEADER]: maxVideoCostHeader, [REQUIRED_PARAMETERS_HEADER]: _requiredParameters, 'x-vpr-session-id': _vprSession,
|
|
4124
4211
|
// Legacy desktop builds (pre AntStation → VPR rename) still send this.
|
|
4125
4212
|
'x-antstation-session-id': _antstationSession, ...headersForPeer } = serializedReq.headers;
|
|
4126
4213
|
let requestForPeer = {
|
|
@@ -4194,9 +4281,18 @@ export class BuyerProxy {
|
|
|
4194
4281
|
const wantsStreaming = clientWantsStreaming && !videoRoute;
|
|
4195
4282
|
if (videoRoute?.action === 'retrieve') {
|
|
4196
4283
|
// The seller hands a job only to the buyer that created it: retrieve with the same wallet.
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4284
|
+
const { protocol, resourceId } = videoRoute;
|
|
4285
|
+
await this._serializeVideoJob(`${protocol}\n${resourceId ?? ''}`, async () => {
|
|
4286
|
+
// Checked again in turn: a retrieve queued behind one that retired the job must not reach the seller.
|
|
4287
|
+
if (!resourceId || !this._resourceRoutes.resolve(protocol, resourceId)) {
|
|
4288
|
+
res.writeHead(404, { 'content-type': 'application/json', [VIDEO_JOB_RETIRED_HEADER]: '1' });
|
|
4289
|
+
res.end(JSON.stringify({ error: { code: 'video_route_not_found', message: 'Unknown video job' } }));
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
await downloadVideo(requestForPeer, res, (request, callbacks, signal) => this._node.sendRequestStream(selectedPeer, request, callbacks, {
|
|
4293
|
+
signal, pinned: true, ...(buyerIdentity ? { buyerIdentity } : {}),
|
|
4294
|
+
}), requestSignal, () => this._retireVideoJob(protocol, resourceId));
|
|
4295
|
+
});
|
|
4200
4296
|
return { done: true };
|
|
4201
4297
|
}
|
|
4202
4298
|
const peerResponseProtocol = selectedRoutePlan.selection?.targetProtocol ?? requestProtocol;
|
|
@@ -4351,10 +4447,12 @@ export class BuyerProxy {
|
|
|
4351
4447
|
return { done: true, emptyAnswer: answeredEmpty };
|
|
4352
4448
|
}
|
|
4353
4449
|
else {
|
|
4450
|
+
const maxVideoCostUsdc = videoRoute?.action === 'create' ? parseUsdcBaseUnits(maxVideoCostHeader) : null;
|
|
4354
4451
|
const upstreamResponse = await this._node.sendRequest(selectedPeer, requestForPeer, {
|
|
4355
4452
|
signal: attemptSignal,
|
|
4356
4453
|
pinned,
|
|
4357
4454
|
...(buyerIdentity ? { buyerIdentity } : {}),
|
|
4455
|
+
...(maxVideoCostUsdc !== null ? { maxVideoCostUsdc } : {}),
|
|
4358
4456
|
});
|
|
4359
4457
|
if (upstreamResponse.statusCode >= 400 && !adaptResponse) {
|
|
4360
4458
|
log(`Upstream raw error detail: ${summarizeErrorResponse(upstreamResponse)}`);
|