@antseed/cli 0.1.152 → 0.1.154
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/daemon.d.ts +3 -0
- package/dist/cli/commands/buyer/daemon.d.ts.map +1 -1
- package/dist/cli/commands/buyer/daemon.js +1 -0
- package/dist/cli/commands/buyer/daemon.js.map +1 -1
- package/dist/cli/commands/buyer/start.d.ts.map +1 -1
- package/dist/cli/commands/buyer/start.js +20 -4
- package/dist/cli/commands/buyer/start.js.map +1 -1
- package/dist/cli/commands/seller/start.d.ts.map +1 -1
- package/dist/cli/commands/seller/start.js +51 -1
- package/dist/cli/commands/seller/start.js.map +1 -1
- package/dist/cli/commands/seller/status.d.ts.map +1 -1
- package/dist/cli/commands/seller/status.js +4 -0
- package/dist/cli/commands/seller/status.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +25 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/loader.test.js +28 -0
- package/dist/config/loader.test.js.map +1 -1
- package/dist/config/types.d.ts +17 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +14 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/proxy/buyer-proxy.d.ts +15 -2
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +92 -21
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +155 -3
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/conversation-identity.d.ts.map +1 -1
- package/dist/proxy/conversation-identity.js +42 -2
- package/dist/proxy/conversation-identity.js.map +1 -1
- package/dist/proxy/conversation-identity.test.js +27 -0
- package/dist/proxy/conversation-identity.test.js.map +1 -1
- package/dist/proxy/routing-parameters.test.js +12 -1
- package/dist/proxy/routing-parameters.test.js.map +1 -1
- package/dist/proxy/routing.d.ts +8 -0
- package/dist/proxy/routing.d.ts.map +1 -1
- package/dist/proxy/routing.js +42 -2
- package/dist/proxy/routing.js.map +1 -1
- package/dist/status/node-status.d.ts +2 -0
- package/dist/status/node-status.d.ts.map +1 -1
- package/dist/status/node-status.js +5 -0
- package/dist/status/node-status.js.map +1 -1
- package/package.json +4 -4
|
@@ -8,7 +8,7 @@ import { canonicalModelKey } from '@antseed/node/model-identity';
|
|
|
8
8
|
import { createStreamingAdapter, detectRequestServiceApiProtocol, transformRequest, transformResponse, } from './service-api-adapter.js';
|
|
9
9
|
import { DEBUG, log, extractRequestedService, summarizeRequestShape, summarizeErrorResponse, requestWantsStreaming, parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, overrideRoutedModelInBody, ROUTED_MODEL_ALIAS, SYSTEM_PROXY_SOURCE_HEADER, SYSTEM_ROUTED_MODEL_HEADER, normalizePeerId, } from './request-utils.js';
|
|
10
10
|
import { buildNetworkModels, effectiveModelReputationScore, normalizedModelReputationScore, parseModelTypeFilter, } from './network-models.js';
|
|
11
|
-
import { findUnannouncedRequestParameters, findAdvertisedServiceOffer, getExplicitProviderOverride, getExplicitPeerIdOverride, resolvePeerRoutePlan, selectCandidatePeersForRouting, } from './routing.js';
|
|
11
|
+
import { findMissingRequiredParameters, findUnannouncedRequestParameters, findAdvertisedServiceOffer, getExplicitProviderOverride, getExplicitPeerIdOverride, parseRequiredParametersHeader, REQUIRED_PARAMETERS_HEADER, resolvePeerRoutePlan, selectCandidatePeersForRouting, } from './routing.js';
|
|
12
12
|
import { computeResponseTelemetry, attachAntseedTelemetryHeaders, attachStreamingAntseedHeaders, } from './telemetry.js';
|
|
13
13
|
import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
|
|
14
14
|
import { extractConversationIdentity, extractFirstUserSnippet, isCompletionRequestPath, isTitleGenerationRequest, parseRequestBodyObject, } from './conversation-identity.js';
|
|
@@ -21,6 +21,11 @@ import { loadConfig } from '../config/loader.js';
|
|
|
21
21
|
// Re-export for backward compatibility (used by tests and other consumers)
|
|
22
22
|
export { selectCandidatePeersForRouting } from './routing.js';
|
|
23
23
|
export { parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, ROUTED_MODEL_ALIAS } from './request-utils.js';
|
|
24
|
+
const WATCHER_ABSENCE_ERRORS = {
|
|
25
|
+
'payments-disabled': 'Deposit watcher unavailable — payments are disabled on this buyer.',
|
|
26
|
+
'no-deposit-relay': 'Deposit watcher unavailable — this chain has no deposit relay.',
|
|
27
|
+
'external-daemon': 'Deposit watcher unavailable — another daemon owns the proxy port and runs the watcher.',
|
|
28
|
+
};
|
|
24
29
|
const RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);
|
|
25
30
|
const MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER = 3;
|
|
26
31
|
const MODEL_RATE_LIMIT_RETRY_DELAYS_MS = [250, 750];
|
|
@@ -227,7 +232,7 @@ function adaptBuyerFaultErrorResponse(response, requestProtocol) {
|
|
|
227
232
|
body: Buffer.from(JSON.stringify(body)),
|
|
228
233
|
};
|
|
229
234
|
}
|
|
230
|
-
function sanitizePeerBuyerFaultMarker(response) {
|
|
235
|
+
export function sanitizePeerBuyerFaultMarker(response) {
|
|
231
236
|
if (response.statusCode < 400)
|
|
232
237
|
return response;
|
|
233
238
|
let parsed;
|
|
@@ -237,18 +242,28 @@ function sanitizePeerBuyerFaultMarker(response) {
|
|
|
237
242
|
catch {
|
|
238
243
|
return response;
|
|
239
244
|
}
|
|
245
|
+
// Scrub the whole tree, not just the top level: a seller nesting the marker
|
|
246
|
+
// deeper (e.g. error.details.code) must not be able to have its failure
|
|
247
|
+
// classified as buyer-fault downstream. Iterate to avoid recursion limits
|
|
248
|
+
// without leaving deeply nested markers trusted by downstream clients.
|
|
240
249
|
let changed = false;
|
|
241
|
-
const
|
|
250
|
+
const pending = [parsed];
|
|
251
|
+
while (pending.length > 0) {
|
|
252
|
+
const value = pending.pop();
|
|
253
|
+
if (value === null || typeof value !== 'object')
|
|
254
|
+
continue;
|
|
255
|
+
if (Array.isArray(value)) {
|
|
256
|
+
pending.push(...value);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const record = value;
|
|
242
260
|
for (const key of ['code', 'type', 'errorCode']) {
|
|
243
261
|
if (record[key] === ANTSEED_BUYER_FAULT_ERROR_CODE) {
|
|
244
262
|
record[key] = 'upstream_error';
|
|
245
263
|
changed = true;
|
|
246
264
|
}
|
|
247
265
|
}
|
|
248
|
-
|
|
249
|
-
scrub(parsed);
|
|
250
|
-
if (parsed.error && typeof parsed.error === 'object' && !Array.isArray(parsed.error)) {
|
|
251
|
-
scrub(parsed.error);
|
|
266
|
+
pending.push(...Object.values(record));
|
|
252
267
|
}
|
|
253
268
|
return changed
|
|
254
269
|
? { ...response, body: Buffer.from(JSON.stringify(parsed)) }
|
|
@@ -615,6 +630,8 @@ export class BuyerProxy {
|
|
|
615
630
|
* plane instead of running a second signer against the same wallet.
|
|
616
631
|
*/
|
|
617
632
|
_depositWatcher = null;
|
|
633
|
+
/** Why no watcher is attached, so UIs can show the actual cause. */
|
|
634
|
+
_depositWatcherAbsence = null;
|
|
618
635
|
/**
|
|
619
636
|
* requestId -> the conversation that issued it, so the node's per-request
|
|
620
637
|
* spend events can be attributed to a chat. Only the proxy knows this
|
|
@@ -708,9 +725,13 @@ export class BuyerProxy {
|
|
|
708
725
|
this._requestConversations.delete(oldest);
|
|
709
726
|
}
|
|
710
727
|
}
|
|
711
|
-
/**
|
|
712
|
-
|
|
728
|
+
/**
|
|
729
|
+
* Attach the hot-wallet deposit watcher (owned by `buyer start`), or record
|
|
730
|
+
* why none runs so the status endpoint can report the cause.
|
|
731
|
+
*/
|
|
732
|
+
setDepositWatcher(watcher, absenceReason = null) {
|
|
713
733
|
this._depositWatcher = watcher;
|
|
734
|
+
this._depositWatcherAbsence = watcher ? null : absenceReason;
|
|
714
735
|
}
|
|
715
736
|
/** Latest relayer receipt for a sweep authNonce, if one has arrived. */
|
|
716
737
|
getSweepReceipt(authNonce) {
|
|
@@ -1706,6 +1727,13 @@ export class BuyerProxy {
|
|
|
1706
1727
|
res.end(JSON.stringify({
|
|
1707
1728
|
ok: true,
|
|
1708
1729
|
watcher: this._depositWatcher !== null,
|
|
1730
|
+
// Why no watcher runs (null while one is attached) — lets UIs say
|
|
1731
|
+
// "payments are disabled" vs "this chain has no deposit relay".
|
|
1732
|
+
reason: this._depositWatcher ? null : this._depositWatcherAbsence,
|
|
1733
|
+
// Live payments health: configured/active flags + chain-RPC
|
|
1734
|
+
// reachability from the node's background monitor. Optional call —
|
|
1735
|
+
// tolerate an older @antseed/node without it.
|
|
1736
|
+
payments: this._node.getPaymentsStatus?.() ?? null,
|
|
1709
1737
|
status: this._depositWatcher?.status() ?? null,
|
|
1710
1738
|
}));
|
|
1711
1739
|
return;
|
|
@@ -1739,11 +1767,10 @@ export class BuyerProxy {
|
|
|
1739
1767
|
}
|
|
1740
1768
|
const watcher = this._depositWatcher;
|
|
1741
1769
|
if (!watcher) {
|
|
1770
|
+
const reason = this._depositWatcherAbsence;
|
|
1771
|
+
const error = (reason && WATCHER_ABSENCE_ERRORS[reason]) ?? 'Deposit watcher unavailable.';
|
|
1742
1772
|
res.writeHead(503, { 'content-type': 'application/json' });
|
|
1743
|
-
res.end(JSON.stringify({
|
|
1744
|
-
ok: false,
|
|
1745
|
-
error: 'Deposit watcher unavailable — payments are disabled or this chain has no deposit relay.',
|
|
1746
|
-
}));
|
|
1773
|
+
res.end(JSON.stringify({ ok: false, reason, error }));
|
|
1747
1774
|
return;
|
|
1748
1775
|
}
|
|
1749
1776
|
if (mode === 'active')
|
|
@@ -1890,6 +1917,19 @@ export class BuyerProxy {
|
|
|
1890
1917
|
headers,
|
|
1891
1918
|
body: new Uint8Array(body),
|
|
1892
1919
|
};
|
|
1920
|
+
const requiredParameters = parseRequiredParametersHeader(serializedReq.headers[REQUIRED_PARAMETERS_HEADER]);
|
|
1921
|
+
if (requiredParameters === null) {
|
|
1922
|
+
res.writeHead(400, { 'content-type': 'application/json' });
|
|
1923
|
+
res.end(JSON.stringify({
|
|
1924
|
+
error: {
|
|
1925
|
+
type: 'invalid_request_error',
|
|
1926
|
+
code: 'invalid_required_parameters',
|
|
1927
|
+
message: `${REQUIRED_PARAMETERS_HEADER} must contain at most 16 comma-separated parameter names.`,
|
|
1928
|
+
param: REQUIRED_PARAMETERS_HEADER,
|
|
1929
|
+
},
|
|
1930
|
+
}));
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1893
1933
|
// Snapshot the session overrides before any await so a concurrent
|
|
1894
1934
|
// _reloadSessionOverrides() cannot change routing mid-request.
|
|
1895
1935
|
const effectivePinnedPeer = this._pinnedPeer;
|
|
@@ -2088,6 +2128,14 @@ export class BuyerProxy {
|
|
|
2088
2128
|
const offer = findAdvertisedServiceOffer(peer, plan.provider, plan.serviceId);
|
|
2089
2129
|
if (!offer)
|
|
2090
2130
|
return null;
|
|
2131
|
+
const missingRequired = plan.selection?.requiresTransform
|
|
2132
|
+
? requiredParameters
|
|
2133
|
+
: findMissingRequiredParameters(peer, plan.provider, plan.serviceId, requiredParameters);
|
|
2134
|
+
if (missingRequired.length > 0) {
|
|
2135
|
+
log(`Capability filter: peer ${peer.peerId.slice(0, 12)}... service="${plan.serviceId}" `
|
|
2136
|
+
+ `missing required parameter(s): ${missingRequired.join(', ')}`);
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2091
2139
|
const requestForPolicy = withRoutedModel(serializedReq, plan.serviceId);
|
|
2092
2140
|
if (!peerAllowedByPolicy(policyRouter, requestForPolicy, peer))
|
|
2093
2141
|
return null;
|
|
@@ -2137,14 +2185,22 @@ export class BuyerProxy {
|
|
|
2137
2185
|
}
|
|
2138
2186
|
}
|
|
2139
2187
|
if (candidates.length === 0) {
|
|
2140
|
-
|
|
2188
|
+
const capabilityRequired = requiredParameters.length > 0;
|
|
2189
|
+
res.writeHead(capabilityRequired ? 422 : 502, { 'content-type': 'application/json' });
|
|
2141
2190
|
res.end(JSON.stringify({
|
|
2142
|
-
error:
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2191
|
+
error: capabilityRequired
|
|
2192
|
+
? {
|
|
2193
|
+
type: 'required_capability_unavailable',
|
|
2194
|
+
code: 'required_capability_unavailable',
|
|
2195
|
+
message: `No policy-allowed seller for model "${requestedService}" advertises required parameter(s): ${requiredParameters.join(', ')}.`,
|
|
2196
|
+
param: REQUIRED_PARAMETERS_HEADER,
|
|
2197
|
+
}
|
|
2198
|
+
: {
|
|
2199
|
+
type: 'model_not_found',
|
|
2200
|
+
code: 'model_not_found',
|
|
2201
|
+
message: `No policy-allowed peer currently serves model "${requestedService}".`,
|
|
2202
|
+
param: 'model',
|
|
2203
|
+
},
|
|
2148
2204
|
}));
|
|
2149
2205
|
return;
|
|
2150
2206
|
}
|
|
@@ -2304,6 +2360,21 @@ export class BuyerProxy {
|
|
|
2304
2360
|
const selectedPlan = routingPlans.get(selectedPeer.peerId)
|
|
2305
2361
|
?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedService, explicitProvider, 'lenient');
|
|
2306
2362
|
const pinnedServiceId = selectedPlan?.serviceId ?? requestedService;
|
|
2363
|
+
const missingRequired = selectedPlan && !selectedPlan.selection?.requiresTransform
|
|
2364
|
+
? findMissingRequiredParameters(selectedPeer, selectedPlan.provider, pinnedServiceId, requiredParameters)
|
|
2365
|
+
: requiredParameters;
|
|
2366
|
+
if (missingRequired.length > 0) {
|
|
2367
|
+
res.writeHead(422, { 'content-type': 'application/json' });
|
|
2368
|
+
res.end(JSON.stringify({
|
|
2369
|
+
error: {
|
|
2370
|
+
type: 'required_capability_unavailable',
|
|
2371
|
+
code: 'required_capability_unavailable',
|
|
2372
|
+
message: `Pinned seller does not advertise required parameter(s) for model "${pinnedServiceId ?? requestedService ?? 'unknown'}": ${missingRequired.join(', ')}.`,
|
|
2373
|
+
param: REQUIRED_PARAMETERS_HEADER,
|
|
2374
|
+
},
|
|
2375
|
+
}));
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2307
2378
|
const pinnedRequest = pinnedServiceId ? withRoutedModel(serializedReq, pinnedServiceId) : serializedReq;
|
|
2308
2379
|
if (!peerAllowedByPolicy(policyRouter, pinnedRequest, selectedPeer)) {
|
|
2309
2380
|
log(`Pinned peer ${selectedPeer.peerId.slice(0, 12)}... filtered out by buyer routing policy`);
|
|
@@ -2418,7 +2489,7 @@ export class BuyerProxy {
|
|
|
2418
2489
|
+ `did not announce for this service: ${unannounced.join(', ')} — the upstream may ignore or reject them`);
|
|
2419
2490
|
}
|
|
2420
2491
|
}
|
|
2421
|
-
const { 'x-antseed-pin-peer': _pinPeer, 'x-antseed-prefer-peer': _preferPeer, 'x-vpr-session-id': _vprSession,
|
|
2492
|
+
const { 'x-antseed-pin-peer': _pinPeer, 'x-antseed-prefer-peer': _preferPeer, [REQUIRED_PARAMETERS_HEADER]: _requiredParameters, 'x-vpr-session-id': _vprSession,
|
|
2422
2493
|
// Legacy desktop builds (pre AntStation → VPR rename) still send this.
|
|
2423
2494
|
'x-antstation-session-id': _antstationSession, ...headersForPeer } = serializedReq.headers;
|
|
2424
2495
|
let requestForPeer = {
|