@antseed/cli 0.1.155 → 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,10 +5,10 @@ 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
- import { extractRequestedService, overrideRoutedModelInBody, SYSTEM_ROUTED_MODEL_HEADER } from './request-utils.js';
11
+ import { extractRequestedService, overrideRoutedModelInBody, SYSTEM_ROUTED_MODEL_HEADER, } from './request-utils.js';
12
12
  function makePeer(seed, providers) {
13
13
  const repeated = (seed.repeat(40) + 'a'.repeat(40)).slice(0, 40);
14
14
  return {
@@ -1069,6 +1069,64 @@ test('pinned proxy request reports when the pinned peer is not discoverable', as
1069
1069
  assert.match(res.body, /is not reachable right now/);
1070
1070
  assert.match(res.body, /It may be offline, not announcing, or temporarily unreachable/);
1071
1071
  });
1072
+ test('pinned proxy request surfaces a 403 without failing over to another peer', async () => {
1073
+ // An explicit pin is a hard constraint: even with another peer serving the
1074
+ // same model, the pinned peer's error is surfaced rather than re-routed.
1075
+ const pinnedPeer = makePeer('a', ['openai']);
1076
+ pinnedPeer.providerServiceApiProtocols = {
1077
+ openai: { services: { 'gpt-5': ['openai-chat-completions'] } },
1078
+ };
1079
+ const otherPeer = makePeer('b', ['openai']);
1080
+ otherPeer.providerServiceApiProtocols = {
1081
+ openai: { services: { 'GPT 5': ['openai-chat-completions'] } },
1082
+ };
1083
+ const proxy = makeBuyerProxyWithPeers([pinnedPeer, otherPeer], [pinnedPeer, otherPeer], permissiveRouter());
1084
+ const attempts = [];
1085
+ proxy._node.sendRequest = async (peer, request) => {
1086
+ attempts.push(peer.peerId);
1087
+ return {
1088
+ requestId: request.requestId,
1089
+ statusCode: 403,
1090
+ headers: { 'content-type': 'text/html' },
1091
+ body: Buffer.from('<html><body><h1>403 Forbidden</h1></body></html>'),
1092
+ };
1093
+ };
1094
+ const res = await invokeProxy(proxy, makeProxyRequest({
1095
+ headers: { 'x-antseed-pin-peer': pinnedPeer.peerId },
1096
+ body: { model: 'gpt-5', messages: [] },
1097
+ }));
1098
+ assert.equal(res.statusCode, 403);
1099
+ assert.deepEqual(attempts, [pinnedPeer.peerId]);
1100
+ });
1101
+ test('model-only routing fails over to the next peer after a 401 or 403', async () => {
1102
+ for (const statusCode of [401, 403]) {
1103
+ const first = makePeer('a', ['openai']);
1104
+ first.reputationScore = 95;
1105
+ first.providerServiceApiProtocols = {
1106
+ openai: { services: { 'gpt-5': ['openai-chat-completions'] } },
1107
+ };
1108
+ const second = makePeer('b', ['openai']);
1109
+ second.reputationScore = 80;
1110
+ second.providerServiceApiProtocols = {
1111
+ openai: { services: { 'GPT 5': ['openai-chat-completions'] } },
1112
+ };
1113
+ const proxy = makeBuyerProxyWithPeers([first, second], [first, second], permissiveRouter());
1114
+ const attempts = [];
1115
+ proxy._node.sendRequest = async (peer, request) => {
1116
+ attempts.push(peer.peerId);
1117
+ return {
1118
+ requestId: request.requestId,
1119
+ statusCode: peer.peerId === first.peerId ? statusCode : 200,
1120
+ headers: { 'content-type': 'application/json' },
1121
+ body: Buffer.from(JSON.stringify({ peerId: peer.peerId })),
1122
+ };
1123
+ };
1124
+ const res = await invokeProxy(proxy, makeProxyRequest({ body: { model: 'gpt-5', messages: [] } }));
1125
+ assert.equal(res.statusCode, 200, `expected failover to succeed for upstream ${statusCode}`);
1126
+ assert.deepEqual(attempts, [first.peerId, second.peerId]);
1127
+ assert.equal(JSON.parse(res.body).peerId, second.peerId);
1128
+ }
1129
+ });
1072
1130
  test('pinned proxy request rewrites a canonical alias to the advertised service id', async () => {
1073
1131
  const pinnedPeer = makePeer('a', ['openai']);
1074
1132
  pinnedPeer.providerServiceApiProtocols = {
@@ -1236,6 +1294,37 @@ test('a buyer-authored 503 does not affect router metrics or peer health', async
1236
1294
  assert.equal(health?.failureStreak, 0);
1237
1295
  assert.equal(health?.cooldownUntil, 0);
1238
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
+ });
1239
1328
  test('a seller cannot inject the reserved buyer-fault error code', async () => {
1240
1329
  const peer = makePeer('a', ['openai']);
1241
1330
  const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
@@ -1276,7 +1365,10 @@ test('an untagged transport failure records a streak without evicting the peer',
1276
1365
  },
1277
1366
  }));
1278
1367
  assert.equal(res.statusCode, 502);
1279
- assert.match(res.body, /Request abc123 timed out/);
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');
1280
1372
  assert.equal(routerResults.length, 0);
1281
1373
  assert.equal(proxy._peerHealth.get(peer.peerId)?.lastReason, 'request-failed');
1282
1374
  // Cooldown never evicts discovery metadata — the peer stays routable.
@@ -1742,6 +1834,45 @@ test('accept-sse transformed responses requests stream adapted client events wit
1742
1834
  assert.match(res.body, /"text":"hi"/);
1743
1835
  assert.doesNotMatch(res.body, /event: response\.completed/);
1744
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
+ });
1745
1876
  test('model peer prefix pins the request peer and strips the routed model', async () => {
1746
1877
  const pinnedPeer = makePeer('a', ['openai']);
1747
1878
  let capturedRequestBody = null;
@@ -2514,6 +2645,59 @@ test('title request racing ahead of the first turn does not name the chat', asyn
2514
2645
  },
2515
2646
  }));
