@antseed/cli 0.1.190 → 0.1.192

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 (68) hide show
  1. package/README.md +9 -0
  2. package/dist/cli/commands/seller/start.d.ts.map +1 -1
  3. package/dist/cli/commands/seller/start.js +2 -1
  4. package/dist/cli/commands/seller/start.js.map +1 -1
  5. package/dist/cli/index.js +3 -5
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/config/service-metadata.test.js +4 -0
  8. package/dist/config/service-metadata.test.js.map +1 -1
  9. package/dist/gateway/gateway-policy.test.js +19 -0
  10. package/dist/gateway/gateway-policy.test.js.map +1 -1
  11. package/dist/gateway/routing-through-gateway.test.d.ts +2 -0
  12. package/dist/gateway/routing-through-gateway.test.d.ts.map +1 -0
  13. package/dist/gateway/routing-through-gateway.test.js +177 -0
  14. package/dist/gateway/routing-through-gateway.test.js.map +1 -0
  15. package/dist/plugins/registry.d.ts.map +1 -1
  16. package/dist/plugins/registry.js +12 -0
  17. package/dist/plugins/registry.js.map +1 -1
  18. package/dist/proxy/buyer-proxy.d.ts +62 -4
  19. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  20. package/dist/proxy/buyer-proxy.js +514 -120
  21. package/dist/proxy/buyer-proxy.js.map +1 -1
  22. package/dist/proxy/buyer-proxy.test.js +1347 -5
  23. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  24. package/dist/proxy/conversation-store.d.ts +12 -0
  25. package/dist/proxy/conversation-store.d.ts.map +1 -1
  26. package/dist/proxy/conversation-store.js +30 -0
  27. package/dist/proxy/conversation-store.js.map +1 -1
  28. package/dist/proxy/conversation-store.test.js +69 -1
  29. package/dist/proxy/conversation-store.test.js.map +1 -1
  30. package/dist/proxy/gateway-policy.test.js +110 -0
  31. package/dist/proxy/gateway-policy.test.js.map +1 -1
  32. package/dist/proxy/network-models.d.ts +27 -1
  33. package/dist/proxy/network-models.d.ts.map +1 -1
  34. package/dist/proxy/network-models.js +2 -0
  35. package/dist/proxy/network-models.js.map +1 -1
  36. package/dist/proxy/request-utils.d.ts +21 -1
  37. package/dist/proxy/request-utils.d.ts.map +1 -1
  38. package/dist/proxy/request-utils.js +24 -1
  39. package/dist/proxy/request-utils.js.map +1 -1
  40. package/dist/proxy/route-policy.d.ts +7 -0
  41. package/dist/proxy/route-policy.d.ts.map +1 -1
  42. package/dist/proxy/route-policy.js +10 -0
  43. package/dist/proxy/route-policy.js.map +1 -1
  44. package/dist/proxy/router-execution.d.ts +63 -0
  45. package/dist/proxy/router-execution.d.ts.map +1 -0
  46. package/dist/proxy/router-execution.js +222 -0
  47. package/dist/proxy/router-execution.js.map +1 -0
  48. package/dist/proxy/router-execution.test.d.ts +2 -0
  49. package/dist/proxy/router-execution.test.d.ts.map +1 -0
  50. package/dist/proxy/router-execution.test.js +269 -0
  51. package/dist/proxy/router-execution.test.js.map +1 -0
  52. package/dist/proxy/routing-services.d.ts +48 -0
  53. package/dist/proxy/routing-services.d.ts.map +1 -0
  54. package/dist/proxy/routing-services.js +66 -0
  55. package/dist/proxy/routing-services.js.map +1 -0
  56. package/dist/proxy/routing-services.test.d.ts +2 -0
  57. package/dist/proxy/routing-services.test.d.ts.map +1 -0
  58. package/dist/proxy/routing-services.test.js +95 -0
  59. package/dist/proxy/routing-services.test.js.map +1 -0
  60. package/dist/proxy/routing-usage.d.ts +4 -0
  61. package/dist/proxy/routing-usage.d.ts.map +1 -0
  62. package/dist/proxy/routing-usage.js +19 -0
  63. package/dist/proxy/routing-usage.js.map +1 -0
  64. package/dist/proxy/routing-usage.test.d.ts +2 -0
  65. package/dist/proxy/routing-usage.test.d.ts.map +1 -0
  66. package/dist/proxy/routing-usage.test.js +32 -0
  67. package/dist/proxy/routing-usage.test.js.map +1 -0
  68. package/package.json +10 -8
