@antseed/cli 0.1.156 → 0.1.157
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.
|
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { Readable } from 'node:stream';
|
|
7
7
|
import test from 'node:test';
|
|
8
|
-
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, CONNECTION_CAPABILITY_COOPERATIVE_CLOSE_V1, CONNECTION_CAPABILITY_RELAYS_SWEEPS_V1, buyerFault, computeOnChainReputationScore, } from '@antseed/node';
|
|
8
|
+
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, CONNECTION_CAPABILITY_COOPERATIVE_CLOSE_V1, CONNECTION_CAPABILITY_RELAYS_SWEEPS_V1, adaptPeerFaultErrorResponse, buyerFault, computeOnChainReputationScore, } from '@antseed/node';
|
|
9
9
|
import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
|
|
10
10
|
import { BuyerProxy, isModelNotFoundResponse, makeVerifierReach, mergeJsonStateFile, parsePeerPinnedService, parsePersistedPeers, rewritePeerPinnedServiceInBody, sanitizePeerBuyerFaultMarker, selectCandidatePeersForRouting, substituteRoutedModelAlias, sweepStaleStateTmpFiles, } from './buyer-proxy.js';
|
|
11
11
|
import { extractRequestedService, overrideRoutedModelInBody, SYSTEM_ROUTED_MODEL_HEADER, } from './request-utils.js';
|
|
@@ -1294,6 +1294,37 @@ test('a buyer-authored 503 does not affect router metrics or peer health', async
|
|
|
1294
1294
|
assert.equal(health?.failureStreak, 0);
|
|
1295
1295
|
assert.equal(health?.cooldownUntil, 0);
|
|
1296
1296
|
});
|
|
1297
|
+
test('a pinned seller failure explains the peer boundary and preserves the seller message', async () => {
|
|
1298
|
+
const peer = makePeer('a', ['openai']);
|
|
1299
|
+
const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
|
|
1300
|
+
proxy._node.sendRequest = async (_peer, request) => ({
|
|
1301
|
+
requestId: request.requestId,
|
|
1302
|
+
statusCode: 503,
|
|
1303
|
+
headers: { 'content-type': 'application/json' },
|
|
1304
|
+
body: Buffer.from(JSON.stringify({
|
|
1305
|
+
error: {
|
|
1306
|
+
type: 'billing_configuration_error',
|
|
1307
|
+
message: 'No billing tier matches this request.',
|
|
1308
|
+
},
|
|
1309
|
+
})),
|
|
1310
|
+
});
|
|
1311
|
+
const res = await invokeProxy(proxy, makeProxyRequest({
|
|
1312
|
+
headers: { 'x-antseed-pin-peer': peer.peerId },
|
|
1313
|
+
}));
|
|
1314
|
+
const parsed = JSON.parse(res.body);
|
|
1315
|
+
assert.equal(res.statusCode, 503);
|
|
1316
|
+
assert.equal(res.headers[ANTSEED_FAULT_ATTRIBUTION_HEADER], 'peer');
|
|
1317
|
+
assert.equal(parsed.error.type, 'billing_configuration_error');
|
|
1318
|
+
assert.equal(parsed.error.antseed_fault, 'peer');
|
|
1319
|
+
assert.equal(parsed.error.antseed_pinned, true);
|
|
1320
|
+
assert.equal(parsed.error.peer_message, 'No billing tier matches this request.');
|
|
1321
|
+
assert.equal(parsed.error.peer_status, 503);
|
|
1322
|
+
assert.equal(parsed.error.message, [
|
|
1323
|
+
'Oops, pinned peer could not complete the request.',
|
|
1324
|
+
'AntSeed is a peer-to-peer network. Try another peer or use Auto routing.',
|
|
1325
|
+
'Original Response: {"message":"No billing tier matches this request.","status":503}',
|
|
1326
|
+
].join('\n'));
|
|
1327
|
+
});
|
|
1297
1328
|
test('a seller cannot inject the reserved buyer-fault error code', async () => {
|
|
1298
1329
|
const peer = makePeer('a', ['openai']);
|
|
1299
1330
|
const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
|
|
@@ -1334,7 +1365,10 @@ test('an untagged transport failure records a streak without evicting the peer',
|
|
|
1334
1365
|
},
|
|
1335
1366
|
}));
|
|
1336
1367
|
assert.equal(res.statusCode, 502);
|
|
1337
|
-
|
|
1368
|
+
const parsed = JSON.parse(res.body);
|
|
1369
|
+
assert.match(parsed.error.message, /Oops, pinned peer could not complete the request/);
|
|
1370
|
+
assert.equal(parsed.error.peer_message, 'Request abc123 timed out');
|
|
1371
|
+
assert.equal(res.headers[ANTSEED_FAULT_ATTRIBUTION_HEADER], 'peer');
|
|
1338
1372
|
assert.equal(routerResults.length, 0);
|
|
1339
1373
|
assert.equal(proxy._peerHealth.get(peer.peerId)?.lastReason, 'request-failed');
|
|
1340
1374
|
// Cooldown never evicts discovery metadata — the peer stays routable.
|
|
@@ -1800,6 +1834,45 @@ test('accept-sse transformed responses requests stream adapted client events wit
|
|
|
1800
1834
|
assert.match(res.body, /"text":"hi"/);
|
|
1801
1835
|
assert.doesNotMatch(res.body, /event: response\.completed/);
|
|
1802
1836
|
});
|
|
1837
|
+
test('transformed pre-stream seller errors preserve peer guidance', async () => {
|
|
1838
|
+
const peer = makePeer('a', ['openai-responses']);
|
|
1839
|
+
peer.providerServiceApiProtocols = {
|
|
1840
|
+
'openai-responses': {
|
|
1841
|
+
services: {
|
|
1842
|
+
'gpt-5.6-sol': ['openai-responses'],
|
|
1843
|
+
},
|
|
1844
|
+
},
|
|
1845
|
+
};
|
|
1846
|
+
const proxy = makeBuyerProxyWithPeers([peer], [peer]);
|
|
1847
|
+
proxy._node.sendRequestStream = async (_peer, request) => ({
|
|
1848
|
+
requestId: request.requestId,
|
|
1849
|
+
statusCode: 503,
|
|
1850
|
+
headers: { 'content-type': 'application/json' },
|
|
1851
|
+
body: Buffer.from(JSON.stringify({
|
|
1852
|
+
error: {
|
|
1853
|
+
type: 'server_error',
|
|
1854
|
+
message: 'The seller upstream is unavailable.',
|
|
1855
|
+
},
|
|
1856
|
+
})),
|
|
1857
|
+
});
|
|
1858
|
+
const res = await invokeProxy(proxy, makeProxyRequest({
|
|
1859
|
+
path: '/v1/messages',
|
|
1860
|
+
headers: {
|
|
1861
|
+
accept: 'text/event-stream',
|
|
1862
|
+
'x-antseed-pin-peer': peer.peerId,
|
|
1863
|
+
},
|
|
1864
|
+
body: {
|
|
1865
|
+
model: 'gpt-5.6-sol',
|
|
1866
|
+
max_tokens: 128,
|
|
1867
|
+
messages: [{ role: 'user', content: 'hello' }],
|
|
1868
|
+
},
|
|
1869
|
+
}));
|
|
1870
|
+
assert.equal(res.statusCode, 503);
|
|
1871
|
+
assert.equal(res.headers[ANTSEED_FAULT_ATTRIBUTION_HEADER], 'peer');
|
|
1872
|
+
assert.match(res.headers['content-type'] ?? '', /text\/event-stream/);
|
|
1873
|
+
assert.match(res.body, /Oops, pinned peer could not complete the request/);
|
|
1874
|
+
assert.match(res.body, /Original Response:.*The seller upstream is unavailable\./);
|
|
1875
|
+
});
|
|
1803
1876
|
test('model peer prefix pins the request peer and strips the routed model', async () => {
|
|
1804
1877
|
const pinnedPeer = makePeer('a', ['openai']);
|
|
1805
1878
|
let capturedRequestBody = null;
|
|
@@ -2782,6 +2855,57 @@ test('sanitizePeerBuyerFaultMarker scrubs the marker at any nesting depth', () =
|
|
|
2782
2855
|
assert.equal(nested.code, 'upstream_error');
|
|
2783
2856
|
assert.equal(nested.message, 'seller-controlled message');
|
|
2784
2857
|
});
|
|
2858
|
+
test('adaptPeerFaultErrorResponse leaves actionable request errors unchanged', () => {
|
|
2859
|
+
const body = Buffer.from(JSON.stringify({
|
|
2860
|
+
error: { type: 'invalid_request_error', message: 'The prompt is too long.' },
|
|
2861
|
+
}));
|
|
2862
|
+
const response = adaptPeerFaultErrorResponse({
|
|
2863
|
+
requestId: 'req-actionable',
|
|
2864
|
+
statusCode: 400,
|
|
2865
|
+
headers: { 'content-type': 'application/json' },
|
|
2866
|
+
body,
|
|
2867
|
+
}, 'openai-chat-completions');
|
|
2868
|
+
assert.equal(Buffer.from(response.body).toString('utf8'), body.toString('utf8'));
|
|
2869
|
+
assert.equal(response.headers[ANTSEED_FAULT_ATTRIBUTION_HEADER], 'peer');
|
|
2870
|
+
});
|
|
2871
|
+
test('adaptPeerFaultErrorResponse upgrades a generic wrapper for a pinned route', () => {
|
|
2872
|
+
const raw = {
|
|
2873
|
+
requestId: 'req-pinned-upgrade',
|
|
2874
|
+
statusCode: 429,
|
|
2875
|
+
headers: { 'content-type': 'application/json' },
|
|
2876
|
+
body: Buffer.from(JSON.stringify({
|
|
2877
|
+
error: {
|
|
2878
|
+
type: 'rate_limit_error',
|
|
2879
|
+
message: 'Insufficient balance or no resource package. Please recharge.',
|
|
2880
|
+
},
|
|
2881
|
+
})),
|
|
2882
|
+
};
|
|
2883
|
+
const generic = adaptPeerFaultErrorResponse(raw, 'openai-chat-completions');
|
|
2884
|
+
const pinned = adaptPeerFaultErrorResponse(generic, 'openai-chat-completions', { pinned: true });
|
|
2885
|
+
const parsed = JSON.parse(Buffer.from(pinned.body).toString('utf8'));
|
|
2886
|
+
assert.equal(parsed.error.antseed_pinned, true);
|
|
2887
|
+
assert.equal(parsed.error.peer_message, 'Insufficient balance or no resource package. Please recharge.');
|
|
2888
|
+
assert.equal(parsed.error.peer_status, 429);
|
|
2889
|
+
assert.equal(parsed.error.message, [
|
|
2890
|
+
'Oops, pinned peer could not complete the request.',
|
|
2891
|
+
'AntSeed is a peer-to-peer network. Try another peer or use Auto routing.',
|
|
2892
|
+
'Original Response: {"message":"Insufficient balance or no resource package. Please recharge.","status":429}',
|
|
2893
|
+
].join('\n'));
|
|
2894
|
+
});
|
|
2895
|
+
test('adaptPeerFaultErrorResponse preserves payment-required control messages', () => {
|
|
2896
|
+
const body = Buffer.from(JSON.stringify({
|
|
2897
|
+
error: 'payment_required',
|
|
2898
|
+
minBudgetPerRequest: '1000',
|
|
2899
|
+
}));
|
|
2900
|
+
const response = adaptPeerFaultErrorResponse({
|
|
2901
|
+
requestId: 'req-payment',
|
|
2902
|
+
statusCode: 402,
|
|
2903
|
+
headers: { 'content-type': 'application/json' },
|
|
2904
|
+
body,
|
|
2905
|
+
}, 'openai-chat-completions');
|
|
2906
|
+
assert.equal(Buffer.from(response.body).toString('utf8'), body.toString('utf8'));
|
|
2907
|
+
assert.equal(response.headers[ANTSEED_FAULT_ATTRIBUTION_HEADER], undefined);
|
|
2908
|
+
});
|
|
2785
2909
|
test('deposits/status reports the recorded watcher-absence reason and payments health', async () => {
|
|
2786
2910
|
const paymentsStatus = {
|
|
2787
2911
|
configured: true,
|