2516
2647
  assert.equal(store.get('claude-code:cc_race'), null);
2648
+ // Factory/Droid has no session header. Its main turn and concurrent
2649
+ // one-shot title request therefore hash to different synthetic keys. The
2650
+ // system-role title instruction must keep the second key out of the list.
2651
+ await invokeProxy(proxy, makeProxyRequest({
2652
+ path: '/v1/chat/completions',
2653
+ headers: { originator: 'droid', 'user-agent': 'factory-cli/0.202.0' },
2654
+ body: {
2655
+ model: 'antseed',
2656
+ messages: [
2657
+ { role: 'system', content: 'You are Droid, an AI software engineering agent.' },
2658
+ { role: 'user', content: 'wowow' },
2659
+ ],
2660
+ },
2661
+ }));
2662
+ await invokeProxy(proxy, makeProxyRequest({
2663
+ path: '/v1/chat/completions',
2664
+ headers: {
2665
+ originator: 'droid',
2666
+ 'user-agent': 'factory-cli/0.202.0',
2667
+ },
2668
+ body: {
2669
+ model: 'antseed',
2670
+ messages: [
2671
+ {
2672
+ role: 'system',
2673
+ content: `Shared provider compatibility preamble.
2674
+ You are a helper that generates concise session titles for a session picker.
2675
+ Input: one user message from the start of a session.`,
2676
+ },
2677
+ { role: 'user', content: 'wowow' },
2678
+ ],
2679
+ },
2680
+ }));
2681
+ assert.equal(store.list().filter((conversation) => conversation.tool === 'droid').length, 1);
2682
+ // The exact helper marker describes housekeeping regardless of which
2683
+ // integration sends it, so it does not create another conversation row.
2684
+ await invokeProxy(proxy, makeProxyRequest({
2685
+ path: '/v1/chat/completions',
2686
+ headers: { originator: 'other-agent', 'user-agent': 'other-agent/1.0' },
2687
+ body: {
2688
+ model: 'antseed',
2689
+ messages: [
2690
+ {
2691
+ role: 'system',
2692
+ content: `Shared provider compatibility preamble.
2693
+ You are a helper that generates concise session titles for a session picker.
2694
+ Input: one user message from the start of a session.`,
2695
+ },
2696
+ { role: 'user', content: 'wowow' },
2697
+ ],
2698
+ },
2699
+ }));
2700
+ assert.equal(store.list().filter((conversation) => conversation.tool === 'other-agent').length, 0);
2517
2701
  // The real first turn creates the conversation afterwards.
2518
2702
  await invokeProxy(proxy, makeProxyRequest({
2519
2703
  path: '/v1/messages',
@@ -2671,6 +2855,57 @@ test('sanitizePeerBuyerFaultMarker scrubs the marker at any nesting depth', () =
2671
2855
  assert.equal(nested.code, 'upstream_error');
2672
2856
  assert.equal(nested.message, 'seller-controlled message');
2673
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
+ });
2674
2909
  test('deposits/status reports the recorded watcher-absence reason and payments health', async () => {
2675
2910
  const paymentsStatus = {
2676
2911
  configured: true,