@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
|
@@ -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({
|