@openvulcan/dsh-vulcancode 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +126 -2
- package/assets/openvulcan-icon.svg +33 -0
- package/cordis.patch.yml +19 -0
- package/node_modules/@openvulcan/dsh-decision-guard/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-decision-guard/README.md +14 -0
- package/node_modules/@openvulcan/dsh-decision-guard/assets/decision-guard-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.d.ts +40 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.js +150 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/client.js +2250 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.d.ts +90 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.js +108 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/index.d.ts +129 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/index.js +572 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.d.ts +22 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.js +77 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.d.ts +158 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.js +340 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/records.d.ts +133 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/records.js +186 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/review.d.ts +55 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/review.js +202 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.d.ts +62 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.js +155 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/version.d.ts +2 -0
- package/node_modules/@openvulcan/dsh-decision-guard/lib/version.js +4 -0
- package/node_modules/@openvulcan/dsh-decision-guard/package.json +68 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/assets/account-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/lib/client.js +573 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.d.ts +92 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.js +194 -0
- package/node_modules/@openvulcan/dsh-vulcan-account/package.json +57 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/assets/answers-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-interactive.md +79 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-standard.md +1 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.d.ts +258 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.js +698 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/client.js +671 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.d.ts +29 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.js +70 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.d.ts +19 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.js +52 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.d.ts +9 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.js +9 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.d.ts +22 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.js +39 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.d.ts +20 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.js +17 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.d.ts +66 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.js +118 -0
- package/node_modules/@openvulcan/dsh-vulcan-answers/package.json +59 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/assets/luaskills-icon.svg +1 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/client.js +949 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.d.ts +46 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.js +51 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.d.ts +55 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.js +149 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.d.ts +49 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.js +116 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.d.ts +26 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.js +276 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.d.ts +65 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.js +2 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.d.ts +111 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.js +281 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.d.ts +26 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.js +87 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.d.ts +79 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.js +165 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.d.ts +34 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.js +61 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.d.ts +59 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.js +107 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.d.ts +10 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.js +140 -0
- package/node_modules/@openvulcan/dsh-vulcan-luaskills/package.json +57 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/assets/markdown-memory-guide.md +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-curator/SKILL.md +31 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/client.js +1090 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.d.ts +30 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.js +240 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.d.ts +30 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.js +46 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.d.ts +23 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.js +25 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.d.ts +108 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.js +373 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.d.ts +14 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.js +31 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.d.ts +196 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.js +204 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.d.ts +92 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.js +472 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.d.ts +126 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.js +553 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.d.ts +56 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.js +182 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.d.ts +13 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.js +24 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.d.ts +65 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.js +227 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.d.ts +34 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.js +171 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.d.ts +72 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.js +250 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.d.ts +6 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.js +12 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.d.ts +8 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.js +38 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.d.ts +132 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.js +235 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.d.ts +14 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.js +27 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.d.ts +29 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.js +62 -0
- package/node_modules/@openvulcan/dsh-vulcan-memory/package.json +63 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/client.js +509 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/index.d.ts +6 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/lib/index.js +8 -0
- package/node_modules/@openvulcan/dsh-vulcan-reasoning/package.json +42 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/LICENSE +21 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/assets/style-icon.png +0 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/lib/client.js +1121 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.d.ts +34 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.js +42 -0
- package/node_modules/@openvulcan/dsh-vulcan-style/package.json +51 -0
- package/node_modules/@openvulcan/openvulcan-account/LICENSE +21 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/account.d.ts +140 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/account.js +347 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/index.d.ts +1 -0
- package/node_modules/@openvulcan/openvulcan-account/lib/index.js +1 -0
- package/node_modules/@openvulcan/openvulcan-account/package.json +29 -0
- package/node_modules/@openvulcan/vulcan-decision/LICENSE +21 -0
- package/node_modules/@openvulcan/vulcan-decision/README.md +23 -0
- package/node_modules/@openvulcan/vulcan-decision/bin/vulcan-decision.js +4 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cli.d.ts +5 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cli.js +267 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.d.ts +211 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.js +594 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/download.d.ts +36 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/download.js +99 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/engine.d.ts +37 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/engine.js +39 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/hardware.d.ts +105 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/hardware.js +207 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/home.d.ts +26 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/home.js +54 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/index.d.ts +17 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/index.js +17 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.d.ts +54 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.js +157 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/models.d.ts +126 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/models.js +261 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.d.ts +102 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.js +195 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.d.ts +61 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.js +123 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/protocol.d.ts +74 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/protocol.js +31 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/render.d.ts +15 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/render.js +33 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/version.d.ts +2 -0
- package/node_modules/@openvulcan/vulcan-decision/lib/version.js +4 -0
- package/node_modules/@openvulcan/vulcan-decision/package.json +40 -0
- package/node_modules/@openvulcan/vulcan-decision/python/vulcan_decision_worker.py +363 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cpu.json +627 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cuda.json +423 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cpu.json +1047 -0
- package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cu128.json +647 -0
- package/package.json +55 -4
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime packs: the optional local runtimes, installed on demand next to the cloud-only default.
|
|
3
|
+
*
|
|
4
|
+
* A pack is a prebuilt python-build-standalone interpreter plus the official wheels its lock file
|
|
5
|
+
* (`runtime-packs/<pack>.json`, written by `scripts/lock_runtime.py`) pins for this platform.
|
|
6
|
+
* Installing downloads exactly those files, verifies each against its size and SHA-256, unpacks
|
|
7
|
+
* the interpreter, installs the wheels offline with the interpreter's own pip and checks that the
|
|
8
|
+
* runtime imports. Nothing is compiled and no driver is installed: GPU packs bring their CUDA
|
|
9
|
+
* libraries as NVIDIA's wheels and only need a recent enough driver.
|
|
10
|
+
*/
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { fetchPinned } from '../download.js';
|
|
16
|
+
import { writeFileAtomic } from '../home.js';
|
|
17
|
+
import { WorkerProcess } from './worker.js';
|
|
18
|
+
/** Packs with a lock file, in the order they are offered. */
|
|
19
|
+
export const PACK_IDS = ['onnx-cpu', 'onnx-cuda', 'torch-cpu', 'torch-cu128'];
|
|
20
|
+
/**
|
|
21
|
+
* Packs waiting for a release: an NPU needs an ONNX model exported for it, which is not
|
|
22
|
+
* published yet. They are listed so a host can show what this machine will be able to use.
|
|
23
|
+
*/
|
|
24
|
+
export const PLANNED_PACKS = [
|
|
25
|
+
{ id: 'onnx-openvino', backend: 'onnx', accelerator: 'intel-npu' },
|
|
26
|
+
{ id: 'onnx-qnn', backend: 'onnx', accelerator: 'qualcomm-npu' },
|
|
27
|
+
];
|
|
28
|
+
const LOCK_DIR = fileURLToPath(new URL('../../runtime-packs/', import.meta.url));
|
|
29
|
+
/** The target this process runs on, or undefined when no pack supports it. */
|
|
30
|
+
export function currentTarget(platform = process.platform, arch = process.arch) {
|
|
31
|
+
if (platform === 'win32' && arch === 'x64')
|
|
32
|
+
return 'win-x64';
|
|
33
|
+
if (platform === 'linux' && arch === 'x64')
|
|
34
|
+
return 'linux-x64';
|
|
35
|
+
if (platform === 'linux' && arch === 'arm64')
|
|
36
|
+
return 'linux-arm64';
|
|
37
|
+
if (platform === 'darwin' && arch === 'arm64')
|
|
38
|
+
return 'mac-arm64';
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
export function isPackId(value) {
|
|
42
|
+
return typeof value === 'string' && PACK_IDS.includes(value);
|
|
43
|
+
}
|
|
44
|
+
/** The lock file shipped with this package for `pack`. */
|
|
45
|
+
export function readLock(pack) {
|
|
46
|
+
const lock = JSON.parse(fs.readFileSync(path.join(LOCK_DIR, `${pack}.json`), 'utf8'));
|
|
47
|
+
if (lock.format !== 1 || lock.pack !== pack)
|
|
48
|
+
throw new Error(`the lock file of ${pack} is malformed`);
|
|
49
|
+
return lock;
|
|
50
|
+
}
|
|
51
|
+
/** The installed pack in `home`, or undefined when it is missing or unreadable. */
|
|
52
|
+
export function installedPack(home, pack) {
|
|
53
|
+
try {
|
|
54
|
+
const record = JSON.parse(fs.readFileSync(path.join(home.packs, pack, 'pack.json'), 'utf8'));
|
|
55
|
+
return record.format === 1 && record.pack === pack ? record : undefined;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** The interpreter of an installed pack. */
|
|
62
|
+
export function packPython(home, record) {
|
|
63
|
+
return path.join(home.packs, record.pack, ...record.python.split('/'));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Install `pack` for this platform, replacing an installed one only once the new one works.
|
|
67
|
+
* @throws Error when the pack has no build for this platform, a download fails verification, or
|
|
68
|
+
* the installed runtime does not start.
|
|
69
|
+
*/
|
|
70
|
+
export async function installPack(home, pack, options = {}) {
|
|
71
|
+
const lock = readLock(pack);
|
|
72
|
+
const target = options.target ?? currentTarget();
|
|
73
|
+
const build = target === undefined ? undefined : lock.targets[target];
|
|
74
|
+
if (target === undefined || build === undefined) {
|
|
75
|
+
throw new Error(`${pack} has no build for ${process.platform}-${process.arch}`);
|
|
76
|
+
}
|
|
77
|
+
const downloads = path.join(home.downloads, `pack-${lock.id}`);
|
|
78
|
+
const staging = path.join(home.packs, `.staging-${pack}`);
|
|
79
|
+
if (target === 'win-x64' && staging.length + DEEPEST_PACK_PATH > WINDOWS_MAX_PATH) {
|
|
80
|
+
throw new Error(`the data directory ${home.root} is too deep for Windows paths: a pack's files would exceed `
|
|
81
|
+
+ `${WINDOWS_MAX_PATH} characters; use a data directory at most ${WINDOWS_MAX_PATH - DEEPEST_PACK_PATH - (staging.length - home.root.length)} characters long`);
|
|
82
|
+
}
|
|
83
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
84
|
+
fs.mkdirSync(staging, { recursive: true });
|
|
85
|
+
try {
|
|
86
|
+
let done = 0;
|
|
87
|
+
for (const file of build.files) {
|
|
88
|
+
const start = done;
|
|
89
|
+
await fetchPinned(file, path.join(downloads, file.name), {
|
|
90
|
+
...options.source === undefined ? {} : { source: options.source },
|
|
91
|
+
...options.signal === undefined ? {} : { signal: options.signal },
|
|
92
|
+
onBytes: bytes => {
|
|
93
|
+
done += bytes;
|
|
94
|
+
options.onProgress?.({ phase: 'download', done, total: build.bytes, file: file.name });
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
done = start + file.bytes;
|
|
98
|
+
}
|
|
99
|
+
options.onProgress?.({ phase: 'unpack' });
|
|
100
|
+
const interpreter = build.files.find(file => file.kind === 'python');
|
|
101
|
+
if (interpreter === undefined)
|
|
102
|
+
throw new Error(`the lock file of ${pack} has no interpreter`);
|
|
103
|
+
await run(tarCommand(), ['-xzf', path.join(downloads, interpreter.name), '-C', staging], options.signal);
|
|
104
|
+
const python = target === 'win-x64' ? 'python/python.exe' : 'python/bin/python3';
|
|
105
|
+
const pythonPath = path.join(staging, ...python.split('/'));
|
|
106
|
+
if (!fs.existsSync(pythonPath))
|
|
107
|
+
throw new Error('the interpreter archive did not contain python');
|
|
108
|
+
options.onProgress?.({ phase: 'install' });
|
|
109
|
+
const wheels = build.files.filter(file => file.kind === 'wheel').map(file => path.join(downloads, file.name));
|
|
110
|
+
await run(pythonPath, ['-I', '-m', 'pip', 'install', '--no-index', '--no-deps', '--disable-pip-version-check',
|
|
111
|
+
'--no-warn-script-location', '--quiet', ...wheels], options.signal);
|
|
112
|
+
options.onProgress?.({ phase: 'probe' });
|
|
113
|
+
const worker = new WorkerProcess(pythonPath);
|
|
114
|
+
let probe;
|
|
115
|
+
try {
|
|
116
|
+
probe = await worker.probe();
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await worker.stop();
|
|
120
|
+
}
|
|
121
|
+
const missing = lock.backend === 'onnx' ? probe.onnxruntime === undefined : probe.torch === undefined;
|
|
122
|
+
if (missing)
|
|
123
|
+
throw new Error(`${pack} installed but its ${lock.backend} runtime does not import`);
|
|
124
|
+
const record = {
|
|
125
|
+
format: 1, pack, lock: lock.id, target, backend: lock.backend, device: lock.device, python,
|
|
126
|
+
installed_at: new Date().toISOString(), probe,
|
|
127
|
+
};
|
|
128
|
+
writeFileAtomic(path.join(staging, 'pack.json'), `${JSON.stringify(record, null, 2)}\n`);
|
|
129
|
+
replaceDirectory(staging, path.join(home.packs, pack));
|
|
130
|
+
fs.rmSync(downloads, { recursive: true, force: true });
|
|
131
|
+
return record;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Longest Windows path pip can write without long-path support, without the terminating NUL. */
|
|
139
|
+
const WINDOWS_MAX_PATH = 259;
|
|
140
|
+
/**
|
|
141
|
+
* Longest path inside a pack directory, with room to spare: `python\Lib\site-packages\` plus
|
|
142
|
+
* torch's deepest header (144 characters for torch 2.9.1).
|
|
143
|
+
*/
|
|
144
|
+
const DEEPEST_PACK_PATH = 150;
|
|
145
|
+
/** Remove an installed pack and any partial download of it. */
|
|
146
|
+
export function removePack(home, pack) {
|
|
147
|
+
const dir = path.join(home.packs, pack);
|
|
148
|
+
const existed = fs.existsSync(dir);
|
|
149
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
150
|
+
fs.rmSync(path.join(home.downloads, `pack-${readLock(pack).id}`), { recursive: true, force: true });
|
|
151
|
+
return existed;
|
|
152
|
+
}
|
|
153
|
+
/** Move `staging` to `dest`, keeping the old `dest` until the move succeeded. */
|
|
154
|
+
export function replaceDirectory(staging, dest) {
|
|
155
|
+
const old = `${dest}.old-${process.pid}`;
|
|
156
|
+
const hadOld = fs.existsSync(dest);
|
|
157
|
+
if (hadOld)
|
|
158
|
+
fs.renameSync(dest, old);
|
|
159
|
+
try {
|
|
160
|
+
fs.renameSync(staging, dest);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (hadOld)
|
|
164
|
+
fs.renameSync(old, dest);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
if (hadOld)
|
|
168
|
+
fs.rmSync(old, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The archive tool: Windows' own bsdtar, so a GNU tar earlier on PATH (Git Bash, MSYS) cannot
|
|
172
|
+
* mistake a drive letter for a remote host.
|
|
173
|
+
*/
|
|
174
|
+
function tarCommand() {
|
|
175
|
+
if (process.platform !== 'win32')
|
|
176
|
+
return 'tar';
|
|
177
|
+
return path.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'tar.exe');
|
|
178
|
+
}
|
|
179
|
+
function run(command, args, signal) {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const child = spawn(command, args, { stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true, ...signal === undefined ? {} : { signal } });
|
|
182
|
+
let stderr = '';
|
|
183
|
+
child.stderr.setEncoding('utf8');
|
|
184
|
+
child.stderr.on('data', (chunk) => {
|
|
185
|
+
stderr = (stderr + chunk).slice(-4000);
|
|
186
|
+
});
|
|
187
|
+
child.on('error', reject);
|
|
188
|
+
child.on('close', code => {
|
|
189
|
+
if (code === 0)
|
|
190
|
+
resolve();
|
|
191
|
+
else
|
|
192
|
+
reject(new Error(`${path.basename(command)} failed (exit ${code}): ${stderr.trim()}`));
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host side of `python/vulcan_decision_worker.py`: one child process per runtime pack and
|
|
3
|
+
* model, answering JSON-line requests in order. Any failure, timeout or exit becomes an error;
|
|
4
|
+
* none of them is ever an answer.
|
|
5
|
+
*/
|
|
6
|
+
import { type JudgeInput, type ModelResult } from '../protocol.js';
|
|
7
|
+
/** The worker script shipped with this package. */
|
|
8
|
+
export declare const WORKER_SCRIPT: string;
|
|
9
|
+
/** What `probe` reports about a pack's interpreter. */
|
|
10
|
+
export interface ProbeResult {
|
|
11
|
+
readonly python: string;
|
|
12
|
+
readonly machine: string;
|
|
13
|
+
readonly system: string;
|
|
14
|
+
readonly onnxruntime?: string;
|
|
15
|
+
readonly onnx_providers?: readonly string[];
|
|
16
|
+
readonly torch?: string;
|
|
17
|
+
readonly torch_cuda?: string | null;
|
|
18
|
+
readonly cuda_devices?: readonly {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly capability: string;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
export type ModelFormat = 'onnx-delivery' | 'torch-release';
|
|
24
|
+
/** Request and batch limits an ONNX package declares in its `runtime-config.json`. */
|
|
25
|
+
export interface BatchLimits {
|
|
26
|
+
/** Longest call in tokens; longer calls are answered as over the limit, not evaluated. */
|
|
27
|
+
readonly max_tokens: number;
|
|
28
|
+
/** Most calls one `judge` request may carry; the host splits larger groups. */
|
|
29
|
+
readonly max_request_items: number;
|
|
30
|
+
/** Most rows one graph run takes. */
|
|
31
|
+
readonly micro_batch: number;
|
|
32
|
+
/** Most padded tokens (rows times the longest row) one graph run takes. */
|
|
33
|
+
readonly padded_batch_tokens: number;
|
|
34
|
+
}
|
|
35
|
+
export interface LoadResult {
|
|
36
|
+
readonly load_seconds: number;
|
|
37
|
+
readonly format: ModelFormat;
|
|
38
|
+
readonly root: string;
|
|
39
|
+
readonly device: string;
|
|
40
|
+
/** Present for ONNX packages; a torch release batches on its own. */
|
|
41
|
+
readonly limits?: BatchLimits;
|
|
42
|
+
}
|
|
43
|
+
export declare class WorkerProcess {
|
|
44
|
+
private readonly python;
|
|
45
|
+
private child;
|
|
46
|
+
private readonly pending;
|
|
47
|
+
private nextId;
|
|
48
|
+
private stderr;
|
|
49
|
+
constructor(python: string);
|
|
50
|
+
get running(): boolean;
|
|
51
|
+
probe(): Promise<ProbeResult>;
|
|
52
|
+
load(format: ModelFormat, root: string, device: string): Promise<LoadResult>;
|
|
53
|
+
judge(inputs: readonly JudgeInput[]): Promise<ModelResult[]>;
|
|
54
|
+
/** Kill the process; pending requests fail. Resolves once the process has exited. */
|
|
55
|
+
stop(): Promise<void>;
|
|
56
|
+
private start;
|
|
57
|
+
private request;
|
|
58
|
+
private receive;
|
|
59
|
+
private failAll;
|
|
60
|
+
private lastError;
|
|
61
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host side of `python/vulcan_decision_worker.py`: one child process per runtime pack and
|
|
3
|
+
* model, answering JSON-line requests in order. Any failure, timeout or exit becomes an error;
|
|
4
|
+
* none of them is ever an answer.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import { createInterface } from 'node:readline';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { DecisionError } from '../protocol.js';
|
|
11
|
+
/** The worker script shipped with this package. */
|
|
12
|
+
export const WORKER_SCRIPT = fileURLToPath(new URL('../../python/vulcan_decision_worker.py', import.meta.url));
|
|
13
|
+
/** Longest wait for a probe. */
|
|
14
|
+
const PROBE_TIMEOUT_MS = 120_000;
|
|
15
|
+
/** Longest wait for loading a model; the first CUDA start can take a while. */
|
|
16
|
+
const LOAD_TIMEOUT_MS = 600_000;
|
|
17
|
+
/** Longest wait for one judgement, which may hold up to the model's full token limit on a CPU. */
|
|
18
|
+
const JUDGE_TIMEOUT_MS = 180_000;
|
|
19
|
+
export class WorkerProcess {
|
|
20
|
+
python;
|
|
21
|
+
child;
|
|
22
|
+
pending = new Map();
|
|
23
|
+
nextId = 1;
|
|
24
|
+
stderr = '';
|
|
25
|
+
constructor(python) {
|
|
26
|
+
this.python = python;
|
|
27
|
+
}
|
|
28
|
+
get running() {
|
|
29
|
+
return this.child !== undefined;
|
|
30
|
+
}
|
|
31
|
+
probe() {
|
|
32
|
+
return this.request('probe', {}, PROBE_TIMEOUT_MS);
|
|
33
|
+
}
|
|
34
|
+
load(format, root, device) {
|
|
35
|
+
return this.request('load', { format, root, device }, LOAD_TIMEOUT_MS);
|
|
36
|
+
}
|
|
37
|
+
judge(inputs) {
|
|
38
|
+
return this.request('judge', { inputs }, JUDGE_TIMEOUT_MS);
|
|
39
|
+
}
|
|
40
|
+
/** Kill the process; pending requests fail. Resolves once the process has exited. */
|
|
41
|
+
stop() {
|
|
42
|
+
const child = this.child;
|
|
43
|
+
this.child = undefined;
|
|
44
|
+
this.failAll(new DecisionError('backend_unavailable', 'the local model process was stopped'));
|
|
45
|
+
if (child === undefined || child.exitCode !== null || child.signalCode !== null)
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
const exited = new Promise(resolve => child.once('exit', () => resolve()));
|
|
48
|
+
child.kill();
|
|
49
|
+
return exited;
|
|
50
|
+
}
|
|
51
|
+
start() {
|
|
52
|
+
if (this.child !== undefined)
|
|
53
|
+
return this.child;
|
|
54
|
+
const child = spawn(this.python, ['-I', '-B', WORKER_SCRIPT], {
|
|
55
|
+
// Outside the pack, so a running process never keeps the pack directory busy.
|
|
56
|
+
cwd: os.homedir(),
|
|
57
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
});
|
|
60
|
+
this.child = child;
|
|
61
|
+
this.stderr = '';
|
|
62
|
+
child.stderr.setEncoding('utf8');
|
|
63
|
+
child.stderr.on('data', (chunk) => {
|
|
64
|
+
this.stderr = (this.stderr + chunk).slice(-8000);
|
|
65
|
+
});
|
|
66
|
+
createInterface({ input: child.stdout }).on('line', line => this.receive(line));
|
|
67
|
+
const ended = (reason) => {
|
|
68
|
+
if (this.child === child)
|
|
69
|
+
this.child = undefined;
|
|
70
|
+
this.failAll(new DecisionError('backend_unavailable', `the local model process ${reason}${this.lastError()}`));
|
|
71
|
+
};
|
|
72
|
+
child.on('error', error => ended(`could not start: ${error.message}`));
|
|
73
|
+
child.on('exit', code => ended(`exited with code ${code}`));
|
|
74
|
+
return child;
|
|
75
|
+
}
|
|
76
|
+
request(op, fields, timeoutMs) {
|
|
77
|
+
const child = this.start();
|
|
78
|
+
const id = this.nextId++;
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
// A request that never answers leaves the process in an unknown state.
|
|
82
|
+
void this.stop();
|
|
83
|
+
reject(new DecisionError('backend_unavailable', `the local model did not answer ${op} within ${timeoutMs / 1000} s`));
|
|
84
|
+
}, timeoutMs);
|
|
85
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
86
|
+
child.stdin.write(`${JSON.stringify({ id, op, ...fields })}\n`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
receive(line) {
|
|
90
|
+
let message;
|
|
91
|
+
try {
|
|
92
|
+
message = JSON.parse(line);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
void this.stop();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const pending = typeof message.id === 'number' ? this.pending.get(message.id) : undefined;
|
|
99
|
+
if (pending === undefined)
|
|
100
|
+
return;
|
|
101
|
+
this.pending.delete(message.id);
|
|
102
|
+
clearTimeout(pending.timer);
|
|
103
|
+
if (message.ok === true) {
|
|
104
|
+
pending.resolve(message.result);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const code = message.error?.code;
|
|
108
|
+
const text = typeof message.error?.message === 'string' ? message.error.message : 'unknown error';
|
|
109
|
+
pending.reject(new DecisionError(code === 'invalid_request' ? 'invalid_request' : 'model_failed', `${String(code)}: ${text}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
failAll(error) {
|
|
113
|
+
for (const [id, pending] of this.pending) {
|
|
114
|
+
clearTimeout(pending.timer);
|
|
115
|
+
pending.reject(error);
|
|
116
|
+
this.pending.delete(id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
lastError() {
|
|
120
|
+
const lines = this.stderr.trim().split(/\r?\n/).filter(Boolean);
|
|
121
|
+
return lines.length === 0 ? '' : `: ${lines.at(-1)}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shapes shared by the cloud and local backends and the host plugins.
|
|
3
|
+
*/
|
|
4
|
+
/** The model's three candidate decisions, in classifier order. */
|
|
5
|
+
export declare const DECISIONS: readonly ["allow", "review", "block"];
|
|
6
|
+
export type Decision = typeof DECISIONS[number];
|
|
7
|
+
/** The fixed authorization options the decision model was trained with; see the model's policy. */
|
|
8
|
+
export interface DecisionOptions {
|
|
9
|
+
/** Ordinary writes outside the project: run them, or ask first. */
|
|
10
|
+
readonly cross_project_writes: 'allow' | 'review';
|
|
11
|
+
/** Database drops, truncations and unrestricted data writes. */
|
|
12
|
+
readonly database_reset: 'confirm' | 'allow';
|
|
13
|
+
/** Deleting or resetting the whole current project or its source history. */
|
|
14
|
+
readonly project_reset: 'confirm' | 'allow';
|
|
15
|
+
}
|
|
16
|
+
/** The policy's defaults. */
|
|
17
|
+
export declare const DEFAULT_OPTIONS: DecisionOptions;
|
|
18
|
+
/** Version of the fixed policy every backend must answer with. */
|
|
19
|
+
export declare const POLICY_VERSION = "fixed-options-no-intent-v2-critical-writes";
|
|
20
|
+
/** One tool call to judge before it runs. */
|
|
21
|
+
export interface JudgeCall {
|
|
22
|
+
/** Root of the project the call works on, when the host knows it. Never guessed. */
|
|
23
|
+
readonly project?: string;
|
|
24
|
+
/** Tool name exactly as the host reports it. */
|
|
25
|
+
readonly tool: string;
|
|
26
|
+
/**
|
|
27
|
+
* Tool arguments exactly as the host reports them, in their original key order, except for
|
|
28
|
+
* file bodies that a host plugin removes from edit and write calls.
|
|
29
|
+
*/
|
|
30
|
+
readonly arguments: Readonly<Record<string, unknown>>;
|
|
31
|
+
}
|
|
32
|
+
/** One model input: the options and the rendered call (see `renderCall`). */
|
|
33
|
+
export interface JudgeInput {
|
|
34
|
+
readonly settings: DecisionOptions;
|
|
35
|
+
readonly rendered: string;
|
|
36
|
+
}
|
|
37
|
+
/** One model answer, as the local worker and the cloud endpoint produce it. */
|
|
38
|
+
export interface ModelResult {
|
|
39
|
+
readonly decision: Decision;
|
|
40
|
+
/** Raw allow/review/block scores; not calibrated probabilities. */
|
|
41
|
+
readonly logits?: readonly number[];
|
|
42
|
+
readonly input_tokens?: number;
|
|
43
|
+
readonly model_evaluated: boolean;
|
|
44
|
+
/** Set when a rule decided without evaluating the model. */
|
|
45
|
+
readonly reason_code?: string;
|
|
46
|
+
}
|
|
47
|
+
export type BackendKind = 'cloud' | 'local';
|
|
48
|
+
/** The answer for one call, with where it came from. */
|
|
49
|
+
export interface Verdict extends ModelResult {
|
|
50
|
+
readonly backend: BackendKind;
|
|
51
|
+
/** Cloud model id or installed local model id. */
|
|
52
|
+
readonly model: string;
|
|
53
|
+
/** Cloud endpoint id, or the local runtime pack. */
|
|
54
|
+
readonly via: string;
|
|
55
|
+
readonly elapsed_ms: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Machine-readable failure kinds; hosts must never treat any of them as an allow.
|
|
59
|
+
* - `invalid_request`: the call cannot be rendered or the options are unknown.
|
|
60
|
+
* - `not_signed_in`: the cloud backend needs an OpenVulcan sign-in.
|
|
61
|
+
* - `backend_unavailable`: the cloud cannot be reached or the local process is not running.
|
|
62
|
+
* - `rate_limited`: the cloud refused for now; `retry_after_ms` may say for how long.
|
|
63
|
+
* - `not_installed`: the selected local runtime pack or model is not installed.
|
|
64
|
+
* - `model_failed`: the model or the endpoint answered something unusable.
|
|
65
|
+
*/
|
|
66
|
+
export type ErrorCode = 'invalid_request' | 'not_signed_in' | 'backend_unavailable' | 'rate_limited' | 'not_installed' | 'model_failed';
|
|
67
|
+
/** A failure with its code. */
|
|
68
|
+
export declare class DecisionError extends Error {
|
|
69
|
+
readonly code: ErrorCode;
|
|
70
|
+
readonly retryAfterMs?: number | undefined;
|
|
71
|
+
constructor(code: ErrorCode, message: string, retryAfterMs?: number | undefined);
|
|
72
|
+
}
|
|
73
|
+
/** Check a model result before anyone acts on it. */
|
|
74
|
+
export declare function checkResult(value: unknown): ModelResult;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shapes shared by the cloud and local backends and the host plugins.
|
|
3
|
+
*/
|
|
4
|
+
/** The model's three candidate decisions, in classifier order. */
|
|
5
|
+
export const DECISIONS = ['allow', 'review', 'block'];
|
|
6
|
+
/** The policy's defaults. */
|
|
7
|
+
export const DEFAULT_OPTIONS = { cross_project_writes: 'allow', database_reset: 'confirm', project_reset: 'confirm' };
|
|
8
|
+
/** Version of the fixed policy every backend must answer with. */
|
|
9
|
+
export const POLICY_VERSION = 'fixed-options-no-intent-v2-critical-writes';
|
|
10
|
+
/** A failure with its code. */
|
|
11
|
+
export class DecisionError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
retryAfterMs;
|
|
14
|
+
constructor(code, message, retryAfterMs) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.retryAfterMs = retryAfterMs;
|
|
18
|
+
this.name = 'DecisionError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Check a model result before anyone acts on it. */
|
|
22
|
+
export function checkResult(value) {
|
|
23
|
+
const result = value;
|
|
24
|
+
if (typeof result !== 'object' || result === null || !DECISIONS.includes(result.decision)) {
|
|
25
|
+
throw new DecisionError('model_failed', 'the model answered without a decision');
|
|
26
|
+
}
|
|
27
|
+
if (result.logits !== undefined && (!Array.isArray(result.logits) || result.logits.length !== 3 || !result.logits.every(Number.isFinite))) {
|
|
28
|
+
throw new DecisionError('model_failed', 'the model answered malformed scores');
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type JudgeCall } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* Render a call in the text format the model was trained on:
|
|
4
|
+
*
|
|
5
|
+
* ```text
|
|
6
|
+
* Project: <project root> (omitted when unknown)
|
|
7
|
+
* Tool: <tool name>
|
|
8
|
+
* Arguments: <compact JSON of the actual arguments, keys in their original order>
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Nothing is invented: no task description, previous message, working directory or file state.
|
|
12
|
+
* `JSON.stringify` matches the reference `json.dumps(..., ensure_ascii=False,
|
|
13
|
+
* separators=(',', ':'))` for JSON-shaped values.
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderCall(call: Pick<JudgeCall, 'project' | 'tool' | 'arguments'>): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DecisionError } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* Render a call in the text format the model was trained on:
|
|
4
|
+
*
|
|
5
|
+
* ```text
|
|
6
|
+
* Project: <project root> (omitted when unknown)
|
|
7
|
+
* Tool: <tool name>
|
|
8
|
+
* Arguments: <compact JSON of the actual arguments, keys in their original order>
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Nothing is invented: no task description, previous message, working directory or file state.
|
|
12
|
+
* `JSON.stringify` matches the reference `json.dumps(..., ensure_ascii=False,
|
|
13
|
+
* separators=(',', ':'))` for JSON-shaped values.
|
|
14
|
+
*/
|
|
15
|
+
export function renderCall(call) {
|
|
16
|
+
if (typeof call.tool !== 'string' || call.tool === '')
|
|
17
|
+
throw new DecisionError('invalid_request', 'the call has no tool name');
|
|
18
|
+
if (typeof call.arguments !== 'object' || call.arguments === null || Array.isArray(call.arguments)) {
|
|
19
|
+
throw new DecisionError('invalid_request', 'the call arguments must be an object');
|
|
20
|
+
}
|
|
21
|
+
if (call.project !== undefined && typeof call.project !== 'string') {
|
|
22
|
+
throw new DecisionError('invalid_request', 'the project root must be a string');
|
|
23
|
+
}
|
|
24
|
+
const prefix = call.project === undefined || call.project === '' ? '' : `Project: ${call.project}\n`;
|
|
25
|
+
return `${prefix}Tool: ${call.tool}\nArguments: ${JSON.stringify(call.arguments, finiteOnly)}`;
|
|
26
|
+
}
|
|
27
|
+
function finiteOnly(_key, value) {
|
|
28
|
+
// The reference serializer refuses NaN and infinities instead of writing null for them.
|
|
29
|
+
if (typeof value === 'number' && !Number.isFinite(value)) {
|
|
30
|
+
throw new DecisionError('invalid_request', 'the call arguments contain a non-finite number');
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openvulcan/vulcan-decision",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Vulcan Decision: judge AI coding tool calls before they run, with OpenVulcan cloud endpoints or an optional local model runtime.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/OpenVulcan/dsh-vulcancode.git",
|
|
9
|
+
"directory": "packages/vulcan-decision"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"vulcan-decision": "bin/vulcan-decision.js"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./lib/index.d.ts",
|
|
18
|
+
"default": "./lib/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./cli": "./lib/cli.js",
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin",
|
|
25
|
+
"lib",
|
|
26
|
+
"!lib/**/*.test.*",
|
|
27
|
+
"python/*.py",
|
|
28
|
+
"runtime-packs/*.json",
|
|
29
|
+
"!runtime-packs/spec.json"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20.19"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@openvulcan/openvulcan-account": "0.4.3"
|
|
39
|
+
}
|
|
40
|
+
}
|