@antseed/cli 0.1.4 → 0.1.5

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/README.md CHANGED
@@ -35,8 +35,8 @@ antseed seed --provider anthropic
35
35
  **Routers** select peers and proxy requests (consumer mode):
36
36
 
37
37
  ```bash
38
- antseed plugin add @antseed/router-local-proxy
39
- antseed connect --router local-proxy
38
+ antseed plugin add @antseed/router-local
39
+ antseed connect --router local
40
40
  ```
41
41
 
42
42
  Run `antseed init` to install all trusted plugins interactively.
@@ -72,10 +72,13 @@ Initialize a new config:
72
72
  antseed config init
73
73
  ```
74
74
 
75
- Pricing is configured in USD per 1M tokens with role-specific defaults and optional provider/model overrides:
75
+ Pricing is configured in USD per 1M tokens with role-specific defaults and optional provider/model overrides. You can also set node `displayName` and optional per-model category tags announced in discovery metadata:
76
76
 
77
77
  ```json
78
78
  {
79
+ "identity": {
80
+ "displayName": "Acme Inference - us-east-1"
81
+ },
79
82
  "seller": {
80
83
  "pricing": {
81
84
  "defaults": {
@@ -92,6 +95,11 @@ Pricing is configured in USD per 1M tokens with role-specific defaults and optio
92
95
  }
93
96
  }
94
97
  }
98
+ },
99
+ "modelCategories": {
100
+ "anthropic": {
101
+ "claude-sonnet-4-5-20250929": ["coding", "privacy"]
102
+ }
95
103
  }
96
104
  },
97
105
  "buyer": {
@@ -106,9 +114,14 @@ Pricing is configured in USD per 1M tokens with role-specific defaults and optio
106
114
  }
107
115
  ```
108
116
 
117
+ Model categories are normalized to lowercase tags. Recommended tags include: `privacy`, `legal`, `uncensored`, `coding`, `finance`, `tee` (custom tags are also allowed).
118
+
109
119
  Role-first config examples:
110
120
 
111
121
  ```bash
122
+ # Identity / metadata display name
123
+ antseed config set identity.displayName "Acme Inference - us-east-1"
124
+
112
125
  # Seller defaults
113
126
  antseed config seller set pricing.defaults.inputUsdPerMillion 12
114
127
  antseed config seller set pricing.defaults.outputUsdPerMillion 36
@@ -116,6 +129,9 @@ antseed config seller set pricing.defaults.outputUsdPerMillion 36
116
129
  # Seller per-model override for a provider
117
130
  antseed config seller set pricing.providers.anthropic.models '{"claude-sonnet-4-5-20250929":{"inputUsdPerMillion":14,"outputUsdPerMillion":42}}'
118
131
 
132
+ # Seller per-model category tags announced in metadata
133
+ antseed config seller set modelCategories.anthropic.claude-sonnet-4-5-20250929 '["coding","legal"]'
134
+
119
135
  # Buyer preferences and max pricing
120
136
  antseed config buyer set preferredProviders '["anthropic","openai"]'
121
137
  antseed config buyer set maxPricing.defaults.inputUsdPerMillion 25
@@ -126,7 +142,7 @@ Runtime-only overrides (do not write your config file):
126
142
 
127
143
  ```bash
128
144
  antseed seed --provider anthropic --input-usd-per-million 10 --output-usd-per-million 30
129
- antseed connect --router local-proxy --max-input-usd-per-million 20 --max-output-usd-per-million 60
145
+ antseed connect --router local --max-input-usd-per-million 20 --max-output-usd-per-million 60
130
146
  ```
131
147
 
132
148
  ## Settlement Runtime (Seeder)
@@ -2,15 +2,14 @@ import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
3
  import { TRUSTED_PLUGINS } from '../../plugins/registry.js';
4
4
  import { buildPluginConfig, LEGACY_PACKAGE_MAP } from '../../plugins/loader.js';
