@antseed/cli 0.1.147 → 0.1.149

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 (46) hide show
  1. package/README.md +18 -3
  2. package/dist/cli/commands/buyer/start.d.ts +1 -0
  3. package/dist/cli/commands/buyer/start.d.ts.map +1 -1
  4. package/dist/cli/commands/buyer/start.js +10 -9
  5. package/dist/cli/commands/buyer/start.js.map +1 -1
  6. package/dist/cli/commands/buyer/start.test.js +30 -1
  7. package/dist/cli/commands/buyer/start.test.js.map +1 -1
  8. package/dist/cli/commands/network/browse.d.ts.map +1 -1
  9. package/dist/cli/commands/network/browse.js +65 -114
  10. package/dist/cli/commands/network/browse.js.map +1 -1
  11. package/dist/config/defaults.d.ts.map +1 -1
  12. package/dist/config/defaults.js +6 -0
  13. package/dist/config/defaults.js.map +1 -1
  14. package/dist/config/loader.d.ts.map +1 -1
  15. package/dist/config/loader.js +39 -0
  16. package/dist/config/loader.js.map +1 -1
  17. package/dist/config/loader.test.js +34 -0
  18. package/dist/config/loader.test.js.map +1 -1
  19. package/dist/config/types.d.ts +3 -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 +17 -0
  23. package/dist/config/validation.js.map +1 -1
  24. package/dist/proxy/buyer-proxy.d.ts +20 -2
  25. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  26. package/dist/proxy/buyer-proxy.js +408 -58
  27. package/dist/proxy/buyer-proxy.js.map +1 -1
  28. package/dist/proxy/buyer-proxy.test.js +828 -56
  29. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  30. package/dist/proxy/conversation-store.d.ts +6 -6
  31. package/dist/proxy/conversation-store.d.ts.map +1 -1
  32. package/dist/proxy/conversation-store.js +16 -0
  33. package/dist/proxy/conversation-store.js.map +1 -1
  34. package/dist/proxy/network-models.d.ts +76 -0
  35. package/dist/proxy/network-models.d.ts.map +1 -0
  36. package/dist/proxy/network-models.js +229 -0
  37. package/dist/proxy/network-models.js.map +1 -0
  38. package/dist/proxy/network-models.test.d.ts +2 -0
  39. package/dist/proxy/network-models.test.d.ts.map +1 -0
  40. package/dist/proxy/network-models.test.js +734 -0
  41. package/dist/proxy/network-models.test.js.map +1 -0
  42. package/dist/proxy/routing.d.ts +3 -1
  43. package/dist/proxy/routing.d.ts.map +1 -1
  44. package/dist/proxy/routing.js +69 -16
  45. package/dist/proxy/routing.js.map +1 -1
  46. package/package.json +5 -5
