@iicp/client 0.5.6 → 0.6.0

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 (64) hide show
  1. package/dist/availability.d.ts +30 -0
  2. package/dist/availability.d.ts.map +1 -0
  3. package/dist/availability.js +64 -0
  4. package/dist/availability.js.map +1 -0
  5. package/dist/backends/base.d.ts +35 -0
  6. package/dist/backends/base.d.ts.map +1 -0
  7. package/dist/backends/base.js +97 -0
  8. package/dist/backends/base.js.map +1 -0
  9. package/dist/backends/index.d.ts +13 -0
  10. package/dist/backends/index.d.ts.map +1 -0
  11. package/dist/backends/index.js +30 -0
  12. package/dist/backends/index.js.map +1 -0
  13. package/dist/backends/llamacpp.d.ts +18 -0
  14. package/dist/backends/llamacpp.d.ts.map +1 -0
  15. package/dist/backends/llamacpp.js +17 -0
  16. package/dist/backends/llamacpp.js.map +1 -0
  17. package/dist/backends/openai_compat.d.ts +6 -10
  18. package/dist/backends/openai_compat.d.ts.map +1 -1
  19. package/dist/backends/openai_compat.js +5 -78
  20. package/dist/backends/openai_compat.js.map +1 -1
  21. package/dist/backends/vllm.d.ts +19 -0
  22. package/dist/backends/vllm.d.ts.map +1 -0
  23. package/dist/backends/vllm.js +17 -0
  24. package/dist/backends/vllm.js.map +1 -0
  25. package/dist/cli.d.ts.map +1 -1
  26. package/dist/cli.js +60 -3
  27. package/dist/cli.js.map +1 -1
  28. package/dist/idempotency.d.ts +17 -0
  29. package/dist/idempotency.d.ts.map +1 -0
  30. package/dist/idempotency.js +39 -0
  31. package/dist/idempotency.js.map +1 -0
  32. package/dist/index.d.ts +16 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +25 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/nat_detection.d.ts +37 -0
  37. package/dist/nat_detection.d.ts.map +1 -1
  38. package/dist/nat_detection.js +398 -29
  39. package/dist/nat_detection.js.map +1 -1
  40. package/dist/node.d.ts +35 -1
  41. package/dist/node.d.ts.map +1 -1
  42. package/dist/node.js +267 -59
  43. package/dist/node.js.map +1 -1
  44. package/dist/otel_tracer.d.ts +26 -0
  45. package/dist/otel_tracer.d.ts.map +1 -0
  46. package/dist/otel_tracer.js +96 -0
  47. package/dist/otel_tracer.js.map +1 -0
  48. package/dist/peer_manager.d.ts +37 -0
  49. package/dist/peer_manager.d.ts.map +1 -0
  50. package/dist/peer_manager.js +135 -0
  51. package/dist/peer_manager.js.map +1 -0
  52. package/dist/scheduler.d.ts +23 -0
  53. package/dist/scheduler.d.ts.map +1 -0
  54. package/dist/scheduler.js +42 -0
  55. package/dist/scheduler.js.map +1 -0
  56. package/dist/token_validator.d.ts +15 -0
  57. package/dist/token_validator.d.ts.map +1 -0
  58. package/dist/token_validator.js +34 -0
  59. package/dist/token_validator.js.map +1 -0
  60. package/dist/trust_auditor.d.ts +34 -0
  61. package/dist/trust_auditor.d.ts.map +1 -0
  62. package/dist/trust_auditor.js +123 -0
  63. package/dist/trust_auditor.js.map +1 -0
  64. package/package.json +1 -1