5
- test('TRUSTED_PLUGINS contains 6 official plugins', () => {
6
- assert.equal(TRUSTED_PLUGINS.length, 6);
5
+ test('TRUSTED_PLUGINS contains 5 official plugins', () => {
6
+ assert.equal(TRUSTED_PLUGINS.length, 5);
7
7
  const names = TRUSTED_PLUGINS.map(p => p.name);
8
8
  assert.ok(names.includes('anthropic'));
9
9
  assert.ok(names.includes('claude-code'));
10
- assert.ok(names.includes('openrouter'));
10
+ assert.ok(names.includes('openai'));
11
11
  assert.ok(names.includes('local-llm'));
12
- assert.ok(names.includes('local-proxy'));
13
- assert.ok(names.includes('local-chat'));
12
+ assert.ok(names.includes('local'));
14
13
  });
15
14
  test('TRUSTED_PLUGINS all have scoped package names', () => {
16
15
  for (const plugin of TRUSTED_PLUGINS) {
@@ -47,7 +46,7 @@ test('buildPluginConfig works without instanceConfig (backwards compatible)', ()
47
46
  assert.equal(result['TEST_KEY_COMPAT'], 'override');
48
47
  });
49
48
  test('LEGACY_PACKAGE_MAP maps old names to scoped names', () => {
50
- assert.equal(LEGACY_PACKAGE_MAP['antseed-provider-anthropic'], '@antseed/provider-claude-code');
51
- assert.equal(LEGACY_PACKAGE_MAP['antseed-router-claude-code'], '@antseed/router-local-proxy');
49
+ assert.equal(LEGACY_PACKAGE_MAP['antseed-provider-anthropic'], '@antseed/provider-anthropic');
50
+ assert.equal(LEGACY_PACKAGE_MAP['antseed-router-claude-code'], '@antseed/router-local');
52
51
  });
53
52
  //# sourceMappingURL=plugin.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.test.js","sourceRoot":"","sources":["../../../src/cli/commands/plugin.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.test.js","sourceRoot":"","sources":["../../../src/cli/commands/plugin.test.ts"],"names":[],"mappings":"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"}
@@ -19,7 +19,7 @@ export function createDefaultConfig() {
19
19
  },
20
20
  },
21
21
  buyer: {
22
- preferredProviders: ['anthropic', 'openai', 'claude-code', 'claude-oauth', 'openrouter', 'local-llm'],
22
+ preferredProviders: ['anthropic', 'openai', 'claude-code', 'claude-oauth', 'local-llm'],
23
23
  maxPricing: {
24
24
  defaults: {
25
25
  inputUsdPerMillion: 100,
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,QAAQ,EAAE;YACR,WAAW,EAAE,cAAc;SAC5B;QACD,SAAS,EAAE,EAAE;QACb,MAAM,EAAE;YACN,YAAY,EAAE,EAAE;YAChB,mBAAmB,EAAE,CAAC;YACtB,gBAAgB,EAAE,EAAE;YACpB,OAAO,EAAE;gBACP,QAAQ,EAAE;oBACR,kBAAkB,EAAE,EAAE;oBACtB,mBAAmB,EAAE,EAAE;iBACxB;aACF;SACF;QACD,KAAK,EAAE;YACL,kBAAkB,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,CAAC;YACrG,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,kBAAkB,EAAE,GAAG;oBACvB,mBAAmB,EAAE,GAAG;iBACzB;aACF;YACD,iBAAiB,EAAE,EAAE;YACrB,SAAS,EAAE,IAAI;SAChB;QACD,QAAQ,EAAE;YACR,eAAe,EAAE,QAAQ;YACzB,eAAe,EAAE,IAAI;YACrB,MAAM,EAAE;gBACN,OAAO,EAAE,YAAY;gBACrB,MAAM,EAAE,uBAAuB;gBAC/B,qBAAqB,EAAE,4CAA4C;gBACnE,mBAAmB,EAAE,4CAA4C;gBACjE,qBAAqB,EAAE,GAAG;aAC3B;SACF;QACD,OAAO,EAAE;YACP,cAAc,EAAE,EAAE;SACnB;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,QAAQ,EAAE;YACR,WAAW,EAAE,cAAc;SAC5B;QACD,SAAS,EAAE,EAAE;QACb,MAAM,EAAE;YACN,YAAY,EAAE,EAAE;YAChB,mBAAmB,EAAE,CAAC;YACtB,gBAAgB,EAAE,EAAE;YACpB,OAAO,EAAE;gBACP,QAAQ,EAAE;oBACR,kBAAkB,EAAE,EAAE;oBACtB,mBAAmB,EAAE,EAAE;iBACxB;aACF;SACF;QACD,KAAK,EAAE;YACL,kBAAkB,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,CAAC;YACvF,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,kBAAkB,EAAE,GAAG;oBACvB,mBAAmB,EAAE,GAAG;iBACzB;aACF;YACD,iBAAiB,EAAE,EAAE;YACrB,SAAS,EAAE,IAAI;SAChB;QACD,QAAQ,EAAE;YACR,eAAe,EAAE,QAAQ;YACzB,eAAe,EAAE,IAAI;YACrB,MAAM,EAAE;gBACN,OAAO,EAAE,YAAY;gBACrB,MAAM,EAAE,uBAAuB;gBAC/B,qBAAqB,EAAE,4CAA4C;gBACnE,mBAAmB,EAAE,4CAA4C;gBACjE,qBAAqB,EAAE,GAAG;aAC3B;SACF;QACD,OAAO,EAAE;YACP,cAAc,EAAE,EAAE;SACnB;KACF,CAAC;AACJ,CAAC"}
@@ -64,7 +64,7 @@ export function buildPluginConfig(configKeys, runtimeOverrides, instanceConfig)
64
64
  }
65
65
  /** Map legacy package names to current names */
66
66
  export const LEGACY_PACKAGE_MAP = {
67
- 'antseed-provider-anthropic': '@antseed/provider-claude-code',
68
- 'antseed-router-claude-code': '@antseed/router-local-proxy',
67
+ 'antseed-provider-anthropic': '@antseed/provider-anthropic',
68
+ 'antseed-router-claude-code': '@antseed/router-local',
69
69
  };
70
70
  //# sourceMappingURL=loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/plugins/registry.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,EAqC1C,CAAA"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/plugins/registry.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,EA+B1C,CAAA"}
@@ -12,10 +12,10 @@ export const TRUSTED_PLUGINS = [
12
12
  package: '@antseed/provider-claude-code',
13
13
  },
14
14
  {
15
- name: 'openrouter',
15
+ name: 'openai',
16
16
  type: 'provider',
17
- description: 'OpenRouter multi-model provider (API key)',
18
- package: '@antseed/provider-openrouter',
17
+ description: 'OpenAI-compatible provider (OpenAI, Together, OpenRouter, API key)',
18
+ package: '@antseed/provider-openai',
19
19
  },
20
20
  {
21
21
  name: 'local-llm',
@@ -24,16 +24,10 @@ export const TRUSTED_PLUGINS = [
24
24
  package: '@antseed/provider-local-llm',
25
25
  },
26
26
  {
27
- name: 'local-proxy',
27
+ name: 'local',
28
28
  type: 'router',
29
- description: 'Local HTTP proxy for Claude Code, Aider, Codex',
30
- package: '@antseed/router-local-proxy',
31
- },
32
- {
33
- name: 'local-chat',
34
- type: 'router',
35
- description: 'Local desktop chat router',
36
- package: '@antseed/router-local-chat',
29
+ description: 'Local router for Claude Code, Aider, Codex',
30
+ package: '@antseed/router-local',
37
31
  },
38
32
  ];
39
33
  //# sourceMappingURL=registry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/plugins/registry.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC9C;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,kCAAkC;QAC/C,OAAO,EAAE,6BAA6B;KACvC;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,8CAA8C;QAC3D,OAAO,EAAE,+BAA+B;KACzC;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,2CAA2C;QACxD,OAAO,EAAE,8BAA8B;KACxC;IACD;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,wCAAwC;QACrD,OAAO,EAAE,6BAA6B;KACvC;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,gDAAgD;QAC7D,OAAO,EAAE,6BAA6B;KACvC;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,2BAA2B;QACxC,OAAO,EAAE,4BAA4B;KACtC;CACF,CAAA"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/plugins/registry.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC9C;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,kCAAkC;QAC/C,OAAO,EAAE,6BAA6B;KACvC;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,8CAA8C;QAC3D,OAAO,EAAE,+BAA+B;KACzC;IACD;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,oEAAoE;QACjF,OAAO,EAAE,0BAA0B;KACpC;IACD;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,wCAAwC;QACrD,OAAO,EAAE,6BAA6B;KACvC;IACD;QACE,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,4CAA4C;QACzD,OAAO,EAAE,uBAAuB;KACjC;CACF,CAAA"}
@@ -1,10 +1,20 @@
1
- import type { AntseedNode } from '@antseed/node';
1
+ import type { AntseedNode, PeerInfo } from '@antseed/node';
2
+ import { type ModelApiProtocol, type TargetProtocolSelection } from './model-api-adapter.js';
2
3
  export interface BuyerProxyConfig {
3
4
  port: number;
4
5
  node: AntseedNode;
5
6
  /** How long to cache discovered peers before re-querying DHT (ms). Default: 30000 */
6
7
  peerCacheTtlMs?: number;
7
8
  }
9
+ type PeerProtocolRoutePlan = {
10
+ provider: string;
11
+ selection: TargetProtocolSelection | null;
12
+ };
13
+ export type CandidatePeerRouteSelection = {
14
+ candidatePeers: PeerInfo[];
15
+ routePlanByPeerId: Map<string, PeerProtocolRoutePlan>;
16
+ };
17
+ export declare function selectCandidatePeersForRouting(peers: PeerInfo[], requestProtocol: ModelApiProtocol | null, requestedModel: string | null, explicitProvider: string | null): CandidatePeerRouteSelection;
8
18
  /**
9
19
  * Local HTTP proxy that forwards requests to P2P sellers.
10
20
  *
@@ -27,4 +37,5 @@ export declare class BuyerProxy {
27
37
  private _formatPeerSelectionDiagnostics;
28
38
  private _handleRequest;
29
39
  }
40
+ export {};
30
41
  //# sourceMappingURL=buyer-proxy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buyer-proxy.d.ts","sourceRoot":"","sources":["../../src/proxy/buyer-proxy.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAShD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,WAAW,CAAA;IACjB,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AA6YD;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IAExC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAAI;gBAEf,MAAM,EAAE,gBAAgB;IAe9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAMb,wBAAwB;YAsDxB,SAAS;IAkCvB,OAAO,CAAC,+BAA+B;YAuBzB,cAAc;CAoL7B"}
1
+ {"version":3,"file":"buyer-proxy.d.ts","sourceRoot":"","sources":["../../src/proxy/buyer-proxy.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAKT,MAAM,eAAe,CAAA;AACtB,OAAO,EAGL,KAAK,gBAAgB,EAErB,KAAK,uBAAuB,EAG7B,MAAM,wBAAwB,CAAA;AAE/B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,WAAW,CAAA;IACjB,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AA+BD,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,uBAAuB,GAAG,IAAI,CAAA;CAC1C,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,cAAc,EAAE,QAAQ,EAAE,CAAA;IAC1B,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAA;CACtD,CAAA;AA0ED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,QAAQ,EAAE,EACjB,eAAe,EAAE,gBAAgB,GAAG,IAAI,EACxC,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAC9B,2BAA2B,CAoB7B;AAwWD;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IAExC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAAI;gBAEf,MAAM,EAAE,gBAAgB;IAe9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAMb,wBAAwB;YAsDxB,SAAS;IAkCvB,OAAO,CAAC,+BAA+B;YAuBzB,cAAc;CAiQ7B"}
@@ -3,12 +3,81 @@ import { randomUUID } from 'node:crypto';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { join } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
+ import { detectRequestModelApiProtocol, inferProviderDefaultModelApiProtocols, selectTargetProtocolForRequest, transformAnthropicMessagesRequestToOpenAIChat, transformOpenAIChatResponseToAnthropicMessage, } from './model-api-adapter.js';
6
7
  const DAEMON_STATE_FILE = join(homedir(), '.antseed', 'daemon.state.json');
7
8
  const DEBUG = () => ['1', 'true', 'yes', 'on'].includes((process.env['ANTSEED_DEBUG'] ?? '').trim().toLowerCase());
8
9
  function log(...args) {
9
10
  if (DEBUG())
10
11
  console.log('[proxy]', ...args);
11
12
  }
13
+ function getExplicitProviderOverride(request) {
14
+ const provider = request.headers['x-antseed-provider']?.trim().toLowerCase();
15
+ return provider && provider.length > 0 ? provider : null;
16
+ }
17
+ function getPeerProviderProtocols(peer, provider, requestedModel) {
18
+ const fromMetadata = peer.providerModelApiProtocols?.[provider]?.models;
19
+ if (fromMetadata) {
20
+ if (requestedModel && fromMetadata[requestedModel]?.length) {
21
+ return Array.from(new Set(fromMetadata[requestedModel]));
22
+ }
23
+ const merged = Object.values(fromMetadata).flat();
24
+ if (merged.length > 0) {
25
+ return Array.from(new Set(merged));
26
+ }
27
+ }
28
+ return inferProviderDefaultModelApiProtocols(provider);
29
+ }
30
+ function resolvePeerRoutePlan(peer, requestProtocol, requestedModel, explicitProvider) {
31
+ const providers = peer.providers
32
+ .map((provider) => provider.trim().toLowerCase())
33
+ .filter((provider) => provider.length > 0);
34
+ if (providers.length === 0) {
35
+ return null;
36
+ }
37
+ if (explicitProvider && !providers.includes(explicitProvider)) {
38
+ return null;
39
+ }
40
+ const candidates = explicitProvider ? [explicitProvider] : providers;
41
+ if (!requestProtocol) {
42
+ const provider = candidates[0];
43
+ return provider ? { provider, selection: null } : null;
44
+ }
45
+ let transformedFallback = null;
46
+ for (const provider of candidates) {
47
+ const supportedProtocols = getPeerProviderProtocols(peer, provider, requestedModel);
48
+ const selection = selectTargetProtocolForRequest(requestProtocol, supportedProtocols);
49
+ if (!selection) {
50
+ continue;
51
+ }
52
+ if (!selection.requiresTransform) {
53
+ return { provider, selection };
54
+ }
55
+ if (!transformedFallback) {
56
+ transformedFallback = { provider, selection };
57
+ }
58
+ }
59
+ return transformedFallback;
60
+ }
61
+ export function selectCandidatePeersForRouting(peers, requestProtocol, requestedModel, explicitProvider) {
62
+ const routePlanByPeerId = new Map();
63
+ if (!requestProtocol && !explicitProvider) {
64
+ return {
65
+ candidatePeers: peers,
66
+ routePlanByPeerId,
67
+ };
68
+ }
69
+ const candidatePeers = peers.filter((peer) => {
70
+ const plan = resolvePeerRoutePlan(peer, requestProtocol, requestedModel, explicitProvider);
71
+ if (!plan)
72
+ return false;
73
+ routePlanByPeerId.set(peer.peerId, plan);
74
+ return true;
75
+ });
76
+ return {
77
+ candidatePeers,
78
+ routePlanByPeerId,
79
+ };
80
+ }
12
81
  function parseTokenCount(value) {
13
82
  const parsed = Number(value);
14
83
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -130,9 +199,9 @@ function parseJsonUsage(body) {
130
199
  }
131
200
  }
132
201
  function pickProviderForPeer(peer, request) {
133
- const explicit = request.headers['x-antseed-provider']?.trim();
134
- if (explicit && explicit.length > 0) {
135
- return explicit.toLowerCase();
202
+ const explicit = getExplicitProviderOverride(request);
203
+ if (explicit) {
204
+ return explicit;
136
205
  }
137
206
  if (request.path.startsWith('/v1/messages') && peer.providers.includes('anthropic')) {
138
207
  return 'anthropic';
@@ -160,33 +229,42 @@ function extractRequestedModel(request) {
160
229
  return null;
161
230
  }
162
231
  }
232
+ function toFiniteNumberOrNull(value) {
233
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
234
+ }
235
+ function setFiniteNumberHeader(headers, name, value) {
236
+ const finite = toFiniteNumberOrNull(value);
237
+ if (finite !== null) {
238
+ headers[name] = String(finite);
239
+ }
240
+ }
241
+ function setPeerIdentityHeaders(headers, selectedPeer) {
242
+ headers['x-antseed-peer-id'] = selectedPeer.peerId;
243
+ if (selectedPeer.publicAddress) {
244
+ headers['x-antseed-peer-address'] = selectedPeer.publicAddress;
245
+ }
246
+ if (selectedPeer.providers.length > 0) {
247
+ headers['x-antseed-peer-providers'] = selectedPeer.providers.join(',');
248
+ }
249
+ }
163
250
  function resolvePeerPricing(peer, provider, model) {
164
251
  const providerPricing = peer.providerPricing?.[provider];
165
252
  if (providerPricing) {
166
- if (model && providerPricing.models?.[model]) {
253
+ const modelPricing = model ? providerPricing.models?.[model] : undefined;
254
+ if (modelPricing) {
167
255
  return {
168
- inputUsdPerMillion: Number.isFinite(providerPricing.models[model].inputUsdPerMillion)
169
- ? providerPricing.models[model].inputUsdPerMillion
170
- : null,
171
- outputUsdPerMillion: Number.isFinite(providerPricing.models[model].outputUsdPerMillion)
172
- ? providerPricing.models[model].outputUsdPerMillion
173
- : null,
256
+ inputUsdPerMillion: toFiniteNumberOrNull(modelPricing.inputUsdPerMillion),
257
+ outputUsdPerMillion: toFiniteNumberOrNull(modelPricing.outputUsdPerMillion),
174
258
  };
175
259
  }
176
260
  return {
177
- inputUsdPerMillion: Number.isFinite(providerPricing.defaults.inputUsdPerMillion)
178
- ? providerPricing.defaults.inputUsdPerMillion
179
- : null,
180
- outputUsdPerMillion: Number.isFinite(providerPricing.defaults.outputUsdPerMillion)
181
- ? providerPricing.defaults.outputUsdPerMillion
182
- : null,
261
+ inputUsdPerMillion: toFiniteNumberOrNull(providerPricing.defaults.inputUsdPerMillion),
262
+ outputUsdPerMillion: toFiniteNumberOrNull(providerPricing.defaults.outputUsdPerMillion),
183
263
  };
184
264
  }
185
- const input = peer.defaultInputUsdPerMillion;
186
- const output = peer.defaultOutputUsdPerMillion;
187
265
  return {
188
- inputUsdPerMillion: Number.isFinite(input) ? input : null,
189
- outputUsdPerMillion: Number.isFinite(output) ? output : null,
266
+ inputUsdPerMillion: toFiniteNumberOrNull(peer.defaultInputUsdPerMillion),
267
+ outputUsdPerMillion: toFiniteNumberOrNull(peer.defaultOutputUsdPerMillion),
190
268
  };
191
269
  }
192
270
  function computeResponseTelemetry(request, responseHeaders, responseBody, selectedPeer) {
@@ -232,35 +310,17 @@ function attachAntseedTelemetryHeaders(upstreamHeaders, selectedPeer, telemetry,
232
310
  const headers = { ...upstreamHeaders };
233
311
  headers['x-antseed-request-id'] = requestId;
234
312
  headers['x-antseed-latency-ms'] = String(Math.max(0, Math.floor(latencyMs)));
235
- headers['x-antseed-peer-id'] = selectedPeer.peerId;
236
- if (selectedPeer.publicAddress) {
237
- headers['x-antseed-peer-address'] = selectedPeer.publicAddress;
238
- }
239
- if (selectedPeer.providers.length > 0) {
240
- headers['x-antseed-peer-providers'] = selectedPeer.providers.join(',');
241
- }
242
- if (typeof selectedPeer.reputationScore === 'number' && Number.isFinite(selectedPeer.reputationScore)) {
243
- headers['x-antseed-peer-reputation'] = String(selectedPeer.reputationScore);
244
- }
245
- if (typeof selectedPeer.trustScore === 'number' && Number.isFinite(selectedPeer.trustScore)) {
246
- headers['x-antseed-peer-trust-score'] = String(selectedPeer.trustScore);
247
- }
248
- if (typeof selectedPeer.currentLoad === 'number' && Number.isFinite(selectedPeer.currentLoad)) {
249
- headers['x-antseed-peer-current-load'] = String(selectedPeer.currentLoad);
250
- }
251
- if (typeof selectedPeer.maxConcurrency === 'number' && Number.isFinite(selectedPeer.maxConcurrency)) {
252
- headers['x-antseed-peer-max-concurrency'] = String(selectedPeer.maxConcurrency);
253
- }
313
+ setPeerIdentityHeaders(headers, selectedPeer);
314
+ setFiniteNumberHeader(headers, 'x-antseed-peer-reputation', selectedPeer.reputationScore);
315
+ setFiniteNumberHeader(headers, 'x-antseed-peer-trust-score', selectedPeer.trustScore);
316
+ setFiniteNumberHeader(headers, 'x-antseed-peer-current-load', selectedPeer.currentLoad);
317
+ setFiniteNumberHeader(headers, 'x-antseed-peer-max-concurrency', selectedPeer.maxConcurrency);
254
318
  headers['x-antseed-provider'] = telemetry.pricing.provider;
255
319
  if (telemetry.pricing.model) {
256
320
  headers['x-antseed-model'] = telemetry.pricing.model;
257
321
  }
258
- if (telemetry.pricing.inputUsdPerMillion !== null) {
259
- headers['x-antseed-input-usd-per-million'] = String(telemetry.pricing.inputUsdPerMillion);
260
- }
261
- if (telemetry.pricing.outputUsdPerMillion !== null) {
262
- headers['x-antseed-output-usd-per-million'] = String(telemetry.pricing.outputUsdPerMillion);
263
- }
322
+ setFiniteNumberHeader(headers, 'x-antseed-input-usd-per-million', telemetry.pricing.inputUsdPerMillion);
323
+ setFiniteNumberHeader(headers, 'x-antseed-output-usd-per-million', telemetry.pricing.outputUsdPerMillion);
264
324
  headers['x-antseed-token-source'] = telemetry.usage.source;
265
325
  headers['x-antseed-input-tokens'] = String(telemetry.usage.inputTokens);
266
326
  headers['x-antseed-output-tokens'] = String(telemetry.usage.outputTokens);
@@ -273,13 +333,7 @@ function attachAntseedTelemetryHeaders(upstreamHeaders, selectedPeer, telemetry,
273
333
  function attachStreamingAntseedHeaders(upstreamHeaders, selectedPeer, requestId) {
274
334
  const headers = { ...upstreamHeaders };
275
335
  headers['x-antseed-request-id'] = requestId;
276
- headers['x-antseed-peer-id'] = selectedPeer.peerId;
277
- if (selectedPeer.publicAddress) {
278
- headers['x-antseed-peer-address'] = selectedPeer.publicAddress;
279
- }
280
- if (selectedPeer.providers.length > 0) {
281
- headers['x-antseed-peer-providers'] = selectedPeer.providers.join(',');
282
- }
336
+ setPeerIdentityHeaders(headers, selectedPeer);
283
337
  return headers;
284
338
  }
285
339
  function requestWantsStreaming(headers, body) {
@@ -492,9 +546,26 @@ export class BuyerProxy {
492
546
  res.end('No sellers available on the network. Is a seeder running?');
493
547
  return;
494
548
  }
549
+ const requestProtocol = detectRequestModelApiProtocol(serializedReq);
550
+ const requestedModel = extractRequestedModel(serializedReq);
551
+ const explicitProvider = getExplicitProviderOverride(serializedReq);
552
+ const { candidatePeers, routePlanByPeerId, } = selectCandidatePeersForRouting(peers, requestProtocol, requestedModel, explicitProvider);
553
+ if (candidatePeers.length === 0) {
554
+ const diagnostics = this._formatPeerSelectionDiagnostics(peers);
555
+ res.writeHead(502, { 'content-type': 'text/plain' });
556
+ if (requestProtocol) {
557
+ const protocolLabel = requestProtocol;
558
+ const providerLabel = explicitProvider ? ` for provider "${explicitProvider}"` : '';
559
+ res.end(`No peers support ${protocolLabel}${providerLabel}. ${diagnostics}`);
560
+ }
561
+ else {
562
+ res.end(`No peers advertise provider "${explicitProvider}". ${diagnostics}`);
563
+ }
564
+ return;
565
+ }
495
566
  // Use router to select peer
496
567
  const router = this._node.router;
497
- const localPeers = peers.filter((peer) => isLoopbackPeer(peer));
568
+ const localPeers = candidatePeers.filter((peer) => isLoopbackPeer(peer));
498
569
  let selectedPeer = null;
499
570
  if (localPeers.length > 0) {
500
571
  selectedPeer = router
@@ -506,29 +577,76 @@ export class BuyerProxy {
506
577
  }
507
578
  if (!selectedPeer) {
508
579
  selectedPeer = router
509
- ? router.selectPeer(serializedReq, peers)
510
- : peers[0] ?? null;
580
+ ? router.selectPeer(serializedReq, candidatePeers)
581
+ : candidatePeers[0] ?? null;
511
582
  }
512
583
  if (!selectedPeer) {
513
- const diagnostics = this._formatPeerSelectionDiagnostics(peers);
584
+ const diagnostics = this._formatPeerSelectionDiagnostics(candidatePeers);
514
585
  log('Router could not select a peer.', diagnostics);
515
586
  res.writeHead(502, { 'content-type': 'text/plain' });
516
587
  res.end(`Router could not select a suitable peer. ${diagnostics}`);
517
588
  return;
518
589
  }
590
+ const selectedRoutePlan = routePlanByPeerId.get(selectedPeer.peerId)
591
+ ?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedModel, explicitProvider);
592
+ if (!selectedRoutePlan) {
593
+ const diagnostics = this._formatPeerSelectionDiagnostics(candidatePeers);
594
+ res.writeHead(502, { 'content-type': 'text/plain' });
595
+ res.end(`No compatible provider route found for selected peer. ${diagnostics}`);
596
+ return;
597
+ }
598
+ let requestForPeer = {
599
+ ...serializedReq,
600
+ headers: {
601
+ ...serializedReq.headers,
602
+ 'x-antseed-provider': selectedRoutePlan.provider,
603
+ },
604
+ };
605
+ let adaptResponse = null;
606
+ let forceDisableUpstreamStreaming = false;
607
+ if (selectedRoutePlan.selection?.requiresTransform) {
608
+ if (requestProtocol === 'anthropic-messages'
609
+ && selectedRoutePlan.selection.targetProtocol === 'openai-chat-completions') {
610
+ log(`Applying protocol adapter anthropic-messages -> openai-chat-completions via provider "${selectedRoutePlan.provider}"`);
611
+ const transformed = transformAnthropicMessagesRequestToOpenAIChat(requestForPeer);
612
+ if (!transformed) {
613
+ res.writeHead(502, { 'content-type': 'text/plain' });
614
+ res.end('Failed to transform Anthropic request for selected provider protocol');
615
+ return;
616
+ }
617
+ requestForPeer = {
618
+ ...transformed.request,
619
+ headers: {
620
+ ...transformed.request.headers,
621
+ 'x-antseed-provider': selectedRoutePlan.provider,
622
+ },
623
+ };
624
+ adaptResponse = (response) => transformOpenAIChatResponseToAnthropicMessage(response, {
625
+ streamRequested: transformed.streamRequested,
626
+ fallbackModel: transformed.requestedModel,
627
+ });
628
+ forceDisableUpstreamStreaming = true;
629
+ }
630
+ else {
631
+ res.writeHead(502, { 'content-type': 'text/plain' });
632
+ res.end('Unsupported protocol transformation path');
633
+ return;
634
+ }
635
+ }
519
636
  log(`Routing to peer ${selectedPeer.peerId.slice(0, 12)}...`);
520
637
  // Forward through P2P
521
- const wantsStreaming = requestWantsStreaming(headers, serializedReq.body);
638
+ const wantsStreaming = !forceDisableUpstreamStreaming
639
+ && requestWantsStreaming(requestForPeer.headers, requestForPeer.body);
522
640
  const startTime = Date.now();
523
641
  try {
524
642
  if (wantsStreaming) {
525
643
  let streamed = false;
526
- const response = await this._node.sendRequestStream(selectedPeer, serializedReq, {
644
+ const response = await this._node.sendRequestStream(selectedPeer, requestForPeer, {
527
645
  onResponseStart: (startResponse, metadata) => {
528
646
  if (!metadata.streaming)
529
647
  return;
530
648
  streamed = true;
531
- const streamingHeaders = attachStreamingAntseedHeaders(startResponse.headers, selectedPeer, serializedReq.requestId);
649
+ const streamingHeaders = attachStreamingAntseedHeaders(startResponse.headers, selectedPeer, requestForPeer.requestId);
532
650
  res.writeHead(startResponse.statusCode, streamingHeaders);
533
651
  if (startResponse.body.length > 0) {
534
652
  res.write(Buffer.from(startResponse.body));
@@ -544,7 +662,7 @@ export class BuyerProxy {
544
662
  });
545
663
  const latencyMs = Date.now() - startTime;
546
664
  log(`Response: ${response.statusCode} (${latencyMs}ms, ${response.body.length} bytes)`);
547
- const telemetry = computeResponseTelemetry(serializedReq, response.headers, response.body, selectedPeer);
665
+ const telemetry = computeResponseTelemetry(requestForPeer, response.headers, response.body, selectedPeer);
548
666
  if (router) {
549
667
  router.onResult(selectedPeer, {
550
668
  success: response.statusCode < 500,
@@ -558,17 +676,20 @@ export class BuyerProxy {
558
676
  }
559
677
  }
560
678
  else {
561
- const responseHeaders = attachAntseedTelemetryHeaders(response.headers, selectedPeer, telemetry, serializedReq.requestId, latencyMs);
679
+ const responseHeaders = attachAntseedTelemetryHeaders(response.headers, selectedPeer, telemetry, requestForPeer.requestId, latencyMs);
562
680
  res.writeHead(response.statusCode, responseHeaders);
563
681
  res.end(Buffer.from(response.body));
564
682
  }
565
683
  }
566
684
  else {
567
- const response = await this._node.sendRequest(selectedPeer, serializedReq);
685
+ let response = await this._node.sendRequest(selectedPeer, requestForPeer);
686
+ if (adaptResponse) {
687
+ response = adaptResponse(response);
688
+ }
568
689
  const latencyMs = Date.now() - startTime;
569
690
  log(`Response: ${response.statusCode} (${latencyMs}ms, ${response.body.length} bytes)`);
570
- const telemetry = computeResponseTelemetry(serializedReq, response.headers, response.body, selectedPeer);
571
- const responseHeaders = attachAntseedTelemetryHeaders(response.headers, selectedPeer, telemetry, serializedReq.requestId, latencyMs);
691
+ const telemetry = computeResponseTelemetry(requestForPeer, response.headers, response.body, selectedPeer);
692
+ const responseHeaders = attachAntseedTelemetryHeaders(response.headers, selectedPeer, telemetry, requestForPeer.requestId, latencyMs);
572
693
  // Report result to router for learning
573
694
  if (router) {
574
695
  router.onResult(selectedPeer, {