@@ -0,0 +1,734 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { buildNetworkModels, normalizedModelReputationScore, parseModelTypeFilter } from './network-models.js';
4
+ const NOW_MS = 1_700_000_000_000;
5
+ function makePeer(overrides) {
6
+ return {
7
+ lastSeen: NOW_MS,
8
+ providers: [],
9
+ ...overrides,
10
+ peerId: overrides.peerId,
11
+ };
12
+ }
13
+ const textSeller = makePeer({
14
+ peerId: 'a'.repeat(40),
15
+ displayName: 'Text Seller',
16
+ onChainTrustScore: 40,
17
+ onChainReputationScore: 35,
18
+ providerPricing: {
19
+ openai: {
20
+ defaults: { inputUsdPerMillion: 1, outputUsdPerMillion: 2 },
21
+ services: {
22
+ 'qwen3-coder': { inputUsdPerMillion: 3, outputUsdPerMillion: 9, cachedInputUsdPerMillion: 0.3 },
23
+ },
24
+ },
25
+ },
26
+ providerServiceApiProtocols: {
27
+ openai: { services: { 'qwen3-coder': ['openai-chat-completions'] } },
28
+ },
29
+ });
30
+ const imageSeller = makePeer({
31
+ peerId: 'b'.repeat(40),
32
+ onChainTrustScore: 80,
33
+ onChainReputationScore: 75,
34
+ providerPricing: {
35
+ openai: { defaults: { inputUsdPerMillion: 0, outputUsdPerMillion: 0 } },
36
+ },
37
+ providerServiceApiProtocols: {
38
+ openai: { services: { 'flux-1-schnell': ['openai-images'] } },
39
+ },
40
+ providerServiceUnitBillingModels: {
41
+ openai: {
42
+ services: {
43
+ 'flux-1-schnell': {
44
+ 'openai-images': {
45
+ version: 1,
46
+ components: [
47
+ { unit: 'output_images', priceUsd: 0.01, match: { size: '512x512' } },
48
+ { unit: 'output_images', priceUsd: 0.05, match: { size: '1024x1024' } },
49
+ ],
50
+ },
51
+ },
52
+ },
53
+ },
54
+ },
55
+ });
56
+ // Announces the same text model as textSeller (different id casing) plus an
57
+ // image model identified only via capability outputs — no protocol announced.
58
+ const mixedSeller = makePeer({
59
+ peerId: 'c'.repeat(40),
60
+ onChainTrustScore: 90,
61
+ onChainReputationScore: 85,
62
+ providerPricing: {
63
+ 'local-llm': { defaults: { inputUsdPerMillion: 0.5, outputUsdPerMillion: 0.7 } },
64
+ },
65
+ providerServiceCapabilities: {
66
+ 'local-llm': {
67
+ services: {
68
+ 'QWEN3-Coder': { outputs: ['text'] },
69
+ 'sdxl-turbo': { outputs: ['image'] },
70
+ },
71
+ },
72
+ },
73
+ });
74
+ test('parseModelTypeFilter maps query values', () => {
75
+ assert.equal(parseModelTypeFilter(null), 'all');
76
+ assert.equal(parseModelTypeFilter(''), 'all');
77
+ assert.equal(parseModelTypeFilter('images'), 'image');
78
+ assert.equal(parseModelTypeFilter('Image'), 'image');
79
+ assert.equal(parseModelTypeFilter('text'), 'text');
80
+ assert.equal(parseModelTypeFilter('audio'), 'invalid');
81
+ });
82
+ test('aggregates one entry per model id across peers, case-insensitively', () => {
83
+ const models = buildNetworkModels([textSeller, imageSeller, mixedSeller], NOW_MS);
84
+ assert.deepEqual(models.map((model) => model.id), ['flux-1-schnell', 'qwen3-coder', 'sdxl-turbo']);
85
+ const qwen = models.find((model) => model.id === 'qwen3-coder');
86
+ assert.ok(qwen);
87
+ assert.equal(qwen.type, 'text');
88
+ assert.equal(qwen.object, 'model');
89
+ assert.equal(qwen.owned_by, 'antseed');
90
+ assert.equal(qwen.created, Math.floor(NOW_MS / 1000));
91
+ assert.deepEqual(qwen.aliases, ['qwen3-coder', 'qwen3coder']);
92
+ // The missing cached-price penalty reduces reputation without forcing the
93
+ // stronger peer behind a substantially weaker complete offer.
94
+ assert.deepEqual(qwen.peers.map((peer) => peer.peerId), [mixedSeller.peerId, textSeller.peerId]);
95
+ assert.deepEqual(qwen.peers.map((peer) => peer.reputationScore), [90, 40]);
96
+ assert.deepEqual(qwen.peers.map((peer) => peer.effectiveReputationScore), [42.5, 35]);
97
+ assert.deepEqual(qwen.peers.map((peer) => peer.onChainTrustScore), [90, 40]);
98
+ assert.deepEqual(qwen.peers.map((peer) => peer.onChainReputationScore), [85, 35]);
99
+ assert.deepEqual(qwen.peers.map((peer) => peer.serviceId), ['QWEN3-Coder', 'qwen3-coder']);
100
+ });
101
+ test('sorts by normalized on-chain reputation like desktop VPR', () => {
102
+ const reputationOnlySeller = makePeer({
103
+ peerId: 'f'.repeat(40),
104
+ onChainReputationScore: 95,
105
+ providerServiceApiProtocols: {
106
+ openai: { services: { 'qwen3-coder': ['openai-chat-completions'] } },
107
+ },
108
+ });
109
+ const models = buildNetworkModels([mixedSeller, reputationOnlySeller], NOW_MS);
110
+ const qwen = models.find((model) => model.id === 'QWEN3-Coder');
111
+ assert.ok(qwen);
112
+ assert.deepEqual(qwen.peers.map((peer) => peer.peerId), [reputationOnlySeller.peerId, mixedSeller.peerId]);
113
+ assert.deepEqual(qwen.peers.map((peer) => peer.reputationScore), [95, 90]);
114
+ assert.deepEqual(qwen.peers.map((peer) => peer.onChainTrustScore), [null, 90]);
115
+ });
116
+ test('sorts /models peers with the configured Price + Trust preferences', () => {
117
+ const cobaltRelay = makePeer({
118
+ peerId: '1'.repeat(40),
119
+ onChainReputationScore: 99,
120
+ providerPricing: {
121
+ openai: {
122
+ defaults: { inputUsdPerMillion: 1.88, outputUsdPerMillion: 9.38 },
123
+ },
124
+ },
125
+ providerServiceApiProtocols: {
126
+ openai: { services: { 'kimi-k3': ['openai-chat-completions'] } },
127
+ },
128
+ });
129
+ const emberRoute = makePeer({
130
+ peerId: '2'.repeat(40),
131
+ onChainReputationScore: 96,
132
+ providerPricing: {
133
+ openai: {
134
+ defaults: { inputUsdPerMillion: 0.9, outputUsdPerMillion: 2.7 },
135
+ },
136
+ },
137
+ providerServiceApiProtocols: {
138
+ openai: { services: { 'Kimi K3': ['openai-chat-completions'] } },
139
+ },
140
+ });
141
+ const [model] = buildNetworkModels([cobaltRelay, emberRoute], NOW_MS, {
142
+ routingPreferences: {
143
+ preferFreePeers: false,
144
+ maxInputUsdPerMillion: 25,
145
+ minTrustScore: 60,
146
+ allowedPeerIds: [],
147
+ blockedPeerIds: [],
148
+ },
149
+ });
150
+ assert.deepEqual(model?.peers.map((peer) => peer.peerId), [emberRoute.peerId, cobaltRelay.peerId]);
151
+ });
152
+ test('configured /models sorting puts cooling peers after healthy routes', () => {
153
+ const cooling = makePeer({
154
+ peerId: '3'.repeat(40),
155
+ onChainReputationScore: 99,
156
+ providerPricing: { openai: { defaults: { inputUsdPerMillion: 0, outputUsdPerMillion: 0 } } },
157
+ providerServiceApiProtocols: {
158
+ openai: { services: { 'health-model': ['openai-chat-completions'] } },
159
+ },
160
+ });
161
+ const healthy = makePeer({
162
+ peerId: '4'.repeat(40),
163
+ onChainReputationScore: 70,
164
+ providerPricing: { openai: { defaults: { inputUsdPerMillion: 10, outputUsdPerMillion: 10 } } },
165
+ providerServiceApiProtocols: {
166
+ openai: { services: { 'health-model': ['openai-chat-completions'] } },
167
+ },
168
+ });
169
+ const [model] = buildNetworkModels([cooling, healthy], NOW_MS, {
170
+ routingPreferences: {
171
+ preferFreePeers: false,
172
+ maxInputUsdPerMillion: 25,
173
+ minTrustScore: 60,
174
+ allowedPeerIds: [],
175
+ blockedPeerIds: [],
176
+ },
177
+ peerHealth: new Map([[cooling.peerId, { cooldownUntil: NOW_MS + 30_000, failureStreak: 3 }]]),
178
+ });
179
+ assert.deepEqual(model?.peers.map((peer) => peer.peerId), [healthy.peerId, cooling.peerId]);
180
+ });
181
+ test('returns null reputation and sorts unknown scores last', () => {
182
+ const unknownSeller = makePeer({
183
+ peerId: '0'.repeat(40),
184
+ providerServiceApiProtocols: {
185
+ openai: { services: { 'qwen3-coder': ['openai-chat-completions'] } },
186
+ },
187
+ });
188
+ const models = buildNetworkModels([unknownSeller, textSeller], NOW_MS);
189
+ const qwen = models.find((model) => model.id === 'qwen3-coder');
190
+ assert.ok(qwen);
191
+ assert.deepEqual(qwen.peers.map((peer) => peer.peerId), [textSeller.peerId, unknownSeller.peerId]);
192
+ assert.deepEqual(qwen.peers.map((peer) => peer.reputationScore), [40, null]);
193
+ });
194
+ test('derives on-chain reputation from raw persisted stats', () => {
195
+ const peer = makePeer({
196
+ peerId: '9'.repeat(40),
197
+ providers: ['claude-oauth'],
198
+ providerServiceApiProtocols: {
199
+ 'claude-oauth': { services: { 'claude-opus-4-6': ['anthropic-messages'] } },
200
+ },
201
+ onChainStakeUsdcMicros: 2_000_000,
202
+ onChainChannelCount: 20,
203
+ onChainGhostCount: 0,
204
+ onChainTotalVolumeUsdcMicros: 100_000_000,
205
+ onChainLastSettledAtSec: Math.floor((NOW_MS - 60_000) / 1000),
206
+ onChainStakedAtSec: Math.floor((NOW_MS - 40 * 86_400_000) / 1000),
207
+ });
208
+ const [model] = buildNetworkModels([peer], NOW_MS);
209
+ const [offer] = model?.peers ?? [];
210
+ assert.ok(offer);
211
+ assert.ok((offer.onChainReputationScore ?? 0) > 0);
212
+ assert.equal(offer.reputationScore, offer.onChainReputationScore);
213
+ });
214
+ test('cached-input pricing penalty can change a close reputation ranking', () => {
215
+ const priced = makePeer({
216
+ peerId: '1'.repeat(40),
217
+ onChainTrustScore: 75,
218
+ providerPricing: {
219
+ openai: {
220
+ defaults: { inputUsdPerMillion: 2, outputUsdPerMillion: 4 },
221
+ services: {
222
+ 'cache-model': { inputUsdPerMillion: 2, outputUsdPerMillion: 4, cachedInputUsdPerMillion: 0.2 },
223
+ },
224
+ },
225
+ },
226
+ providerServiceApiProtocols: {
227
+ openai: { services: { 'cache-model': ['openai-chat-completions'] } },
228
+ },
229
+ });
230
+ const unpriced = makePeer({
231
+ peerId: '2'.repeat(40),
232
+ onChainTrustScore: 90,
233
+ providerPricing: {
234
+ openai: {
235
+ defaults: { inputUsdPerMillion: 1, outputUsdPerMillion: 2 },
236
+ services: { 'cache-model': { inputUsdPerMillion: 1, outputUsdPerMillion: 2 } },
237
+ },
238
+ },
239
+ providerServiceApiProtocols: {
240
+ openai: { services: { 'cache-model': ['openai-chat-completions'] } },
241
+ },
242
+ });
243
+ const model = buildNetworkModels([unpriced, priced], NOW_MS)[0];
244
+ assert.deepEqual(model?.peers.map((peer) => peer.peerId), [priced.peerId, unpriced.peerId]);
245
+ assert.deepEqual(model?.peers.map((peer) => peer.reputationScore), [75, 90]);
246
+ assert.ok((model?.peers[0]?.effectiveReputationScore ?? 0) > (model?.peers[1]?.effectiveReputationScore ?? 0));
247
+ });
248
+ test('cached-input penalty uses normalized reputation instead of raw trust', () => {
249
+ const flash = makePeer({
250
+ peerId: '3'.repeat(40),
251
+ onChainTrustScore: 10_425.074,
252
+ onChainReputationScore: 99.93,
253
+ providerServiceApiProtocols: {
254
+ 'claude-oauth': { services: { 'claude-opus-4-6': ['anthropic-messages'] } },
255
+ },
256
+ });
257
+ const venice = makePeer({
258
+ peerId: '4'.repeat(40),
259
+ onChainTrustScore: 5_428.609,
260
+ onChainReputationScore: 90.31,
261
+ providerPricing: {
262
+ 'claude-oauth': {
263
+ defaults: { inputUsdPerMillion: 3, outputUsdPerMillion: 15 },
264
+ services: {
265
+ 'opus-4.6': { inputUsdPerMillion: 3, outputUsdPerMillion: 15, cachedInputUsdPerMillion: 0.3 },
266
+ },
267
+ },
268
+ },
269
+ providerServiceApiProtocols: {
270
+ 'claude-oauth': { services: { 'opus-4.6': ['anthropic-messages'] } },
271
+ },
272
+ });
273
+ const model = buildNetworkModels([flash, venice], NOW_MS)[0];
274
+ assert.deepEqual(model?.peers.map((peer) => peer.peerId), [venice.peerId, flash.peerId]);
275
+ assert.equal(model?.peers[0]?.effectiveReputationScore, 90.31);
276
+ assert.ok(Math.abs((model?.peers[1]?.effectiveReputationScore ?? 0) - 49.965) < 1e-9);
277
+ });
278
+ test('cached-input pricing penalty does not bury a substantially stronger peer', () => {
279
+ const priced = makePeer({
280
+ peerId: '5'.repeat(40),
281
+ onChainTrustScore: 20,
282
+ onChainReputationScore: 20,
283
+ providerPricing: {
284
+ openai: {
285
+ defaults: { inputUsdPerMillion: 2, outputUsdPerMillion: 4 },
286
+ services: {
287
+ 'cache-model': { inputUsdPerMillion: 2, outputUsdPerMillion: 4, cachedInputUsdPerMillion: 0.2 },
288
+ },
289
+ },
290
+ },
291
+ providerServiceApiProtocols: {
292
+ openai: { services: { 'cache-model': ['openai-chat-completions'] } },
293
+ },
294
+ });
295
+ const unpriced = makePeer({
296
+ peerId: '6'.repeat(40),
297
+ onChainTrustScore: 100,
298
+ onChainReputationScore: 100,
299
+ providerServiceApiProtocols: {
300
+ openai: { services: { 'cache-model': ['openai-chat-completions'] } },
301
+ },
302
+ });
303
+ const model = buildNetworkModels([priced, unpriced], NOW_MS)[0];
304
+ assert.deepEqual(model?.peers.map((peer) => peer.peerId), [unpriced.peerId, priced.peerId]);
305
+ assert.ok((model?.peers[0]?.effectiveReputationScore ?? 0) > (model?.peers[1]?.effectiveReputationScore ?? 0));
306
+ });
307
+ test('keeps reputation ordering when no offer reports cached-input pricing', () => {
308
+ const lower = makePeer({
309
+ peerId: '3'.repeat(40),
310
+ onChainTrustScore: 20,
311
+ providerServiceApiProtocols: {
312
+ openai: { services: { 'no-cache-model': ['openai-chat-completions'] } },
313
+ },
314
+ });
315
+ const higher = makePeer({
316
+ peerId: '4'.repeat(40),
317
+ onChainTrustScore: 100,
318
+ providerServiceApiProtocols: {
319
+ openai: { services: { 'no-cache-model': ['openai-chat-completions'] } },
320
+ },
321
+ });
322
+ const model = buildNetworkModels([lower, higher], NOW_MS)[0];
323
+ assert.deepEqual(model?.peers.map((peer) => peer.peerId), [higher.peerId, lower.peerId]);
324
+ assert.deepEqual(model?.peers.map((peer) => peer.effectiveReputationScore), [normalizedModelReputationScore(higher, NOW_MS), normalizedModelReputationScore(lower, NOW_MS)]);
325
+ });
326
+ test('merges conservative family aliases while preserving advertised service ids', () => {
327
+ const branded = makePeer({
328
+ peerId: 'd'.repeat(40),
329
+ providerServiceApiProtocols: {
330
+ anthropic: { services: { 'claude-opus-5': ['anthropic-messages'] } },
331
+ },
332
+ });
333
+ const unbranded = makePeer({
334
+ peerId: 'e'.repeat(40),
335
+ providerServiceApiProtocols: {
336
+ anthropic: { services: { 'Opus 5': ['anthropic-messages'] } },
337
+ },
338
+ });
339
+ const models = buildNetworkModels([branded, unbranded], NOW_MS);
340
+ assert.equal(models.length, 1);
341
+ assert.equal(models[0]?.id, 'claude-opus-5');
342
+ assert.deepEqual(models[0]?.aliases, ['claude-opus-5', 'opus-5', 'opus5']);
343
+ assert.deepEqual(models[0]?.peers.map((peer) => peer.serviceId), ['claude-opus-5', 'Opus 5']);
344
+ });
345
+ test('merges Fable aliases and the coding-only service variant', () => {
346
+ const branded = makePeer({
347
+ peerId: 'a'.repeat(40),
348
+ providerServiceApiProtocols: {
349
+ openai: { services: { 'claude-fable-5': ['openai-chat-completions'] } },
350
+ },
351
+ });
352
+ const unbranded = makePeer({
353
+ peerId: 'b'.repeat(40),
354
+ providerServiceApiProtocols: {
355
+ openai: { services: { 'fable-5': ['openai-chat-completions'] } },
356
+ },
357
+ });
358
+ const codingOnly = makePeer({
359
+ peerId: 'c'.repeat(40),
360
+ providerServiceApiProtocols: {
361
+ 'claude-oauth': { services: { 'fable-5-coding-only': ['anthropic-messages'] } },
362
+ },
363
+ });
364
+ const models = buildNetworkModels([branded, unbranded, codingOnly], NOW_MS);
365
+ assert.equal(models.length, 1);
366
+ assert.equal(models[0]?.id, 'claude-fable-5');
367
+ assert.deepEqual(models[0]?.aliases, [
368
+ 'claude-fable-5',
369
+ 'fable-5',
370
+ 'fable-5-coding-only',
371
+ 'fable5',
372
+ ]);
373
+ assert.deepEqual(models[0]?.supported_protocols, ['anthropic-messages', 'openai-chat-completions']);
374
+ assert.deepEqual(models[0]?.peers.map((peer) => peer.serviceId), [
375
+ 'claude-fable-5',
376
+ 'fable-5',
377
+ 'fable-5-coding-only',
378
+ ]);
379
+ });
380
+ test('merges Claude coding-only aliases and displays decimal versions', () => {
381
+ const base = makePeer({
382
+ peerId: '7'.repeat(40),
383
+ providerServiceApiProtocols: {
384
+ anthropic: { services: { 'claude-opus-4.8': ['anthropic-messages'] } },
385
+ },
386
+ });
387
+ const codingOnly = makePeer({
388
+ peerId: '8'.repeat(40),
389
+ providerServiceApiProtocols: {
390
+ 'claude-oauth': { services: { 'opus-4.8-coding-only': ['anthropic-messages'] } },
391
+ },
392
+ });
393
+ const models = buildNetworkModels([base, codingOnly], NOW_MS);
394
+ assert.equal(models.length, 1);
395
+ assert.equal(models[0]?.name, 'Claude Opus 4.8');
396
+ assert.deepEqual(models[0]?.peers.map((peer) => peer.serviceId), ['claude-opus-4.8', 'opus-4.8-coding-only']);
397
+ });
398
+ test('merges compact numeric versions and flattened vendor prefixes', () => {
399
+ const dotted = makePeer({
400
+ peerId: '1'.repeat(40),
401
+ providerServiceApiProtocols: {
402
+ openai: { services: { 'gpt-5.6-sol': ['openai-responses'] } },
403
+ },
404
+ });
405
+ const compact = makePeer({
406
+ peerId: '2'.repeat(40),
407
+ providerServiceApiProtocols: {
408
+ openai: { services: { 'gpt-56-sol': ['openai-responses'] } },
409
+ },
410
+ });
411
+ const vendorPrefixed = makePeer({
412
+ peerId: '3'.repeat(40),
413
+ providerServiceApiProtocols: {
414
+ openai: { services: { 'openai-gpt-56-sol': ['openai-responses'] } },
415
+ },
416
+ });
417
+ const models = buildNetworkModels([dotted, compact, vendorPrefixed], NOW_MS);
418
+ assert.equal(models.length, 1);
419
+ assert.equal(models[0]?.id, 'gpt-5.6-sol');
420
+ assert.equal(models[0]?.name, 'GPT 5.6 Sol');
421
+ assert.deepEqual(models[0]?.aliases, ['gpt-5.6-sol', 'gpt-56-sol', 'gpt56sol', 'openai-gpt-56-sol']);
422
+ assert.deepEqual(models[0]?.peers.map((peer) => peer.serviceId), [
423
+ 'gpt-5.6-sol',
424
+ 'gpt-56-sol',
425
+ 'openai-gpt-56-sol',
426
+ ]);
427
+ });
428
+ test('uses preferred MiniMax decimal display names across aliases', () => {
429
+ const dotted = makePeer({
430
+ peerId: '4'.repeat(40),
431
+ providerServiceApiProtocols: {
432
+ openai: { services: { 'minimax-m2.5': ['openai-chat-completions'] } },
433
+ },
434
+ });
435
+ const separated = makePeer({
436
+ peerId: '5'.repeat(40),
437
+ providerServiceApiProtocols: {
438
+ openai: { services: { 'minimax-m2-5': ['openai-chat-completions'] } },
439
+ },
440
+ });
441
+ const compact = makePeer({
442
+ peerId: '6'.repeat(40),
443
+ providerServiceApiProtocols: {
444
+ openai: { services: { 'minimax-m25': ['openai-chat-completions'] } },
445
+ },
446
+ });
447
+ const models = buildNetworkModels([dotted, separated, compact], NOW_MS);
448
+ assert.equal(models.length, 1);
449
+ assert.equal(models[0]?.name, 'MiniMax M2.5');
450
+ });
451
+ test('keeps only the lowest-priced duplicate text offer from one peer', () => {
452
+ const peer = makePeer({
453
+ peerId: '8'.repeat(40),
454
+ providerPricing: {
455
+ openai: {
456
+ defaults: { inputUsdPerMillion: 0, outputUsdPerMillion: 0 },
457
+ services: {
458
+ 'gpt-5.6-sol': { inputUsdPerMillion: 4, outputUsdPerMillion: 8 },
459
+ 'gpt-56-sol': { inputUsdPerMillion: 1, outputUsdPerMillion: 2 },
460
+ },
461
+ },
462
+ },
463
+ providerServiceApiProtocols: {
464
+ openai: {
465
+ services: {
466
+ 'gpt-5.6-sol': ['openai-responses'],
467
+ 'gpt-56-sol': ['openai-responses'],
468
+ },
469
+ },
470
+ },
471
+ });
472
+ const model = buildNetworkModels([peer], NOW_MS)[0];
473
+ assert.ok(model);
474
+ assert.equal(model.peers.length, 1);
475
+ assert.equal(model.peers[0]?.serviceId, 'gpt-56-sol');
476
+ assert.equal(model.peers[0]?.inputUsdPerMillion, 1);
477
+ assert.equal(model.peers[0]?.outputUsdPerMillion, 2);
478
+ assert.deepEqual(model.aliases, ['gpt-5.6-sol', 'gpt-56-sol', 'gpt56sol']);
479
+ });
480
+ test('keeps only the cheapest canonical offer across providers for one peer', () => {
481
+ const peer = makePeer({
482
+ peerId: 'a'.repeat(40),
483
+ providers: ['anthropic', 'claude-oauth', 'openai'],
484
+ providerPricing: {
485
+ anthropic: {
486
+ defaults: { inputUsdPerMillion: 18, outputUsdPerMillion: 9 },
487
+ services: { 'claude-opus-4.8': { inputUsdPerMillion: 18, outputUsdPerMillion: 9 } },
488
+ },
489
+ 'claude-oauth': {
490
+ defaults: { inputUsdPerMillion: 2.25, outputUsdPerMillion: 11.25 },
491
+ services: { 'opus-4.8-coding-only': { inputUsdPerMillion: 2.25, outputUsdPerMillion: 11.25 } },
492
+ },
493
+ openai: {
494
+ defaults: { inputUsdPerMillion: 0.35, outputUsdPerMillion: 0.95 },
495
+ services: { 'claude-opus-4-8': { inputUsdPerMillion: 0.35, outputUsdPerMillion: 0.95 } },
496
+ },
497
+ },
498
+ providerServiceApiProtocols: {
499
+ anthropic: { services: { 'claude-opus-4.8': ['anthropic-messages'] } },
500
+ 'claude-oauth': { services: { 'opus-4.8-coding-only': ['anthropic-messages'] } },
501
+ openai: { services: { 'claude-opus-4-8': ['openai-chat-completions'] } },
502
+ },
503
+ });
504
+ const model = buildNetworkModels([peer], NOW_MS)[0];
505
+ assert.ok(model);
506
+ assert.equal(model.peers.length, 1);
507
+ assert.equal(model.peers[0]?.provider, 'openai');
508
+ assert.equal(model.peers[0]?.serviceId, 'claude-opus-4-8');
509
+ assert.equal(model.peers[0]?.inputUsdPerMillion, 0.35);
510
+ assert.deepEqual(model.aliases, [
511
+ 'claude-opus-4-8',
512
+ 'claude-opus-4.8',
513
+ 'opus-4.8-coding-only',
514
+ 'opus48',
515
+ ]);
516
+ });
517
+ test('keeps only the lowest-priced duplicate image offer from one peer', () => {
518
+ const peer = makePeer({
519
+ peerId: '9'.repeat(40),
520
+ providerServiceApiProtocols: {
521
+ openai: {
522
+ services: {
523
+ 'flux-1-schnell': ['openai-images'],
524
+ 'flux1-schnell': ['openai-images'],
525
+ },
526
+ },
527
+ },
528
+ providerServiceUnitBillingModels: {
529
+ openai: {
530
+ services: {
531
+ 'flux-1-schnell': {
532
+ 'openai-images': {
533
+ version: 1,
534
+ components: [{ unit: 'output_images', priceUsd: 0.04 }],
535
+ },
536
+ },
537
+ 'flux1-schnell': {
538
+ 'openai-images': {
539
+ version: 1,
540
+ components: [{ unit: 'output_images', priceUsd: 0.01 }],
541
+ },
542
+ },
543
+ },
544
+ },
545
+ },
546
+ });
547
+ const model = buildNetworkModels([peer], NOW_MS)[0];
548
+ assert.ok(model);
549
+ assert.equal(model.peers.length, 1);
550
+ assert.equal(model.peers[0]?.serviceId, 'flux1-schnell');
551
+ assert.equal(model.peers[0]?.minImageUsdPerImage, 0.01);
552
+ });
553
+ test('prefers a known duplicate price over an unknown price', () => {
554
+ const peer = makePeer({
555
+ peerId: '0'.repeat(40),
556
+ providerServiceApiProtocols: {
557
+ openai: {
558
+ services: {
559
+ 'flux-1-schnell': ['openai-images'],
560
+ 'flux1-schnell': ['openai-images'],
561
+ },
562
+ },
563
+ },
564
+ providerServiceUnitBillingModels: {
565
+ openai: {
566
+ services: {
567
+ 'flux1-schnell': {
568
+ 'openai-images': {
569
+ version: 1,
570
+ components: [{ unit: 'output_images', priceUsd: 0.02 }],
571
+ },
572
+ },
573
+ },
574
+ },
575
+ },
576
+ });
577
+ const model = buildNetworkModels([peer], NOW_MS)[0];
578
+ assert.equal(model?.peers[0]?.serviceId, 'flux1-schnell');
579
+ });
580
+ test('service pricing overrides provider defaults', () => {
581
+ const models = buildNetworkModels([textSeller], NOW_MS);
582
+ const offer = models[0]?.peers[0];
583
+ assert.ok(offer);
584
+ assert.equal(offer.inputUsdPerMillion, 3);
585
+ assert.equal(offer.outputUsdPerMillion, 9);
586
+ assert.equal(offer.cachedInputUsdPerMillion, 0.3);
587
+ assert.equal(offer.displayName, 'Text Seller');
588
+ });
589
+ test('exposes peer capabilities and conservative model-level guarantees', () => {
590
+ const first = makePeer({
591
+ peerId: '4'.repeat(40),
592
+ providerServiceApiProtocols: {
593
+ openai: { services: { 'gpt-capable': ['openai-responses'] } },
594
+ },
595
+ providerServiceCapabilities: {
596
+ openai: {
597
+ services: {
598
+ 'gpt-capable': {
599
+ contextWindow: 200_000,
600
+ maxOutputTokens: 64_000,
601
+ inputs: ['text', 'image'],
602
+ outputs: ['text'],
603
+ reasoning: true,
604
+ toolUse: true,
605
+ structuredOutput: true,
606
+ supportedParameters: ['temperature', 'tools', 'response_format'],
607
+ },
608
+ },
609
+ },
610
+ },
611
+ providerServiceCategories: {
612
+ openai: { services: { 'gpt-capable': ['chat', 'reasoning'] } },
613
+ },
614
+ });
615
+ const second = makePeer({
616
+ peerId: '5'.repeat(40),
617
+ providerServiceApiProtocols: {
618
+ openai: { services: { 'gpt-capable': ['openai-chat-completions'] } },
619
+ },
620
+ providerServiceCapabilities: {
621
+ openai: {
622
+ services: {
623
+ 'gpt-capable': {
624
+ contextWindow: 128_000,
625
+ maxOutputTokens: 32_000,
626
+ inputs: ['text'],
627
+ outputs: ['text'],
628
+ reasoning: false,
629
+ toolUse: true,
630
+ structuredOutput: false,
631
+ supportedParameters: ['tools', 'top_p', 'temperature'],
632
+ },
633
+ },
634
+ },
635
+ },
636
+ });
637
+ const model = buildNetworkModels([first, second], NOW_MS)[0];
638
+ assert.ok(model);
639
+ assert.deepEqual(model.supported_protocols, ['openai-chat-completions', 'openai-responses']);
640
+ assert.equal(model.context_length, 128_000);
641
+ assert.equal(model.max_output_tokens, 32_000);
642
+ assert.deepEqual(model.architecture, {
643
+ input_modalities: ['text'],
644
+ output_modalities: ['text'],
645
+ });
646
+ assert.deepEqual(model.capabilities, {
647
+ reasoning: false,
648
+ tool_use: true,
649
+ structured_output: false,
650
+ });
651
+ assert.deepEqual(model.supported_parameters, ['temperature', 'tools']);
652
+ assert.deepEqual(model.capability_coverage, {
653
+ total_offers: 2,
654
+ context_length: 2,
655
+ max_output_tokens: 2,
656
+ input_modalities: 2,
657
+ output_modalities: 2,
658
+ reasoning: 2,
659
+ tool_use: 2,
660
+ structured_output: 2,
661
+ supported_parameters: 2,
662
+ });
663
+ assert.equal(model.peers[0]?.protocol, 'openai-responses');
664
+ assert.deepEqual(model.peers[0]?.categories, ['chat', 'reasoning']);
665
+ assert.equal(model.peers[0]?.capabilities?.contextWindow, 200_000);
666
+ });
667
+ test('does not claim model-level guarantees when any offer omits a capability', () => {
668
+ const reported = makePeer({
669
+ peerId: '6'.repeat(40),
670
+ providerServiceCapabilities: {
671
+ openai: {
672
+ services: {
673
+ 'partially-described': {
674
+ contextWindow: 128_000,
675
+ inputs: ['text', 'image'],
676
+ toolUse: true,
677
+ },
678
+ },
679
+ },
680
+ },
681
+ });
682
+ const unknown = makePeer({
683
+ peerId: '7'.repeat(40),
684
+ providerServiceApiProtocols: {
685
+ openai: { services: { 'partially-described': ['openai-chat-completions'] } },
686
+ },
687
+ });
688
+ const model = buildNetworkModels([reported, unknown], NOW_MS)[0];
689
+ assert.ok(model);
690
+ assert.equal(model.context_length, undefined);
691
+ assert.equal(model.architecture, undefined);
692
+ assert.equal(model.capabilities, undefined);
693
+ assert.equal(model.supported_parameters, undefined);
694
+ assert.equal(model.capability_coverage.context_length, 1);
695
+ assert.equal(model.capability_coverage.input_modalities, 1);
696
+ assert.equal(model.capability_coverage.tool_use, 1);
697
+ });
698
+ test('image models are detected via protocol or capability outputs', () => {
699
+ const models = buildNetworkModels([textSeller, imageSeller, mixedSeller], NOW_MS);
700
+ const flux = models.find((model) => model.id === 'flux-1-schnell');
701
+ const sdxl = models.find((model) => model.id === 'sdxl-turbo');
702
+ assert.equal(flux?.type, 'image');
703
+ assert.equal(sdxl?.type, 'image');
704
+ assert.equal(flux?.peers[0]?.minImageUsdPerImage, 0.01);
705
+ assert.equal(flux?.peers[0]?.maxImageUsdPerImage, 0.05);
706
+ });
707
+ test('type filter splits text and image models', () => {
708
+ const models = buildNetworkModels([textSeller, imageSeller, mixedSeller], NOW_MS);
709
+ const images = models.filter((model) => model.type === 'image');
710
+ const text = models.filter((model) => model.type === 'text');
711
+ assert.deepEqual(images.map((model) => model.id), ['flux-1-schnell', 'sdxl-turbo']);
712
+ assert.deepEqual(text.map((model) => model.id), ['qwen3-coder']);
713
+ });
714
+ test('returns an empty list when no peers are discovered', () => {
715
+ assert.deepEqual(buildNetworkModels([], NOW_MS), []);
716
+ });
717
+ test('keeps a canonically merged model text-routable when any peer serves text', () => {
718
+ const image = makePeer({
719
+ peerId: '4'.repeat(40),
720
+ providerServiceApiProtocols: {
721
+ openai: { services: { 'shared-model': ['openai-images'] } },
722
+ },
723
+ });
724
+ const text = makePeer({
725
+ peerId: '5'.repeat(40),
726
+ providerServiceApiProtocols: {
727
+ openai: { services: { 'shared model': ['openai-chat-completions'] } },
728
+ },
729
+ });
730
+ const [model] = buildNetworkModels([image, text], NOW_MS);
731
+ assert.equal(model?.type, 'text');
732
+ assert.deepEqual(model?.peers.map((peer) => peer.type).sort(), ['image', 'text']);
733
+ });
734
+ //# sourceMappingURL=network-models.test.js.map