@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.
Files changed (45) hide show
  1. package/dist/cli/commands/buyer/daemon.d.ts +3 -0
  2. package/dist/cli/commands/buyer/daemon.d.ts.map +1 -1
  3. package/dist/cli/commands/buyer/daemon.js +1 -0
  4. package/dist/cli/commands/buyer/daemon.js.map +1 -1
  5. package/dist/cli/commands/buyer/start.d.ts.map +1 -1
  6. package/dist/cli/commands/buyer/start.js +20 -4
  7. package/dist/cli/commands/buyer/start.js.map +1 -1
  8. package/dist/cli/commands/seller/start.d.ts.map +1 -1
  9. package/dist/cli/commands/seller/start.js +51 -1
  10. package/dist/cli/commands/seller/start.js.map +1 -1
  11. package/dist/cli/commands/seller/status.d.ts.map +1 -1
  12. package/dist/cli/commands/seller/status.js +4 -0
  13. package/dist/cli/commands/seller/status.js.map +1 -1
  14. package/dist/config/loader.d.ts.map +1 -1
  15. package/dist/config/loader.js +25 -0
  16. package/dist/config/loader.js.map +1 -1
  17. package/dist/config/loader.test.js +28 -0
  18. package/dist/config/loader.test.js.map +1 -1
  19. package/dist/config/types.d.ts +17 -0
  20. package/dist/config/types.d.ts.map +1 -1
  21. package/dist/config/validation.d.ts.map +1 -1
  22. package/dist/config/validation.js +14 -0
  23. package/dist/config/validation.js.map +1 -1
  24. package/dist/proxy/buyer-proxy.d.ts +15 -2
  25. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  26. package/dist/proxy/buyer-proxy.js +92 -21
  27. package/dist/proxy/buyer-proxy.js.map +1 -1
  28. package/dist/proxy/buyer-proxy.test.js +155 -3
  29. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  30. package/dist/proxy/conversation-identity.d.ts.map +1 -1
  31. package/dist/proxy/conversation-identity.js +42 -2
  32. package/dist/proxy/conversation-identity.js.map +1 -1
  33. package/dist/proxy/conversation-identity.test.js +27 -0
  34. package/dist/proxy/conversation-identity.test.js.map +1 -1
  35. package/dist/proxy/routing-parameters.test.js +12 -1
  36. package/dist/proxy/routing-parameters.test.js.map +1 -1
  37. package/dist/proxy/routing.d.ts +8 -0
  38. package/dist/proxy/routing.d.ts.map +1 -1
  39. package/dist/proxy/routing.js +42 -2
  40. package/dist/proxy/routing.js.map +1 -1
  41. package/dist/status/node-status.d.ts +2 -0
  42. package/dist/status/node-status.d.ts.map +1 -1
  43. package/dist/status/node-status.js +5 -0
  44. package/dist/status/node-status.js.map +1 -1
  45. package/package.json +4 -4
@@ -7,7 +7,7 @@ import { Readable } from 'node:stream';
7
7
  import test from 'node:test';
8
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';
9
9
  import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
10
- import { BuyerProxy, isModelNotFoundResponse, makeVerifierReach, mergeJsonStateFile, parsePeerPinnedService, parsePersistedPeers, rewritePeerPinnedServiceInBody, selectCandidatePeersForRouting, substituteRoutedModelAlias, sweepStaleStateTmpFiles, } from './buyer-proxy.js';
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';
12
12
  function makePeer(seed, providers) {
13
13
  const repeated = (seed.repeat(40) + 'a'.repeat(40)).slice(0, 40);
@@ -455,6 +455,98 @@ test('model-only request routes to the highest-ranked canonical service match',
455
455
  assert.equal(selectedPeerId, higher.peerId);
456
456
  assert.equal(selectedBody?.['model'], 'opus-5');
457
457
  });
