@openvulcan/dsh-vulcancode 0.0.0-stage → 0.4.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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -2
  3. package/assets/openvulcan-icon.svg +33 -0
  4. package/cordis.patch.yml +17 -0
  5. package/node_modules/@openvulcan/dsh-decision-guard/LICENSE +21 -0
  6. package/node_modules/@openvulcan/dsh-decision-guard/README.md +14 -0
  7. package/node_modules/@openvulcan/dsh-decision-guard/assets/decision-guard-icon.png +0 -0
  8. package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.d.ts +40 -0
  9. package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.js +150 -0
  10. package/node_modules/@openvulcan/dsh-decision-guard/lib/client.js +2193 -0
  11. package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.d.ts +90 -0
  12. package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.js +108 -0
  13. package/node_modules/@openvulcan/dsh-decision-guard/lib/index.d.ts +129 -0
  14. package/node_modules/@openvulcan/dsh-decision-guard/lib/index.js +572 -0
  15. package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.d.ts +22 -0
  16. package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.js +77 -0
  17. package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.d.ts +158 -0
  18. package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.js +340 -0
  19. package/node_modules/@openvulcan/dsh-decision-guard/lib/records.d.ts +133 -0
  20. package/node_modules/@openvulcan/dsh-decision-guard/lib/records.js +186 -0
  21. package/node_modules/@openvulcan/dsh-decision-guard/lib/review.d.ts +55 -0
  22. package/node_modules/@openvulcan/dsh-decision-guard/lib/review.js +202 -0
  23. package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.d.ts +62 -0
  24. package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.js +155 -0
  25. package/node_modules/@openvulcan/dsh-decision-guard/lib/version.d.ts +2 -0
  26. package/node_modules/@openvulcan/dsh-decision-guard/lib/version.js +4 -0
  27. package/node_modules/@openvulcan/dsh-decision-guard/package.json +68 -0
  28. package/node_modules/@openvulcan/dsh-vulcan-account/LICENSE +21 -0
  29. package/node_modules/@openvulcan/dsh-vulcan-account/assets/account-icon.png +0 -0
  30. package/node_modules/@openvulcan/dsh-vulcan-account/lib/client.js +565 -0
  31. package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.d.ts +92 -0
  32. package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.js +194 -0
  33. package/node_modules/@openvulcan/dsh-vulcan-account/package.json +57 -0
  34. package/node_modules/@openvulcan/dsh-vulcan-answers/LICENSE +21 -0
  35. package/node_modules/@openvulcan/dsh-vulcan-answers/assets/answers-icon.png +0 -0
  36. package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-interactive.md +79 -0
  37. package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-standard.md +1 -0
  38. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.d.ts +258 -0
  39. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.js +698 -0
  40. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/client.js +671 -0
  41. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.d.ts +29 -0
  42. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.js +70 -0
  43. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.d.ts +19 -0
  44. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.js +52 -0
  45. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.d.ts +9 -0
  46. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.js +9 -0
  47. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.d.ts +22 -0
  48. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.js +39 -0
  49. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.d.ts +20 -0
  50. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.js +17 -0
  51. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.d.ts +66 -0
  52. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.js +118 -0
  53. package/node_modules/@openvulcan/dsh-vulcan-answers/package.json +59 -0
  54. package/node_modules/@openvulcan/dsh-vulcan-luaskills/LICENSE +21 -0
  55. package/node_modules/@openvulcan/dsh-vulcan-luaskills/assets/luaskills-icon.svg +1 -0
  56. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/client.js +940 -0
  57. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.d.ts +46 -0
  58. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.js +51 -0
  59. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.d.ts +55 -0
  60. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.js +149 -0
  61. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.d.ts +49 -0
  62. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.js +116 -0
  63. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.d.ts +26 -0
  64. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.js +276 -0
  65. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.d.ts +65 -0
  66. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.js +2 -0
  67. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.d.ts +111 -0
  68. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.js +281 -0
  69. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.d.ts +26 -0
  70. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.js +87 -0
  71. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.d.ts +79 -0
  72. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.js +165 -0
  73. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.d.ts +34 -0
  74. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.js +61 -0
  75. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.d.ts +59 -0
  76. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.js +107 -0
  77. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.d.ts +10 -0
  78. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.js +140 -0
  79. package/node_modules/@openvulcan/dsh-vulcan-luaskills/package.json +57 -0
  80. package/node_modules/@openvulcan/dsh-vulcan-memory/LICENSE +21 -0
  81. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/markdown-memory-guide.md +21 -0
  82. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-curator/SKILL.md +31 -0
  83. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-icon.png +0 -0
  84. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/client.js +1090 -0
  85. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.d.ts +30 -0
  86. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.js +240 -0
  87. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.d.ts +30 -0
  88. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.js +46 -0
  89. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.d.ts +23 -0
  90. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.js +25 -0
  91. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.d.ts +108 -0
  92. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.js +373 -0
  93. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.d.ts +14 -0
  94. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.js +31 -0
  95. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.d.ts +196 -0
  96. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.js +204 -0
  97. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.d.ts +92 -0
  98. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.js +472 -0
  99. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.d.ts +126 -0
  100. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.js +553 -0
  101. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.d.ts +56 -0
  102. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.js +182 -0
  103. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.d.ts +13 -0
  104. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.js +24 -0
  105. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.d.ts +65 -0
  106. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.js +227 -0
  107. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.d.ts +34 -0
  108. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.js +171 -0
  109. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.d.ts +72 -0
  110. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.js +250 -0
  111. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.d.ts +6 -0
  112. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.js +12 -0
  113. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.d.ts +8 -0
  114. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.js +38 -0
  115. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.d.ts +132 -0
  116. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.js +235 -0
  117. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.d.ts +14 -0
  118. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.js +27 -0
  119. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.d.ts +29 -0
  120. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.js +62 -0
  121. package/node_modules/@openvulcan/dsh-vulcan-memory/package.json +63 -0
  122. package/node_modules/@openvulcan/dsh-vulcan-style/LICENSE +21 -0
  123. package/node_modules/@openvulcan/dsh-vulcan-style/assets/style-icon.png +0 -0
  124. package/node_modules/@openvulcan/dsh-vulcan-style/lib/client.js +1061 -0
  125. package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.d.ts +34 -0
  126. package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.js +42 -0
  127. package/node_modules/@openvulcan/dsh-vulcan-style/package.json +51 -0
  128. package/node_modules/@openvulcan/openvulcan-account/LICENSE +21 -0
  129. package/node_modules/@openvulcan/openvulcan-account/lib/account.d.ts +140 -0
  130. package/node_modules/@openvulcan/openvulcan-account/lib/account.js +347 -0
  131. package/node_modules/@openvulcan/openvulcan-account/lib/index.d.ts +1 -0
  132. package/node_modules/@openvulcan/openvulcan-account/lib/index.js +1 -0
  133. package/node_modules/@openvulcan/openvulcan-account/package.json +29 -0
  134. package/node_modules/@openvulcan/vulcan-decision/LICENSE +21 -0
  135. package/node_modules/@openvulcan/vulcan-decision/README.md +23 -0
  136. package/node_modules/@openvulcan/vulcan-decision/bin/vulcan-decision.js +4 -0
  137. package/node_modules/@openvulcan/vulcan-decision/lib/cli.d.ts +5 -0
  138. package/node_modules/@openvulcan/vulcan-decision/lib/cli.js +267 -0
  139. package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.d.ts +211 -0
  140. package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.js +594 -0
  141. package/node_modules/@openvulcan/vulcan-decision/lib/download.d.ts +36 -0
  142. package/node_modules/@openvulcan/vulcan-decision/lib/download.js +99 -0
  143. package/node_modules/@openvulcan/vulcan-decision/lib/engine.d.ts +37 -0
  144. package/node_modules/@openvulcan/vulcan-decision/lib/engine.js +39 -0
  145. package/node_modules/@openvulcan/vulcan-decision/lib/hardware.d.ts +105 -0
  146. package/node_modules/@openvulcan/vulcan-decision/lib/hardware.js +207 -0
  147. package/node_modules/@openvulcan/vulcan-decision/lib/home.d.ts +26 -0
  148. package/node_modules/@openvulcan/vulcan-decision/lib/home.js +54 -0
  149. package/node_modules/@openvulcan/vulcan-decision/lib/index.d.ts +17 -0
  150. package/node_modules/@openvulcan/vulcan-decision/lib/index.js +17 -0
  151. package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.d.ts +54 -0
  152. package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.js +157 -0
  153. package/node_modules/@openvulcan/vulcan-decision/lib/local/models.d.ts +126 -0
  154. package/node_modules/@openvulcan/vulcan-decision/lib/local/models.js +261 -0
  155. package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.d.ts +102 -0
  156. package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.js +195 -0
  157. package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.d.ts +61 -0
  158. package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.js +123 -0
  159. package/node_modules/@openvulcan/vulcan-decision/lib/protocol.d.ts +74 -0
  160. package/node_modules/@openvulcan/vulcan-decision/lib/protocol.js +31 -0
  161. package/node_modules/@openvulcan/vulcan-decision/lib/render.d.ts +15 -0
  162. package/node_modules/@openvulcan/vulcan-decision/lib/render.js +33 -0
  163. package/node_modules/@openvulcan/vulcan-decision/lib/version.d.ts +2 -0
  164. package/node_modules/@openvulcan/vulcan-decision/lib/version.js +4 -0
  165. package/node_modules/@openvulcan/vulcan-decision/package.json +40 -0
  166. package/node_modules/@openvulcan/vulcan-decision/python/vulcan_decision_worker.py +363 -0
  167. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cpu.json +627 -0
  168. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cuda.json +423 -0
  169. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cpu.json +1047 -0
  170. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cu128.json +647 -0
  171. package/package.json +53 -4
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Pinned downloads: every file has a known size and SHA-256, is fetched through a partial file
3
+ * that resumes after an interruption, and is refused unless both match.
4
+ *
5
+ * The `cn` source rewrites the official hosts to mirrors reachable from mainland China. A mirror
6
+ * serves the same bytes, so the same pins apply; a failing mirror is reported, never silently
7
+ * replaced by the official host.
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { Readable, Transform } from 'node:stream';
13
+ import { pipeline } from 'node:stream/promises';
14
+ /** Where runtime downloads come from. */
15
+ export const DOWNLOAD_SOURCES = ['official', 'cn'];
16
+ /** Official prefix → mainland China mirror prefix. */
17
+ const CN_MIRRORS = [
18
+ ['https://github.com/astral-sh/python-build-standalone/releases/download/', 'https://registry.npmmirror.com/-/binary/python-build-standalone/'],
19
+ ['https://files.pythonhosted.org/packages/', 'https://mirrors.aliyun.com/pypi/packages/'],
20
+ ['https://download-r2.pytorch.org/whl/', 'https://mirrors.aliyun.com/pytorch-wheels/'],
21
+ ['https://download.pytorch.org/whl/', 'https://mirrors.aliyun.com/pytorch-wheels/'],
22
+ ];
23
+ /** The URL to fetch `url` from with `source`. */
24
+ export function sourceUrl(url, source) {
25
+ if (source === 'official')
26
+ return url;
27
+ for (const [official, mirror] of CN_MIRRORS) {
28
+ // The PyTorch mirror stores file names unescaped, like `torch-2.9.1+cu128-...whl`.
29
+ if (url.startsWith(official))
30
+ return mirror + (official.includes('pytorch') ? decodeURIComponent(url.slice(official.length)) : url.slice(official.length));
31
+ }
32
+ throw new Error(`no mirror is known for ${new URL(url).host}`);
33
+ }
34
+ /**
35
+ * Make `dest` hold exactly `file`: kept when it already matches, otherwise fetched (resuming a
36
+ * `.part` file left by an earlier attempt) and verified.
37
+ * @throws Error on HTTP failure or when the size or digest does not match.
38
+ */
39
+ export async function fetchPinned(file, dest, options = {}) {
40
+ if (await matches(dest, file)) {
41
+ options.onBytes?.(file.bytes);
42
+ return;
43
+ }
44
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
45
+ const partial = `${dest}.part`;
46
+ let have = fs.statSync(partial, { throwIfNoEntry: false })?.size ?? 0;
47
+ if (have > file.bytes) {
48
+ fs.rmSync(partial, { force: true });
49
+ have = 0;
50
+ }
51
+ const url = sourceUrl(file.url, options.source ?? 'official');
52
+ if (have < file.bytes) {
53
+ const response = await fetch(url, {
54
+ ...options.signal === undefined ? {} : { signal: options.signal },
55
+ headers: have > 0 ? { Range: `bytes=${have}-` } : {},
56
+ });
57
+ const resumed = response.status === 206;
58
+ if (!response.ok || response.body === null)
59
+ throw new Error(`cannot download ${file.name} from ${new URL(url).host}: HTTP ${response.status}`);
60
+ if (!resumed)
61
+ have = 0;
62
+ options.onBytes?.(have);
63
+ const counter = new CountingStream(options.onBytes);
64
+ // The DOM and node:stream/web declarations of ReadableStream differ only in their typings.
65
+ await pipeline(Readable.fromWeb(response.body), counter, fs.createWriteStream(partial, { flags: resumed ? 'a' : 'w' }), ...options.signal === undefined ? [] : [{ signal: options.signal }]);
66
+ }
67
+ else {
68
+ options.onBytes?.(have);
69
+ }
70
+ if (!await matches(partial, file)) {
71
+ fs.rmSync(partial, { force: true });
72
+ throw new Error(`${file.name} does not match its pinned size and SHA-256`);
73
+ }
74
+ fs.renameSync(partial, dest);
75
+ }
76
+ /** Whether `local` is a file of the pinned size and digest. */
77
+ export async function matches(local, file) {
78
+ const stat = fs.statSync(local, { throwIfNoEntry: false });
79
+ if (stat === undefined || !stat.isFile() || stat.size !== file.bytes)
80
+ return false;
81
+ return await sha256Of(local) === file.sha256.toLowerCase();
82
+ }
83
+ /** SHA-256 of a file, as lowercase hex. */
84
+ export async function sha256Of(file) {
85
+ const hash = createHash('sha256');
86
+ await pipeline(fs.createReadStream(file), hash);
87
+ return hash.digest('hex');
88
+ }
89
+ class CountingStream extends Transform {
90
+ onBytes;
91
+ constructor(onBytes) {
92
+ super();
93
+ this.onBytes = onBytes;
94
+ }
95
+ _transform(chunk, _encoding, done) {
96
+ this.onBytes?.(chunk.length);
97
+ done(null, chunk);
98
+ }
99
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * One entry point for hosts: judge a call with the cloud or the local backend, as selected.
3
+ */
4
+ import type { AccountSource } from '@openvulcan/openvulcan-account';
5
+ import { EdgeClient } from './cloud/edge.js';
6
+ import type { DecisionHome } from './home.js';
7
+ import { LocalEngine, type LocalSelection } from './local/engine.js';
8
+ import { type BackendKind, type DecisionOptions, type JudgeCall, type Verdict } from './protocol.js';
9
+ /** How the host wants calls judged. */
10
+ export interface EngineSettings {
11
+ readonly backend: BackendKind;
12
+ /** Cloud model id; empty uses the first model OpenVulcan lists for the account. */
13
+ readonly cloudModel: string;
14
+ /** Endpoint id the user locked; absent means the measured best. */
15
+ readonly cloudEndpoint?: string;
16
+ /** Installed pack and model for the local backend. */
17
+ readonly local?: LocalSelection;
18
+ readonly options: DecisionOptions;
19
+ }
20
+ export interface EngineOptions {
21
+ readonly home: DecisionHome;
22
+ /** The OpenVulcan account the cloud backend signs its requests with; the host owns signing in. */
23
+ readonly account: AccountSource;
24
+ }
25
+ export declare class DecisionEngine {
26
+ readonly home: DecisionHome;
27
+ readonly account: AccountSource;
28
+ readonly cloud: EdgeClient;
29
+ readonly local: LocalEngine;
30
+ constructor(options: EngineOptions);
31
+ /**
32
+ * Judge one call.
33
+ * @throws DecisionError for every failure; hosts must treat it as "not allowed".
34
+ */
35
+ judge(call: JudgeCall, settings: EngineSettings): Promise<Verdict>;
36
+ stop(): void;
37
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * One entry point for hosts: judge a call with the cloud or the local backend, as selected.
3
+ */
4
+ import { EdgeClient } from './cloud/edge.js';
5
+ import { LocalEngine } from './local/engine.js';
6
+ import { DecisionError } from './protocol.js';
7
+ import { renderCall } from './render.js';
8
+ export class DecisionEngine {
9
+ home;
10
+ account;
11
+ cloud;
12
+ local;
13
+ constructor(options) {
14
+ this.home = options.home;
15
+ this.account = options.account;
16
+ this.cloud = new EdgeClient(this.account);
17
+ this.local = new LocalEngine(options.home);
18
+ }
19
+ /**
20
+ * Judge one call.
21
+ * @throws DecisionError for every failure; hosts must treat it as "not allowed".
22
+ */
23
+ async judge(call, settings) {
24
+ const input = { settings: settings.options, rendered: renderCall(call) };
25
+ const started = performance.now();
26
+ if (settings.backend === 'cloud') {
27
+ const model = await this.cloud.resolveModel(settings.cloudModel);
28
+ const { results, endpoint } = await this.cloud.judge(model, [input], settings.cloudEndpoint);
29
+ return { ...results[0], backend: 'cloud', model, via: endpoint, elapsed_ms: Math.round(performance.now() - started) };
30
+ }
31
+ if (settings.local === undefined)
32
+ throw new DecisionError('not_installed', 'no local runtime pack and model are selected');
33
+ const [result] = await this.local.judge(settings.local, [input]);
34
+ return { ...result, backend: 'local', model: settings.local.model, via: settings.local.pack, elapsed_ms: Math.round(performance.now() - started) };
35
+ }
36
+ stop() {
37
+ this.local.stop();
38
+ }
39
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * What this machine offers the local model, and which runtime pack to recommend for it.
3
+ *
4
+ * Only ready-made runtimes are considered. NVIDIA cards run the CUDA packs, whose CUDA libraries
5
+ * come as NVIDIA's own wheels, so only the driver has to be recent enough; this package never
6
+ * installs or updates a driver. AMD, Intel and Apple graphics have no published runtime for these
7
+ * models, so they get the CPU pack or the cloud. An NPU is reported, but it needs an ONNX model
8
+ * exported for it, which is not published yet.
9
+ */
10
+ import { type PackId, type Target } from './local/packs.js';
11
+ /** One graphics card. */
12
+ export interface GpuInfo {
13
+ readonly name: string;
14
+ readonly vendor: 'nvidia' | 'amd' | 'intel' | 'apple' | 'other';
15
+ /** Dedicated memory, when the driver reports it. */
16
+ readonly memoryBytes?: number;
17
+ /** CUDA compute capability such as `8.9`, NVIDIA only. */
18
+ readonly computeCapability?: string;
19
+ readonly driver?: string;
20
+ }
21
+ /** One neural processing unit. */
22
+ export interface NpuInfo {
23
+ readonly name: string;
24
+ readonly vendor: 'intel' | 'amd' | 'qualcomm' | 'apple' | 'other';
25
+ }
26
+ export interface Hardware {
27
+ readonly platform: NodeJS.Platform;
28
+ readonly arch: string;
29
+ readonly target?: Target;
30
+ readonly totalMemory: number;
31
+ readonly cpus: number;
32
+ readonly gpus: readonly GpuInfo[];
33
+ readonly npus: readonly NpuInfo[];
34
+ }
35
+ /** Raw observations, injectable for tests. */
36
+ export interface Probes {
37
+ readonly platform: NodeJS.Platform;
38
+ readonly arch: string;
39
+ readonly totalMemory: number;
40
+ readonly cpus: number;
41
+ /** NVIDIA cards from `nvidia-smi`; undefined when no NVIDIA driver answered. */
42
+ readonly nvidia: readonly GpuInfo[] | undefined;
43
+ /** Display adapter names the system reports. */
44
+ readonly adapters: readonly string[];
45
+ /** NPU device names the system reports. */
46
+ readonly npus: readonly string[];
47
+ }
48
+ export declare function assess(probes: Probes): Hardware;
49
+ /** How an NVIDIA generation handles the model's arithmetic. */
50
+ export interface GpuGeneration {
51
+ /** Architecture name, such as `Ada Lovelace`. */
52
+ readonly architecture: string;
53
+ /** Fast half precision; the recommended format computes in fp16 on the GPU. */
54
+ readonly fp16: 'tensor-cores' | 'native' | 'slow';
55
+ /** Whether bf16 is supported; it is never used for these models, which lose accuracy in it. */
56
+ readonly bf16: boolean;
57
+ /** Whether the CUDA packs run on it. */
58
+ readonly supported: boolean;
59
+ }
60
+ /** Lowest compute capability the CUDA packs run on (Turing). */
61
+ export declare const MIN_COMPUTE_CAPABILITY = 7.5;
62
+ export declare function gpuGeneration(computeCapability: string): GpuGeneration;
63
+ /**
64
+ * Why a pack is or is not usable here.
65
+ * - `ok`: everything it needs was found.
66
+ * - `no_build`: the pack has no build for this platform.
67
+ * - `no_nvidia_gpu`: no NVIDIA card answered `nvidia-smi`.
68
+ * - `gpu_too_old`: the card's compute capability is below {@link MIN_COMPUTE_CAPABILITY}.
69
+ * - `driver_too_old`: the driver is older than the pack needs; update it from NVIDIA.
70
+ */
71
+ export type PackFit = 'ok' | 'no_build' | 'no_nvidia_gpu' | 'gpu_too_old' | 'driver_too_old';
72
+ export interface PackAssessment {
73
+ readonly pack: PackId;
74
+ readonly fit: PackFit;
75
+ /** Download size for this platform. */
76
+ readonly bytes?: number;
77
+ /** The driver version the pack needs, for `driver_too_old`. */
78
+ readonly needsDriver?: string;
79
+ }
80
+ /** Whether `pack` can run here. */
81
+ export declare function assessPack(hardware: Hardware, pack: PackId): PackAssessment;
82
+ /** Compare dotted numeric versions. */
83
+ export declare function compareVersions(a: string, b: string): number;
84
+ /** The recommendation for this machine. */
85
+ export interface Recommendation {
86
+ /** Cloud stays the default; local is recommended only with a usable NVIDIA card. */
87
+ readonly backend: 'cloud' | 'local';
88
+ /** The pack to install for torch-release models (the published format). */
89
+ readonly torchPack: PackId;
90
+ /** The pack to install for ONNX deliveries. */
91
+ readonly onnxPack: PackId;
92
+ /**
93
+ * Why: `cuda` (a supported NVIDIA card), `cpu_only` (no usable GPU runtime),
94
+ * `driver_update` (a supported card whose driver is too old), `gpu_too_old`.
95
+ */
96
+ readonly reason: 'cuda' | 'cpu_only' | 'driver_update' | 'gpu_too_old';
97
+ /** Set when an NPU was found; it waits for an ONNX model exported for it. */
98
+ readonly npuWaiting: boolean;
99
+ readonly packs: readonly PackAssessment[];
100
+ }
101
+ export declare function recommend(hardware: Hardware): Recommendation;
102
+ /** Probe this machine once per process; blocking, a few seconds at most. */
103
+ export declare function detectHardware(): Hardware;
104
+ /** Parse `name,driver_version,memory.total` rows (memory in MiB) and optional `compute_cap` rows. */
105
+ export declare function parseNvidiaRows(rows: string, capabilities?: readonly string[]): GpuInfo[];
@@ -0,0 +1,207 @@
1
+ /**
2
+ * What this machine offers the local model, and which runtime pack to recommend for it.
3
+ *
4
+ * Only ready-made runtimes are considered. NVIDIA cards run the CUDA packs, whose CUDA libraries
5
+ * come as NVIDIA's own wheels, so only the driver has to be recent enough; this package never
6
+ * installs or updates a driver. AMD, Intel and Apple graphics have no published runtime for these
7
+ * models, so they get the CPU pack or the cloud. An NPU is reported, but it needs an ONNX model
8
+ * exported for it, which is not published yet.
9
+ */
10
+ import { spawnSync } from 'node:child_process';
11
+ import fs from 'node:fs';
12
+ import os from 'node:os';
13
+ import { currentTarget, readLock } from './local/packs.js';
14
+ /** Adapters that are not graphics hardware: the software renderer and remote or virtual displays. */
15
+ const NON_GPU_ADAPTER = /microsoft basic (display|render)|remote display|virtual|indirect display|idd ?driver|parsec|citrix/i;
16
+ export function assess(probes) {
17
+ const nvidia = probes.nvidia ?? [];
18
+ const others = probes.adapters
19
+ .filter(name => !NON_GPU_ADAPTER.test(name) && !nvidia.some(gpu => gpu.name === name) && !(probes.nvidia !== undefined && /nvidia/i.test(name)))
20
+ .map((name) => ({ name, vendor: vendorOf(name) }));
21
+ const target = currentTarget(probes.platform, probes.arch);
22
+ const npus = probes.npus.map((name) => ({ name, vendor: npuVendorOf(name) }));
23
+ if (probes.platform === 'darwin' && probes.arch === 'arm64')
24
+ npus.push({ name: 'Apple Neural Engine', vendor: 'apple' });
25
+ return {
26
+ platform: probes.platform, arch: probes.arch, ...target === undefined ? {} : { target },
27
+ totalMemory: probes.totalMemory, cpus: probes.cpus, gpus: [...nvidia, ...others], npus,
28
+ };
29
+ }
30
+ function vendorOf(name) {
31
+ if (/nvidia|geforce|quadro|tesla|rtx/i.test(name))
32
+ return 'nvidia';
33
+ if (/amd|radeon/i.test(name))
34
+ return 'amd';
35
+ if (/intel/i.test(name))
36
+ return 'intel';
37
+ if (/apple/i.test(name))
38
+ return 'apple';
39
+ return 'other';
40
+ }
41
+ function npuVendorOf(name) {
42
+ if (/intel|ai boost/i.test(name))
43
+ return 'intel';
44
+ if (/amd|ryzen/i.test(name))
45
+ return 'amd';
46
+ if (/qualcomm|hexagon|snapdragon/i.test(name))
47
+ return 'qualcomm';
48
+ return 'other';
49
+ }
50
+ /** Lowest compute capability the CUDA packs run on (Turing). */
51
+ export const MIN_COMPUTE_CAPABILITY = 7.5;
52
+ export function gpuGeneration(computeCapability) {
53
+ const capability = Number(computeCapability);
54
+ const supported = capability >= MIN_COMPUTE_CAPABILITY;
55
+ if (capability >= 12)
56
+ return { architecture: 'Blackwell', fp16: 'tensor-cores', bf16: true, supported };
57
+ if (capability >= 10)
58
+ return { architecture: 'Blackwell (data center)', fp16: 'tensor-cores', bf16: true, supported };
59
+ if (capability >= 9)
60
+ return { architecture: 'Hopper', fp16: 'tensor-cores', bf16: true, supported };
61
+ if (capability >= 8.9)
62
+ return { architecture: 'Ada Lovelace', fp16: 'tensor-cores', bf16: true, supported };
63
+ if (capability >= 8)
64
+ return { architecture: 'Ampere', fp16: 'tensor-cores', bf16: true, supported };
65
+ if (capability >= 7.5)
66
+ return { architecture: 'Turing', fp16: 'tensor-cores', bf16: false, supported };
67
+ if (capability >= 7)
68
+ return { architecture: 'Volta', fp16: 'tensor-cores', bf16: false, supported };
69
+ if (capability === 6 || capability === 5.3 || capability === 6.2)
70
+ return { architecture: 'Pascal', fp16: 'native', bf16: false, supported };
71
+ if (capability >= 6)
72
+ return { architecture: 'Pascal', fp16: 'slow', bf16: false, supported };
73
+ return { architecture: 'Maxwell or older', fp16: 'slow', bf16: false, supported };
74
+ }
75
+ /** Whether `pack` can run here. */
76
+ export function assessPack(hardware, pack) {
77
+ const lock = readLock(pack);
78
+ const build = hardware.target === undefined ? undefined : lock.targets[hardware.target];
79
+ if (build === undefined)
80
+ return { pack, fit: 'no_build' };
81
+ const base = { pack, bytes: build.bytes };
82
+ if (lock.device === 'cpu')
83
+ return { ...base, fit: 'ok' };
84
+ const cards = hardware.gpus.filter(gpu => gpu.vendor === 'nvidia' && gpu.driver !== undefined);
85
+ if (cards.length === 0)
86
+ return { ...base, fit: 'no_nvidia_gpu' };
87
+ const best = cards.reduce((a, b) => Number(b.computeCapability ?? 0) > Number(a.computeCapability ?? 0) ? b : a);
88
+ const minimum = Number(lock.requires.compute_capability ?? MIN_COMPUTE_CAPABILITY);
89
+ if (best.computeCapability !== undefined && Number(best.computeCapability) < minimum)
90
+ return { ...base, fit: 'gpu_too_old' };
91
+ const needed = lock.requires.nvidia_driver;
92
+ if (needed !== undefined && best.driver !== undefined && compareVersions(best.driver, needed) < 0) {
93
+ return { ...base, fit: 'driver_too_old', needsDriver: needed };
94
+ }
95
+ return { ...base, fit: 'ok' };
96
+ }
97
+ /** Compare dotted numeric versions. */
98
+ export function compareVersions(a, b) {
99
+ const left = a.split('.').map(Number);
100
+ const right = b.split('.').map(Number);
101
+ for (let index = 0; index < Math.max(left.length, right.length); index++) {
102
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
103
+ if (difference !== 0)
104
+ return Math.sign(difference);
105
+ }
106
+ return 0;
107
+ }
108
+ export function recommend(hardware) {
109
+ const packs = ['onnx-cpu', 'onnx-cuda', 'torch-cpu', 'torch-cu128'].map(pack => assessPack(hardware, pack));
110
+ const fit = (pack) => packs.find(item => item.pack === pack)?.fit;
111
+ const torchCuda = fit('torch-cu128') === 'ok';
112
+ const onnxCuda = fit('onnx-cuda') === 'ok';
113
+ const cudaFits = [fit('torch-cu128'), fit('onnx-cuda')];
114
+ const reason = torchCuda || onnxCuda ? 'cuda'
115
+ : cudaFits.includes('driver_too_old') ? 'driver_update'
116
+ : cudaFits.includes('gpu_too_old') ? 'gpu_too_old' : 'cpu_only';
117
+ return {
118
+ backend: torchCuda || onnxCuda ? 'local' : 'cloud',
119
+ torchPack: torchCuda ? 'torch-cu128' : 'torch-cpu',
120
+ onnxPack: onnxCuda ? 'onnx-cuda' : 'onnx-cpu',
121
+ reason,
122
+ npuWaiting: hardware.npus.length > 0,
123
+ packs,
124
+ };
125
+ }
126
+ /** Longest wait for one probing command. */
127
+ const PROBE_TIMEOUT_MS = 15_000;
128
+ let cached;
129
+ /** Probe this machine once per process; blocking, a few seconds at most. */
130
+ export function detectHardware() {
131
+ cached ??= assess(systemProbes());
132
+ return cached;
133
+ }
134
+ function systemProbes() {
135
+ return {
136
+ platform: process.platform,
137
+ arch: process.arch,
138
+ totalMemory: os.totalmem(),
139
+ cpus: os.availableParallelism(),
140
+ nvidia: probeNvidia(),
141
+ adapters: process.platform === 'win32' ? windowsDevices('Win32_VideoController', 'Name') : linuxAdapters(),
142
+ npus: process.platform === 'win32' ? windowsNpus() : linuxNpus(),
143
+ };
144
+ }
145
+ function run(command, args) {
146
+ const result = spawnSync(command, args, { encoding: 'utf8', timeout: PROBE_TIMEOUT_MS, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
147
+ return result.status === 0 ? result.stdout : undefined;
148
+ }
149
+ function probeNvidia() {
150
+ const rows = run('nvidia-smi', ['--query-gpu=name,driver_version,memory.total', '--format=csv,noheader,nounits']);
151
+ if (rows === undefined)
152
+ return undefined;
153
+ // Older drivers reject the field, which leaves the capability unknown.
154
+ const capabilities = run('nvidia-smi', ['--query-gpu=compute_cap', '--format=csv,noheader'])?.split(/\r?\n/).filter(Boolean);
155
+ return parseNvidiaRows(rows, capabilities);
156
+ }
157
+ /** Parse `name,driver_version,memory.total` rows (memory in MiB) and optional `compute_cap` rows. */
158
+ export function parseNvidiaRows(rows, capabilities) {
159
+ return rows.split(/\r?\n/).filter(line => line.trim() !== '').map((line, index) => {
160
+ const [name = '', driver, memory] = line.split(',').map(field => field.trim());
161
+ const mib = Number(memory);
162
+ const capability = capabilities?.[index]?.trim();
163
+ return {
164
+ name,
165
+ vendor: 'nvidia',
166
+ ...driver ? { driver } : {},
167
+ ...Number.isFinite(mib) && mib > 0 ? { memoryBytes: mib * 1024 ** 2 } : {},
168
+ ...capability !== undefined && /^\d+\.\d+$/.test(capability) ? { computeCapability: capability } : {},
169
+ };
170
+ });
171
+ }
172
+ function windowsDevices(cimClass, field, filter = '') {
173
+ const output = run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `(Get-CimInstance ${cimClass}${filter}).${field}`]);
174
+ return output === undefined ? [] : output.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
175
+ }
176
+ /** NPUs: Windows lists them in the `ComputeAccelerator` device class (Intel AI Boost, AMD IPU, Qualcomm Hexagon). */
177
+ function windowsNpus() {
178
+ return windowsDevices('Win32_PnPEntity', 'Name', ` -Filter "PNPClass='ComputeAccelerator'"`);
179
+ }
180
+ /** Linux exposes accelerators under `/sys/class/accel` (Intel `intel_vpu`, AMD `amdxdna`). */
181
+ function linuxNpus() {
182
+ if (process.platform !== 'linux')
183
+ return [];
184
+ try {
185
+ return fs.readdirSync('/sys/class/accel').map(device => {
186
+ try {
187
+ const driver = fs.readlinkSync(`/sys/class/accel/${device}/device/driver`).split('/').at(-1) ?? device;
188
+ return driver === 'intel_vpu' ? 'Intel NPU' : driver === 'amdxdna' ? 'AMD NPU' : driver;
189
+ }
190
+ catch {
191
+ return device;
192
+ }
193
+ });
194
+ }
195
+ catch {
196
+ return [];
197
+ }
198
+ }
199
+ function linuxAdapters() {
200
+ if (process.platform !== 'linux')
201
+ return [];
202
+ const output = run('lspci', []);
203
+ if (output === undefined)
204
+ return [];
205
+ return output.split(/\r?\n/).filter(line => /VGA compatible controller|3D controller|Display controller/.test(line))
206
+ .map(line => line.replace(/^.*?controller:\s*/, '').trim());
207
+ }
@@ -0,0 +1,26 @@
1
+ /** Overrides the default data directory of the command line; used by tests and isolated setups. */
2
+ export declare const HOME_ENV = "VULCAN_DECISION_HOME";
3
+ /**
4
+ * One data directory: installed runtime packs, installed models, partial downloads and, for the
5
+ * command line, its account file and settings. A host passes its own directory (dsh uses
6
+ * `$DSH_HOME/vulcan-decision`); the command line uses {@link defaultHome}.
7
+ */
8
+ export declare class DecisionHome {
9
+ readonly root: string;
10
+ constructor(root: string);
11
+ /** `<root>/packs/<pack id>/`, one installed runtime pack each. */
12
+ get packs(): string;
13
+ /** `<root>/models/<install id>/`, one installed model package each. */
14
+ get models(): string;
15
+ /** Partial downloads, kept between attempts so an interrupted download resumes. */
16
+ get downloads(): string;
17
+ /** A file directly in the data directory, which is created first. */
18
+ file(name: string): string;
19
+ private dir;
20
+ }
21
+ /** The command line's data directory: `VULCAN_DECISION_HOME`, else `~/.vulcan-decision`. */
22
+ export declare function defaultHome(): DecisionHome;
23
+ /** Create `dir` and its parents with owner-only access. */
24
+ export declare function createPrivateDir(dir: string): void;
25
+ /** Write `text` to `file` through a temporary file, so readers never see a partial file. */
26
+ export declare function writeFileAtomic(file: string, text: string): void;
@@ -0,0 +1,54 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ /** Overrides the default data directory of the command line; used by tests and isolated setups. */
5
+ export const HOME_ENV = 'VULCAN_DECISION_HOME';
6
+ /**
7
+ * One data directory: installed runtime packs, installed models, partial downloads and, for the
8
+ * command line, its account file and settings. A host passes its own directory (dsh uses
9
+ * `$DSH_HOME/vulcan-decision`); the command line uses {@link defaultHome}.
10
+ */
11
+ export class DecisionHome {
12
+ root;
13
+ constructor(root) {
14
+ this.root = path.resolve(root);
15
+ }
16
+ /** `<root>/packs/<pack id>/`, one installed runtime pack each. */
17
+ get packs() {
18
+ return this.dir('packs');
19
+ }
20
+ /** `<root>/models/<install id>/`, one installed model package each. */
21
+ get models() {
22
+ return this.dir('models');
23
+ }
24
+ /** Partial downloads, kept between attempts so an interrupted download resumes. */
25
+ get downloads() {
26
+ return this.dir('downloads');
27
+ }
28
+ /** A file directly in the data directory, which is created first. */
29
+ file(name) {
30
+ createPrivateDir(this.root);
31
+ return path.join(this.root, name);
32
+ }
33
+ dir(name) {
34
+ const dir = path.join(this.root, name);
35
+ createPrivateDir(dir);
36
+ return dir;
37
+ }
38
+ }
39
+ /** The command line's data directory: `VULCAN_DECISION_HOME`, else `~/.vulcan-decision`. */
40
+ export function defaultHome() {
41
+ return new DecisionHome(process.env[HOME_ENV] || path.join(os.homedir(), '.vulcan-decision'));
42
+ }
43
+ /** Create `dir` and its parents with owner-only access. */
44
+ export function createPrivateDir(dir) {
45
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
46
+ if (process.platform !== 'win32')
47
+ fs.chmodSync(dir, 0o700);
48
+ }
49
+ /** Write `text` to `file` through a temporary file, so readers never see a partial file. */
50
+ export function writeFileAtomic(file, text) {
51
+ const temporary = `${file}.${process.pid}.tmp`;
52
+ fs.writeFileSync(temporary, text, { mode: 0o600 });
53
+ fs.renameSync(temporary, file);
54
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Vulcan Decision: judges AI coding tool calls before they run, with OpenVulcan's cloud endpoints
3
+ * or a locally installed `vulcan-decision-shell` model.
4
+ */
5
+ export * from '@openvulcan/openvulcan-account';
6
+ export * from './cloud/edge.js';
7
+ export * from './download.js';
8
+ export * from './engine.js';
9
+ export * from './hardware.js';
10
+ export * from './home.js';
11
+ export * from './local/engine.js';
12
+ export * from './local/models.js';
13
+ export * from './local/packs.js';
14
+ export { WORKER_SCRIPT, WorkerProcess, type LoadResult, type ModelFormat, type ProbeResult as WorkerProbe } from './local/worker.js';
15
+ export * from './protocol.js';
16
+ export { renderCall } from './render.js';
17
+ export { VERSION } from './version.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Vulcan Decision: judges AI coding tool calls before they run, with OpenVulcan's cloud endpoints
3
+ * or a locally installed `vulcan-decision-shell` model.
4
+ */
5
+ export * from '@openvulcan/openvulcan-account';
6
+ export * from './cloud/edge.js';
7
+ export * from './download.js';
8
+ export * from './engine.js';
9
+ export * from './hardware.js';
10
+ export * from './home.js';
11
+ export * from './local/engine.js';
12
+ export * from './local/models.js';
13
+ export * from './local/packs.js';
14
+ export { WORKER_SCRIPT, WorkerProcess } from './local/worker.js';
15
+ export * from './protocol.js';
16
+ export { renderCall } from './render.js';
17
+ export { VERSION } from './version.js';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The local backend: keeps one worker process with the selected model loaded, restarts it when
3
+ * the selection changes and stops it after a quiet period.
4
+ */
5
+ import { type JudgeInput, type ModelResult } from '../protocol.js';
6
+ import type { DecisionHome } from '../home.js';
7
+ import { type PackId } from './packs.js';
8
+ import { type LoadResult } from './worker.js';
9
+ /** Which installed pack runs which installed model. */
10
+ export interface LocalSelection {
11
+ readonly pack: PackId;
12
+ readonly model: string;
13
+ }
14
+ /** Split `inputs` into requests of at most `limit` inputs, in order; no limit means one request. */
15
+ export declare function mergeRequests<T>(inputs: readonly T[], limit: number | undefined): T[][];
16
+ /** Hand each caller back its own slice of the merged results. */
17
+ export declare function splitResults<T>(groups: readonly (readonly unknown[])[], results: readonly T[]): T[][];
18
+ /** Stop the process after this long without a judgement, to give its memory back. */
19
+ export declare const LOCAL_IDLE_MS: number;
20
+ export interface LocalStatus {
21
+ readonly running: boolean;
22
+ readonly selection?: LocalSelection;
23
+ readonly loaded?: LoadResult;
24
+ }
25
+ export declare class LocalEngine {
26
+ private readonly home;
27
+ private readonly idleMs;
28
+ private worker;
29
+ private current;
30
+ private idleTimer;
31
+ /** Loads and merged judgements run one at a time, in arrival order. */
32
+ private queue;
33
+ /** Callers waiting for the next merged judgement. */
34
+ private waiting;
35
+ /** Whether a drain of `waiting` is already queued behind the running work. */
36
+ private drainQueued;
37
+ constructor(home: DecisionHome, idleMs?: number);
38
+ status(): LocalStatus;
39
+ /**
40
+ * Judge `inputs` with `selection`, loading it first when needed. Calls that arrive while the
41
+ * worker is busy wait together and go out as one merged request, split into requests of at most
42
+ * the package's `max_request_items` calls.
43
+ */
44
+ judge(selection: LocalSelection, inputs: readonly JudgeInput[]): Promise<ModelResult[]>;
45
+ /** Load `selection` ahead of the first judgement. */
46
+ warm(selection: LocalSelection): Promise<LoadResult>;
47
+ stop(): void;
48
+ /** Answer every waiting caller, one selection at a time in arrival order. */
49
+ private drain;
50
+ /** Judge several callers' inputs with `selection` in merged requests; answers per caller. */
51
+ private run;
52
+ private ensure;
53
+ private armIdle;
54
+ }