@@ -0,0 +1,26 @@
1
+ /**
2
+ * OpenTelemetry span helpers for IICP SDK nodes — ADR-014 TRACE-01/TRACE-02.
3
+ *
4
+ * Node-side spans:
5
+ * iicp.task.validate (TRACE-01) — request parsing, nonce check, auth
6
+ * iicp.task.execute (TRACE-02) — handler dispatch and response
7
+ *
8
+ * Behaviour:
9
+ * - When @opentelemetry/api is installed AND OTEL_EXPORTER_OTLP_ENDPOINT is set:
10
+ * exports spans to the configured collector via OTLP/HTTP.
11
+ * - Otherwise: uses a no-op tracer — call sites need no conditionals.
12
+ *
13
+ * W3C traceparent propagation is handled in node.ts at the HTTP layer; this
14
+ * module manages the span lifecycle within the node process.
15
+ */
16
+ interface SpanLike {
17
+ setAttribute(key: string, value: string | number | boolean): void;
18
+ recordException(err: Error): void;
19
+ end(): void;
20
+ }
21
+ /** TRACE-01: iicp.task.validate — wraps request parsing and auth check. */
22
+ export declare function withTaskValidateSpan<T>(taskId: string, fn: (span: SpanLike) => T): Promise<T>;
23
+ /** TRACE-02: iicp.task.execute — wraps handler dispatch and response. */
24
+ export declare function withTaskExecuteSpan<T>(taskId: string, intent: string, fn: (span: SpanLike) => Promise<T>): Promise<T>;
25
+ export {};
26
+ //# sourceMappingURL=otel_tracer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel_tracer.d.ts","sourceRoot":"","sources":["../src/otel_tracer.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAEH,UAAU,QAAQ;IAChB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAClE,eAAe,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;IAClC,GAAG,IAAI,IAAI,CAAC;CACb;AAqDD,2EAA2E;AAC3E,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CASnG;AAED,yEAAyE;AACzE,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAa3H"}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * OpenTelemetry span helpers for IICP SDK nodes — ADR-014 TRACE-01/TRACE-02.
5
+ *
6
+ * Node-side spans:
7
+ * iicp.task.validate (TRACE-01) — request parsing, nonce check, auth
8
+ * iicp.task.execute (TRACE-02) — handler dispatch and response
9
+ *
10
+ * Behaviour:
11
+ * - When @opentelemetry/api is installed AND OTEL_EXPORTER_OTLP_ENDPOINT is set:
12
+ * exports spans to the configured collector via OTLP/HTTP.
13
+ * - Otherwise: uses a no-op tracer — call sites need no conditionals.
14
+ *
15
+ * W3C traceparent propagation is handled in node.ts at the HTTP layer; this
16
+ * module manages the span lifecycle within the node process.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.withTaskValidateSpan = withTaskValidateSpan;
20
+ exports.withTaskExecuteSpan = withTaskExecuteSpan;
21
+ class NoopSpan {
22
+ setAttribute(_key, _value) { }
23
+ recordException(_err) { }
24
+ end() { }
25
+ }
26
+ class NoopTracer {
27
+ startSpan(_name) { return new NoopSpan(); }
28
+ }
29
+ let _tracer = null;
30
+ let _initialised = false;
31
+ async function _init() {
32
+ if (_tracer)
33
+ return _tracer;
34
+ if (_initialised)
35
+ return new NoopTracer();
36
+ _initialised = true;
37
+ const endpoint = process.env["OTEL_EXPORTER_OTLP_ENDPOINT"] ?? "";
38
+ try {
39
+ const api = await import("@opentelemetry/api").catch(() => null);
40
+ if (!api) {
41
+ _tracer = new NoopTracer();
42
+ return _tracer;
43
+ }
44
+ if (endpoint) {
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ const sdk = await import("@opentelemetry/sdk-trace-node").catch(() => null);
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ const exp = await import("@opentelemetry/exporter-trace-otlp-http").catch(() => null);
49
+ if (sdk && exp) {
50
+ const provider = new sdk.NodeTracerProvider();
51
+ const exporter = new exp.OTLPTraceExporter({ url: endpoint });
52
+ provider.addSpanProcessor(new sdk.BatchSpanProcessor(exporter));
53
+ provider.register();
54
+ _tracer = api.trace.getTracer("iicp.client");
55
+ return _tracer;
56
+ }
57
+ }
58
+ _tracer = api.trace.getTracer("iicp.client");
59
+ return _tracer;
60
+ }
61
+ catch {
62
+ _tracer = new NoopTracer();
63
+ return _tracer;
64
+ }
65
+ }
66
+ /** TRACE-01: iicp.task.validate — wraps request parsing and auth check. */
67
+ async function withTaskValidateSpan(taskId, fn) {
68
+ const tracer = await _init();
69
+ const span = tracer.startSpan("iicp.task.validate");
70
+ span.setAttribute("iicp.task_id", taskId);
71
+ try {
72
+ return fn(span);
73
+ }
74
+ finally {
75
+ span.end();
76
+ }
77
+ }
78
+ /** TRACE-02: iicp.task.execute — wraps handler dispatch and response. */
79
+ async function withTaskExecuteSpan(taskId, intent, fn) {
80
+ const tracer = await _init();
81
+ const span = tracer.startSpan("iicp.task.execute");
82
+ span.setAttribute("iicp.task_id", taskId);
83
+ span.setAttribute("iicp.intent", intent);
84
+ try {
85
+ return await fn(span);
86
+ }
87
+ catch (err) {
88
+ if (err instanceof Error)
89
+ span.recordException(err);
90
+ throw err;
91
+ }
92
+ finally {
93
+ span.end();
94
+ }
95
+ }
96
+ //# sourceMappingURL=otel_tracer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel_tracer.js","sourceRoot":"","sources":["../src/otel_tracer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Phase 2 mesh layer — peer discovery, gossip, and relay support (parity Block F, #340).
3
+ *
4
+ * Port of iicp-adapter `network/peer_manager.py` + `handlers/{peers,relay}.py` (ADR-009,
5
+ * ADR-022). Bootstraps an initial peer set from the directory, gossips a random known peer
6
+ * every 30s with an HMAC-SHA256-signed exchange (reusing the pricing HMAC key), prunes
7
+ * peers idle for 90s, and resolves relay targets for POST /v1/relay forwarding.
8
+ */
9
+ export interface PeerInfo {
10
+ node_id: string;
11
+ endpoint: string;
12
+ region: string;
13
+ last_seen: string;
14
+ last_contact: number;
15
+ }
16
+ export declare class PeerManager {
17
+ private readonly directoryUrl;
18
+ private readonly nodeToken;
19
+ private peers;
20
+ private ownId;
21
+ private timer;
22
+ constructor(directoryUrl: string, nodeToken?: string);
23
+ getPeers(): PeerInfo[];
24
+ relayTarget(nodeId: string): PeerInfo | undefined;
25
+ /** Merge incoming peer entries. Returns the count of newly added peers. */
26
+ mergePeers(incoming: Array<Partial<PeerInfo>>): number;
27
+ /** Drop peers not contacted within the expiry window. Returns count pruned. */
28
+ prune(): number;
29
+ /** Verify an inbound /v1/peers HMAC signature. No token configured → accept. */
30
+ verifyExchange(body: string, signature: string | undefined | null): boolean;
31
+ start(nodeId: string): Promise<void>;
32
+ stop(): void;
33
+ gossipRound(): Promise<void>;
34
+ private bootstrap;
35
+ private exchange;
36
+ }
37
+ //# sourceMappingURL=peer_manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_manager.d.ts","sourceRoot":"","sources":["../src/peer_manager.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAQH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,KAAK,CAA6C;gBAE9C,YAAY,EAAE,MAAM,EAAE,SAAS,SAAK;IAKhD,QAAQ,IAAI,QAAQ,EAAE;IAItB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAIjD,2EAA2E;IAC3E,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM;IAkBtD,+EAA+E;IAC/E,KAAK,IAAI,MAAM;IAYf,gFAAgF;IAChF,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO;IAMrE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1C,IAAI,IAAI,IAAI;IAIN,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;YAWpB,SAAS;YAaT,QAAQ;CAsBvB"}
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Phase 2 mesh layer — peer discovery, gossip, and relay support (parity Block F, #340).
5
+ *
6
+ * Port of iicp-adapter `network/peer_manager.py` + `handlers/{peers,relay}.py` (ADR-009,
7
+ * ADR-022). Bootstraps an initial peer set from the directory, gossips a random known peer
8
+ * every 30s with an HMAC-SHA256-signed exchange (reusing the pricing HMAC key), prunes
9
+ * peers idle for 90s, and resolves relay targets for POST /v1/relay forwarding.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PeerManager = void 0;
13
+ const pricing_js_1 = require("./pricing.js");
14
+ const GOSSIP_INTERVAL_MS = 30_000;
15
+ const PEER_EXPIRY_MS = 90_000;
16
+ const BOOTSTRAP_LIMIT = 5;
17
+ class PeerManager {
18
+ directoryUrl;
19
+ nodeToken;
20
+ peers = new Map();
21
+ ownId = "";
22
+ timer;
23
+ constructor(directoryUrl, nodeToken = "") {
24
+ this.directoryUrl = directoryUrl.replace(/\/$/, "");
25
+ this.nodeToken = nodeToken;
26
+ }
27
+ getPeers() {
28
+ return [...this.peers.values()];
29
+ }
30
+ relayTarget(nodeId) {
31
+ return this.peers.get(nodeId);
32
+ }
33
+ /** Merge incoming peer entries. Returns the count of newly added peers. */
34
+ mergePeers(incoming) {
35
+ const now = Date.now();
36
+ let added = 0;
37
+ for (const p of incoming) {
38
+ const nid = p.node_id;
39
+ if (!nid || nid === this.ownId)
40
+ continue;
41
+ if (!this.peers.has(nid))
42
+ added++;
43
+ this.peers.set(nid, {
44
+ node_id: nid,
45
+ endpoint: p.endpoint ?? "",
46
+ region: p.region ?? "",
47
+ last_seen: p.last_seen ?? "",
48
+ last_contact: now,
49
+ });
50
+ }
51
+ return added;
52
+ }
53
+ /** Drop peers not contacted within the expiry window. Returns count pruned. */
54
+ prune() {
55
+ const cutoff = Date.now() - PEER_EXPIRY_MS;
56
+ let pruned = 0;
57
+ for (const [nid, p] of this.peers) {
58
+ if (p.last_contact < cutoff) {
59
+ this.peers.delete(nid);
60
+ pruned++;
61
+ }
62
+ }
63
+ return pruned;
64
+ }
65
+ /** Verify an inbound /v1/peers HMAC signature. No token configured → accept. */
66
+ verifyExchange(body, signature) {
67
+ if (!this.nodeToken)
68
+ return true;
69
+ if (!signature)
70
+ return false;
71
+ return (0, pricing_js_1.signBody)(body, this.nodeToken) === signature;
72
+ }
73
+ async start(nodeId) {
74
+ this.ownId = nodeId;
75
+ await this.bootstrap();
76
+ this.timer = setInterval(() => {
77
+ void this.gossipRound();
78
+ }, GOSSIP_INTERVAL_MS);
79
+ }
80
+ stop() {
81
+ if (this.timer)
82
+ clearInterval(this.timer);
83
+ }
84
+ async gossipRound() {
85
+ const peers = this.getPeers();
86
+ if (peers.length === 0) {
87
+ await this.bootstrap();
88
+ return;
89
+ }
90
+ const target = peers[Math.floor(Math.random() * peers.length)];
91
+ await this.exchange(target);
92
+ this.prune();
93
+ }
94
+ async bootstrap() {
95
+ try {
96
+ const url = `${this.directoryUrl}/v1/bootstrap?limit=${BOOTSTRAP_LIMIT}`;
97
+ const resp = await fetch(url, { signal: AbortSignal.timeout(5000) });
98
+ if (resp.ok) {
99
+ const body = (await resp.json());
100
+ this.mergePeers(body.peers ?? []);
101
+ }
102
+ }
103
+ catch {
104
+ /* directory unreachable — keep persisted/known peers */
105
+ }
106
+ }
107
+ async exchange(target) {
108
+ const body = JSON.stringify({ known_peers: [...this.peers.keys()] });
109
+ const headers = { "Content-Type": "application/json" };
110
+ if (this.nodeToken)
111
+ headers["X-IICP-Signature"] = (0, pricing_js_1.signBody)(body, this.nodeToken);
112
+ try {
113
+ const resp = await fetch(`${target.endpoint.replace(/\/$/, "")}/v1/peers`, {
114
+ method: "POST",
115
+ headers,
116
+ body,
117
+ signal: AbortSignal.timeout(5000),
118
+ });
119
+ if (resp.ok) {
120
+ const data = (await resp.json());
121
+ this.mergePeers(data.peers ?? []);
122
+ const t = this.peers.get(target.node_id);
123
+ if (t)
124
+ t.last_contact = Date.now();
125
+ }
126
+ }
127
+ catch {
128
+ const t = this.peers.get(target.node_id);
129
+ if (t)
130
+ t.last_contact = 0;
131
+ }
132
+ }
133
+ }
134
+ exports.PeerManager = PeerManager;
135
+ //# sourceMappingURL=peer_manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_manager.js","sourceRoot":"","sources":["../src/peer_manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * QoS-aware admission policy for the provider serve path (parity Block C, #340).
3
+ *
4
+ * Port of the QoS *contract* from iicp-adapter `scheduling/queue.py`. The adapter runs a
5
+ * full PriorityQueue dispatcher; the SDK serve gate is deliberately fail-fast (queuing
6
+ * would hide overload from the proxy). To close the cat-8 parity gap without contradicting
7
+ * that design, the SDK applies QoS-aware admission:
8
+ *
9
+ * - realtime / interactive → queue-eligible: wait briefly (QUEUE_WAIT_MS) for a slot.
10
+ * - batch / best-effort / unspecified → fail fast with IICP-E021.
11
+ *
12
+ * Priority ordering (lower = higher priority) is exposed for telemetry parity.
13
+ */
14
+ export declare const QOS_PRIORITY: Record<string, number>;
15
+ /** Tiers that wait briefly for a slot rather than failing fast at capacity. */
16
+ export declare const QUEUE_ELIGIBLE: ReadonlySet<string>;
17
+ /** Bounded wait for queue-eligible tiers (ms). */
18
+ export declare const QUEUE_WAIT_MS = 2000;
19
+ /** Priority rank for a QoS class (lower = higher priority; unknown → 3). */
20
+ export declare function qosPriority(qos: string | null | undefined): number;
21
+ /** True if a task of this QoS class should wait briefly for a slot at capacity. */
22
+ export declare function isQueueEligible(qos: string | null | undefined): boolean;
23
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAGH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAM/C,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,MAAM,CAAwC,CAAC;AAExF,kDAAkD;AAClD,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGlE;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEvE"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * QoS-aware admission policy for the provider serve path (parity Block C, #340).
5
+ *
6
+ * Port of the QoS *contract* from iicp-adapter `scheduling/queue.py`. The adapter runs a
7
+ * full PriorityQueue dispatcher; the SDK serve gate is deliberately fail-fast (queuing
8
+ * would hide overload from the proxy). To close the cat-8 parity gap without contradicting
9
+ * that design, the SDK applies QoS-aware admission:
10
+ *
11
+ * - realtime / interactive → queue-eligible: wait briefly (QUEUE_WAIT_MS) for a slot.
12
+ * - batch / best-effort / unspecified → fail fast with IICP-E021.
13
+ *
14
+ * Priority ordering (lower = higher priority) is exposed for telemetry parity.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.QUEUE_WAIT_MS = exports.QUEUE_ELIGIBLE = exports.QOS_PRIORITY = void 0;
18
+ exports.qosPriority = qosPriority;
19
+ exports.isQueueEligible = isQueueEligible;
20
+ // Lower value = higher priority. Both spellings accepted (adapter uses "best-effort").
21
+ exports.QOS_PRIORITY = {
22
+ realtime: 0,
23
+ interactive: 1,
24
+ batch: 2,
25
+ best_effort: 3,
26
+ "best-effort": 3,
27
+ };
28
+ /** Tiers that wait briefly for a slot rather than failing fast at capacity. */
29
+ exports.QUEUE_ELIGIBLE = new Set(["realtime", "interactive"]);
30
+ /** Bounded wait for queue-eligible tiers (ms). */
31
+ exports.QUEUE_WAIT_MS = 2000;
32
+ /** Priority rank for a QoS class (lower = higher priority; unknown → 3). */
33
+ function qosPriority(qos) {
34
+ if (qos == null)
35
+ return 3;
36
+ return exports.QOS_PRIORITY[qos] ?? 3;
37
+ }
38
+ /** True if a task of this QoS class should wait briefly for a slot at capacity. */
39
+ function isQueueEligible(qos) {
40
+ return qos != null && exports.QUEUE_ELIGIBLE.has(qos);
41
+ }
42
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":";AAAA,sCAAsC;AACtC;;;;;;;;;;;;GAYG;;;AAkBH,kCAGC;AAGD,0CAEC;AAxBD,uFAAuF;AAC1E,QAAA,YAAY,GAA2B;IAClD,QAAQ,EAAE,CAAC;IACX,WAAW,EAAE,CAAC;IACd,KAAK,EAAE,CAAC;IACR,WAAW,EAAE,CAAC;IACd,aAAa,EAAE,CAAC;CACjB,CAAC;AAEF,+EAA+E;AAClE,QAAA,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC;AAExF,kDAAkD;AACrC,QAAA,aAAa,GAAG,IAAI,CAAC;AAElC,4EAA4E;AAC5E,SAAgB,WAAW,CAAC,GAA8B;IACxD,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,oBAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,mFAAmF;AACnF,SAAgB,eAAe,CAAC,GAA8B;IAC5D,OAAO,GAAG,IAAI,IAAI,IAAI,sBAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Constant-time bearer-token validation (parity Block E, #340).
3
+ *
4
+ * Port of iicp-adapter `services/token_validator.py`. Compares a presented token against
5
+ * the expected one with `crypto.timingSafeEqual` so a timing side-channel can't recover
6
+ * the token byte-by-byte. The expected token is updated after registration.
7
+ */
8
+ export declare class TokenValidator {
9
+ private expected;
10
+ constructor(expectedToken?: string);
11
+ isValid(presented: string | null | undefined): boolean;
12
+ /** Set the expected token after registration (directory-issued). */
13
+ updateToken(newToken: string): void;
14
+ }
15
+ //# sourceMappingURL=token_validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token_validator.d.ts","sourceRoot":"","sources":["../src/token_validator.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AAIH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAS;gBAEb,aAAa,SAAK;IAI9B,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAStD,oEAAoE;IACpE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGpC"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Constant-time bearer-token validation (parity Block E, #340).
5
+ *
6
+ * Port of iicp-adapter `services/token_validator.py`. Compares a presented token against
7
+ * the expected one with `crypto.timingSafeEqual` so a timing side-channel can't recover
8
+ * the token byte-by-byte. The expected token is updated after registration.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.TokenValidator = void 0;
12
+ const node_crypto_1 = require("node:crypto");
13
+ class TokenValidator {
14
+ expected;
15
+ constructor(expectedToken = "") {
16
+ this.expected = expectedToken;
17
+ }
18
+ isValid(presented) {
19
+ if (!this.expected || !presented)
20
+ return false;
21
+ const a = Buffer.from(this.expected);
22
+ const b = Buffer.from(presented);
23
+ // timingSafeEqual throws on length mismatch; guard first (length is not secret).
24
+ if (a.length !== b.length)
25
+ return false;
26
+ return (0, node_crypto_1.timingSafeEqual)(a, b);
27
+ }
28
+ /** Set the expected token after registration (directory-issued). */
29
+ updateToken(newToken) {
30
+ this.expected = newToken;
31
+ }
32
+ }
33
+ exports.TokenValidator = TokenValidator;
34
+ //# sourceMappingURL=token_validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token_validator.js","sourceRoot":"","sources":["../src/token_validator.ts"],"names":[],"mappings":";AAAA,sCAAsC;AACtC;;;;;;GAMG;;;AAEH,6CAA8C;AAE9C,MAAa,cAAc;IACjB,QAAQ,CAAS;IAEzB,YAAY,aAAa,GAAG,EAAE;QAC5B,IAAI,CAAC,QAAQ,GAAG,aAAa,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,SAAoC;QAC1C,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QAC/C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,iFAAiF;QACjF,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QACxC,OAAO,IAAA,6BAAe,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,oEAAoE;IACpE,WAAW,CAAC,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AApBD,wCAoBC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Trust auditor — cross-node declaration consistency check (parity Block E, #340).
3
+ *
4
+ * Port of iicp-adapter `services/trust_auditor.py` (#118). Discovers active peers via the
5
+ * directory, probes each peer's `/iicp/health`, and verifies the directory-registered
6
+ * models actually appear in the peer's live health response. Missing models are a
7
+ * "declaration divergence" reported to `/v1/audit-report`.
8
+ *
9
+ * Opt-in background capability (call `runAuditPass` on a timer); not in the request hot
10
+ * path. The pure `modelsDiverge` helper is the unit-testable core.
11
+ */
12
+ /** Registered models absent from the peer's health response (empty == consistent). */
13
+ export declare function modelsDiverge(registered: string[], health: string[]): string[];
14
+ export interface NodeAuditResult {
15
+ node_id: string;
16
+ endpoint: string;
17
+ passed: boolean;
18
+ health_reachable: boolean;
19
+ declared_models_match: boolean;
20
+ registered_models: string[];
21
+ health_models: string[];
22
+ latency_ms: number | null;
23
+ detail: string;
24
+ }
25
+ export interface AuditReport {
26
+ run_at: string;
27
+ nodes_probed: number;
28
+ nodes_passed: number;
29
+ nodes_failed: number;
30
+ results: NodeAuditResult[];
31
+ }
32
+ /** Discover peers, probe each concurrently, report divergences. One pass. */
33
+ export declare function runAuditPass(directoryUrl: string, ownNodeId: string, nodeToken?: string): Promise<AuditReport>;
34
+ //# sourceMappingURL=trust_auditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trust_auditor.d.ts","sourceRoot":"","sources":["../src/trust_auditor.ts"],"names":[],"mappings":"AACA;;;;;;;;;;GAUG;AAOH,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAG9E;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAuFD,6EAA6E;AAC7E,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,SAAK,GACb,OAAO,CAAC,WAAW,CAAC,CAmBtB"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Trust auditor — cross-node declaration consistency check (parity Block E, #340).
5
+ *
6
+ * Port of iicp-adapter `services/trust_auditor.py` (#118). Discovers active peers via the
7
+ * directory, probes each peer's `/iicp/health`, and verifies the directory-registered
8
+ * models actually appear in the peer's live health response. Missing models are a
9
+ * "declaration divergence" reported to `/v1/audit-report`.
10
+ *
11
+ * Opt-in background capability (call `runAuditPass` on a timer); not in the request hot
12
+ * path. The pure `modelsDiverge` helper is the unit-testable core.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.modelsDiverge = modelsDiverge;
16
+ exports.runAuditPass = runAuditPass;
17
+ const DISCOVER_INTENT = "urn:iicp:intent:llm:chat:v1";
18
+ const PROBE_TIMEOUT_MS = 5000;
19
+ const DISCOVER_TIMEOUT_MS = 8000;
20
+ const AUDIT_REPORT_TIMEOUT_MS = 5000;
21
+ /** Registered models absent from the peer's health response (empty == consistent). */
22
+ function modelsDiverge(registered, health) {
23
+ const h = new Set(health);
24
+ return registered.filter((m) => !h.has(m));
25
+ }
26
+ async function discoverPeers(directoryUrl, ownNodeId) {
27
+ try {
28
+ const url = `${directoryUrl.replace(/\/$/, "")}/v1/discover?intent=${encodeURIComponent(DISCOVER_INTENT)}`;
29
+ const resp = await fetch(url, { signal: AbortSignal.timeout(DISCOVER_TIMEOUT_MS) });
30
+ if (!resp.ok)
31
+ return [];
32
+ const body = (await resp.json());
33
+ return (body.nodes ?? []).filter((n) => n.node_id !== ownNodeId);
34
+ }
35
+ catch {
36
+ return [];
37
+ }
38
+ }
39
+ async function probeNode(node) {
40
+ const nodeId = node.node_id ?? "unknown";
41
+ const endpoint = node.operator_url ?? node.endpoint ?? "";
42
+ const registered = node.models ?? [];
43
+ const base = {
44
+ node_id: nodeId,
45
+ endpoint,
46
+ passed: false,
47
+ health_reachable: false,
48
+ declared_models_match: false,
49
+ registered_models: registered,
50
+ health_models: [],
51
+ latency_ms: null,
52
+ detail: "",
53
+ };
54
+ if (!endpoint)
55
+ return { ...base, detail: "no endpoint" };
56
+ const t0 = Date.now();
57
+ try {
58
+ const resp = await fetch(`${endpoint.replace(/\/$/, "")}/iicp/health`, {
59
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
60
+ });
61
+ const latency = Date.now() - t0;
62
+ if (!resp.ok)
63
+ return { ...base, latency_ms: latency, detail: `HTTP ${resp.status}` };
64
+ const health = (await resp.json());
65
+ const healthModels = health.models ?? [];
66
+ const missing = modelsDiverge(registered, healthModels);
67
+ const ok = missing.length === 0;
68
+ return {
69
+ ...base,
70
+ health_reachable: true,
71
+ declared_models_match: ok,
72
+ passed: ok,
73
+ health_models: healthModels,
74
+ latency_ms: latency,
75
+ detail: ok ? "OK" : `registered ${JSON.stringify(missing)} absent from health`,
76
+ };
77
+ }
78
+ catch (exc) {
79
+ const msg = exc instanceof Error ? exc.message : String(exc);
80
+ return { ...base, latency_ms: Date.now() - t0, detail: `connection error: ${msg}` };
81
+ }
82
+ }
83
+ async function reportDivergence(directoryUrl, ownNodeId, nodeToken, targetNodeId) {
84
+ if (!ownNodeId || !nodeToken)
85
+ return;
86
+ try {
87
+ await fetch(`${directoryUrl.replace(/\/$/, "")}/v1/audit-report`, {
88
+ method: "POST",
89
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${nodeToken}` },
90
+ body: JSON.stringify({
91
+ node_id: ownNodeId,
92
+ target_node_id: targetNodeId,
93
+ finding: "declaration_divergence",
94
+ }),
95
+ signal: AbortSignal.timeout(AUDIT_REPORT_TIMEOUT_MS),
96
+ });
97
+ }
98
+ catch {
99
+ /* best-effort */
100
+ }
101
+ }
102
+ /** Discover peers, probe each concurrently, report divergences. One pass. */
103
+ async function runAuditPass(directoryUrl, ownNodeId, nodeToken = "") {
104
+ const nodes = await discoverPeers(directoryUrl, ownNodeId);
105
+ const runAt = new Date().toISOString();
106
+ if (nodes.length === 0) {
107
+ return { run_at: runAt, nodes_probed: 0, nodes_passed: 0, nodes_failed: 0, results: [] };
108
+ }
109
+ const results = await Promise.all(nodes.map(probeNode));
110
+ for (const r of results) {
111
+ if (r.health_reachable && !r.declared_models_match) {
112
+ await reportDivergence(directoryUrl, ownNodeId, nodeToken, r.node_id);
113
+ }
114
+ }
115
+ return {
116
+ run_at: runAt,
117
+ nodes_probed: results.length,
118
+ nodes_passed: results.filter((r) => r.passed).length,
119
+ nodes_failed: results.filter((r) => !r.passed).length,
120
+ results,
121
+ };
122
+ }
123
+ //# sourceMappingURL=trust_auditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trust_auditor.js","sourceRoot":"","sources":["../src/trust_auditor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iicp/client",
3
- "version": "0.5.6",
3
+ "version": "0.6.0",
4
4
  "description": "Official TypeScript/JavaScript SDK for the IICP protocol (ADR-016)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",