458
+ test('required parameters filter automatic routes and are stripped before seller dispatch', async () => {
459
+ const unsupported = makePeer('a', ['openai']);
460
+ unsupported.reputationScore = 99;
461
+ unsupported.providerServiceApiProtocols = {
462
+ openai: { services: { 'venice-sd35': ['openai-images'] } },
463
+ };
464
+ unsupported.providerServiceCapabilities = {
465
+ openai: { services: { 'venice-sd35': { outputs: ['image'] } } },
466
+ };
467
+ const supported = makePeer('b', ['openai']);
468
+ supported.reputationScore = 70;
469
+ supported.providerServiceApiProtocols = {
470
+ openai: { services: { 'venice-sd35': ['openai-images'] } },
471
+ };
472
+ supported.providerServiceCapabilities = {
473
+ openai: {
474
+ services: {
475
+ 'venice-sd35': { outputs: ['image'], supportedParameters: ['moderation'] },
476
+ },
477
+ },
478
+ };
479
+ const proxy = makeBuyerProxyWithPeers([unsupported, supported], [unsupported, supported], permissiveRouter());
480
+ let selectedPeerId = '';
481
+ let forwardedHeaders = {};
482
+ proxy._node.sendRequest = async (peer, request) => {
483
+ selectedPeerId = peer.peerId;
484
+ forwardedHeaders = request.headers;
485
+ return {
486
+ requestId: request.requestId,
487
+ statusCode: 200,
488
+ headers: { 'content-type': 'application/json' },
489
+ body: Buffer.from(JSON.stringify({ data: [{ b64_json: 'image' }] })),
490
+ };
491
+ };
492
+ const res = await invokeProxy(proxy, makeProxyRequest({
493
+ path: '/v1/images/generations',
494
+ headers: { 'x-antseed-required-parameters': 'moderation' },
495
+ body: { model: 'venice-sd35', prompt: 'a landscape', moderation: 'low' },
496
+ }));
497
+ assert.equal(res.statusCode, 200);
498
+ assert.equal(selectedPeerId, supported.peerId);
499
+ assert.equal(forwardedHeaders['x-antseed-required-parameters'], undefined);
500
+ });
501
+ test('required parameters fail clearly when no automatic route advertises support', async () => {
502
+ const peer = makePeer('a', ['openai']);
503
+ peer.providerServiceApiProtocols = {
504
+ openai: { services: { 'venice-sd35': ['openai-images'] } },
505
+ };
506
+ peer.providerServiceCapabilities = {
507
+ openai: { services: { 'venice-sd35': { outputs: ['image'] } } },
508
+ };
509
+ const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
510
+ let dispatches = 0;
511
+ proxy._node.sendRequest = async () => {
512
+ dispatches += 1;
513
+ throw new Error('must not dispatch');
514
+ };
515
+ const res = await invokeProxy(proxy, makeProxyRequest({
516
+ path: '/v1/images/generations',
517
+ headers: { 'x-antseed-required-parameters': 'moderation' },
518
+ body: { model: 'venice-sd35', prompt: 'a landscape', moderation: 'low' },
519
+ }));
520
+ assert.equal(res.statusCode, 422);
521
+ assert.equal(JSON.parse(res.body).error.code, 'required_capability_unavailable');
522
+ assert.equal(dispatches, 0);
523
+ });
524
+ test('pinned routes fail clearly when the seller lacks a required parameter', async () => {
525
+ const peer = makePeer('a', ['openai']);
526
+ peer.providerServiceApiProtocols = {
527
+ openai: { services: { 'venice-sd35': ['openai-images'] } },
528
+ };
529
+ peer.providerServiceCapabilities = {
530
+ openai: { services: { 'venice-sd35': { outputs: ['image'] } } },
531
+ };
532
+ const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
533
+ let dispatches = 0;
534
+ proxy._node.sendRequest = async () => {
535
+ dispatches += 1;
536
+ throw new Error('must not dispatch');
537
+ };
538
+ const res = await invokeProxy(proxy, makeProxyRequest({
539
+ path: '/v1/images/generations',
540
+ headers: {
541
+ 'x-antseed-pin-peer': peer.peerId,
542
+ 'x-antseed-required-parameters': 'moderation',
543
+ },
544
+ body: { model: 'venice-sd35', prompt: 'a landscape', moderation: 'low' },
545
+ }));
546
+ assert.equal(res.statusCode, 422);
547
+ assert.equal(JSON.parse(res.body).error.code, 'required_capability_unavailable');
548
+ assert.equal(dispatches, 0);
549
+ });
458
550
  test('conversation routing keeps the actual peer as a soft preference and fails over when needed', async () => {
459
551
  const preferred = makePeer('c', ['openai']);
460
552
  preferred.reputationScore = 70;
@@ -2544,7 +2636,67 @@ test('deposits/status reports no watcher until one is attached', async () => {
2544
2636
  });
2545
2637
  const res = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/deposits/status' }));
2546
2638
  assert.equal(res.statusCode, 200);
2547
- assert.deepEqual(JSON.parse(res.body), { ok: true, watcher: false, status: null });
2639
+ assert.deepEqual(JSON.parse(res.body), { ok: true, watcher: false, reason: null, payments: null, status: null });
2640
+ });
2641
+ test('sanitizePeerBuyerFaultMarker scrubs the marker at any nesting depth', () => {
2642
+ let deeplyNested = {
2643
+ code: ANTSEED_BUYER_FAULT_ERROR_CODE,
2644
+ message: 'seller-controlled message',
2645
+ };
2646
+ for (let depth = 0; depth < 20; depth += 1) {
2647
+ deeplyNested = { inner: deeplyNested };
2648
+ }
2649
+ const body = {
2650
+ error: {
2651
+ type: 'server_error',
2652
+ details: {
2653
+ code: ANTSEED_BUYER_FAULT_ERROR_CODE,
2654
+ inner: [{ errorCode: ANTSEED_BUYER_FAULT_ERROR_CODE }, deeplyNested],
2655
+ },
2656
+ },
2657
+ };
2658
+ const sanitized = sanitizePeerBuyerFaultMarker({
2659
+ requestId: 'r1',
2660
+ statusCode: 503,
2661
+ headers: { 'content-type': 'application/json' },
2662
+ body: new TextEncoder().encode(JSON.stringify(body)),
2663
+ });
2664
+ const parsed = JSON.parse(Buffer.from(sanitized.body).toString('utf-8'));
2665
+ assert.equal(parsed.error.details.code, 'upstream_error');
2666
+ assert.equal(parsed.error.details.inner[0].errorCode, 'upstream_error');
2667
+ let nested = parsed.error.details.inner[1];
2668
+ for (let depth = 0; depth < 20; depth += 1) {
2669
+ nested = nested.inner;
2670
+ }
2671
+ assert.equal(nested.code, 'upstream_error');
2672
+ assert.equal(nested.message, 'seller-controlled message');
2673
+ });
2674
+ test('deposits/status reports the recorded watcher-absence reason and payments health', async () => {
2675
+ const paymentsStatus = {
2676
+ configured: true,
2677
+ buyerActive: true,
2678
+ sellerActive: false,
2679
+ chainId: 8453,
2680
+ rpc: { state: 'unreachable', lastCheckedAt: 123, lastReadyAt: null, lastError: 'probe failed', attempts: 2 },
2681
+ };
2682
+ const proxy = new BuyerProxy({
2683
+ port: 0,
2684
+ dataDir: '/tmp/antseed-test',
2685
+ node: { router: null, getPaymentsStatus: () => paymentsStatus },
2686
+ });
2687
+ proxy.setDepositWatcher(null, 'payments-disabled');
2688
+ const res = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/deposits/status' }));
2689
+ assert.equal(res.statusCode, 200);
2690
+ const body = JSON.parse(res.body);
2691
+ assert.equal(body.watcher, false);
2692
+ assert.equal(body.reason, 'payments-disabled');
2693
+ assert.deepEqual(body.payments, paymentsStatus);
2694
+ const watchRes = await invokeProxy(proxy, makeProxyRequest({ path: '/_antseed/deposits/watch', body: { mode: 'active' } }));
2695
+ assert.equal(watchRes.statusCode, 503);
2696
+ const watchBody = JSON.parse(watchRes.body);
2697
+ assert.equal(watchBody.ok, false);
2698
+ assert.equal(watchBody.reason, 'payments-disabled');
2699
+ assert.match(watchBody.error, /payments are disabled/);
2548
2700
  });
2549
2701
  test('deposits/watch returns 503 when no watcher is attached', async () => {
2550
2702
  const proxy = new BuyerProxy({
@@ -2585,7 +2737,7 @@ test('deposits/watch promotes and demotes an attached watcher and returns its st
2585
2737
  assert.equal(invalid.statusCode, 400);
2586
2738
  assert.deepEqual(calls, ['promote', 'demote']);
2587
2739
  const status = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/deposits/status' }));
2588
- assert.deepEqual(JSON.parse(status.body), { ok: true, watcher: true, status: fakeStatus });
2740
+ assert.deepEqual(JSON.parse(status.body), { ok: true, watcher: true, reason: null, payments: null, status: fakeStatus });
2589
2741
  });
2590
2742
  test('getSweepReceipt returns cached relayer receipts case-insensitively', () => {
2591
2743
  const proxy = new BuyerProxy({