@iicp/client 0.2.0 → 0.5.3

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
@@ -23,6 +23,12 @@ npm install iicp-client
23
23
  # pnpm add iicp-client
24
24
  ```
25
25
 
26
+ > **Upgrade note (0.5.3)** — if you operate a node and use the native IICP
27
+ > TCP transport on port 9484, upgrade to `^0.5.3`. Releases 0.5.0–0.5.2
28
+ > emitted a non-standard CBOR dialect that does not interoperate with the
29
+ > Python or Rust SDK on the binary transport. The HTTP `/v1/task` path is
30
+ > unaffected. See [`CHANGELOG.md`](./CHANGELOG.md) for details.
31
+
26
32
  ---
27
33
 
28
34
  ## Quickstart
@@ -0,0 +1,38 @@
1
+ /**
2
+ * OpenAI-compatible backend helper (Ollama / vLLM / LM Studio / OpenAI / ...).
3
+ *
4
+ * TypeScript port of iicp-client-python's backends/openai_compat.py (iter-1423).
5
+ * Returns a TaskHandler suitable for IicpNode.serve() AND IicpTcpServer.
6
+ *
7
+ * import { openaiCompatHandler } from "@iicp/client";
8
+ *
9
+ * const handler = openaiCompatHandler({
10
+ * baseUrl: "http://localhost:11434/v1", // Ollama
11
+ * model: "qwen2.5:0.5b",
12
+ * });
13
+ * await node.serve(handler, { port: 8080, nodeToken });
14
+ *
15
+ * Intent routing matches the Python module + adapter reference:
16
+ *
17
+ * urn:iicp:intent:llm:chat:v1 → /chat/completions
18
+ * urn:iicp:intent:llm:completion:v1 → /completions
19
+ * urn:iicp:intent:llm:embedding:v1 → /embeddings
20
+ */
21
+ export interface OpenAiCompatOptions {
22
+ /** Provider HTTP root (no trailing slash needed). Default: Ollama `http://localhost:11434/v1`. */
23
+ baseUrl?: string;
24
+ /** Default model name. If unset, the task payload MUST include `model`. */
25
+ model?: string;
26
+ /** Bearer token for the provider. Empty for local Ollama/vLLM. */
27
+ apiKey?: string;
28
+ /** Per-request HTTP timeout in milliseconds. Default 30000. */
29
+ timeoutMs?: number;
30
+ }
31
+ export type TaskHandlerInput = {
32
+ task_id?: string;
33
+ intent?: string;
34
+ payload?: Record<string, unknown>;
35
+ };
36
+ export type TaskHandlerOutput = Record<string, unknown>;
37
+ export declare function openaiCompatHandler(opts?: OpenAiCompatOptions): (task: TaskHandlerInput) => Promise<TaskHandlerOutput>;
38
+ //# sourceMappingURL=openai_compat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai_compat.d.ts","sourceRoot":"","sources":["../../src/backends/openai_compat.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,MAAM,WAAW,mBAAmB;IAClC,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExD,wBAAgB,mBAAmB,CACjC,IAAI,GAAE,mBAAwB,GAC7B,CAAC,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CA8ExD"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * OpenAI-compatible backend helper (Ollama / vLLM / LM Studio / OpenAI / ...).
5
+ *
6
+ * TypeScript port of iicp-client-python's backends/openai_compat.py (iter-1423).
7
+ * Returns a TaskHandler suitable for IicpNode.serve() AND IicpTcpServer.
8
+ *
9
+ * import { openaiCompatHandler } from "@iicp/client";
10
+ *
11
+ * const handler = openaiCompatHandler({
12
+ * baseUrl: "http://localhost:11434/v1", // Ollama
13
+ * model: "qwen2.5:0.5b",
14
+ * });
15
+ * await node.serve(handler, { port: 8080, nodeToken });
16
+ *
17
+ * Intent routing matches the Python module + adapter reference:
18
+ *
19
+ * urn:iicp:intent:llm:chat:v1 → /chat/completions
20
+ * urn:iicp:intent:llm:completion:v1 → /completions
21
+ * urn:iicp:intent:llm:embedding:v1 → /embeddings
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.openaiCompatHandler = openaiCompatHandler;
25
+ const INTENT_TO_PATH = {
26
+ "urn:iicp:intent:llm:chat:v1": "/chat/completions",
27
+ "urn:iicp:intent:llm:completion:v1": "/completions",
28
+ "urn:iicp:intent:llm:embedding:v1": "/embeddings",
29
+ };
30
+ function openaiCompatHandler(opts = {}) {
31
+ const baseUrl = (opts.baseUrl ?? "http://localhost:11434/v1").replace(/\/$/, "");
32
+ const model = opts.model;
33
+ const apiKey = opts.apiKey ?? "";
34
+ const timeoutMs = opts.timeoutMs ?? 30000;
35
+ return async function handler(task) {
36
+ const intent = String(task.intent ?? "");
37
+ const payload = task.payload;
38
+ if (payload !== undefined && payload !== null && (typeof payload !== "object" || Array.isArray(payload))) {
39
+ return {
40
+ error_code: 400,
41
+ error_message: `openai_compat: task.payload must be a dict, got ${typeof payload}`,
42
+ };
43
+ }
44
+ const path = INTENT_TO_PATH[intent];
45
+ if (!path) {
46
+ return {
47
+ error_code: 400,
48
+ error_message: `openai_compat: unsupported intent ${JSON.stringify(intent)}; supported: ${JSON.stringify(Object.keys(INTENT_TO_PATH).sort())}`,
49
+ };
50
+ }
51
+ const body = { ...(payload ?? {}) };
52
+ if (body.model === undefined && model !== undefined)
53
+ body.model = model;
54
+ if (!body.model) {
55
+ return {
56
+ error_code: 400,
57
+ error_message: "openai_compat: no model — either pass `model` to openaiCompatHandler(...) " +
58
+ "or include `model` in the task payload",
59
+ };
60
+ }
61
+ const headers = { "Content-Type": "application/json" };
62
+ if (apiKey)
63
+ headers["Authorization"] = `Bearer ${apiKey}`;
64
+ const ctrl = new AbortController();
65
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
66
+ let resp;
67
+ try {
68
+ resp = await fetch(`${baseUrl}${path}`, {
69
+ method: "POST",
70
+ headers,
71
+ body: JSON.stringify(body),
72
+ signal: ctrl.signal,
73
+ });
74
+ }
75
+ catch (exc) {
76
+ clearTimeout(t);
77
+ const msg = exc instanceof Error ? exc.message : String(exc);
78
+ if (ctrl.signal.aborted) {
79
+ return { error_code: 408, error_message: "openai_compat: backend timed out" };
80
+ }
81
+ return { error_code: 502, error_message: `openai_compat: HTTP transport error: ${msg}` };
82
+ }
83
+ clearTimeout(t);
84
+ if (!resp.ok) {
85
+ const text = await resp.text().catch(() => "");
86
+ return {
87
+ error_code: resp.status,
88
+ error_message: `openai_compat: upstream ${resp.status}: ${text.slice(0, 512)}`,
89
+ };
90
+ }
91
+ let data;
92
+ try {
93
+ data = await resp.json();
94
+ }
95
+ catch (exc) {
96
+ const msg = exc instanceof Error ? exc.message : String(exc);
97
+ return { error_code: 502, error_message: `openai_compat: upstream returned non-JSON: ${msg}` };
98
+ }
99
+ return { result: data };
100
+ };
101
+ }
102
+ //# sourceMappingURL=openai_compat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai_compat.js","sourceRoot":"","sources":["../../src/backends/openai_compat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * CIP-W01/CIP-W02 provider-side policy gate — S.12, ADR-012.
3
+ *
4
+ * TypeScript port of iicp-client-python's cip_policy.py (iter-1429).
5
+ *
6
+ * The Cooperative Inference Profile gives a node three operator-controlled
7
+ * roles (consumer / coordinator / worker). Safe Phase-4 defaults: all three
8
+ * are OFF until the operator opts in. The capacity gate enforces S.12 §2.2:
9
+ * worker tasks at capacity MUST return IICP-E021 rather than silently queue.
10
+ */
11
+ export interface CooperativeInferencePolicyOptions {
12
+ enabled?: boolean;
13
+ allowCoordinator?: boolean;
14
+ allowWorker?: boolean;
15
+ maxReplicas?: number;
16
+ /** Bounded to [1, 60000]ms. */
17
+ maxWorkerTimeoutMs?: number;
18
+ maxConcurrentRemote?: number;
19
+ }
20
+ /** Safe-by-default CIP policy with the S.12 §2.2 capacity gate built-in. */
21
+ export declare class CooperativeInferencePolicy {
22
+ readonly enabled: boolean;
23
+ readonly allowCoordinator: boolean;
24
+ readonly allowWorker: boolean;
25
+ readonly maxReplicas: number;
26
+ readonly maxWorkerTimeoutMs: number;
27
+ readonly maxConcurrentRemote: number;
28
+ private _inFlight;
29
+ constructor(opts?: CooperativeInferencePolicyOptions);
30
+ /** CIP-W01: returns true if this node may act as a CIP coordinator. */
31
+ checkCoordinator(): boolean;
32
+ /** CIP-W02: returns true if this node may accept CIP worker tasks. */
33
+ checkWorker(): boolean;
34
+ /**
35
+ * CIP-A1-GATE-06: try to acquire a worker concurrency slot. Returns true
36
+ * on success — caller MUST call releaseCipSlot() when done. Returns false
37
+ * when at capacity, in which case the caller MUST respond with IICP-E021
38
+ * rather than queue or delay (S.12 §2.2 explicit non-silent-queue rule).
39
+ */
40
+ tryAcquireCipSlot(): boolean;
41
+ /** Release a slot acquired via tryAcquireCipSlot(). */
42
+ releaseCipSlot(): void;
43
+ /**
44
+ * Build the `policy` sub-object the directory expects in /v1/register.
45
+ * Only emits keys when CIP is enabled — disabled-by-default operators
46
+ * shouldn't clutter the payload with `allow_*: false`.
47
+ */
48
+ asRegisterPolicyBlock(): Record<string, unknown>;
49
+ }
50
+ /** Return the active CIP policy (safe defaults until configureCipPolicy is called). */
51
+ export declare function getCipPolicy(): CooperativeInferencePolicy;
52
+ /** Replace the module-level CIP policy. Returns the new policy for chaining. */
53
+ export declare function configureCipPolicy(opts?: CooperativeInferencePolicyOptions): CooperativeInferencePolicy;
54
+ //# sourceMappingURL=cip_policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cip_policy.d.ts","sourceRoot":"","sources":["../src/cip_policy.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAEH,MAAM,WAAW,iCAAiC;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,4EAA4E;AAC5E,qBAAa,0BAA0B;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,OAAO,CAAC,SAAS,CAAK;gBAEV,IAAI,GAAE,iCAAsC;IASxD,uEAAuE;IACvE,gBAAgB,IAAI,OAAO;IAI3B,sEAAsE;IACtE,WAAW,IAAI,OAAO;IAItB;;;;;OAKG;IACH,iBAAiB,IAAI,OAAO;IAM5B,uDAAuD;IACvD,cAAc,IAAI,IAAI;IAItB;;;;OAIG;IACH,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAMjD;AAMD,uFAAuF;AACvF,wBAAgB,YAAY,IAAI,0BAA0B,CAEzD;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAChC,IAAI,GAAE,iCAAsC,GAC3C,0BAA0B,CAG5B"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * CIP-W01/CIP-W02 provider-side policy gate — S.12, ADR-012.
5
+ *
6
+ * TypeScript port of iicp-client-python's cip_policy.py (iter-1429).
7
+ *
8
+ * The Cooperative Inference Profile gives a node three operator-controlled
9
+ * roles (consumer / coordinator / worker). Safe Phase-4 defaults: all three
10
+ * are OFF until the operator opts in. The capacity gate enforces S.12 §2.2:
11
+ * worker tasks at capacity MUST return IICP-E021 rather than silently queue.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.CooperativeInferencePolicy = void 0;
15
+ exports.getCipPolicy = getCipPolicy;
16
+ exports.configureCipPolicy = configureCipPolicy;
17
+ /** Safe-by-default CIP policy with the S.12 §2.2 capacity gate built-in. */
18
+ class CooperativeInferencePolicy {
19
+ enabled;
20
+ allowCoordinator;
21
+ allowWorker;
22
+ maxReplicas;
23
+ maxWorkerTimeoutMs;
24
+ maxConcurrentRemote;
25
+ _inFlight = 0;
26
+ constructor(opts = {}) {
27
+ this.enabled = opts.enabled ?? false;
28
+ this.allowCoordinator = opts.allowCoordinator ?? false;
29
+ this.allowWorker = opts.allowWorker ?? false;
30
+ this.maxReplicas = Math.max(1, opts.maxReplicas ?? 3);
31
+ this.maxWorkerTimeoutMs = Math.max(1, Math.min(60_000, opts.maxWorkerTimeoutMs ?? 30_000));
32
+ this.maxConcurrentRemote = Math.max(1, opts.maxConcurrentRemote ?? 2);
33
+ }
34
+ /** CIP-W01: returns true if this node may act as a CIP coordinator. */
35
+ checkCoordinator() {
36
+ return this.enabled && this.allowCoordinator;
37
+ }
38
+ /** CIP-W02: returns true if this node may accept CIP worker tasks. */
39
+ checkWorker() {
40
+ return this.enabled && this.allowWorker;
41
+ }
42
+ /**
43
+ * CIP-A1-GATE-06: try to acquire a worker concurrency slot. Returns true
44
+ * on success — caller MUST call releaseCipSlot() when done. Returns false
45
+ * when at capacity, in which case the caller MUST respond with IICP-E021
46
+ * rather than queue or delay (S.12 §2.2 explicit non-silent-queue rule).
47
+ */
48
+ tryAcquireCipSlot() {
49
+ if (this._inFlight >= this.maxConcurrentRemote)
50
+ return false;
51
+ this._inFlight += 1;
52
+ return true;
53
+ }
54
+ /** Release a slot acquired via tryAcquireCipSlot(). */
55
+ releaseCipSlot() {
56
+ if (this._inFlight > 0)
57
+ this._inFlight -= 1;
58
+ }
59
+ /**
60
+ * Build the `policy` sub-object the directory expects in /v1/register.
61
+ * Only emits keys when CIP is enabled — disabled-by-default operators
62
+ * shouldn't clutter the payload with `allow_*: false`.
63
+ */
64
+ asRegisterPolicyBlock() {
65
+ if (!this.enabled)
66
+ return {};
67
+ return {
68
+ allow_remote_inference: this.allowWorker,
69
+ };
70
+ }
71
+ }
72
+ exports.CooperativeInferencePolicy = CooperativeInferencePolicy;
73
+ // ── Module-level default policy ──────────────────────────────────────────
74
+ let _policy = new CooperativeInferencePolicy();
75
+ /** Return the active CIP policy (safe defaults until configureCipPolicy is called). */
76
+ function getCipPolicy() {
77
+ return _policy;
78
+ }
79
+ /** Replace the module-level CIP policy. Returns the new policy for chaining. */
80
+ function configureCipPolicy(opts = {}) {
81
+ _policy = new CooperativeInferencePolicy(opts);
82
+ return _policy;
83
+ }
84
+ //# sourceMappingURL=cip_policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cip_policy.js","sourceRoot":"","sources":["../src/cip_policy.ts"],"names":[],"mappings":";AAAA,sCAAsC;AACtC;;;;;;;;;GASG;;;AA4EH,oCAEC;AAGD,gDAKC;AA1ED,4EAA4E;AAC5E,MAAa,0BAA0B;IAC5B,OAAO,CAAU;IACjB,gBAAgB,CAAU;IAC1B,WAAW,CAAU;IACrB,WAAW,CAAS;IACpB,kBAAkB,CAAS;IAC3B,mBAAmB,CAAS;IAC7B,SAAS,GAAG,CAAC,CAAC;IAEtB,YAAY,OAA0C,EAAE;QACtD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;QACrC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,KAAK,CAAC;QACvD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC;QAC7C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,IAAI,MAAM,CAAC,CAAC,CAAC;QAC3F,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,mBAAmB,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,uEAAuE;IACvE,gBAAgB;QACd,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,gBAAgB,CAAC;IAC/C,CAAC;IAED,sEAAsE;IACtE,WAAW;QACT,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,WAAW,CAAC;IAC1C,CAAC;IAED;;;;;OAKG;IACH,iBAAiB;QACf,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,mBAAmB;YAAE,OAAO,KAAK,CAAC;QAC7D,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uDAAuD;IACvD,cAAc;QACZ,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC;YAAE,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACH,qBAAqB;QACnB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC7B,OAAO;YACL,sBAAsB,EAAE,IAAI,CAAC,WAAW;SACzC,CAAC;IACJ,CAAC;CACF;AAxDD,gEAwDC;AAED,4EAA4E;AAE5E,IAAI,OAAO,GAA+B,IAAI,0BAA0B,EAAE,CAAC;AAE3E,uFAAuF;AACvF,SAAgB,YAAY;IAC1B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,gFAAgF;AAChF,SAAgB,kBAAkB,CAChC,OAA0C,EAAE;IAE5C,OAAO,GAAG,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC;IAC/C,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Unified concurrency gate for the hybrid client.
3
+ *
4
+ * TypeScript port of iicp-client-python's concurrency.py (iter-1438).
5
+ * Tier 2 Item 5 of #340. Caps simultaneous inference tasks at
6
+ * `max_concurrent` from the node's registration envelope; when full the
7
+ * SDK responds with IICP-E021 (429) on whichever transport carries the
8
+ * CALL — HTTP /v1/task OR native IICP TCP RESPONSE frame.
9
+ *
10
+ * Non-blocking acquire — CapacityExceededError raises immediately rather
11
+ * than queueing. The proxy MUST learn back-pressure right away so it
12
+ * can route elsewhere (ADR-008). Queueing would mask overload.
13
+ */
14
+ /** Raised by [`ConcurrencyGate.acquire`] when the gate is at capacity. */
15
+ export declare class CapacityExceededError extends Error {
16
+ readonly maxConcurrent: number;
17
+ constructor(maxConcurrent: number);
18
+ }
19
+ /**
20
+ * Cap simultaneous inference tasks. Single shared primitive across HTTP
21
+ * and native IICP transports so the directory's NodeScorer sees the same
22
+ * back-pressure regardless of which transport carried the CALL.
23
+ *
24
+ * Usage::
25
+ *
26
+ * const gate = new ConcurrencyGate(4);
27
+ * try {
28
+ * await gate.acquire();
29
+ * try {
30
+ * await runTask();
31
+ * } finally {
32
+ * gate.release();
33
+ * }
34
+ * } catch (e) {
35
+ * if (e instanceof CapacityExceededError) return { error_code: 429, ... };
36
+ * throw e;
37
+ * }
38
+ *
39
+ * Or via the `run()` helper that auto-releases:
40
+ *
41
+ * const result = await gate.run(async () => runTask());
42
+ */
43
+ export declare class ConcurrencyGate {
44
+ readonly maxConcurrent: number;
45
+ private _active;
46
+ constructor(maxConcurrent: number);
47
+ /** Non-blocking acquire. Throws CapacityExceededError when full. */
48
+ acquire(): void;
49
+ /** Release a slot acquired via [`acquire`]. */
50
+ release(): void;
51
+ /** Convenience: run `fn` while holding a slot, releasing on success or error. */
52
+ run<T>(fn: () => Promise<T>): Promise<T>;
53
+ get activeJobs(): number;
54
+ /** Load fraction in [0, 1]. Reported in heartbeats so the NodeScorer can
55
+ * down-rank busy nodes (ADR-008). */
56
+ get load(): number;
57
+ }
58
+ //# sourceMappingURL=concurrency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAEH,0EAA0E;AAC1E,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBACnB,aAAa,EAAE,MAAM;CAKlC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,OAAO,CAAK;gBAER,aAAa,EAAE,MAAM;IAOjC,oEAAoE;IACpE,OAAO,IAAI,IAAI;IAOf,+CAA+C;IAC/C,OAAO,IAAI,IAAI;IAIf,iFAAiF;IAC3E,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAS9C,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;yCACqC;IACrC,IAAI,IAAI,IAAI,MAAM,CAGjB;CACF"}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Unified concurrency gate for the hybrid client.
5
+ *
6
+ * TypeScript port of iicp-client-python's concurrency.py (iter-1438).
7
+ * Tier 2 Item 5 of #340. Caps simultaneous inference tasks at
8
+ * `max_concurrent` from the node's registration envelope; when full the
9
+ * SDK responds with IICP-E021 (429) on whichever transport carries the
10
+ * CALL — HTTP /v1/task OR native IICP TCP RESPONSE frame.
11
+ *
12
+ * Non-blocking acquire — CapacityExceededError raises immediately rather
13
+ * than queueing. The proxy MUST learn back-pressure right away so it
14
+ * can route elsewhere (ADR-008). Queueing would mask overload.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.ConcurrencyGate = exports.CapacityExceededError = void 0;
18
+ /** Raised by [`ConcurrencyGate.acquire`] when the gate is at capacity. */
19
+ class CapacityExceededError extends Error {
20
+ maxConcurrent;
21
+ constructor(maxConcurrent) {
22
+ super(`max_concurrent (${maxConcurrent}) reached`);
23
+ this.name = "CapacityExceededError";
24
+ this.maxConcurrent = maxConcurrent;
25
+ }
26
+ }
27
+ exports.CapacityExceededError = CapacityExceededError;
28
+ /**
29
+ * Cap simultaneous inference tasks. Single shared primitive across HTTP
30
+ * and native IICP transports so the directory's NodeScorer sees the same
31
+ * back-pressure regardless of which transport carried the CALL.
32
+ *
33
+ * Usage::
34
+ *
35
+ * const gate = new ConcurrencyGate(4);
36
+ * try {
37
+ * await gate.acquire();
38
+ * try {
39
+ * await runTask();
40
+ * } finally {
41
+ * gate.release();
42
+ * }
43
+ * } catch (e) {
44
+ * if (e instanceof CapacityExceededError) return { error_code: 429, ... };
45
+ * throw e;
46
+ * }
47
+ *
48
+ * Or via the `run()` helper that auto-releases:
49
+ *
50
+ * const result = await gate.run(async () => runTask());
51
+ */
52
+ class ConcurrencyGate {
53
+ maxConcurrent;
54
+ _active = 0;
55
+ constructor(maxConcurrent) {
56
+ if (maxConcurrent < 1) {
57
+ throw new Error(`max_concurrent must be >= 1, got ${maxConcurrent}`);
58
+ }
59
+ this.maxConcurrent = maxConcurrent;
60
+ }
61
+ /** Non-blocking acquire. Throws CapacityExceededError when full. */
62
+ acquire() {
63
+ if (this._active >= this.maxConcurrent) {
64
+ throw new CapacityExceededError(this.maxConcurrent);
65
+ }
66
+ this._active += 1;
67
+ }
68
+ /** Release a slot acquired via [`acquire`]. */
69
+ release() {
70
+ if (this._active > 0)
71
+ this._active -= 1;
72
+ }
73
+ /** Convenience: run `fn` while holding a slot, releasing on success or error. */
74
+ async run(fn) {
75
+ this.acquire();
76
+ try {
77
+ return await fn();
78
+ }
79
+ finally {
80
+ this.release();
81
+ }
82
+ }
83
+ get activeJobs() {
84
+ return this._active;
85
+ }
86
+ /** Load fraction in [0, 1]. Reported in heartbeats so the NodeScorer can
87
+ * down-rank busy nodes (ADR-008). */
88
+ get load() {
89
+ if (this.maxConcurrent === 0)
90
+ return 1.0;
91
+ return this._active / this.maxConcurrent;
92
+ }
93
+ }
94
+ exports.ConcurrencyGate = ConcurrencyGate;
95
+ //# sourceMappingURL=concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":";AAAA,sCAAsC;AACtC;;;;;;;;;;;;GAYG;;;AAEH,0EAA0E;AAC1E,MAAa,qBAAsB,SAAQ,KAAK;IACrC,aAAa,CAAS;IAC/B,YAAY,aAAqB;QAC/B,KAAK,CAAC,mBAAmB,aAAa,WAAW,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;CACF;AAPD,sDAOC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,eAAe;IACjB,aAAa,CAAS;IACvB,OAAO,GAAG,CAAC,CAAC;IAEpB,YAAY,aAAqB;QAC/B,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,oCAAoC,aAAa,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;IAED,oEAAoE;IACpE,OAAO;QACL,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvC,MAAM,IAAI,qBAAqB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,+CAA+C;IAC/C,OAAO;QACL,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,GAAG,CAAI,EAAoB;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;yCACqC;IACrC,IAAI,IAAI;QACN,IAAI,IAAI,CAAC,aAAa,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC;QACzC,OAAO,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC;IAC3C,CAAC;CACF;AA5CD,0CA4CC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Self-conformance probes — operator-side health verification.
3
+ *
4
+ * TypeScript port of iicp-client-python's conformance.py (iter-1435).
5
+ * Tier 2 Item 4 of the iicp-adapter → iicp-client migration (#340).
6
+ *
7
+ * Operators run these periodically (or in their /health endpoint) to confirm
8
+ * their hybrid node is fully conformant without depending on the external
9
+ * REACH daemon. Four probes mirror the adapter set:
10
+ *
11
+ * CONF-REG-01 — node_id + node_token set
12
+ * CONF-HEALTH-01 — local /iicp/health returns 200 with required schema
13
+ * CONF-REACH-01 — directory /v1/probe confirms internet reachability
14
+ * CONF-DISC-01 — own node_id appears in /v1/discover NODELIST
15
+ */
16
+ export interface ProbeResult {
17
+ testId: string;
18
+ passed: boolean;
19
+ message: string;
20
+ latencyMs: number | null;
21
+ }
22
+ export interface ConformanceReport {
23
+ passCount: number;
24
+ failCount: number;
25
+ lastRunAt: string;
26
+ tests: ProbeResult[];
27
+ }
28
+ /**
29
+ * Run the four conformance probes concurrently and return a report.
30
+ * Pass `nodeToken` to make CONF-REG-01 verify the token in addition to node_id.
31
+ */
32
+ export declare function runConformanceChecks(node: any, localPort?: number, opts?: {
33
+ nodeToken?: string;
34
+ }): Promise<ConformanceReport>;
35
+ //# sourceMappingURL=conformance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAqND;;;GAGG;AACH,wBAAsB,oBAAoB,CAExC,IAAI,EAAE,GAAG,EACT,SAAS,GAAE,MAAa,EACxB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAgB5B"}