@@ -0,0 +1,48 @@
1
+ import { type AntseedNode, type PeerInfo, type RoutingProtocol, type RoutingServiceTarget } from '@antseed/node';
2
+ import { type ModelRoutingClientApi } from '@antseed/router-core';
3
+ import { RoutingModelsCache } from './router-execution.js';
4
+ type PeerProvider = NonNullable<PeerInfo['metadata']>['providers'][number];
5
+ /**
6
+ * The routing services a peer advertises, of any routing protocol. A protocol whose requests
7
+ * name no service is skipped when the peer advertises more than one service of it (buyers
8
+ * cannot address them).
9
+ */
10
+ export declare function routingServicesOf(peer: PeerInfo): Array<{
11
+ provider: PeerProvider;
12
+ serviceId: string;
13
+ protocol: RoutingProtocol;
14
+ }>;
15
+ /** The routing service a `<peerId>@<serviceId>` pin names, when that peer advertises it as a routing service. */
16
+ export declare function findPinnedRoutingService(peers: PeerInfo[], peerId: string, serviceId: string): RoutingServiceTarget | null;
17
+ export declare function buildRoutingServices(peers: PeerInfo[], client: ModelRoutingClientApi, node: Pick<AntseedNode, 'sendRequest'>, modelsCache?: RoutingModelsCache): Promise<({
18
+ catalog: {
19
+ models: {
20
+ provider: string;
21
+ serviceId: string;
22
+ }[];
23
+ };
24
+ catalogExpiresAt: number;
25
+ catalogError: undefined;
26
+ protocol: "irp-suggest";
27
+ options: readonly import("@antseed/router-core").RouterOptionSpec[];
28
+ label: string;
29
+ sellerName: string;
30
+ priceMicroUsdc: string;
31
+ peerId: import("@antseed/node").PeerId;
32
+ provider: string;
33
+ serviceId: string;
34
+ } | {
35
+ catalog: undefined;
36
+ catalogExpiresAt: undefined;
37
+ catalogError: string;
38
+ protocol: "irp-suggest";
39
+ options: readonly import("@antseed/router-core").RouterOptionSpec[];
40
+ label: string;
41
+ sellerName: string;
42
+ priceMicroUsdc: string;
43
+ peerId: import("@antseed/node").PeerId;
44
+ provider: string;
45
+ serviceId: string;
46
+ })[]>;
47
+ export {};
48
+ //# sourceMappingURL=routing-services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-services.d.ts","sourceRoot":"","sources":["../../src/proxy/routing-services.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiI,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAC/O,OAAO,EAA+D,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC9H,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAE1D,KAAK,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1E;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IAAE,QAAQ,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,CAAC,CAOjI;AAED,iHAAiH;AACjH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAM1H;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,qBAAqB,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,WAAW,qBAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6B1K"}
@@ -0,0 +1,66 @@
1
+ import { buildNetworkServiceOffers, evaluateUnitBilling, routingProtocolOf, routingWireProtocol, validateUnitBillingModelForProtocolV1 } from '@antseed/node';
2
+ import { candidatePricing, routerModelResolver, routingClientAdapter } from '@antseed/router-core';
3
+ import { RoutingModelsCache } from './router-execution.js';
4
+ /**
5
+ * The routing services a peer advertises, of any routing protocol. A protocol whose requests
6
+ * name no service is skipped when the peer advertises more than one service of it (buyers
7
+ * cannot address them).
8
+ */
9
+ export function routingServicesOf(peer) {
10
+ const all = (peer.metadata?.providers ?? []).flatMap(provider => provider.services.flatMap(serviceId => {
11
+ const protocol = routingProtocolOf(provider.serviceApiProtocols?.[serviceId]);
12
+ return protocol ? [{ provider, serviceId, protocol }] : [];
13
+ }));
14
+ return all.filter(entry => !routingWireProtocol(entry.protocol).oneServicePerSeller
15
+ || all.filter(other => other.protocol === entry.protocol).length === 1);
16
+ }
17
+ /** The routing service a `<peerId>@<serviceId>` pin names, when that peer advertises it as a routing service. */
18
+ export function findPinnedRoutingService(peers, peerId, serviceId) {
19
+ const peer = peers.find(candidate => candidate.peerId.toLowerCase() === peerId.toLowerCase());
20
+ const wanted = serviceId.trim().toLowerCase();
21
+ const routing = peer ? routingServicesOf(peer).find(entry => entry.serviceId.trim().toLowerCase() === wanted) : undefined;
22
+ if (!peer || !routing)
23
+ return null;
24
+ return { peerId: peer.peerId, provider: routing.provider.provider, serviceId: routing.serviceId };
25
+ }
26
+ export async function buildRoutingServices(peers, client, node, modelsCache = new RoutingModelsCache()) {
27
+ const offers = buildNetworkServiceOffers(peers);
28
+ const services = peers.flatMap(peer => routingServicesOf(peer).flatMap(({ provider, serviceId, protocol }) => {
29
+ try {
30
+ const target = { peerId: peer.peerId, provider: provider.provider, serviceId };
31
+ const model = provider.serviceUnitBillingModels?.[serviceId]?.[protocol];
32
+ if (!model || validateUnitBillingModelForProtocolV1(protocol, model).length)
33
+ return [];
34
+ const price = evaluateUnitBilling(model, { sellerPeerId: peer.peerId, provider: provider.provider, service: serviceId, serviceApiProtocol: protocol }, { units: { completed_requests: 1 } });
35
+ return [{ peer, target, protocol, priceMicroUsdc: price.toString() }];
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }));
41
+ return Promise.all(services.map(async ({ peer, target, protocol, priceMicroUsdc }) => {
42
+ const service = {
43
+ ...target, protocol,
44
+ // The settings this router's protocol accepts with each ranking, for hosts to render.
45
+ options: routingClientAdapter(protocol).options,
46
+ label: peer.displayName || target.provider, sellerName: peer.displayName || target.provider, priceMicroUsdc,
47
+ };
48
+ try {
49
+ const resolveModel = routerModelResolver(await modelsCache.get(client, target, peers, node));
50
+ const models = [...new Map(offers.filter(offer => offer.type === 'text' && resolveModel(offer.serviceId, offerPricing(offer)))
51
+ .map(({ provider, serviceId }) => [JSON.stringify([provider, serviceId]), { provider, serviceId }])).values()];
52
+ return { ...service, catalog: { models }, catalogExpiresAt: modelsCache.expiresAt(target), catalogError: undefined };
53
+ }
54
+ catch (error) {
55
+ return { ...service, catalog: undefined, catalogExpiresAt: undefined, catalogError: error instanceof Error ? error.message : 'Router models unavailable' };
56
+ }
57
+ }));
58
+ }
59
+ /** Candidate pricing for an offer, when it has token prices; the router's price limits only apply to priced offers. */
60
+ function offerPricing(offer) {
61
+ if (offer.inputUsdPerMillion === undefined || offer.outputUsdPerMillion === undefined)
62
+ return undefined;
63
+ return candidatePricing({ inputUsdPerMillion: offer.inputUsdPerMillion, outputUsdPerMillion: offer.outputUsdPerMillion,
64
+ ...(offer.cachedInputUsdPerMillion !== undefined ? { cachedInputUsdPerMillion: offer.cachedInputUsdPerMillion } : {}) });
65
+ }
66
+ //# sourceMappingURL=routing-services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-services.js","sourceRoot":"","sources":["../../src/proxy/routing-services.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routing-services.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-services.test.d.ts","sourceRoot":"","sources":["../../src/proxy/routing-services.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,95 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { ModelRoutingClient } from '@antseed/router-core';
4
+ import { buildRoutingServices } from './routing-services.js';
5
+ import { RoutingModelsCache } from './router-execution.js';
6
+ const node = { sendRequest: async () => { throw new Error('unused'); } };
7
+ const client = { listModels: async () => [{ id: 'openai/gpt-5' }], selectRoute: async () => null };
8
+ function peer() {
9
+ const peerId = 'a'.repeat(40);
10
+ return { peerId, lastSeen: 0, providers: ['router'], displayName: 'Test router',
11
+ providerServiceApiProtocols: { router: { services: { routing: ['irp-suggest'], image: ['openai-images'], 'gpt-5': ['openai-chat-completions'] } } },
12
+ metadata: {
13
+ peerId, version: 12, region: 'test', timestamp: 0, signature: '', providers: [{
14
+ provider: 'router', services: ['routing', 'image', 'gpt-5'],
15
+ defaultPricing: { inputUsdPerMillion: 1, outputUsdPerMillion: 2 }, maxConcurrency: 10, currentLoad: 0,
16
+ serviceApiProtocols: { routing: ['irp-suggest'], image: ['openai-images'], 'gpt-5': ['openai-chat-completions'] },
17
+ serviceUnitBillingModels: {
18
+ routing: { 'irp-suggest': { version: 1, components: [{ unit: 'completed_requests', priceUsd: 0.001 }] } },
19
+ image: { 'openai-images': { version: 1, components: [{ unit: 'output_images', priceUsd: 0.01 }] } },
20
+ },
21
+ }],
22
+ } };
23
+ }
24
+ test('discovery exposes IRP completed-request offers and canonically matched network models', async () => {
25
+ const services = await buildRoutingServices([peer()], client, node);
26
+ assert.equal(services.length, 1);
27
+ const found = services[0];
28
+ assert.equal(found.serviceId, 'routing');
29
+ assert.equal(found.priceMicroUsdc, '1000');
30
+ assert.equal(found.label, 'Test router');
31
+ assert.deepEqual(found.catalog, { models: [{ provider: 'router', serviceId: 'gpt-5' }] });
32
+ const unnamed = peer();
33
+ delete unnamed.displayName;
34
+ assert.equal((await buildRoutingServices([unnamed], client, node))[0].label, 'router');
35
+ });
36
+ test('missing, ambiguous and token-only offers cannot become routing purchases', async () => {
37
+ const missing = peer();
38
+ delete missing.metadata;
39
+ const ambiguous = peer();
40
+ ambiguous.metadata.providers.push(structuredClone(ambiguous.metadata.providers[0]));
41
+ const tokenOnly = peer();
42
+ delete tokenOnly.metadata.providers[0].serviceUnitBillingModels;
43
+ assert.deepEqual(await buildRoutingServices([missing, ambiguous, tokenOnly], client, node), []);
44
+ });
45
+ test('discovery caches listModels, surfaces failures and recovers without stale data', async (context) => {
46
+ context.mock.timers.enable({ apis: ['Date'], now: 1_000 });
47
+ let calls = 0;
48
+ let fail = false;
49
+ const routingClient = { ...client, listModels: async () => {
50
+ calls++;
51
+ if (fail)
52
+ throw new Error('Router models unavailable (503)');
53
+ return [{ id: 'openai/gpt-5' }];
54
+ } };
55
+ const cache = new RoutingModelsCache(60_000);
56
+ const found = (await buildRoutingServices([peer()], routingClient, node, cache))[0];
57
+ assert.equal(found.catalogExpiresAt, 61_000);
58
+ await buildRoutingServices([peer()], routingClient, node, cache);
59
+ assert.equal(calls, 1);
60
+ fail = true;
61
+ context.mock.timers.setTime(62_000);
62
+ const failed = (await buildRoutingServices([peer()], routingClient, node, cache))[0];
63
+ assert.equal(failed.catalog, undefined);
64
+ assert.match(failed.catalogError, /503/);
65
+ fail = false;
66
+ assert.deepEqual((await buildRoutingServices([peer()], routingClient, node, cache))[0].catalog, found.catalog);
67
+ assert.equal(calls, 3);
68
+ });
69
+ test('discovery rejects multiple routing services on a seller before fetching models', async () => {
70
+ const sameProvider = peer();
71
+ sameProvider.metadata.providers[0].services.push('other-route');
72
+ sameProvider.metadata.providers[0].serviceApiProtocols['other-route'] = ['irp-suggest'];
73
+ const otherProvider = peer();
74
+ otherProvider.metadata.providers.push({ ...structuredClone(otherProvider.metadata.providers[0]), provider: 'other-router' });
75
+ const noModels = { ...client, listModels: async () => { throw new Error('Ambiguous sellers must be filtered before model discovery'); } };
76
+ assert.deepEqual(await buildRoutingServices([sameProvider, otherProvider], noModels, node), []);
77
+ });
78
+ test('discovery uses free IRP models requests and rejects malformed model responses', async () => {
79
+ let invalid = false;
80
+ const routingClient = new ModelRoutingClient();
81
+ const transport = { sendRequest: async (target, request, options) => {
82
+ assert.equal(target.peerId, peer().peerId);
83
+ assert.equal(request.method, 'GET');
84
+ assert.equal(request.path, '/v1/routing/models');
85
+ assert.deepEqual(request.headers, { accept: 'application/json' });
86
+ assert.equal(options?.controlPlane, true);
87
+ return { requestId: request.requestId, statusCode: 200, headers: {}, body: Buffer.from(JSON.stringify(invalid ? { models: ['gpt-5'] } : { object: 'list', data: [{ id: 'openai/gpt-5', object: 'model' }] })) };
88
+ } };
89
+ assert.ok((await buildRoutingServices([peer()], routingClient, transport))[0].catalog);
90
+ invalid = true;
91
+ const result = (await buildRoutingServices([peer()], routingClient, transport))[0];
92
+ assert.equal(result.catalog, undefined);
93
+ assert.ok(result.catalogError);
94
+ });
95
+ //# sourceMappingURL=routing-services.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-services.test.js","sourceRoot":"","sources":["../../src/proxy/routing-services.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { PeerInfo, SerializedHttpRequest, SerializedHttpResponse } from '@antseed/node';
2
+ import type { ModelRoutingClientApi } from '@antseed/router-core';
3
+ export declare function recordRouterUsage(client: Pick<ModelRoutingClientApi, 'recordUsage'>, conversationKey: string | null, request: SerializedHttpRequest, response: SerializedHttpResponse, peer: PeerInfo, signal: AbortSignal): void;
4
+ //# sourceMappingURL=routing-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-usage.d.ts","sourceRoot":"","sources":["../../src/proxy/routing-usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAC5F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAIjE,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,CAAC,EAClD,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,sBAAsB,EAChC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,WAAW,GAClB,IAAI,CAYN"}
@@ -0,0 +1,19 @@
1
+ import { computeResponseTelemetry } from './telemetry.js';
2
+ import { isCompletionRequestPath } from './conversation-identity.js';
3
+ export function recordRouterUsage(client, conversationKey, request, response, peer, signal) {
4
+ if (!client.recordUsage || !conversationKey || signal.aborted || !isCompletionRequestPath(request.path)
5
+ || response.statusCode < 200 || response.statusCode >= 300)
6
+ return;
7
+ const telemetry = computeResponseTelemetry(request, response.headers, response.body, peer);
8
+ if (telemetry.usage.source !== 'usage' || telemetry.usage.inputTokens <= 0 || !telemetry.pricing.service)
9
+ return;
10
+ try {
11
+ client.recordUsage({
12
+ conversationKey, requestId: request.requestId, peerId: peer.peerId,
13
+ provider: telemetry.pricing.provider, serviceId: telemetry.pricing.service,
14
+ inputTokens: telemetry.usage.inputTokens, cachedInputTokens: telemetry.usage.cachedInputTokens,
15
+ });
16
+ }
17
+ catch { }
18
+ }
19
+ //# sourceMappingURL=routing-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-usage.js","sourceRoot":"","sources":["../../src/proxy/routing-usage.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AAEpE,MAAM,UAAU,iBAAiB,CAC/B,MAAkD,EAClD,eAA8B,EAC9B,OAA8B,EAC9B,QAAgC,EAChC,IAAc,EACd,MAAmB;IAEnB,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,eAAe,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,IAAI,CAAC;WAClG,QAAQ,CAAC,UAAU,GAAG,GAAG,IAAI,QAAQ,CAAC,UAAU,IAAI,GAAG;QAAE,OAAM;IACpE,MAAM,SAAS,GAAG,wBAAwB,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAC1F,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,KAAK,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO;QAAE,OAAM;IAChH,IAAI,CAAC;QACH,MAAM,CAAC,WAAW,CAAC;YACjB,eAAe,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;YAClE,QAAQ,EAAE,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,OAAO;YAC1E,WAAW,EAAE,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,iBAAiB,EAAE,SAAS,CAAC,KAAK,CAAC,iBAAiB;SAC/F,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACZ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routing-usage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-usage.test.d.ts","sourceRoot":"","sources":["../../src/proxy/routing-usage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { recordRouterUsage } from './routing-usage.js';
4
+ const peer = { peerId: 'a'.repeat(40), providers: ['openai'], lastSeen: Date.now() };
5
+ const request = { requestId: 'inference', method: 'POST', path: '/v1/chat/completions', headers: { 'content-type': 'application/json', 'x-antseed-provider': 'openai' }, body: Buffer.from('{"model":"model-a","messages":[]}') };
6
+ const response = { requestId: 'inference', statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from('{"usage":{"prompt_tokens":100,"completion_tokens":10,"prompt_tokens_details":{"cached_tokens":80}}}') };
7
+ test('records completed native usage once with the exact request, model, peer and conversation', () => {
8
+ const observed = [];
9
+ const routers = { recordUsage: (entry) => { observed.push(entry); } };
10
+ recordRouterUsage(routers, 'tool:chat', request, response, peer, new AbortController().signal);
11
+ assert.deepEqual(observed, [{ conversationKey: 'tool:chat', requestId: 'inference', peerId: peer.peerId, provider: 'openai', serviceId: 'model-a', inputTokens: 100, cachedInputTokens: 80 }]);
12
+ });
13
+ test('does not learn from errors, cancellation, untracked conversations, estimates or routing-service calls', () => {
14
+ let observations = 0;
15
+ const routers = { recordUsage: () => { observations++; } };
16
+ recordRouterUsage(routers, 'chat', request, { ...response, statusCode: 500 }, peer, new AbortController().signal);
17
+ recordRouterUsage(routers, 'chat', request, response, peer, AbortSignal.abort());
18
+ recordRouterUsage(routers, null, request, response, peer, new AbortController().signal);
19
+ recordRouterUsage(routers, 'chat', request, { ...response, body: Buffer.from('{"choices":[]}') }, peer, new AbortController().signal);
20
+ recordRouterUsage(routers, 'chat', { ...request, path: '/_antseed/route' }, response, peer, new AbortController().signal);
21
+ assert.equal(observations, 0);
22
+ });
23
+ test('collects streaming cache usage from the completed native response, not individual frames', () => {
24
+ const observed = [];
25
+ const routers = { recordUsage: (entry) => { observed.push(entry); } };
26
+ const body = 'data: {"type":"message_start","message":{"usage":{"input_tokens":20,"cache_read_input_tokens":80}}}\n\ndata: {"type":"message_delta","usage":{"output_tokens":10}}\n\ndata: {"type":"message_stop"}\n\n';
27
+ recordRouterUsage(routers, 'chat', { ...request, path: '/v1/messages' }, { ...response, headers: { 'content-type': 'text/event-stream' }, body: Buffer.from(body) }, peer, new AbortController().signal);
28
+ assert.equal(observed.length, 1);
29
+ assert.equal(observed[0].inputTokens, 100);
30
+ assert.equal(observed[0].cachedInputTokens, 80);
31
+ });
32
+ //# sourceMappingURL=routing-usage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-usage.test.js","sourceRoot":"","sources":["../../src/proxy/routing-usage.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antseed/cli",
3
- "version": "0.1.190",
3
+ "version": "0.1.192",
4
4
  "description": "Antseed Network CLI and Web Dashboard",
5
5
  "type": "module",
6
6
  "files": [
@@ -32,21 +32,23 @@
32
32
  "qrcode": "^1.5.4",
33
33
  "reflect-metadata": "^0.2.2",
34
34
  "zod": "^4",
35
- "@antseed/ants": "0.1.25",
36
35
  "@antseed/ant-agent": "0.1.35",
37
- "@antseed/api-adapter": "0.1.52",
38
- "@antseed/connected-apps": "0.1.2",
39
- "@antseed/node": "0.2.142",
40
- "@antseed/gateway-console": "0.1.7",
36
+ "@antseed/api-adapter": "0.1.53",
37
+ "@antseed/connected-apps": "0.1.3",
38
+ "@antseed/node": "0.2.143",
39
+ "@antseed/payments": "0.1.70",
41
40
  "@antseed/provider-core": "0.2.59",
42
- "@antseed/payments": "0.1.69"
41
+ "@antseed/gateway-console": "0.1.7",
42
+ "@antseed/router-core": "0.1.50",
43
+ "@antseed/ants": "0.1.26"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/better-sqlite3": "^7.6.13",
46
47
  "@types/geoip-country": "^5",
47
48
  "@types/node": "^20.11.0",
48
49
  "tsx": "^4.7.0",
49
- "typescript": "^5.3.0"
50
+ "typescript": "^5.3.0",
51
+ "@antseed/router-local": "0.1.50"
50
52
  },
51
53
  "scripts": {
52
54
  "prebuild": "node ../../scripts/remove-paths.mjs dist",