@antseed/cli 0.1.25 → 0.1.26

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 (42) hide show
  1. package/README.md +19 -19
  2. package/dist/cli/commands/browse.d.ts +1 -1
  3. package/dist/cli/commands/browse.js +4 -4
  4. package/dist/cli/commands/browse.js.map +1 -1
  5. package/dist/cli/commands/connection.js +21 -21
  6. package/dist/cli/commands/connection.js.map +1 -1
  7. package/dist/cli/commands/plugin-create.js +1 -1
  8. package/dist/cli/commands/seed.d.ts.map +1 -1
  9. package/dist/cli/commands/seed.js +40 -24
  10. package/dist/cli/commands/seed.js.map +1 -1
  11. package/dist/cli/commands/seed.test.js +6 -6
  12. package/dist/cli/commands/seed.test.js.map +1 -1
  13. package/dist/config/loader.js +21 -21
  14. package/dist/config/loader.js.map +1 -1
  15. package/dist/config/loader.test.js +12 -12
  16. package/dist/config/loader.test.js.map +1 -1
  17. package/dist/config/types.d.ts +17 -9
  18. package/dist/config/types.d.ts.map +1 -1
  19. package/dist/config/validation.js +14 -14
  20. package/dist/config/validation.js.map +1 -1
  21. package/dist/proxy/buyer-proxy.d.ts +7 -7
  22. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  23. package/dist/proxy/buyer-proxy.js +72 -72
  24. package/dist/proxy/buyer-proxy.js.map +1 -1
  25. package/dist/proxy/buyer-proxy.test.js +6 -6
  26. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  27. package/dist/proxy/service-api-adapter.d.ts +2 -0
  28. package/dist/proxy/service-api-adapter.d.ts.map +1 -0
  29. package/dist/proxy/service-api-adapter.js +5 -0
  30. package/dist/proxy/service-api-adapter.js.map +1 -0
  31. package/dist/proxy/service-api-adapter.test.d.ts +2 -0
  32. package/dist/proxy/service-api-adapter.test.d.ts.map +1 -0
  33. package/dist/proxy/{model-api-adapter.test.js → service-api-adapter.test.js} +2 -2
  34. package/dist/proxy/service-api-adapter.test.js.map +1 -0
  35. package/package.json +4 -4
  36. package/dist/proxy/model-api-adapter.d.ts +0 -2
  37. package/dist/proxy/model-api-adapter.d.ts.map +0 -1
  38. package/dist/proxy/model-api-adapter.js +0 -5
  39. package/dist/proxy/model-api-adapter.js.map +0 -1
  40. package/dist/proxy/model-api-adapter.test.d.ts +0 -2
  41. package/dist/proxy/model-api-adapter.test.d.ts.map +0 -1
  42. package/dist/proxy/model-api-adapter.test.js.map +0 -1
@@ -4,7 +4,7 @@ import { watch } from 'node:fs';
4
4
  import { readFile, writeFile, rename, mkdir } from 'node:fs/promises';
5
5
  import { join } from 'node:path';
6
6
  import { homedir } from 'node:os';
7
- import { detectRequestModelApiProtocol, inferProviderDefaultModelApiProtocols, selectTargetProtocolForRequest, transformAnthropicMessagesRequestToOpenAIChat, transformOpenAIChatResponseToAnthropicMessage, transformOpenAIResponsesRequestToOpenAIChat, transformOpenAIChatResponseToOpenAIResponses, } from './model-api-adapter.js';
7
+ import { detectRequestServiceApiProtocol, inferProviderDefaultServiceApiProtocols, selectTargetProtocolForRequest, transformAnthropicMessagesRequestToOpenAIChat, transformOpenAIChatResponseToAnthropicMessage, transformOpenAIResponsesRequestToOpenAIChat, transformOpenAIChatResponseToOpenAIResponses, } from './service-api-adapter.js';
8
8
  const DAEMON_STATE_FILE = join(homedir(), '.antseed', 'daemon.state.json');
9
9
  const BUYER_STATE_FILE = join(homedir(), '.antseed', 'buyer.state.json');
