@antseed/cli 0.1.194 → 0.1.196

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.
Files changed (39) hide show
  1. package/dist/cli/commands/seller/start.d.ts.map +1 -1
  2. package/dist/cli/commands/seller/start.js +5 -3
  3. package/dist/cli/commands/seller/start.js.map +1 -1
  4. package/dist/gateway/routing-through-gateway.test.js +1 -1
  5. package/dist/gateway/routing-through-gateway.test.js.map +1 -1
  6. package/dist/proxy/buyer-proxy.d.ts +30 -12
  7. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  8. package/dist/proxy/buyer-proxy.js +330 -61
  9. package/dist/proxy/buyer-proxy.js.map +1 -1
  10. package/dist/proxy/buyer-proxy.test.js +446 -23
  11. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  12. package/dist/proxy/gateway-policy.test.js +7 -6
  13. package/dist/proxy/gateway-policy.test.js.map +1 -1
  14. package/dist/proxy/response-quality.d.ts +25 -0
  15. package/dist/proxy/response-quality.d.ts.map +1 -0
  16. package/dist/proxy/response-quality.js +203 -0
  17. package/dist/proxy/response-quality.js.map +1 -0
  18. package/dist/proxy/response-quality.test.d.ts +2 -0
  19. package/dist/proxy/response-quality.test.d.ts.map +1 -0
  20. package/dist/proxy/response-quality.test.js +72 -0
  21. package/dist/proxy/response-quality.test.js.map +1 -0
  22. package/dist/proxy/route-policy.d.ts +2 -0
  23. package/dist/proxy/route-policy.d.ts.map +1 -1
  24. package/dist/proxy/route-policy.js +9 -8
  25. package/dist/proxy/route-policy.js.map +1 -1
  26. package/dist/proxy/route-policy.test.js +6 -0
  27. package/dist/proxy/route-policy.test.js.map +1 -1
  28. package/dist/proxy/routing-parameters.test.js +10 -0
  29. package/dist/proxy/routing-parameters.test.js.map +1 -1
  30. package/dist/proxy/routing.d.ts.map +1 -1
  31. package/dist/proxy/routing.js +2 -0
  32. package/dist/proxy/routing.js.map +1 -1
  33. package/dist/proxy/video-download.d.ts +9 -0
  34. package/dist/proxy/video-download.d.ts.map +1 -1
  35. package/dist/proxy/video-download.js +62 -9
  36. package/dist/proxy/video-download.js.map +1 -1
  37. package/dist/proxy/video-download.test.js +73 -10
  38. package/dist/proxy/video-download.test.js.map +1 -1
  39. package/package.json +7 -7
@@ -1,5 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { randomUUID } from 'node:crypto';
3
+ import { EventEmitter } from 'node:events';
3
4
  import { mkdir, mkdtemp, readdir, readFile, rm, utimes, writeFile } from 'node:fs/promises';
4
5
  import { tmpdir } from 'node:os';
5
6
  import { join } from 'node:path';
@@ -786,7 +787,7 @@ test('explicit routing-service target and tradeoff survive the local state-file
786
787
  const response = await invokeProxy(proxy, makeProxyRequest({ path: '/_antseed/route', body: routeBody(selection) }));
787
788
  assert.equal(response.statusCode, 200, response.body);
788
789
  assert.deepEqual(JSON.parse(await readFile(stateFile, 'utf8')).defaultRouter, routeBody(selection).router);
789
- const restarted = new BuyerProxy({ port: 0, dataDir: directory, node: { router: permissiveRouter() }, modelRoutingClient: client });
790
+ const restarted = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 }, port: 0, dataDir: directory, node: { router: permissiveRouter() }, modelRoutingClient: client });
790
791
  await restarted._reloadSessionOverrides();
791
792
  const restored = await invokeProxy(restarted, makeProxyRequest({ path: '/_antseed/route', method: 'GET' }));
792
793
  assert.deepEqual(JSON.parse(restored.body).router, routeBody(selection).router);
@@ -1594,7 +1595,7 @@ function listModelsWith(models = TEST_ROUTER_MODELS) {
1594
1595
  return async () => models.map(id => ({ id }));
1595
1596
  }
1596
1597
  function makeBuyerProxyWithPeers(initialPeers, refreshedPeers = initialPeers, router = null, now, routingPreferences, modelRoutingClient) {
1597
- const proxy = new BuyerProxy({
1598
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
1598
1599
  port: 0,
1599
1600
  dataDir: '/tmp/antseed-test',
1600
1601
  modelRoutingClient,
@@ -1602,8 +1603,7 @@ function makeBuyerProxyWithPeers(initialPeers, refreshedPeers = initialPeers, ro
1602
1603
  router,
1603
1604
  },
1604
1605
  ...(now ? { now } : {}),
1605
- ...(routingPreferences ? { routingPreferences } : {}),
1606
- });
1606
+ ...(routingPreferences ? { routingPreferences } : {}), });
1607
1607
  proxy._getPeers = async (options) => options?.forceRefresh ? refreshedPeers : initialPeers;
1608
1608
  proxy._cacheLastUpdatedAtMs = Date.now();
1609
1609
  return proxy;
