@antseed/cli 0.1.196 → 0.1.198
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/start.d.ts.map +1 -1
- package/dist/cli/commands/buyer/start.js +1 -0
- package/dist/cli/commands/buyer/start.js.map +1 -1
- package/dist/cli/commands/gateway/activity.d.ts.map +1 -1
- package/dist/cli/commands/gateway/activity.js +26 -5
- package/dist/cli/commands/gateway/activity.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/gateway/accounting.d.ts +118 -6
- package/dist/gateway/accounting.d.ts.map +1 -1
- package/dist/gateway/accounting.js +319 -21
- package/dist/gateway/accounting.js.map +1 -1
- package/dist/gateway/console-api/handlers/favorites.d.ts +9 -0
- package/dist/gateway/console-api/handlers/favorites.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/favorites.js +44 -0
- package/dist/gateway/console-api/handlers/favorites.js.map +1 -0
- package/dist/gateway/console-api/handlers/favorites.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/favorites.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/favorites.test.js +41 -0
- package/dist/gateway/console-api/handlers/favorites.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/index.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/index.js +2 -0
- package/dist/gateway/console-api/handlers/index.js.map +1 -1
- package/dist/gateway/console-api/handlers/network-mapping.d.ts +7 -1
- package/dist/gateway/console-api/handlers/network-mapping.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/network-mapping.js +46 -0
- package/dist/gateway/console-api/handlers/network-mapping.js.map +1 -1
- package/dist/gateway/console-api/handlers/network.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/network.js +19 -3
- package/dist/gateway/console-api/handlers/network.js.map +1 -1
- package/dist/gateway/console-api/handlers/network.test.js +70 -8
- package/dist/gateway/console-api/handlers/network.test.js.map +1 -1
- package/dist/gateway/console-api/handlers/usage.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/usage.js +2 -1
- package/dist/gateway/console-api/handlers/usage.js.map +1 -1
- package/dist/gateway/console-api/types.d.ts +75 -1
- package/dist/gateway/console-api/types.d.ts.map +1 -1
- package/dist/gateway/console-api/types.js.map +1 -1
- package/dist/gateway/gateway.test.js +829 -3
- package/dist/gateway/gateway.test.js.map +1 -1
- package/dist/gateway/request-errors.test.js +30 -0
- package/dist/gateway/request-errors.test.js.map +1 -1
- package/dist/gateway/routing-through-gateway.test.js +120 -4
- package/dist/gateway/routing-through-gateway.test.js.map +1 -1
- package/dist/gateway/runtime.d.ts.map +1 -1
- package/dist/gateway/runtime.js +4 -0
- package/dist/gateway/runtime.js.map +1 -1
- package/dist/gateway/server.d.ts +20 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +181 -25
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/services/network.d.ts +6 -1
- package/dist/gateway/services/network.d.ts.map +1 -1
- package/dist/gateway/services/network.js +12 -1
- package/dist/gateway/services/network.js.map +1 -1
- package/dist/gateway/services/usage.d.ts +2 -0
- package/dist/gateway/services/usage.d.ts.map +1 -1
- package/dist/gateway/services/usage.js +19 -5
- package/dist/gateway/services/usage.js.map +1 -1
- package/dist/gateway/spend-feed.d.ts +4 -1
- package/dist/gateway/spend-feed.d.ts.map +1 -1
- package/dist/gateway/spend-feed.js +7 -0
- package/dist/gateway/spend-feed.js.map +1 -1
- package/dist/gateway/store-data.test.js +1 -1
- package/dist/gateway/store-data.test.js.map +1 -1
- package/dist/gateway/store-migrations.d.ts.map +1 -1
- package/dist/gateway/store-migrations.js +40 -0
- package/dist/gateway/store-migrations.js.map +1 -1
- package/dist/gateway/store.d.ts +63 -1
- package/dist/gateway/store.d.ts.map +1 -1
- package/dist/gateway/store.js +142 -21
- package/dist/gateway/store.js.map +1 -1
- package/dist/gateway/video-jobs.d.ts +12 -0
- package/dist/gateway/video-jobs.d.ts.map +1 -0
- package/dist/gateway/video-jobs.js +24 -0
- package/dist/gateway/video-jobs.js.map +1 -0
- package/dist/proxy/buyer-proxy.d.ts +15 -0
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +166 -19
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +158 -1
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/gateway-policy.test.js +75 -1
- package/dist/proxy/gateway-policy.test.js.map +1 -1
- package/dist/proxy/request-utils.d.ts +4 -0
- package/dist/proxy/request-utils.d.ts.map +1 -1
- package/dist/proxy/request-utils.js +4 -0
- package/dist/proxy/request-utils.js.map +1 -1
- package/dist/proxy/resource-routes.d.ts +2 -0
- package/dist/proxy/resource-routes.d.ts.map +1 -1
- package/dist/proxy/resource-routes.js +4 -0
- package/dist/proxy/resource-routes.js.map +1 -1
- package/dist/proxy/route-selection.d.ts +79 -0
- package/dist/proxy/route-selection.d.ts.map +1 -0
- package/dist/proxy/route-selection.js +79 -0
- package/dist/proxy/route-selection.js.map +1 -0
- package/dist/proxy/spend-attribution.d.ts +17 -1
- package/dist/proxy/spend-attribution.d.ts.map +1 -1
- package/dist/proxy/spend-attribution.js +45 -10
- package/dist/proxy/spend-attribution.js.map +1 -1
- package/dist/proxy/spend-attribution.test.d.ts +2 -0
- package/dist/proxy/spend-attribution.test.d.ts.map +1 -0
- package/dist/proxy/spend-attribution.test.js +63 -0
- package/dist/proxy/spend-attribution.test.js.map +1 -0
- package/dist/proxy/telemetry.d.ts +15 -0
- package/dist/proxy/telemetry.d.ts.map +1 -1
- package/dist/proxy/telemetry.js +26 -3
- package/dist/proxy/telemetry.js.map +1 -1
- package/dist/proxy/video-download.d.ts +9 -1
- package/dist/proxy/video-download.d.ts.map +1 -1
- package/dist/proxy/video-download.js +41 -6
- package/dist/proxy/video-download.js.map +1 -1
- package/dist/proxy/video-download.test.js +33 -3
- package/dist/proxy/video-download.test.js.map +1 -1
- package/package.json +7 -7
|
@@ -1453,6 +1453,157 @@ test('native video retrieves use the buyer identity that created the job', async
|
|
|
1453
1453
|
{ path: '/api/v1/video/retrieve', identity: 'team-a' },
|
|
1454
1454
|
]);
|
|
1455
1455
|
});
|
|
1456
|
+
test('a video create passes the caller spend cap to the buyer and never to the seller', async () => {
|
|
1457
|
+
const peer = makePeer('a', ['venice']);
|
|
1458
|
+
peer.providerServiceApiProtocols = { venice: { services: { 'video-model': ['venice-video'] } } };
|
|
1459
|
+
const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
|
|
1460
|
+
proxy._persistResourceRoutes = async () => { };
|
|
1461
|
+
const sent = [];
|
|
1462
|
+
proxy._node.sendRequest = async (_peer, request, options) => {
|
|
1463
|
+
sent.push({ path: request.path, cap: options?.maxVideoCostUsdc, header: request.headers['x-antseed-max-video-usdc'] });
|
|
1464
|
+
return { requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json', 'x-antseed-video-price-usdc': '650000' }, body: Buffer.from(JSON.stringify({ queue_id: 'job' })) };
|
|
1465
|
+
};
|
|
1466
|
+
for (const cap of ['1500000', 'lots']) {
|
|
1467
|
+
const queued = await invokeProxy(proxy, makeProxyRequest({ path: '/api/v1/video/queue', body: { model: 'video-model', prompt: 'cat', duration: '5s' }, headers: { 'x-antseed-max-video-usdc': cap } }));
|
|
1468
|
+
assert.equal(queued.statusCode, 200, queued.body);
|
|
1469
|
+
// The accepted job's price reaches the caller, so a gateway can keep it reserved until delivery.
|
|
1470
|
+
assert.equal(queued.headers['x-antseed-video-price-usdc'], '650000');
|
|
1471
|
+
}
|
|
1472
|
+
assert.deepEqual(sent, [
|
|
1473
|
+
{ path: '/api/v1/video/queue', cap: 1500000n, header: undefined },
|
|
1474
|
+
{ path: '/api/v1/video/queue', cap: undefined, header: undefined },
|
|
1475
|
+
]);
|
|
1476
|
+
});
|
|
1477
|
+
test('an unknown video job is not said to be retired while the saved routes could not be read', async () => {
|
|
1478
|
+
const peer = makePeer('a', ['venice']);
|
|
1479
|
+
peer.providerServiceApiProtocols = { venice: { services: { 'video-model': ['venice-video'] } } };
|
|
1480
|
+
const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
|
|
1481
|
+
const retrieve = () => invokeProxy(proxy, makeProxyRequest({ method: 'POST', path: '/api/v1/video/retrieve', body: { model: 'video-model', queue_id: 'job' } }));
|
|
1482
|
+
const known = await retrieve();
|
|
1483
|
+
assert.equal(known.statusCode, 404);
|
|
1484
|
+
assert.equal(known.headers['x-antseed-video-job-retired'], '1', 'with the routes read, an unknown job can never be charged');
|
|
1485
|
+
proxy._resourceRoutesUnread = true;
|
|
1486
|
+
const unread = await retrieve();
|
|
1487
|
+
assert.equal(unread.statusCode, 404);
|
|
1488
|
+
assert.equal(JSON.parse(unread.body).error.code, 'video_route_not_found');
|
|
1489
|
+
assert.equal(unread.headers['x-antseed-video-job-retired'], undefined);
|
|
1490
|
+
});
|
|
1491
|
+
test('a video job that reports failed is retired: later retrieves never reach the seller, so it cannot be charged', async () => {
|
|
1492
|
+
for (const upstream of [{ status: 'FAILED' }, { status: 'COMPLETED' }]) {
|
|
1493
|
+
const peer = makePeer('a', ['venice']);
|
|
1494
|
+
peer.providerServiceApiProtocols = { venice: { services: { 'video-model': ['venice-video'] } } };
|
|
1495
|
+
const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
|
|
1496
|
+
let persisted = 0;
|
|
1497
|
+
proxy._persistResourceRoutes = async () => { persisted += 1; };
|
|
1498
|
+
proxy._node.sendRequest = async (_peer, request) => ({ requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ queue_id: 'job' })) });
|
|
1499
|
+
let retrieves = 0;
|
|
1500
|
+
proxy._node.sendRequestStream = async (_peer, request) => {
|
|
1501
|
+
retrieves += 1;
|
|
1502
|
+
return { requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify(upstream)) };
|
|
1503
|
+
};
|
|
1504
|
+
assert.equal((await invokeProxy(proxy, makeProxyRequest({ path: '/api/v1/video/queue', body: { model: 'video-model', prompt: 'cat', duration: '5s' } }))).statusCode, 200);
|
|
1505
|
+
const retrieve = () => invokeProxy(proxy, makeProxyRequest({ method: 'POST', path: '/api/v1/video/retrieve', body: { model: 'video-model', queue_id: 'job' } }));
|
|
1506
|
+
const failed = await retrieve();
|
|
1507
|
+
assert.equal(failed.statusCode, 200);
|
|
1508
|
+
assert.equal(JSON.parse(failed.body).status, 'failed');
|
|
1509
|
+
assert.equal(failed.headers['x-antseed-video-job-retired'], '1', 'a gateway may release the job');
|
|
1510
|
+
const after = await retrieve();
|
|
1511
|
+
assert.equal(after.statusCode, 404, after.body);
|
|
1512
|
+
assert.equal(JSON.parse(after.body).error.code, 'video_route_not_found');
|
|
1513
|
+
assert.equal(after.headers['x-antseed-video-job-retired'], '1');
|
|
1514
|
+
assert.equal(retrieves, 1, 'the retired job is never sent to the seller again');
|
|
1515
|
+
assert.equal(persisted, 2, 'the route is saved when accepted and again when retired');
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
function failingVideoProxy(persist, answer) {
|
|
1519
|
+
const peer = makePeer('a', ['venice']);
|
|
1520
|
+
peer.providerServiceApiProtocols = { venice: { services: { 'video-model': ['venice-video'] } } };
|
|
1521
|
+
const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
|
|
1522
|
+
proxy._persistResourceRoutes = persist;
|
|
1523
|
+
proxy._node.sendRequest = async (_peer, request) => ({ requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ queue_id: 'job' })) });
|
|
1524
|
+
const calls = { retrieves: 0 };
|
|
1525
|
+
proxy._node.sendRequestStream = async (_peer, request) => {
|
|
1526
|
+
const body = await answer(++calls.retrieves);
|
|
1527
|
+
return { requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify(body)) };
|
|
1528
|
+
};
|
|
1529
|
+
const create = () => invokeProxy(proxy, makeProxyRequest({ path: '/api/v1/video/queue', body: { model: 'video-model', prompt: 'cat', duration: '5s' } }));
|
|
1530
|
+
const retrieve = () => invokeProxy(proxy, makeProxyRequest({ method: 'POST', path: '/api/v1/video/retrieve', body: { model: 'video-model', queue_id: 'job' } }));
|
|
1531
|
+
return { create, retrieve, calls };
|
|
1532
|
+
}
|
|
1533
|
+
test('a retrieve queued behind one that retires the job as failed never reaches the seller', async () => {
|
|
1534
|
+
const video = failingVideoProxy(async () => { }, async (call) => {
|
|
1535
|
+
if (call === 1)
|
|
1536
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
1537
|
+
return { status: 'FAILED' };
|
|
1538
|
+
});
|
|
1539
|
+
assert.equal((await video.create()).statusCode, 200);
|
|
1540
|
+
const [first, second] = await Promise.all([video.retrieve(), video.retrieve()]);
|
|
1541
|
+
assert.equal(JSON.parse(first.body).status, 'failed');
|
|
1542
|
+
assert.equal(second.statusCode, 404, second.body);
|
|
1543
|
+
assert.equal(video.calls.retrieves, 1);
|
|
1544
|
+
});
|
|
1545
|
+
test('a retrieve that arrives while a retirement is being saved waits, and keeps the job if the save fails', async () => {
|
|
1546
|
+
let rejectWrite = () => { };
|
|
1547
|
+
let writes = 0;
|
|
1548
|
+
const video = failingVideoProxy(() => {
|
|
1549
|
+
writes += 1;
|
|
1550
|
+
// The create's write succeeds; the retirement's write is held, then fails.
|
|
1551
|
+
return writes === 1 ? Promise.resolve() : new Promise((_resolve, reject) => { rejectWrite = reject; });
|
|
1552
|
+
}, async (call) => (call === 1 ? { status: 'FAILED' } : { status: 'PROCESSING' }));
|
|
1553
|
+
assert.equal((await video.create()).statusCode, 200);
|
|
1554
|
+
const first = video.retrieve();
|
|
1555
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1556
|
+
const second = video.retrieve();
|
|
1557
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1558
|
+
rejectWrite(new Error('disk full'));
|
|
1559
|
+
const [a, b] = await Promise.all([first, second]);
|
|
1560
|
+
// Neither answer says the job is gone, so a gateway keeps it held; the second poll reached the seller.
|
|
1561
|
+
assert.deepEqual([JSON.parse(a.body).status, JSON.parse(b.body).status], ['unknown', 'processing']);
|
|
1562
|
+
assert.equal(a.headers['x-antseed-video-job-retired'], undefined);
|
|
1563
|
+
assert.equal(b.headers['x-antseed-video-job-retired'], undefined);
|
|
1564
|
+
assert.equal(video.calls.retrieves, 2);
|
|
1565
|
+
});
|
|
1566
|
+
test('a job its seller no longer has is retired; a status still processing is not', async () => {
|
|
1567
|
+
const peer = makePeer('a', ['venice']);
|
|
1568
|
+
peer.providerServiceApiProtocols = { venice: { services: { 'video-model': ['venice-video'] } } };
|
|
1569
|
+
const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
|
|
1570
|
+
proxy._persistResourceRoutes = async () => { };
|
|
1571
|
+
proxy._node.sendRequest = async (_peer, request) => ({ requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ queue_id: 'job' })) });
|
|
1572
|
+
const answers = [
|
|
1573
|
+
// A seller cannot release a job by naming the buyer's own code, or by setting the buyer's marker: only the buyer's retirement counts.
|
|
1574
|
+
{ statusCode: 200, headers: { 'content-type': 'application/json', 'X-Antseed-Video-Job-Retired': '1' }, body: { status: 'PROCESSING' } },
|
|
1575
|
+
{ statusCode: 404, headers: { 'content-type': 'application/json' }, body: { error: { code: 'video_route_not_found' } } },
|
|
1576
|
+
];
|
|
1577
|
+
proxy._node.sendRequestStream = async (_peer, request) => {
|
|
1578
|
+
const answer = answers.shift();
|
|
1579
|
+
return { requestId: request.requestId, statusCode: answer.statusCode, headers: answer.headers, body: Buffer.from(JSON.stringify(answer.body)) };
|
|
1580
|
+
};
|
|
1581
|
+
assert.equal((await invokeProxy(proxy, makeProxyRequest({ path: '/api/v1/video/queue', body: { model: 'video-model', prompt: 'cat', duration: '5s' } }))).statusCode, 200);
|
|
1582
|
+
const retrieve = () => invokeProxy(proxy, makeProxyRequest({ method: 'POST', path: '/api/v1/video/retrieve', body: { model: 'video-model', queue_id: 'job' } }));
|
|
1583
|
+
const processing = await retrieve();
|
|
1584
|
+
assert.equal(processing.headers['x-antseed-video-job-retired'], undefined);
|
|
1585
|
+
const gone = await retrieve();
|
|
1586
|
+
assert.equal(gone.statusCode, 404);
|
|
1587
|
+
assert.equal(gone.headers['x-antseed-video-job-retired'], '1', 'the buyer retired it: the seller no longer has it');
|
|
1588
|
+
assert.equal((await retrieve()).statusCode, 404);
|
|
1589
|
+
assert.equal(answers.length, 0, 'the retired job is not sent to the seller again');
|
|
1590
|
+
});
|
|
1591
|
+
test('a failed job whose retirement cannot be saved is reported as unknown and kept', async () => {
|
|
1592
|
+
let failWrites = false;
|
|
1593
|
+
const video = failingVideoProxy(async () => { if (failWrites)
|
|
1594
|
+
throw new Error('disk full'); }, async () => ({ status: 'FAILED' }));
|
|
1595
|
+
assert.equal((await video.create()).statusCode, 200);
|
|
1596
|
+
failWrites = true;
|
|
1597
|
+
const unsaved = await video.retrieve();
|
|
1598
|
+
assert.equal(unsaved.statusCode, 200);
|
|
1599
|
+
assert.deepEqual([JSON.parse(unsaved.body).status, JSON.parse(unsaved.body).retryable], ['unknown', true]);
|
|
1600
|
+
assert.equal(unsaved.headers['x-antseed-video-job-retired'], undefined, 'not retired, so a gateway keeps the job held');
|
|
1601
|
+
// The route was put back, so the job is still polled (and retired once the write succeeds).
|
|
1602
|
+
failWrites = false;
|
|
1603
|
+
assert.equal(JSON.parse((await video.retrieve()).body).status, 'failed');
|
|
1604
|
+
assert.equal((await video.retrieve()).statusCode, 404);
|
|
1605
|
+
assert.equal(video.calls.retrieves, 2);
|
|
1606
|
+
});
|
|
1456
1607
|
test('native resource follow-ups fail when the recorded provider loses support instead of switching providers', async () => {
|
|
1457
1608
|
for (const provider of ['venice']) {
|
|
1458
1609
|
const protocol = 'venice-video';
|
|
@@ -1562,6 +1713,8 @@ function makeProxyRequest(options) {
|
|
|
1562
1713
|
return req;
|
|
1563
1714
|
}
|
|
1564
1715
|
function makeProxyResponse() {
|
|
1716
|
+
// Headers set before writeHead merge into it, as on a Node ServerResponse.
|
|
1717
|
+
const pending = {};
|
|
1565
1718
|
return {
|
|
1566
1719
|
statusCode: 0,
|
|
1567
1720
|
headers: {},
|
|
@@ -1570,10 +1723,14 @@ function makeProxyResponse() {
|
|
|
1570
1723
|
writableEnded: false,
|
|
1571
1724
|
writeHead(statusCode, headers) {
|
|
1572
1725
|
this.statusCode = statusCode;
|
|
1573
|
-
this.headers = headers;
|
|
1726
|
+
this.headers = { ...pending, ...headers };
|
|
1574
1727
|
this.headersSent = true;
|
|
1575
1728
|
return this;
|
|
1576
1729
|
},
|
|
1730
|
+
setHeader(name, value) {
|
|
1731
|
+
pending[name.toLowerCase()] = value;
|
|
1732
|
+
return this;
|
|
1733
|
+
},
|
|
1577
1734
|
write(chunk) {
|
|
1578
1735
|
this.body += Buffer.from(chunk).toString('utf8');
|
|
1579
1736
|
return true;
|