10
10
  const DEBUG = () => ['1', 'true', 'yes', 'on'].includes((process.env['ANTSEED_DEBUG'] ?? '').trim().toLowerCase());
@@ -13,9 +13,9 @@ function log(...args) {
13
13
  console.log('[proxy]', ...args);
14
14
  }
15
15
  const CLAUDE_PROVIDER_PREFERENCE = ['claude-oauth', 'anthropic', 'claude-code'];
16
- function inferPreferredProvidersForRequest(requestProtocol, requestedModel) {
17
- const model = requestedModel?.trim().toLowerCase() ?? '';
18
- if (model.length === 0) {
16
+ function inferPreferredProvidersForRequest(requestProtocol, requestedService) {
17
+ const service = requestedService?.trim().toLowerCase() ?? '';
18
+ if (service.length === 0) {
19
19
  return [];
20
20
  }
21
21
  const providers = [];
@@ -26,11 +26,11 @@ function inferPreferredProvidersForRequest(requestProtocol, requestedModel) {
26
26
  }
27
27
  providers.push(provider);
28
28
  };
29
- const slashIndex = model.indexOf('/');
29
+ const slashIndex = service.indexOf('/');
30
30
  if (slashIndex > 0) {
31
- pushProvider(model.slice(0, slashIndex));
31
+ pushProvider(service.slice(0, slashIndex));
32
32
  }
33
- if (requestProtocol === 'anthropic-messages' || model.startsWith('claude-') || model.includes('claude')) {
33
+ if (requestProtocol === 'anthropic-messages' || service.startsWith('claude-') || service.includes('claude')) {
34
34
  for (const provider of CLAUDE_PROVIDER_PREFERENCE) {
35
35
  pushProvider(provider);
36
36
  }
@@ -55,36 +55,36 @@ function getPreferredPeerIdHint(request) {
55
55
  }
56
56
  return header;
57
57
  }
58
- function getPeerProviderProtocols(peer, provider, requestedModel) {
59
- const normalizedRequestedModel = requestedModel?.trim();
60
- const fromMetadata = peer.providerModelApiProtocols?.[provider]?.models;
58
+ function getPeerProviderProtocols(peer, provider, requestedService) {
59
+ const normalizedRequestedService = requestedService?.trim();
60
+ const fromMetadata = peer.providerServiceApiProtocols?.[provider]?.services;
61
61
  if (fromMetadata) {
62
- if (normalizedRequestedModel) {
63
- const directMatchKey = Object.keys(fromMetadata).find((model) => model.toLowerCase() === normalizedRequestedModel.toLowerCase());
62
+ if (normalizedRequestedService) {
63
+ const directMatchKey = Object.keys(fromMetadata).find((key) => key.toLowerCase() === normalizedRequestedService.toLowerCase());
64
64
  if (directMatchKey && fromMetadata[directMatchKey]?.length) {
65
- log(`Model match: peer ${peer.peerId.slice(0, 8)} provider=${provider} model="${normalizedRequestedModel}" `
65
+ log(`Service match: peer ${peer.peerId.slice(0, 8)} provider=${provider} service="${normalizedRequestedService}" `
66
66
  + `→ [${fromMetadata[directMatchKey].join(',')}]`);
67
67
  return Array.from(new Set(fromMetadata[directMatchKey]));
68
68
  }
69
69
  if (Object.keys(fromMetadata).length > 0) {
70
- log(`Model strict-miss: peer ${peer.peerId.slice(0, 8)} provider=${provider} model="${normalizedRequestedModel}" `
70
+ log(`Service strict-miss: peer ${peer.peerId.slice(0, 8)} provider=${provider} service="${normalizedRequestedService}" `
71
71
  + 'not in metadata; excluding from route candidates.');
72
72
  return [];
73
73
  }
74
74
  }
75
75
  const merged = Object.values(fromMetadata).flat();
76
76
  if (merged.length > 0) {
77
- if (requestedModel) {
78
- log(`Model hint miss: peer ${peer.peerId.slice(0, 8)} provider=${provider} model="${requestedModel}" not in metadata; falling back to provider protocol set [${Array.from(new Set(merged)).join(',')}]`);
77
+ if (requestedService) {
78
+ log(`Service hint miss: peer ${peer.peerId.slice(0, 8)} provider=${provider} service="${requestedService}" not in metadata; falling back to provider protocol set [${Array.from(new Set(merged)).join(',')}]`);
79
79
  }
80
80
  return Array.from(new Set(merged));
81
81
  }
82
82
  }
83
- const inferred = inferProviderDefaultModelApiProtocols(provider);
83
+ const inferred = inferProviderDefaultServiceApiProtocols(provider);
84
84
  log(`No metadata: peer ${peer.peerId.slice(0, 8)} provider=${provider} → inferred [${inferred.join(',')}]`);
85
85
  return inferred;
86
86
  }
87
- function resolvePeerRoutePlan(peer, requestProtocol, requestedModel, explicitProvider) {
87
+ function resolvePeerRoutePlan(peer, requestProtocol, requestedService, explicitProvider) {
88
88
  const providers = peer.providers
89
89
  .map((provider) => provider.trim().toLowerCase())
90
90
  .filter((provider) => provider.length > 0);
@@ -101,7 +101,7 @@ function resolvePeerRoutePlan(peer, requestProtocol, requestedModel, explicitPro
101
101
  }
102
102
  let transformedFallback = null;
103
103
  for (const provider of candidates) {
104
- const supportedProtocols = getPeerProviderProtocols(peer, provider, requestedModel);
104
+ const supportedProtocols = getPeerProviderProtocols(peer, provider, requestedService);
105
105
  const selection = selectTargetProtocolForRequest(requestProtocol, supportedProtocols);
106
106
  if (!selection) {
107
107
  continue;
@@ -115,7 +115,7 @@ function resolvePeerRoutePlan(peer, requestProtocol, requestedModel, explicitPro
115
115
  }
116
116
  return transformedFallback;
117
117
  }
118
- export function selectCandidatePeersForRouting(peers, requestProtocol, requestedModel, explicitProvider) {
118
+ export function selectCandidatePeersForRouting(peers, requestProtocol, requestedService, explicitProvider) {
119
119
  const routePlanByPeerId = new Map();
120
120
  if (!requestProtocol && !explicitProvider) {
121
121
  return {
@@ -124,7 +124,7 @@ export function selectCandidatePeersForRouting(peers, requestProtocol, requested
124
124
  };
125
125
  }
126
126
  const candidatePeers = peers.filter((peer) => {
127
- const plan = resolvePeerRoutePlan(peer, requestProtocol, requestedModel, explicitProvider);
127
+ const plan = resolvePeerRoutePlan(peer, requestProtocol, requestedService, explicitProvider);
128
128
  if (!plan)
129
129
  return false;
130
130
  routePlanByPeerId.set(peer.peerId, plan);
@@ -269,16 +269,16 @@ function pickProviderForPeer(peer, request) {
269
269
  }
270
270
  return 'unknown';
271
271
  }
272
- function extractRequestedModel(request) {
272
+ function extractRequestedService(request) {
273
273
  const contentType = (request.headers['content-type'] ?? request.headers['Content-Type'] ?? '').toLowerCase();
274
274
  if (!contentType.includes('application/json')) {
275
275
  return null;
276
276
  }
277
277
  try {
278
278
  const parsed = JSON.parse(new TextDecoder().decode(request.body));
279
- const model = parsed.model;
280
- if (typeof model === 'string' && model.trim().length > 0) {
281
- return model.trim();
279
+ const service = parsed.service ?? parsed.model;
280
+ if (typeof service === 'string' && service.trim().length > 0) {
281
+ return service.trim();
282
282
  }
283
283
  return null;
284
284
  }
@@ -374,7 +374,7 @@ function summarizeRequestShape(request) {
374
374
  const accept = (request.headers['accept'] ?? request.headers['Accept'] ?? '').toLowerCase();
375
375
  const providerHeader = request.headers['x-antseed-provider'] ?? 'none';
376
376
  const preferPeerHeader = request.headers['x-antseed-prefer-peer'] ?? 'none';
377
- const model = extractRequestedModel(request) ?? 'none';
377
+ const service = extractRequestedService(request) ?? 'none';
378
378
  const wantsStreaming = requestWantsStreaming(request.headers, request.body);
379
379
  const baseParts = [
380
380
  `method=${request.method}`,
@@ -384,7 +384,7 @@ function summarizeRequestShape(request) {
384
384
  `contentType=${contentType || 'none'}`,
385
385
  `accept=${accept || 'none'}`,
386
386
  `stream=${String(wantsStreaming)}`,
387
- `model=${model}`,
387
+ `service=${service}`,
388
388
  `bodyBytes=${String(request.body.length)}`,
389
389
  ];
390
390
  const jsonBody = decodeJsonBody(request.body);
@@ -460,14 +460,14 @@ function setPeerIdentityHeaders(headers, selectedPeer) {
460
460
  headers['x-antseed-peer-providers'] = selectedPeer.providers.join(',');
461
461
  }
462
462
  }
463
- function resolvePeerPricing(peer, provider, model) {
463
+ function resolvePeerPricing(peer, provider, service) {
464
464
  const providerPricing = peer.providerPricing?.[provider];
465
465
  if (providerPricing) {
466
- const modelPricing = model ? providerPricing.models?.[model] : undefined;
467
- if (modelPricing) {
466
+ const servicePricing = service ? providerPricing.services?.[service] : undefined;
467
+ if (servicePricing) {
468
468
  return {
469
- inputUsdPerMillion: toFiniteNumberOrNull(modelPricing.inputUsdPerMillion),
470
- outputUsdPerMillion: toFiniteNumberOrNull(modelPricing.outputUsdPerMillion),
469
+ inputUsdPerMillion: toFiniteNumberOrNull(servicePricing.inputUsdPerMillion),
470
+ outputUsdPerMillion: toFiniteNumberOrNull(servicePricing.outputUsdPerMillion),
471
471
  };
472
472
  }
473
473
  return {
@@ -482,8 +482,8 @@ function resolvePeerPricing(peer, provider, model) {
482
482
  }
483
483
  function computeResponseTelemetry(request, responseHeaders, responseBody, selectedPeer) {
484
484
  const provider = pickProviderForPeer(selectedPeer, request);
485
- const model = extractRequestedModel(request);
486
- const pricing = resolvePeerPricing(selectedPeer, provider, model);
485
+ const service = extractRequestedService(request);
486
+ const pricing = resolvePeerPricing(selectedPeer, provider, service);
487
487
  const contentType = (responseHeaders['content-type'] ?? '').toLowerCase();
488
488
  const usageFromBody = contentType.includes('text/event-stream')
489
489
  ? parseSseUsage(responseBody)
@@ -512,7 +512,7 @@ function computeResponseTelemetry(request, responseHeaders, responseBody, select
512
512
  usage,
513
513
  pricing: {
514
514
  provider,
515
- model,
515
+ service,
516
516
  inputUsdPerMillion: pricing.inputUsdPerMillion,
517
517
  outputUsdPerMillion: pricing.outputUsdPerMillion,
518
518
  },
@@ -529,8 +529,8 @@ function attachAntseedTelemetryHeaders(upstreamHeaders, selectedPeer, telemetry,
529
529
  setFiniteNumberHeader(headers, 'x-antseed-peer-current-load', selectedPeer.currentLoad);
530
530
  setFiniteNumberHeader(headers, 'x-antseed-peer-max-concurrency', selectedPeer.maxConcurrency);
531
531
  headers['x-antseed-provider'] = telemetry.pricing.provider;
532
- if (telemetry.pricing.model) {
533
- headers['x-antseed-model'] = telemetry.pricing.model;
532
+ if (telemetry.pricing.service) {
533
+ headers['x-antseed-service'] = telemetry.pricing.service;
534
534
  }
535
535
  setFiniteNumberHeader(headers, 'x-antseed-input-usd-per-million', telemetry.pricing.inputUsdPerMillion);
536
536
  setFiniteNumberHeader(headers, 'x-antseed-output-usd-per-million', telemetry.pricing.outputUsdPerMillion);
@@ -646,7 +646,7 @@ export class BuyerProxy {
646
646
  _bgRefreshIntervalMs;
647
647
  _peerCacheTtlMs;
648
648
  _pinnedPeer;
649
- _pinnedModel;
649
+ _pinnedService;
650
650
  _stateFileWatcher = null;
651
651
  _stateWatchDebounce = null;
652
652
  _cachedPeers = [];
@@ -663,7 +663,7 @@ export class BuyerProxy {
663
663
  this._bgRefreshIntervalMs = config.backgroundRefreshIntervalMs ?? 5 * 60_000;
664
664
  this._peerCacheTtlMs = Math.max(0, config.peerCacheTtlMs ?? 30_000);
665
665
  this._pinnedPeer = config.pinnedPeerId?.toLowerCase() ?? null;
666
- this._pinnedModel = config.pinnedModel?.trim() ?? null;
666
+ this._pinnedService = config.pinnedService?.trim() ?? null;
667
667
  this._server = createServer((req, res) => {
668
668
  this._handleRequest(req, res).catch((err) => {
669
669
  log('Unhandled error:', err);
@@ -726,15 +726,15 @@ export class BuyerProxy {
726
726
  try {
727
727
  const raw = await readFile(BUYER_STATE_FILE, 'utf-8');
728
728
  const parsed = JSON.parse(raw);
729
- const pinnedModel = typeof parsed.pinnedModel === 'string' && parsed.pinnedModel.trim().length > 0
730
- ? parsed.pinnedModel.trim()
729
+ const pinnedService = typeof parsed.pinnedService === 'string' && parsed.pinnedService.trim().length > 0
730
+ ? parsed.pinnedService.trim()
731
731
  : null;
732
732
  const pinnedPeer = typeof parsed.pinnedPeerId === 'string' && parsed.pinnedPeerId.trim().length > 0
733
733
  ? parsed.pinnedPeerId.trim().toLowerCase()
734
734
  : null;
735
- this._pinnedModel = pinnedModel;
735
+ this._pinnedService = pinnedService;
736
736
  this._pinnedPeer = pinnedPeer;
737
- log(`Session overrides reloaded: model=${pinnedModel ?? 'none'} peer=${pinnedPeer ?? 'none'}`);
737
+ log(`Session overrides reloaded: service=${pinnedService ?? 'none'} peer=${pinnedPeer ?? 'none'}`);
738
738
  }
739
739
  catch {
740
740
  // state file unreadable; keep current values
@@ -752,11 +752,11 @@ export class BuyerProxy {
752
752
  catch {
753
753
  // file doesn't exist yet
754
754
  }
755
- // When stopping, preserve whatever pinnedModel/pinnedPeerId is already
755
+ // When stopping, preserve whatever pinnedService/pinnedPeerId is already
756
756
  // in the file — the debounce may have been cancelled before
757
757
  // _reloadSessionOverrides could commit the latest CLI-written values.
758
758
  const sessionOverrides = state === 'connected'
759
- ? { pinnedModel: this._pinnedModel, pinnedPeerId: this._pinnedPeer }
759
+ ? { pinnedService: this._pinnedService, pinnedPeerId: this._pinnedPeer }
760
760
  : {};
761
761
  const data = {
762
762
  ...existing,
@@ -819,7 +819,7 @@ export class BuyerProxy {
819
819
  }
820
820
  }
821
821
  }
822
- _buildRouteKey(path, requestProtocol, requestedModel, explicitProvider) {
822
+ _buildRouteKey(path, requestProtocol, requestedService, explicitProvider) {
823
823
  const normalizedPath = path.split('?')[0]?.trim().toLowerCase() ?? '/';
824
824
  const pathGroup = (normalizedPath.startsWith('/v1/messages')
825
825
  ? '/v1/messages'
@@ -833,7 +833,7 @@ export class BuyerProxy {
833
833
  return [
834
834
  pathGroup,
835
835
  requestProtocol ?? 'unknown-protocol',
836
- requestedModel ?? 'unknown-model',
836
+ requestedService ?? 'unknown-service',
837
837
  explicitProvider ?? 'auto-provider',
838
838
  ].join('|');
839
839
  }
@@ -991,14 +991,14 @@ export class BuyerProxy {
991
991
  body: new Uint8Array(body),
992
992
  };
993
993
  // Snapshot both session overrides together before any await so a concurrent
994
- // _reloadSessionOverrides() cannot produce a model/peer mismatch mid-request.
995
- const effectivePinnedModel = this._pinnedModel;
994
+ // _reloadSessionOverrides() cannot produce a service/peer mismatch mid-request.
995
+ const effectivePinnedService = this._pinnedService;
996
996
  const effectivePinnedPeer = this._pinnedPeer;
997
- if (effectivePinnedModel) {
998
- const { body: rewrittenBody, headers: rewrittenHeaders } = rewriteModelInBody(serializedReq.body, serializedReq.headers, effectivePinnedModel);
997
+ if (effectivePinnedService) {
998
+ const { body: rewrittenBody, headers: rewrittenHeaders } = rewriteModelInBody(serializedReq.body, serializedReq.headers, effectivePinnedService);
999
999
  if (rewrittenBody !== serializedReq.body) {
1000
1000
  serializedReq = { ...serializedReq, body: rewrittenBody, headers: rewrittenHeaders };
1001
- log(`Model override applied: ${effectivePinnedModel}`);
1001
+ log(`Service override applied: ${effectivePinnedService}`);
1002
1002
  }
1003
1003
  }
1004
1004
  const clientAbortController = new AbortController();
@@ -1027,15 +1027,15 @@ export class BuyerProxy {
1027
1027
  res.end('No sellers available on the network. Is a seeder running?');
1028
1028
  return;
1029
1029
  }
1030
- const requestProtocol = detectRequestModelApiProtocol(serializedReq);
1031
- const requestedModel = extractRequestedModel(serializedReq);
1032
- log(`Routing: protocol=${requestProtocol ?? 'null'} model=${requestedModel ?? 'null'}`);
1030
+ const requestProtocol = detectRequestServiceApiProtocol(serializedReq);
1031
+ const requestedService = extractRequestedService(serializedReq);
1032
+ log(`Routing: protocol=${requestProtocol ?? 'null'} service=${requestedService ?? 'null'}`);
1033
1033
  const explicitProvider = getExplicitProviderOverride(serializedReq);
1034
1034
  const explicitPeerId = getExplicitPeerIdOverride(serializedReq, effectivePinnedPeer ?? undefined);
1035
1035
  const preferredPeerId = getPreferredPeerIdHint(serializedReq);
1036
1036
  log(`Routing hints: provider=${explicitProvider ?? 'auto'} pin-peer=${explicitPeerId ?? 'none'} prefer-peer=${preferredPeerId ?? 'none'}`);
1037
- const routeKey = this._buildRouteKey(serializedReq.path, requestProtocol, requestedModel, explicitProvider);
1038
- const selectPeers = (candidateSources) => selectCandidatePeersForRouting(candidateSources, requestProtocol, requestedModel, explicitProvider);
1037
+ const routeKey = this._buildRouteKey(serializedReq.path, requestProtocol, requestedService, explicitProvider);
1038
+ const selectPeers = (candidateSources) => selectCandidatePeersForRouting(candidateSources, requestProtocol, requestedService, explicitProvider);
1039
1039
  let hasForcedRefresh = false;
1040
1040
  const refreshPeerSelection = async (reason) => {
1041
1041
  if (hasForcedRefresh) {
@@ -1071,7 +1071,7 @@ export class BuyerProxy {
1071
1071
  }
1072
1072
  const preferredProviders = explicitProvider
1073
1073
  ? []
1074
- : inferPreferredProvidersForRequest(requestProtocol, requestedModel);
1074
+ : inferPreferredProvidersForRequest(requestProtocol, requestedService);
1075
1075
  let hasPreferredProviderCandidate = preferredProviders.length > 0
1076
1076
  && routingPeers.some((peer) => {
1077
1077
  const provider = routingPlans.get(peer.peerId)?.provider?.trim().toLowerCase();
@@ -1110,9 +1110,9 @@ export class BuyerProxy {
1110
1110
  const peerDiscovered = discoveredPeers.some((peer) => peer.peerId.toLowerCase() === explicitPeerId);
1111
1111
  const protocolLabel = requestProtocol ? `protocol=${requestProtocol}` : 'protocol=unknown';
1112
1112
  const providerLabel = explicitProvider ? `provider=${explicitProvider}` : 'provider=auto';
1113
- const modelLabel = requestedModel ? `model=${requestedModel}` : 'model=none';
1113
+ const serviceLabel = requestedService ? `service=${requestedService}` : 'service=none';
1114
1114
  const mismatchHint = peerDiscovered
1115
- ? `Peer is discoverable but filtered as incompatible (${protocolLabel}, ${providerLabel}, ${modelLabel}).`
1115
+ ? `Peer is discoverable but filtered as incompatible (${protocolLabel}, ${providerLabel}, ${serviceLabel}).`
1116
1116
  : 'Peer is not discoverable right now.';
1117
1117
  log(`Pinned peer ${explicitPeerId.slice(0, 12)}... not found in candidate list (${source})`);
1118
1118
  res.writeHead(502, { 'content-type': 'text/plain' });
@@ -1120,7 +1120,7 @@ export class BuyerProxy {
1120
1120
  return;
1121
1121
  }
1122
1122
  log(`Using pinned peer ${selectedPeer.peerId.slice(0, 12)}...`);
1123
- const result = await this._dispatchToPeer(res, serializedReq, selectedPeer, routeKey, pinnedRoutePlans, requestProtocol, requestedModel, explicitProvider, router, RETRYABLE_STATUS_CODES, clientAbortController.signal);
1123
+ const result = await this._dispatchToPeer(res, serializedReq, selectedPeer, routeKey, pinnedRoutePlans, requestProtocol, requestedService, explicitProvider, router, RETRYABLE_STATUS_CODES, clientAbortController.signal);
1124
1124
  if (!result.done) {
1125
1125
  this._forgetSuccessfulPeer(routeKey, selectedPeer.peerId);
1126
1126
  // Pinned peer returned a retryable error, but we don't retry — send error to client
@@ -1169,7 +1169,7 @@ export class BuyerProxy {
1169
1169
  }
1170
1170
  }
1171
1171
  // Fallback to the latest globally successful peer.
1172
- if (!selectedPeer && attempt === 0 && this._lastSuccessfulPeerId && !requestedModel) {
1172
+ if (!selectedPeer && attempt === 0 && this._lastSuccessfulPeerId && !requestedService) {
1173
1173
  const remembered = availableCandidates.find((peer) => peer.peerId === this._lastSuccessfulPeerId) ?? null;
1174
1174
  if (remembered) {
1175
1175
  selectedPeer = remembered;
@@ -1184,7 +1184,7 @@ export class BuyerProxy {
1184
1184
  log(`Preferring requested peer ${selectedPeer.peerId.slice(0, 12)}...`);
1185
1185
  }
1186
1186
  }
1187
- // Strongly prefer providers that match the requested model family (e.g. claude-* -> claude/anthropic providers).
1187
+ // Strongly prefer providers that match the requested service family (e.g. claude-* -> claude/anthropic providers).
1188
1188
  if (!selectedPeer && attempt === 0 && preferredProviders.length > 0) {
1189
1189
  const providerMatchedPeers = availableCandidates.filter((peer) => {
1190
1190
  const plannedProvider = routingPlans.get(peer.peerId)?.provider?.trim().toLowerCase();
@@ -1196,7 +1196,7 @@ export class BuyerProxy {
1196
1196
  : providerMatchedPeers[0] ?? null;
1197
1197
  if (selectedPeer) {
1198
1198
  const plannedProvider = routingPlans.get(selectedPeer.peerId)?.provider ?? 'unknown';
1199
- log(`Preferring model-matched provider "${plannedProvider}" for model "${requestedModel ?? 'unknown'}"`);
1199
+ log(`Preferring service-matched provider "${plannedProvider}" for service "${requestedService ?? 'unknown'}"`);
1200
1200
  }
1201
1201
  }
1202
1202
  }
@@ -1214,7 +1214,7 @@ export class BuyerProxy {
1214
1214
  }
1215
1215
  // Prefer peers that can serve the request protocol directly without adapter transform.
1216
1216
  if (!selectedPeer && requestProtocol === 'anthropic-messages') {
1217
- const shouldPreferDirect = !requestedModel || /claude|anthropic/i.test(requestedModel);
1217
+ const shouldPreferDirect = !requestedService || /claude|anthropic/i.test(requestedService);
1218
1218
  if (shouldPreferDirect) {
1219
1219
  const directPeers = availableCandidates.filter((peer) => {
1220
1220
  const plan = routingPlans.get(peer.peerId);
@@ -1240,7 +1240,7 @@ export class BuyerProxy {
1240
1240
  if (!selectedPeer)
1241
1241
  break;
1242
1242
  triedPeerIds.add(selectedPeer.peerId);
1243
- const result = await this._dispatchToPeer(res, serializedReq, selectedPeer, routeKey, routingPlans, requestProtocol, requestedModel, explicitProvider, router, RETRYABLE_STATUS_CODES, clientAbortController.signal);
1243
+ const result = await this._dispatchToPeer(res, serializedReq, selectedPeer, routeKey, routingPlans, requestProtocol, requestedService, explicitProvider, router, RETRYABLE_STATUS_CODES, clientAbortController.signal);
1244
1244
  if (result.done)
1245
1245
  return;
1246
1246
  this._forgetSuccessfulPeer(routeKey, selectedPeer.peerId);
@@ -1272,9 +1272,9 @@ export class BuyerProxy {
1272
1272
  * was sent to the client (success or non-retryable error), or retry info if the
1273
1273
  * caller should try another peer.
1274
1274
  */
1275
- async _dispatchToPeer(res, serializedReq, selectedPeer, routeKey, routePlanByPeerId, requestProtocol, requestedModel, explicitProvider, router, retryableStatusCodes, requestSignal) {
1275
+ async _dispatchToPeer(res, serializedReq, selectedPeer, routeKey, routePlanByPeerId, requestProtocol, requestedService, explicitProvider, router, retryableStatusCodes, requestSignal) {
1276
1276
  const selectedRoutePlan = routePlanByPeerId.get(selectedPeer.peerId)
1277
- ?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedModel, explicitProvider);
1277
+ ?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedService, explicitProvider);
1278
1278
  if (!selectedRoutePlan) {
1279
1279
  return { done: false, statusCode: 502, responseBody: Buffer.from('No compatible provider route'), responseHeaders: { 'content-type': 'text/plain' }, errorMessage: null };
1280
1280
  }
@@ -1488,11 +1488,11 @@ export class BuyerProxy {
1488
1488
  tokens: 0,
1489
1489
  });
1490
1490
  }
1491
- // Avoid poisoning routing cache from control-plane model enumeration failures.
1492
- // Some peers can time out on /v1/models while still serving inference paths.
1491
+ // Avoid poisoning routing cache from control-plane service enumeration failures.
1492
+ // Some peers can time out on /v1/models (service probe) while still serving inference paths.
1493
1493
  const normalizedPath = requestForPeer.path.toLowerCase();
1494
- const isControlPlaneModelsRequest = normalizedPath.startsWith('/v1/models');
1495
- if (isControlPlaneModelsRequest) {
1494
+ const isControlPlaneServicesRequest = normalizedPath.startsWith('/v1/models');
1495
+ if (isControlPlaneServicesRequest) {
1496
1496
  log(`Skipping peer eviction for control-plane failure on ${requestForPeer.path}`);
1497
1497
  }
1498
1498
  else if (connectionChurnError) {