@@ -1650,7 +1650,7 @@ async function invokeProxy(proxy, req) {
1650
1650
  return res;
1651
1651
  }
1652
1652
  test('BuyerProxy defaults to the configured 5 min background refresh interval', () => {
1653
- const proxy = new BuyerProxy({
1653
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
1654
1654
  port: 0,
1655
1655
  dataDir: '/tmp/antseed-test',
1656
1656
  node: { router: null },
@@ -1658,16 +1658,15 @@ test('BuyerProxy defaults to the configured 5 min background refresh interval',
1658
1658
  assert.equal(proxy._bgRefreshIntervalMs, DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS);
1659
1659
  });
1660
1660
  test('BuyerProxy accepts a custom background refresh interval', () => {
1661
- const proxy = new BuyerProxy({
1661
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
1662
1662
  port: 0,
1663
1663
  dataDir: '/tmp/antseed-test',
1664
1664
  node: { router: null },
1665
- backgroundRefreshIntervalMs: 15_000,
1666
- });
1665
+ backgroundRefreshIntervalMs: 15_000, });
1667
1666
  assert.equal(proxy._bgRefreshIntervalMs, 15_000);
1668
1667
  });
1669
1668
  test('BuyerProxy rewrites upstream 413 responses into a clear request size error', () => {
1670
- const proxy = new BuyerProxy({
1669
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
1671
1670
  port: 0,
1672
1671
  dataDir: '/tmp/antseed-test',
1673
1672
  node: { router: null },
@@ -1701,7 +1700,7 @@ test('BuyerProxy reloads model routing preferences from config', async (t) => {
1701
1700
  },
1702
1701
  },
1703
1702
  }));
1704
- const proxy = new BuyerProxy({
1703
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
1705
1704
  port: 0,
1706
1705
  dataDir,
1707
1706
  configPath,
@@ -1721,7 +1720,7 @@ test('BuyerProxy starts incremental discovery on startup', async (t) => {
1721
1720
  const dir = await mkdtemp(join(tmpdir(), 'antseed-buyer-proxy-'));
1722
1721
  t.after(() => rm(dir, { recursive: true, force: true }));
1723
1722
  let sweepCalls = 0;
1724
- const proxy = new BuyerProxy({
1723
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
1725
1724
  port: 0,
1726
1725
  dataDir: dir,
1727
1726
  node: {
@@ -1729,8 +1728,7 @@ test('BuyerProxy starts incremental discovery on startup', async (t) => {
1729
1728
  on: () => undefined,
1730
1729
  startBackgroundPeerDiscoverySweep: () => { sweepCalls += 1; },
1731
1730
  },
1732
- backgroundRefreshIntervalMs: 60 * 60_000,
1733
- });
1731
+ backgroundRefreshIntervalMs: 60 * 60_000, });
1734
1732
  proxy._refreshPeersNow = async () => [];
1735
1733
  await proxy.start();
1736
1734
  await proxy.stop();
@@ -1782,7 +1780,7 @@ test('sweep control endpoint validates and dispatches sequentially via the runni
1782
1780
  };
1783
1781
  const dispatches = [];
1784
1782
  const listeners = new Map();
1785
- const proxy = new BuyerProxy({
1783
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
1786
1784
  port: 0,
1787
1785
  dataDir: '/tmp/antseed-test',
1788
1786
  node: {
@@ -1818,6 +1816,22 @@ test('sweep control endpoint validates and dispatches sequentially via the runni
1818
1816
  const missing = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: `/_antseed/sweep/0x${'bb'.repeat(32)}` }));
1819
1817
  assert.deepEqual(JSON.parse(missing.body), { ok: true, receipt: null });
1820
1818
  });
1819
+ test('peer refresh drops services a seller stopped advertising', () => {
1820
+ const advertised = makePeer('a', ['venice-video']);
1821
+ advertised.providerPricing = { 'venice-video': { defaults: { inputUsdPerMillion: 0, outputUsdPerMillion: 0 }, services: { 'video-model': { inputUsdPerMillion: 0, outputUsdPerMillion: 0 } } } };
1822
+ advertised.providerServiceApiProtocols = { 'venice-video': { services: { 'video-model': ['venice-video'] } } };
1823
+ advertised.lastReachedAt = Date.now() - 1000;
1824
+ const proxy = makeBuyerProxyWithPeers([]);
1825
+ proxy._replacePeers([advertised]);
1826
+ const unadvertised = makePeer('a', []);
1827
+ proxy._replacePeers([unadvertised]);
1828
+ const cached = proxy._cachedPeers;
1829
+ assert.equal(cached.length, 1);
1830
+ assert.deepEqual(cached[0].providers, []);
1831
+ assert.equal(cached[0].providerPricing, undefined);
1832
+ assert.equal(cached[0].providerServiceApiProtocols, undefined);
1833
+ assert.equal(cached[0].lastReachedAt, advertised.lastReachedAt);
1834
+ });
1821
1835
  test('peer refresh control endpoint triggers immediate refresh', async () => {
1822
1836
  const refreshedPeer = makePeer('a', ['anthropic']);
1823
1837
  const proxy = makeBuyerProxyWithPeers([], [refreshedPeer]);
@@ -4133,7 +4147,7 @@ test('overrideRoutedModelInBody no-ops on a matching model, a missing model, or
4133
4147
  test('route control endpoint sets, persists, and returns the default routed model', async (t) => {
4134
4148
  const dir = await mkdtemp(join(tmpdir(), 'antseed-buyer-route-'));
4135
4149
  t.after(() => rm(dir, { recursive: true, force: true }));
4136
- const proxy = new BuyerProxy({
4150
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4137
4151
  port: 0,
4138
4152
  dataDir: dir,
4139
4153
  node: { router: null },
@@ -4164,7 +4178,7 @@ test('route control endpoint sets, persists, and returns the default routed mode
4164
4178
  assert.deepEqual(JSON.parse(cleared.body), { ok: true, model: null, router: null });
4165
4179
  });
4166
4180
  test('buyer-usage endpoint reports lastActivityAt, null until a request is dispatched', async () => {
4167
- const proxy = new BuyerProxy({
4181
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4168
4182
  port: 0,
4169
4183
  dataDir: '/tmp/antseed-test',
4170
4184
  node: { router: null, getBuyerUsageTotals: () => ({ totalRequests: 0 }) },
@@ -4207,7 +4221,7 @@ test('rewritePeerPinnedServiceInBody returns original when body is not a JSON ob
4207
4221
  // ---------- Per-chat conversations (tracking, pins, control endpoints) ----------
4208
4222
  async function makeConversationProxy() {
4209
4223
  const dir = await mkdtemp(join(tmpdir(), 'antseed-buyer-conv-'));
4210
- const proxy = new BuyerProxy({
4224
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4211
4225
  port: 0,
4212
4226
  dataDir: dir,
4213
4227
  node: { router: null },
@@ -4625,7 +4639,7 @@ test('sweepStaleStateTmpFiles: tolerates a missing directory', async () => {
4625
4639
  });
4626
4640
  // ---------- Deposit watcher control plane ----------
4627
4641
  test('deposits/status reports no watcher until one is attached', async () => {
4628
- const proxy = new BuyerProxy({
4642
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4629
4643
  port: 0,
4630
4644
  dataDir: '/tmp/antseed-test',
4631
4645
  node: { router: null },
@@ -4752,7 +4766,7 @@ test('deposits/status reports the recorded watcher-absence reason and payments h
4752
4766
  chainId: 8453,
4753
4767
  rpc: { state: 'unreachable', lastCheckedAt: 123, lastReadyAt: null, lastError: 'probe failed', attempts: 2 },
4754
4768
  };
4755
- const proxy = new BuyerProxy({
4769
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4756
4770
  port: 0,
4757
4771
  dataDir: '/tmp/antseed-test',
4758
4772
  node: { router: null, getPaymentsStatus: () => paymentsStatus },
@@ -4772,7 +4786,7 @@ test('deposits/status reports the recorded watcher-absence reason and payments h
4772
4786
  assert.match(watchBody.error, /payments are disabled/);
4773
4787
  });
4774
4788
  test('deposits/watch returns 503 when no watcher is attached', async () => {
4775
- const proxy = new BuyerProxy({
4789
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4776
4790
  port: 0,
4777
4791
  dataDir: '/tmp/antseed-test',
4778
4792
  node: { router: null },
@@ -4782,7 +4796,7 @@ test('deposits/watch returns 503 when no watcher is attached', async () => {
4782
4796
  assert.equal(JSON.parse(res.body).ok, false);
4783
4797
  });
4784
4798
  test('deposits/watch promotes and demotes an attached watcher and returns its status', async () => {
4785
- const proxy = new BuyerProxy({
4799
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4786
4800
  port: 0,
4787
4801
  dataDir: '/tmp/antseed-test',
4788
4802
  node: { router: null },
@@ -4813,7 +4827,7 @@ test('deposits/watch promotes and demotes an attached watcher and returns its st
4813
4827
  assert.deepEqual(JSON.parse(status.body), { ok: true, watcher: true, reason: null, payments: null, status: fakeStatus });
4814
4828
  });
4815
4829
  test('getSweepReceipt returns cached relayer receipts case-insensitively', () => {
4816
- const proxy = new BuyerProxy({
4830
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4817
4831
  port: 0,
4818
4832
  dataDir: '/tmp/antseed-test',
4819
4833
  node: { router: null },
@@ -4886,7 +4900,7 @@ test('switching a chat route while a request is in flight is not reverted when t
4886
4900
  oldPeer.providerServiceApiProtocols = { openai: { services: { 'gpt-5': ['openai-chat-completions'] } } };
4887
4901
  const newPeer = makePeer('b', ['openai']);
4888
4902
  newPeer.providerServiceApiProtocols = { openai: { services: { 'claude-sonnet': ['openai-chat-completions'] } } };
4889
- const proxy = new BuyerProxy({
4903
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 },
4890
4904
  port: 0,
4891
4905
  dataDir: dir,
4892
4906
  node: { router: permissiveRouter() },
@@ -4958,4 +4972,413 @@ test('switching a chat route while a request is in flight is not reverted when t
4958
4972
  await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
4959
4973
  }
4960
4974
  });
4975
+ /** Sellers of one model at trust 80: `cheap` slightly undercuts `pricier`; `extra` costs ten times as much. */
4976
+ function makeModelSellers() {
4977
+ const seller = (seed, input, output) => {
4978
+ const peer = makePeer(seed, ['openai']);
4979
+ peer.reputationScore = 80;
4980
+ peer.providerPricing = { openai: { defaults: { inputUsdPerMillion: input, outputUsdPerMillion: output }, services: { 'gpt-5': { inputUsdPerMillion: input, outputUsdPerMillion: output } } } };
4981
+ peer.providerServiceApiProtocols = { openai: { services: { 'gpt-5': ['openai-chat-completions'] } } };
4982
+ return peer;
4983
+ };
4984
+ return { cheap: seller('a', 1, 2), pricier: seller('b', 1, 2.4), extra: seller('c', 10, 20) };
4985
+ }
4986
+ function jsonResponse(requestId, statusCode, body) {
4987
+ return { requestId, statusCode, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify(body)) };
4988
+ }
4989
+ const answer = (content = 'ok') => ({ choices: [{ message: { content }, finish_reason: 'stop' }] });
4990
+ const modelRequest = () => makeProxyRequest({ body: { model: 'gpt-5', messages: [] } });
4991
+ const sellerHealthOf = (proxy, peer, now) => proxy._sellerHealth.stats(peer.peerId, proxy._healthServiceKey('gpt-5'), now);
4992
+ test('a seller that rejects a request another seller serves loses its place to a slightly pricier seller', async () => {
4993
+ const clock = makeTestClock();
4994
+ const { cheap, pricier } = makeModelSellers();
4995
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
4996
+ const attempts = [];
4997
+ proxy._node.sendRequest = async (peer, request) => {
4998
+ attempts.push(peer.peerId);
4999
+ return peer.peerId === cheap.peerId
5000
+ ? jsonResponse(request.requestId, 422, { error: { type: 'invalid_request_error', message: 'unsupported parameter: top_k' } })
5001
+ : jsonResponse(request.requestId, 200, answer());
5002
+ };
5003
+ const first = await invokeProxy(proxy, modelRequest());
5004
+ assert.equal(first.statusCode, 200);
5005
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId]);
5006
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()).failures, 1);
5007
+ attempts.length = 0;
5008
+ clock.advance(60_000);
5009
+ assert.equal((await invokeProxy(proxy, modelRequest())).statusCode, 200);
5010
+ assert.deepEqual(attempts, [pricier.peerId], 'the failing seller is no longer tried first');
5011
+ const health = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/peer-health' }));
5012
+ const entry = JSON.parse(health.body).sellerHealth.find((row) => row.peerId === cheap.peerId);
5013
+ assert.match(entry?.lastFailureReason ?? '', /HTTP 422/);
5014
+ });
5015
+ test('a stream that breaks after its headers does not prove an earlier rejection, and counts against the broken seller', async () => {
5016
+ const clock = makeTestClock();
5017
+ const { cheap, pricier } = makeModelSellers();
5018
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5019
+ const attempts = [];
5020
+ proxy._node.sendRequestStream = async (peer, request, callbacks) => {
5021
+ attempts.push(peer.peerId);
5022
+ if (peer.peerId === cheap.peerId) {
5023
+ const rejected = jsonResponse(request.requestId, 422, { error: { type: 'invalid_request_error', message: 'unsupported parameter' } });
5024
+ callbacks.onResponseStart(rejected, { streaming: false });
5025
+ return rejected;
5026
+ }
5027
+ callbacks.onResponseStart({ requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'text/event-stream' }, body: new Uint8Array() }, { streaming: true });
5028
+ callbacks.onResponseChunk({ requestId: request.requestId, data: Buffer.from('data: {"choices":[{"delta":{"content":"par"}}]}\n\n'), done: false });
5029
+ throw new Error('connection reset by peer');
5030
+ };
5031
+ await invokeProxy(proxy, makeProxyRequest({ headers: { accept: 'text/event-stream' }, body: { model: 'gpt-5', messages: [], stream: true } }));
5032
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId]);
5033
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()), null, 'the rejection is unproven: nobody completed the request');
5034
+ assert.equal(sellerHealthOf(proxy, pricier, clock.now()).failures, 1);
5035
+ assert.equal(sellerHealthOf(proxy, pricier, clock.now()).successes, 0);
5036
+ });
5037
+ for (const [label, partialBytes] of [['short', 0], ['larger than 4 MiB', 5 * 1024 * 1024]])
5038
+ test(`a stream whose provider fails after partial text is not a served answer (${label})`, async () => {
5039
+ const clock = makeTestClock();
5040
+ const { cheap, pricier } = makeModelSellers();
5041
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5042
+ const attempts = [];
5043
+ proxy._node.sendRequestStream = async (peer, request, callbacks) => {
5044
+ attempts.push(peer.peerId);
5045
+ if (peer.peerId === cheap.peerId) {
5046
+ const rejected = jsonResponse(request.requestId, 422, { error: { type: 'invalid_request_error', message: 'unsupported parameter' } });
5047
+ callbacks.onResponseStart(rejected, { streaming: false });
5048
+ return rejected;
5049
+ }
5050
+ const headers = { 'content-type': 'text/event-stream' };
5051
+ callbacks.onResponseStart({ requestId: request.requestId, statusCode: 200, headers, body: new Uint8Array() }, { streaming: true });
5052
+ // The seller's wire behavior when its provider throws mid-stream: an
5053
+ // error frame, then a normal terminal chunk.
5054
+ const line = 'data: {"choices":[{"delta":{"content":"' + 'x'.repeat(1000) + '"}}]}\n\n';
5055
+ const text = Buffer.from(partialBytes === 0 ? 'data: {"choices":[{"delta":{"content":"partial"}}]}\n\n' : line.repeat(Math.ceil(partialBytes / line.length)));
5056
+ const errorFrame = Buffer.from('event: error\ndata: upstream failed\n\n');
5057
+ callbacks.onResponseChunk({ requestId: request.requestId, data: text, done: false });
5058
+ callbacks.onResponseChunk({ requestId: request.requestId, data: errorFrame, done: false });
5059
+ callbacks.onResponseChunk({ requestId: request.requestId, data: new Uint8Array(0), done: true });
5060
+ return { requestId: request.requestId, statusCode: 200, headers, body: Buffer.concat([text, errorFrame]) };
5061
+ };
5062
+ await invokeProxy(proxy, makeProxyRequest({ headers: { accept: 'text/event-stream' }, body: { model: 'gpt-5', messages: [], stream: true } }));
5063
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId]);
5064
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()), null, 'the rejection is unproven: nobody completed the request');
5065
+ assert.equal(sellerHealthOf(proxy, pricier, clock.now()).failures, 1);
5066
+ assert.equal(sellerHealthOf(proxy, pricier, clock.now()).successes, 0);
5067
+ });
5068
+ test('a rejection every seller shares reaches the client after one extra seller and counts against nobody', async () => {
5069
+ const clock = makeTestClock();
5070
+ const { cheap, pricier, extra } = makeModelSellers();
5071
+ const peers = [cheap, pricier, extra];
5072
+ const proxy = makeBuyerProxyWithPeers(peers, peers, permissiveRouter(), clock.now, priceAndTrustPreferences);
5073
+ const attempts = [];
5074
+ proxy._node.sendRequest = async (peer, request) => {
5075
+ attempts.push(peer.peerId);
5076
+ return jsonResponse(request.requestId, 400, { error: { type: 'invalid_request_error', message: 'messages must not be empty' } });
5077
+ };
5078
+ const res = await invokeProxy(proxy, modelRequest());
5079
+ assert.equal(res.statusCode, 400);
5080
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId]);
5081
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()), null);
5082
+ assert.equal(sellerHealthOf(proxy, pricier, clock.now()), null);
5083
+ });
5084
+ test('a pinned seller keeps its rejection and it does not count against it', async () => {
5085
+ const clock = makeTestClock();
5086
+ const { cheap, pricier } = makeModelSellers();
5087
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5088
+ const attempts = [];
5089
+ proxy._node.sendRequest = async (peer, request) => {
5090
+ attempts.push(peer.peerId);
5091
+ return jsonResponse(request.requestId, 422, { error: { type: 'invalid_request_error', message: 'nope' } });
5092
+ };
5093
+ const res = await invokeProxy(proxy, makeProxyRequest({
5094
+ headers: { 'x-antseed-pin-peer': cheap.peerId },
5095
+ body: { model: 'gpt-5', messages: [] },
5096
+ }));
5097
+ assert.equal(res.statusCode, 422);
5098
+ assert.deepEqual(attempts, [cheap.peerId]);
5099
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()), null);
5100
+ });
5101
+ test('a seller whose signed response fails verification falls behind; buyer-side mismatches are ignored', async () => {
5102
+ const clock = makeTestClock();
5103
+ const { cheap, pricier } = makeModelSellers();
5104
+ const node = Object.assign(new EventEmitter(), { router: permissiveRouter() });
5105
+ const proxy = new BuyerProxy({ adaptiveRouting: { spreadTolerance: 0, explorationRate: 0 }, port: 0, dataDir: '/tmp/antseed-test', node: node, now: clock.now, routingPreferences: priceAndTrustPreferences });
5106
+ proxy._getPeers = async () => [cheap, pricier];
5107
+ proxy._cacheLastUpdatedAtMs = Date.now();
5108
+ const attempts = [];
5109
+ proxy._node.sendRequest = async (peer, request) => {
5110
+ attempts.push(peer.peerId);
5111
+ return jsonResponse(request.requestId, 200, answer());
5112
+ };
5113
+ node.emit('response-auth', { requestId: 'r1', sellerPeerId: cheap.peerId, advertisedService: 'gpt-5', verified: false, verificationError: 'channel_id_mismatch' });
5114
+ await invokeProxy(proxy, modelRequest());
5115
+ assert.deepEqual(attempts, [cheap.peerId]);
5116
+ node.emit('response-auth', { requestId: 'r2', sellerPeerId: cheap.peerId, advertisedService: 'gpt-5', verified: false, verificationError: 'response_hash_mismatch' });
5117
+ attempts.length = 0;
5118
+ await invokeProxy(proxy, modelRequest());
5119
+ assert.deepEqual(attempts, [pricier.peerId]);
5120
+ });
5121
+ test('failures fade with time, with no timer: a seller that failed a while ago is first again', async () => {
5122
+ const clock = makeTestClock();
5123
+ const { cheap, pricier } = makeModelSellers();
5124
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5125
+ for (let i = 0; i < 3; i += 1)
5126
+ proxy._recordSellerFailure(cheap.peerId, 'gpt-5', 'HTTP 503');
5127
+ const attempts = [];
5128
+ proxy._node.sendRequest = async (peer, request) => {
5129
+ attempts.push(peer.peerId);
5130
+ return jsonResponse(request.requestId, 200, answer());
5131
+ };
5132
+ await invokeProxy(proxy, modelRequest());
5133
+ assert.deepEqual(attempts, [pricier.peerId]);
5134
+ attempts.length = 0;
5135
+ clock.advance(2 * 60 * 60_000);
5136
+ await invokeProxy(proxy, modelRequest());
5137
+ assert.deepEqual(attempts, [cheap.peerId]);
5138
+ });
5139
+ test('any failure another seller then serves counts against the failing seller: a 403, a dropped request', async () => {
5140
+ for (const failure of ['403', 'transport']) {
5141
+ const clock = makeTestClock();
5142
+ const { cheap, pricier } = makeModelSellers();
5143
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5144
+ const attempts = [];
5145
+ proxy._node.sendRequest = async (peer, request) => {
5146
+ attempts.push(peer.peerId);
5147
+ if (peer.peerId !== cheap.peerId)
5148
+ return jsonResponse(request.requestId, 200, answer());
5149
+ if (failure === 'transport')
5150
+ throw new Error('connection reset by peer');
5151
+ return jsonResponse(request.requestId, 403, { error: { message: 'forbidden' } });
5152
+ };
5153
+ assert.equal((await invokeProxy(proxy, modelRequest())).statusCode, 200, failure);
5154
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId], failure);
5155
+ attempts.length = 0;
5156
+ clock.advance(30_000);
5157
+ assert.equal((await invokeProxy(proxy, modelRequest())).statusCode, 200, failure);
5158
+ assert.deepEqual(attempts, [pricier.peerId], `${failure}: the failing seller is no longer tried first`);
5159
+ }
5160
+ });
5161
+ test('an empty answer is retried on one more seller, and counts against the empty one when that one answers', async () => {
5162
+ const clock = makeTestClock();
5163
+ const { cheap, pricier } = makeModelSellers();
5164
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5165
+ const attempts = [];
5166
+ proxy._node.sendRequest = async (peer, request) => {
5167
+ attempts.push(peer.peerId);
5168
+ return jsonResponse(request.requestId, 200, answer(peer.peerId === cheap.peerId ? '' : 'ok'));
5169
+ };
5170
+ const res = await invokeProxy(proxy, modelRequest());
5171
+ assert.equal(res.statusCode, 200);
5172
+ assert.equal(JSON.parse(res.body).choices[0].message.content, 'ok');
5173
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId]);
5174
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()).failures, 1);
5175
+ assert.equal(sellerHealthOf(proxy, pricier, clock.now()).successes, 1);
5176
+ });
5177
+ test('a refusal is an answer: not retried and not counted against the seller', async () => {
5178
+ const clock = makeTestClock();
5179
+ const { cheap, pricier } = makeModelSellers();
5180
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5181
+ const attempts = [];
5182
+ proxy._node.sendRequest = async (peer, request) => {
5183
+ attempts.push(peer.peerId);
5184
+ return jsonResponse(request.requestId, 200, {
5185
+ id: 'r', object: 'chat.completion', model: 'gpt-5',
5186
+ choices: [{ index: 0, message: { role: 'assistant', content: null, refusal: 'I can not help with that.' }, finish_reason: 'stop' }],
5187
+ });
5188
+ };
5189
+ const res = await invokeProxy(proxy, modelRequest());
5190
+ assert.equal(res.statusCode, 200);
5191
+ assert.equal(JSON.parse(res.body).choices[0].message.refusal, 'I can not help with that.');
5192
+ assert.deepEqual(attempts, [cheap.peerId]);
5193
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()).failures, 0);
5194
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()).successes, 1);
5195
+ });
5196
+ test('a legacy completion is an answer: not retried and not counted against the seller', async () => {
5197
+ const clock = makeTestClock();
5198
+ const { cheap, pricier } = makeModelSellers();
5199
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5200
+ const attempts = [];
5201
+ proxy._node.sendRequest = async (peer, request) => {
5202
+ attempts.push(peer.peerId);
5203
+ return jsonResponse(request.requestId, 200, {
5204
+ id: 'c', object: 'text_completion', model: 'gpt-5',
5205
+ choices: [{ index: 0, text: 'Hello', finish_reason: 'stop' }],
5206
+ });
5207
+ };
5208
+ const res = await invokeProxy(proxy, modelRequest());
5209
+ assert.equal(res.statusCode, 200);
5210
+ assert.deepEqual(attempts, [cheap.peerId]);
5211
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()).failures, 0);
5212
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()).successes, 1);
5213
+ });
5214
+ test('when every seller answers empty, the client gets the answer and nobody is counted against', async () => {
5215
+ const clock = makeTestClock();
5216
+ const { cheap, pricier, extra } = makeModelSellers();
5217
+ const peers = [cheap, pricier, extra];
5218
+ const proxy = makeBuyerProxyWithPeers(peers, peers, permissiveRouter(), clock.now, priceAndTrustPreferences);
5219
+ const attempts = [];
5220
+ proxy._node.sendRequest = async (peer, request) => {
5221
+ attempts.push(peer.peerId);
5222
+ return jsonResponse(request.requestId, 200, answer(''));
5223
+ };
5224
+ const res = await invokeProxy(proxy, modelRequest());
5225
+ assert.equal(res.statusCode, 200);
5226
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId]);
5227
+ assert.equal(sellerHealthOf(proxy, cheap, clock.now()), null);
5228
+ });
5229
+ test('traffic is spread across sellers close in cost, not sent to one', async () => {
5230
+ const clock = makeTestClock();
5231
+ const { cheap, pricier, extra } = makeModelSellers();
5232
+ const peers = [cheap, pricier, extra];
5233
+ let draw = 0;
5234
+ const proxy = new BuyerProxy({
5235
+ adaptiveRouting: { random: () => { draw = (draw + 0.37) % 1; return draw; }, explorationRate: 0 },
5236
+ port: 0, dataDir: '/tmp/antseed-test', node: { router: permissiveRouter() }, now: clock.now, routingPreferences: priceAndTrustPreferences,
5237
+ });
5238
+ proxy._getPeers = async () => peers;
5239
+ proxy._cacheLastUpdatedAtMs = Date.now();
5240
+ const served = new Map();
5241
+ proxy._node.sendRequest = async (peer, request) => {
5242
+ served.set(peer.peerId, (served.get(peer.peerId) ?? 0) + 1);
5243
+ return jsonResponse(request.requestId, 200, answer());
5244
+ };
5245
+ for (let i = 0; i < 20; i += 1)
5246
+ await invokeProxy(proxy, modelRequest());
5247
+ assert.ok((served.get(cheap.peerId) ?? 0) > 0 && (served.get(pricier.peerId) ?? 0) > 0, JSON.stringify([...served]));
5248
+ assert.equal(served.get(extra.peerId) ?? 0, 0, 'a seller ten times the price gets no share');
5249
+ });
5250
+ test('a seller far slower than usual is given up on so the next seller can answer', async () => {
5251
+ const clock = makeTestClock();
5252
+ const { cheap, pricier } = makeModelSellers();
5253
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5254
+ for (let i = 0; i < 3; i += 1)
5255
+ proxy._recordSellerSuccess(cheap.peerId, 'gpt-5', { firstTokenMs: 100, outputTokens: 0, streamed: false });
5256
+ const attempts = [];
5257
+ proxy._node.sendRequest = async (peer, request, options) => {
5258
+ attempts.push(peer.peerId);
5259
+ if (peer.peerId !== cheap.peerId)
5260
+ return jsonResponse(request.requestId, 200, answer());
5261
+ return new Promise((_resolve, reject) => {
5262
+ options.signal.addEventListener('abort', () => reject(options.signal.reason), { once: true });
5263
+ });
5264
+ };
5265
+ const timers = [];
5266
+ const realSetTimeout = globalThis.setTimeout;
5267
+ globalThis.setTimeout = (fn, ms) => {
5268
+ // The per-attempt timeout (at least 30 s): fire it now instead of waiting.
5269
+ if (ms >= 30_000) {
5270
+ timers.push(fn);
5271
+ return { unref() { } };
5272
+ }
5273
+ return realSetTimeout(fn, ms);
5274
+ };
5275
+ try {
5276
+ const pending = invokeProxy(proxy, modelRequest());
5277
+ while (timers.length === 0)
5278
+ await new Promise((resolve) => realSetTimeout(resolve, 1));
5279
+ timers.shift()();
5280
+ const res = await pending;
5281
+ assert.equal(res.statusCode, 200);
5282
+ assert.deepEqual(attempts, [cheap.peerId, pricier.peerId]);
5283
+ assert.match(sellerHealthOf(proxy, cheap, clock.now()).lastFailureReason, /no answer within \d+s/);
5284
+ }
5285
+ finally {
5286
+ ;
5287
+ globalThis.setTimeout = realSetTimeout;
5288
+ }
5289
+ });
5290
+ test('seller health survives a restart', async () => {
5291
+ const dataDir = await mkdtemp(join(tmpdir(), 'antseed-health-'));
5292
+ try {
5293
+ const clock = makeTestClock();
5294
+ const first = new BuyerProxy({ port: 0, dataDir, node: { router: null }, now: clock.now });
5295
+ first._recordSellerFailure('a'.repeat(40), 'gpt-5', 'HTTP 403');
5296
+ await first.stop();
5297
+ const second = new BuyerProxy({ port: 0, dataDir, node: { router: null }, now: clock.now });
5298
+ await second._hydratePeersFromStateFile();
5299
+ assert.equal(second._peerLatency('a'.repeat(40), 'gpt-5'), null);
5300
+ assert.equal(second._sellerHealth.stats('a'.repeat(40), second._healthServiceKey('gpt-5'), clock.now()).failures, 1);
5301
+ }
5302
+ finally {
5303
+ await rm(dataDir, { recursive: true, force: true });
5304
+ }
5305
+ });
5306
+ test('requests of one identified conversation stay on one seller; others spread', async () => {
5307
+ const clock = makeTestClock();
5308
+ const { cheap, pricier } = makeModelSellers();
5309
+ const proxy = new BuyerProxy({
5310
+ adaptiveRouting: { random: Math.random, explorationRate: 0 },
5311
+ port: 0, dataDir: '/tmp/antseed-test', node: { router: permissiveRouter() }, now: clock.now, routingPreferences: priceAndTrustPreferences,
5312
+ });
5313
+ proxy._getPeers = async () => [cheap, pricier];
5314
+ proxy._cacheLastUpdatedAtMs = Date.now();
5315
+ const served = [];
5316
+ proxy._node.sendRequest = async (peer, request) => {
5317
+ served.push(peer.peerId);
5318
+ return jsonResponse(request.requestId, 200, answer());
5319
+ };
5320
+ const turn = (cacheKey, n) => makeProxyRequest({
5321
+ body: { model: 'gpt-5', messages: [{ role: 'user', content: `turn ${n}` }], ...(cacheKey ? { prompt_cache_key: cacheKey } : {}) },
5322
+ });
5323
+ for (let n = 0; n < 8; n += 1)
5324
+ await invokeProxy(proxy, turn('conversation-1', n));
5325
+ assert.equal(new Set(served).size, 1, 'every turn on the seller holding the cache');
5326
+ served.length = 0;
5327
+ for (let n = 0; n < 30; n += 1)
5328
+ await invokeProxy(proxy, turn(`conversation-${n + 2}`, 0));
5329
+ assert.equal(new Set(served).size, 2, 'different conversations still spread');
5330
+ });
5331
+ test('spreading stays on sellers with an open channel instead of reserving on new ones', async () => {
5332
+ const clock = makeTestClock();
5333
+ const { cheap, pricier } = makeModelSellers();
5334
+ const proxy = new BuyerProxy({
5335
+ adaptiveRouting: { random: Math.random },
5336
+ port: 0, dataDir: '/tmp/antseed-test',
5337
+ node: { router: permissiveRouter(), getActiveBuyerChannels: () => [{ peerId: pricier.peerId }] },
5338
+ now: clock.now, routingPreferences: priceAndTrustPreferences,
5339
+ });
5340
+ proxy._getPeers = async () => [cheap, pricier];
5341
+ proxy._cacheLastUpdatedAtMs = Date.now();
5342
+ const served = new Set();
5343
+ proxy._node.sendRequest = async (peer, request) => {
5344
+ served.add(peer.peerId);
5345
+ return jsonResponse(request.requestId, 200, answer());
5346
+ };
5347
+ for (let i = 0; i < 20; i += 1)
5348
+ await invokeProxy(proxy, modelRequest());
5349
+ assert.deepEqual([...served], [pricier.peerId]);
5350
+ });
5351
+ test('a seller already holding as many of our requests as it advertises it can take is not chosen first', async () => {
5352
+ const clock = makeTestClock();
5353
+ const { cheap, pricier } = makeModelSellers();
5354
+ cheap.maxConcurrency = 1;
5355
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5356
+ const attempts = [];
5357
+ proxy._node.sendRequest = async (peer, request) => {
5358
+ attempts.push(peer.peerId);
5359
+ return jsonResponse(request.requestId, 200, answer());
5360
+ };
5361
+ proxy._inFlightBySeller.set(cheap.peerId, 1);
5362
+ await invokeProxy(proxy, modelRequest());
5363
+ assert.deepEqual(attempts, [pricier.peerId]);
5364
+ assert.equal(proxy._inFlightBySeller.get(pricier.peerId) ?? 0, 0, 'in-flight counts are released');
5365
+ });
5366
+ test('a seller that answers on its own retry after a 429 is not counted against', async () => {
5367
+ const clock = makeTestClock();
5368
+ const { cheap, pricier } = makeModelSellers();
5369
+ const proxy = makeBuyerProxyWithPeers([cheap, pricier], [cheap, pricier], permissiveRouter(), clock.now, priceAndTrustPreferences);
5370
+ const attempts = [];
5371
+ proxy._node.sendRequest = async (peer, request) => {
5372
+ attempts.push(peer.peerId);
5373
+ return attempts.length === 1
5374
+ ? { requestId: request.requestId, statusCode: 429, headers: { 'content-type': 'application/json', 'retry-after': '0' }, body: Buffer.from('{"error":{"message":"busy"}}') }
5375
+ : jsonResponse(request.requestId, 200, answer());
5376
+ };
5377
+ const res = await invokeProxy(proxy, modelRequest());
5378
+ assert.equal(res.statusCode, 200);
5379
+ assert.deepEqual(attempts, [cheap.peerId, cheap.peerId]);
5380
+ const health = sellerHealthOf(proxy, cheap, clock.now());
5381
+ assert.equal(health.successes, 1);
5382
+ assert.equal(health.failures, 0);
5383
+ });
4961
5384
  //# sourceMappingURL=buyer-proxy.test.js.map