@iicp/client 0.2.0 → 0.5.4
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 +6 -0
- package/dist/backends/openai_compat.d.ts +38 -0
- package/dist/backends/openai_compat.d.ts.map +1 -0
- package/dist/backends/openai_compat.js +102 -0
- package/dist/backends/openai_compat.js.map +1 -0
- package/dist/cip_policy.d.ts +54 -0
- package/dist/cip_policy.d.ts.map +1 -0
- package/dist/cip_policy.js +84 -0
- package/dist/cip_policy.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +166 -0
- package/dist/cli.js.map +1 -0
- package/dist/concurrency.d.ts +58 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +95 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/conformance.d.ts +35 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +243 -0
- package/dist/conformance.js.map +1 -0
- package/dist/iicp_tcp.d.ts +146 -0
- package/dist/iicp_tcp.d.ts.map +1 -0
- package/dist/iicp_tcp.js +712 -0
- package/dist/iicp_tcp.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/nat_detection.d.ts +62 -0
- package/dist/nat_detection.d.ts.map +1 -0
- package/dist/nat_detection.js +367 -0
- package/dist/nat_detection.js.map +1 -0
- package/dist/node.d.ts +47 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +101 -8
- package/dist/node.js.map +1 -1
- package/dist/pricing.d.ts +46 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +73 -0
- package/dist/pricing.js.map +1 -0
- package/package.json +32 -7
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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA8HA,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAyDlF"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.main = main;
|
|
5
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
/**
|
|
7
|
+
* iicp-node — turn @iicp/client into a runnable provider node.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* iicp-node serve --model qwen2.5:0.5b --backend-url http://localhost:11434
|
|
11
|
+
*
|
|
12
|
+
* All flags also read from env (IICP_BACKEND_URL, IICP_BACKEND_MODEL,
|
|
13
|
+
* IICP_PUBLIC_ENDPOINT, IICP_DIRECTORY_URL, IICP_REGION,
|
|
14
|
+
* IICP_MAX_CONCURRENT, IICP_NODE_ID, IICP_INTENT, IICP_PORT, IICP_HOST).
|
|
15
|
+
*
|
|
16
|
+
* Mirrors iicp_client.cli (Python) so operators choosing TypeScript get the
|
|
17
|
+
* same one-liner setup path.
|
|
18
|
+
*/
|
|
19
|
+
const node_util_1 = require("node:util");
|
|
20
|
+
const node_crypto_1 = require("node:crypto");
|
|
21
|
+
const node_js_1 = require("./node.js");
|
|
22
|
+
const openai_compat_js_1 = require("./backends/openai_compat.js");
|
|
23
|
+
function envOr(name, fallback) {
|
|
24
|
+
const v = process.env[name];
|
|
25
|
+
return v ?? fallback;
|
|
26
|
+
}
|
|
27
|
+
function envInt(name, fallback) {
|
|
28
|
+
const v = process.env[name];
|
|
29
|
+
if (!v)
|
|
30
|
+
return fallback;
|
|
31
|
+
const n = parseInt(v, 10);
|
|
32
|
+
return Number.isFinite(n) ? n : fallback;
|
|
33
|
+
}
|
|
34
|
+
function printHelp() {
|
|
35
|
+
process.stdout.write(`usage: iicp-node serve [options]\n\n` +
|
|
36
|
+
`Run an IICP provider node backed by an OpenAI-compatible server.\n\n` +
|
|
37
|
+
`Required (flag or env):\n` +
|
|
38
|
+
` --backend-url URL IICP_BACKEND_URL — Ollama / vLLM / LM Studio endpoint\n` +
|
|
39
|
+
` --model NAME IICP_BACKEND_MODEL — model name (e.g. qwen2.5:0.5b)\n\n` +
|
|
40
|
+
`Optional:\n` +
|
|
41
|
+
` --public-endpoint URL IICP_PUBLIC_ENDPOINT — externally reachable URL of this node\n` +
|
|
42
|
+
` --directory-url URL IICP_DIRECTORY_URL (default https://iicp.network/api)\n` +
|
|
43
|
+
` --region REGION IICP_REGION (default eu-central)\n` +
|
|
44
|
+
` --intent URN IICP_INTENT (default urn:iicp:intent:llm:chat:v1)\n` +
|
|
45
|
+
` --max-concurrent N IICP_MAX_CONCURRENT (default 4)\n` +
|
|
46
|
+
` --node-id ID IICP_NODE_ID (auto-generated if absent)\n` +
|
|
47
|
+
` --port N IICP_PORT (default 8020)\n` +
|
|
48
|
+
` --host HOST IICP_HOST (default 0.0.0.0)\n` +
|
|
49
|
+
` --skip-registration IICP_SKIP_REGISTRATION — register-free dev mode\n`);
|
|
50
|
+
}
|
|
51
|
+
async function runServe(opts) {
|
|
52
|
+
if (!opts.backendUrl || !opts.model) {
|
|
53
|
+
process.stderr.write("ERROR: --backend-url and --model are required (or IICP_BACKEND_URL / IICP_BACKEND_MODEL).\n");
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
const nodeId = opts.nodeId ||
|
|
57
|
+
`sdk-${opts.model.replace(/:/g, "-")}-${(0, node_crypto_1.randomBytes)(4).toString("hex")}`;
|
|
58
|
+
const publicEndpoint = opts.publicEndpoint || `http://localhost:${opts.port}`;
|
|
59
|
+
const node = new node_js_1.IicpNode({
|
|
60
|
+
nodeId,
|
|
61
|
+
endpoint: publicEndpoint,
|
|
62
|
+
intent: opts.intent,
|
|
63
|
+
model: opts.model,
|
|
64
|
+
region: opts.region,
|
|
65
|
+
directoryUrl: opts.directoryUrl,
|
|
66
|
+
maxConcurrent: opts.maxConcurrent,
|
|
67
|
+
});
|
|
68
|
+
const handler = (0, openai_compat_js_1.openaiCompatHandler)({
|
|
69
|
+
baseUrl: opts.backendUrl,
|
|
70
|
+
model: opts.model,
|
|
71
|
+
});
|
|
72
|
+
let token;
|
|
73
|
+
if (!opts.skipRegistration) {
|
|
74
|
+
try {
|
|
75
|
+
token = await node.register();
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.log(`[iicp-node] registered as ${nodeId} (token=${(token ?? "").slice(0, 8)}…)`);
|
|
78
|
+
}
|
|
79
|
+
catch (exc) {
|
|
80
|
+
const msg = exc instanceof Error ? exc.message : String(exc);
|
|
81
|
+
// eslint-disable-next-line no-console
|
|
82
|
+
console.warn(`[iicp-node] registration failed: ${msg} — continuing without heartbeat`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.log(`[iicp-node] serving ${opts.intent} on ${opts.host}:${opts.port} — ` +
|
|
87
|
+
`backend ${opts.backendUrl} (model=${opts.model}, max_concurrent=${opts.maxConcurrent})`);
|
|
88
|
+
// serve() returns a stop() handle but never resolves on its own; we wait for
|
|
89
|
+
// SIGINT/SIGTERM to terminate.
|
|
90
|
+
const stop = node.serve(handler, { host: opts.host, port: opts.port, nodeToken: token });
|
|
91
|
+
await new Promise((resolve) => {
|
|
92
|
+
const shutdown = (sig) => {
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.log(`[iicp-node] ${sig} received — shutting down`);
|
|
95
|
+
stop();
|
|
96
|
+
resolve();
|
|
97
|
+
};
|
|
98
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
99
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
100
|
+
});
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
async function main(argv = process.argv.slice(2)) {
|
|
104
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
105
|
+
printHelp();
|
|
106
|
+
return argv.length === 0 ? 2 : 0;
|
|
107
|
+
}
|
|
108
|
+
if (argv[0] !== "serve") {
|
|
109
|
+
process.stderr.write(`unknown command: ${argv[0]}\n`);
|
|
110
|
+
printHelp();
|
|
111
|
+
return 2;
|
|
112
|
+
}
|
|
113
|
+
const { values } = (0, node_util_1.parseArgs)({
|
|
114
|
+
args: argv.slice(1),
|
|
115
|
+
options: {
|
|
116
|
+
"backend-url": { type: "string" },
|
|
117
|
+
model: { type: "string" },
|
|
118
|
+
"public-endpoint": { type: "string" },
|
|
119
|
+
"directory-url": { type: "string" },
|
|
120
|
+
region: { type: "string" },
|
|
121
|
+
intent: { type: "string" },
|
|
122
|
+
"max-concurrent": { type: "string" },
|
|
123
|
+
"node-id": { type: "string" },
|
|
124
|
+
port: { type: "string" },
|
|
125
|
+
host: { type: "string" },
|
|
126
|
+
"skip-registration": { type: "boolean" },
|
|
127
|
+
help: { type: "boolean", short: "h" },
|
|
128
|
+
},
|
|
129
|
+
allowPositionals: false,
|
|
130
|
+
});
|
|
131
|
+
if (values.help) {
|
|
132
|
+
printHelp();
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
const opts = {
|
|
136
|
+
backendUrl: values["backend-url"] ?? envOr("IICP_BACKEND_URL") ?? "",
|
|
137
|
+
model: values.model ?? envOr("IICP_BACKEND_MODEL") ?? "",
|
|
138
|
+
publicEndpoint: values["public-endpoint"] ?? envOr("IICP_PUBLIC_ENDPOINT") ?? "",
|
|
139
|
+
directoryUrl: values["directory-url"] ??
|
|
140
|
+
envOr("IICP_DIRECTORY_URL", "https://iicp.network/api"),
|
|
141
|
+
region: values.region ?? envOr("IICP_REGION", "eu-central"),
|
|
142
|
+
intent: values.intent ?? envOr("IICP_INTENT", "urn:iicp:intent:llm:chat:v1"),
|
|
143
|
+
maxConcurrent: values["max-concurrent"] !== undefined
|
|
144
|
+
? parseInt(values["max-concurrent"], 10)
|
|
145
|
+
: envInt("IICP_MAX_CONCURRENT", 4),
|
|
146
|
+
nodeId: values["node-id"] ?? envOr("IICP_NODE_ID") ?? "",
|
|
147
|
+
port: values.port !== undefined
|
|
148
|
+
? parseInt(values.port, 10)
|
|
149
|
+
: envInt("IICP_PORT", 8020),
|
|
150
|
+
host: values.host ?? envOr("IICP_HOST", "0.0.0.0"),
|
|
151
|
+
skipRegistration: Boolean(values["skip-registration"]) ||
|
|
152
|
+
(envOr("IICP_SKIP_REGISTRATION", "false") ?? "").toLowerCase() === "true",
|
|
153
|
+
};
|
|
154
|
+
return runServe(opts);
|
|
155
|
+
}
|
|
156
|
+
// Direct invocation (node dist/cli.js or via the bin shim)
|
|
157
|
+
if (require.main === module) {
|
|
158
|
+
main()
|
|
159
|
+
.then((code) => process.exit(code))
|
|
160
|
+
.catch((err) => {
|
|
161
|
+
// eslint-disable-next-line no-console
|
|
162
|
+
console.error(err);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|