@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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenVulcan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # @openvulcan/vulcan-decision
2
+
3
+ Vulcan Decision 的判定引擎与命令行。它判断 AI 编码工具的一次工具调用应当放行(allow)、确认(review)还是
4
+ 拦截(block),供各宿主插件共用。判定默认走 OpenVulcan 云端节点;也可以按需安装本机运行包,在本机运行
5
+ `vulcan-decision-shell` 模型。本包没有原生依赖与安装脚本。
6
+
7
+ ```sh
8
+ npx @openvulcan/vulcan-decision login --site cn # 或 global
9
+ npx @openvulcan/vulcan-decision cloud probe
10
+ npx @openvulcan/vulcan-decision judge --tool shell_command --args '{"command":"git status"}'
11
+
12
+ npx @openvulcan/vulcan-decision hardware # 显卡、NPU 与推荐的运行包
13
+ npx @openvulcan/vulcan-decision packs install torch-cu128 --source cn
14
+ npx @openvulcan/vulcan-decision models install <模型目录>
15
+ npx @openvulcan/vulcan-decision judge --tool shell_command --args '{"command":"git status"}' --pack torch-cu128 --model <模型 id>
16
+ ```
17
+
18
+ 运行包(`onnx-cpu`、`onnx-cuda`、`torch-cpu`、`torch-cu128`)是预编译的 Python 与官方 wheel,按锁定的 SHA-256
19
+ 校验,不编译任何东西,也不安装驱动。数据目录默认为 `~/.vulcan-decision`,可用 `VULCAN_DECISION_HOME` 改到别处;
20
+ 宿主插件使用自己的目录,账号由宿主提供。本包属于
21
+ [dsh-vulcancode](https://github.com/OpenVulcan/dsh-vulcancode) 套件。
22
+
23
+ 许可:MIT
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../lib/cli.js'
3
+
4
+ process.exitCode = await main(process.argv.slice(2))
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The `vulcan-decision` command line: the account, runtime packs, models and test judgements.
3
+ */
4
+ /** Run one command. @returns the process exit code. */
5
+ export declare function main(args: readonly string[]): Promise<number>;
@@ -0,0 +1,267 @@
1
+ /**
2
+ * The `vulcan-decision` command line: the account, runtime packs, models and test judgements.
3
+ */
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import { parseArgs } from 'node:util';
7
+ import { AccountError, FileCredentialStore, isSite, OpenVulcanAccount } from '@openvulcan/openvulcan-account';
8
+ import { EdgeClient, TEST_PRESETS } from './cloud/edge.js';
9
+ import { DOWNLOAD_SOURCES } from './download.js';
10
+ import { DecisionEngine } from './engine.js';
11
+ import { detectHardware, gpuGeneration, recommend } from './hardware.js';
12
+ import { defaultHome } from './home.js';
13
+ import { LocalEngine } from './local/engine.js';
14
+ import { backendOf, downloadModel, installModelFromDirectory, listModels, MODEL_CATALOG, MODEL_FAMILIES, MODEL_SOURCES, PACKAGE_PRECISIONS, preferredPrecision, removeModel, sourcesOf } from './local/models.js';
15
+ import { installPack, installedPack, isPackId, PACK_IDS, PLANNED_PACKS, readLock, removePack } from './local/packs.js';
16
+ import { DEFAULT_OPTIONS, DecisionError } from './protocol.js';
17
+ import { VERSION } from './version.js';
18
+ const USAGE = `vulcan-decision ${VERSION}
19
+
20
+ Usage:
21
+ vulcan-decision hardware Graphics cards, NPUs and the recommended runtime pack
22
+ vulcan-decision login [--site cn|global] Sign in to OpenVulcan in the browser
23
+ vulcan-decision logout Sign out and revoke this device
24
+ vulcan-decision account Show the signed-in account
25
+ vulcan-decision cloud models Models this account may use
26
+ vulcan-decision cloud probe [--model <id>] Measure the endpoints of a cloud model
27
+ vulcan-decision cloud test --endpoint <id> [--model <id>] [--preset <${TEST_PRESETS.join('|')}> | --command <cmd>]
28
+ Time five real judgements on one endpoint
29
+ vulcan-decision packs Runtime packs and whether they fit this machine
30
+ vulcan-decision packs install <pack> [--source official|cn]
31
+ vulcan-decision packs remove <pack>
32
+ vulcan-decision models Installed and published local models
33
+ vulcan-decision models install <dir> Install a model package from a local directory
34
+ vulcan-decision models download <mini|flash|standard> --from <huggingface|modelscope-intl|modelscope-cn>
35
+ vulcan-decision models remove <id>
36
+ vulcan-decision judge --tool <name> --args <json> [--project <dir>] [--cloud <model> | --pack <pack> --model <id>]
37
+ vulcan-decision eval --input <file> --pack <pack> --model <id>
38
+ Judge {settings, rendered} records locally, printing JSON
39
+
40
+ The data directory is ${defaultHome().root}; set VULCAN_DECISION_HOME to use another one.`;
41
+ /** Run one command. @returns the process exit code. */
42
+ export async function main(args) {
43
+ const [command, ...rest] = args;
44
+ const home = defaultHome();
45
+ try {
46
+ switch (command) {
47
+ case 'hardware':
48
+ return runHardware();
49
+ case 'login':
50
+ return await runLogin(home, rest);
51
+ case 'logout':
52
+ await account(home).signOut();
53
+ console.log('signed out');
54
+ return 0;
55
+ case 'account':
56
+ console.log(JSON.stringify(await account(home).status(), null, 2));
57
+ return 0;
58
+ case 'cloud':
59
+ return await runCloud(home, rest);
60
+ case 'packs':
61
+ return await runPacks(home, rest);
62
+ case 'models':
63
+ return await runModels(home, rest);
64
+ case 'judge':
65
+ return await runJudge(home, rest);
66
+ case 'eval':
67
+ return await runEval(home, rest);
68
+ case undefined:
69
+ case 'help':
70
+ case '--help':
71
+ case '-h':
72
+ console.log(USAGE);
73
+ return 0;
74
+ default:
75
+ console.error(`unknown command ${command}\n\n${USAGE}`);
76
+ return 2;
77
+ }
78
+ }
79
+ catch (error) {
80
+ console.error(error instanceof DecisionError || error instanceof AccountError ? `${error.code}: ${error.message}` : error.message);
81
+ return 1;
82
+ }
83
+ }
84
+ function account(home) {
85
+ return new OpenVulcanAccount(new FileCredentialStore(home.file('account.json')), device(), process.env.VULCAN_DECISION_AUTH_ORIGIN);
86
+ }
87
+ function device() {
88
+ return { display_name: `Vulcan Decision CLI on ${os.hostname()}`, client_version: `vulcan-decision ${VERSION}` };
89
+ }
90
+ function runHardware() {
91
+ const hardware = detectHardware();
92
+ const recommendation = recommend(hardware);
93
+ const gpus = hardware.gpus.map(gpu => ({
94
+ ...gpu,
95
+ ...gpu.computeCapability === undefined ? {} : { generation: gpuGeneration(gpu.computeCapability) },
96
+ }));
97
+ console.log(JSON.stringify({ ...hardware, gpus, recommendation }, null, 2));
98
+ return 0;
99
+ }
100
+ async function runLogin(home, args) {
101
+ const { values } = parseArgs({ args: [...args], options: { site: { type: 'string', default: 'global' } } });
102
+ if (!isSite(values.site))
103
+ throw new Error('--site must be cn or global');
104
+ const pending = await account(home).signIn(values.site);
105
+ console.log(`Open this page to approve the sign-in:\n${pending.approvalUrl}`);
106
+ const status = await pending.done;
107
+ console.log(`signed in to ${status.site}, valid until ${status.expiresAt}`);
108
+ return 0;
109
+ }
110
+ async function runCloud(home, args) {
111
+ const [sub, ...rest] = args;
112
+ const edge = new EdgeClient(account(home));
113
+ if (sub === 'models') {
114
+ console.log((await edge.models()).join('\n'));
115
+ return 0;
116
+ }
117
+ if (sub === 'probe') {
118
+ const { values } = parseArgs({ args: rest, options: { model: { type: 'string' } } });
119
+ console.log(JSON.stringify(await edge.probe(await edge.resolveModel(values.model)), null, 2));
120
+ return 0;
121
+ }
122
+ if (sub === 'test') {
123
+ const { values } = parseArgs({ args: rest, options: { model: { type: 'string' }, endpoint: { type: 'string' }, preset: { type: 'string', default: 'ordinary' }, command: { type: 'string' } } });
124
+ if (values.endpoint === undefined)
125
+ throw new Error('--endpoint is required');
126
+ if (values.command === undefined && !TEST_PRESETS.includes(values.preset))
127
+ throw new Error(`--preset must be one of ${TEST_PRESETS.join(', ')}`);
128
+ const input = values.command === undefined ? { kind: 'preset', preset: values.preset } : { kind: 'custom', command: values.command };
129
+ console.log(JSON.stringify(await edge.requestTest(await edge.resolveModel(values.model), values.endpoint, input), null, 2));
130
+ return 0;
131
+ }
132
+ throw new Error('usage: cloud models | cloud probe [--model <id>] | cloud test --endpoint <id>');
133
+ }
134
+ async function runPacks(home, args) {
135
+ const [sub, id, ...rest] = args;
136
+ if (sub === undefined) {
137
+ const recommendation = recommend(detectHardware());
138
+ for (const pack of PACK_IDS) {
139
+ const fit = recommendation.packs.find(item => item.pack === pack);
140
+ const installed = installedPack(home, pack);
141
+ const state = installed === undefined ? 'not installed' : installed.lock === readLock(pack).id ? 'installed' : 'installed, outdated';
142
+ const size = fit.bytes === undefined ? '' : `${(fit.bytes / 1024 ** 2).toFixed(0)} MiB`;
143
+ console.log(`${pack.padEnd(12)} ${fit.fit.padEnd(16)} ${size.padStart(9)} ${state}`);
144
+ }
145
+ for (const planned of PLANNED_PACKS)
146
+ console.log(`${planned.id.padEnd(12)} planned (${planned.accelerator}: waiting for an NPU model release)`);
147
+ return 0;
148
+ }
149
+ if (!isPackId(id))
150
+ throw new Error(`unknown pack ${String(id)}; known: ${PACK_IDS.join(', ')}`);
151
+ if (sub === 'install') {
152
+ const { values } = parseArgs({ args: rest, options: { source: { type: 'string', default: 'official' } } });
153
+ if (!DOWNLOAD_SOURCES.includes(values.source))
154
+ throw new Error('--source must be official or cn');
155
+ let lastPercent = -1;
156
+ const record = await installPack(home, id, {
157
+ source: values.source,
158
+ onProgress: progress => {
159
+ if (progress.phase !== 'download') {
160
+ console.error(progress.phase);
161
+ return;
162
+ }
163
+ const percent = Math.floor((progress.done / progress.total) * 100);
164
+ if (percent !== lastPercent && percent % 5 === 0)
165
+ console.error(`download ${percent}% (${progress.file})`);
166
+ lastPercent = percent;
167
+ },
168
+ });
169
+ console.log(JSON.stringify(record, null, 2));
170
+ return 0;
171
+ }
172
+ if (sub === 'remove') {
173
+ console.log(removePack(home, id) ? `removed ${id}` : `${id} was not installed`);
174
+ return 0;
175
+ }
176
+ throw new Error('usage: packs [install <pack> | remove <pack>]');
177
+ }
178
+ async function runModels(home, args) {
179
+ const [sub, target, ...rest] = args;
180
+ const progress = (done, total, file) => {
181
+ if (file !== '')
182
+ console.error(`${total === 0 ? 100 : Math.floor((done / total) * 100)}% ${file}`);
183
+ };
184
+ if (sub === undefined) {
185
+ for (const model of listModels(home))
186
+ console.log(`installed ${model.id} ${model.package_format} ${(model.bytes / 1024 ** 2).toFixed(0)} MiB`);
187
+ for (const model of MODEL_CATALOG) {
188
+ const sources = sourcesOf(model);
189
+ console.log(`published ${model.family} (${model.name}, ${model.parameters}, ${Object.keys(model.packages).join('/')}) ${sources.length === 0 ? 'no download address yet' : sources.join(', ')}`);
190
+ }
191
+ return 0;
192
+ }
193
+ if (sub === 'install' && target !== undefined) {
194
+ const model = await installModelFromDirectory(home, target, { onProgress: progress });
195
+ console.log(`installed ${model.id} (${model.package_format})`);
196
+ return 0;
197
+ }
198
+ if (sub === 'download' && target !== undefined) {
199
+ const { values } = parseArgs({ args: rest, options: { from: { type: 'string' }, precision: { type: 'string' } } });
200
+ const entry = MODEL_CATALOG.find(model => model.family === target);
201
+ if (entry === undefined)
202
+ throw new Error(`unknown model ${target}; known: ${MODEL_FAMILIES.join(', ')}`);
203
+ if (!MODEL_SOURCES.includes(values.from))
204
+ throw new Error(`--from must be one of ${MODEL_SOURCES.join(', ')}`);
205
+ if (values.precision !== undefined && !PACKAGE_PRECISIONS.includes(values.precision))
206
+ throw new Error(`--precision must be one of ${PACKAGE_PRECISIONS.join(', ')}`);
207
+ const precision = values.precision ?? preferredPrecision(entry, recommend(detectHardware()).onnxPack);
208
+ if (precision === undefined)
209
+ throw new Error(`${entry.family} has no published package`);
210
+ const model = await downloadModel(home, entry.family, precision, values.from, { onProgress: progress });
211
+ console.log(`installed ${model.id}`);
212
+ return 0;
213
+ }
214
+ if (sub === 'remove' && target !== undefined) {
215
+ console.log(removeModel(home, target) ? `removed ${target}` : `${target} was not installed`);
216
+ return 0;
217
+ }
218
+ throw new Error('usage: models [install <dir> | download <family> --from <source> [--precision fp16|fp32] | remove <id>]');
219
+ }
220
+ async function runJudge(home, args) {
221
+ const { values } = parseArgs({
222
+ args: [...args],
223
+ options: {
224
+ tool: { type: 'string' }, args: { type: 'string', default: '{}' }, project: { type: 'string' },
225
+ cloud: { type: 'string' }, pack: { type: 'string' }, model: { type: 'string' },
226
+ },
227
+ });
228
+ if (values.tool === undefined)
229
+ throw new Error('--tool is required');
230
+ const engine = new DecisionEngine({ home, account: account(home) });
231
+ try {
232
+ const local = values.pack === undefined ? undefined : { pack: packArgument(values.pack), model: values.model ?? '' };
233
+ const verdict = await engine.judge({ tool: values.tool, arguments: JSON.parse(values.args), ...values.project === undefined ? {} : { project: values.project } }, { backend: local === undefined ? 'cloud' : 'local', cloudModel: values.cloud ?? '', ...local === undefined ? {} : { local }, options: DEFAULT_OPTIONS });
234
+ console.log(JSON.stringify(verdict, null, 2));
235
+ return 0;
236
+ }
237
+ finally {
238
+ engine.stop();
239
+ }
240
+ }
241
+ async function runEval(home, args) {
242
+ const { values } = parseArgs({ args: [...args], options: { input: { type: 'string' }, pack: { type: 'string' }, model: { type: 'string' } } });
243
+ if (values.input === undefined || values.pack === undefined || values.model === undefined)
244
+ throw new Error('--input, --pack and --model are required');
245
+ const inputs = JSON.parse(fs.readFileSync(values.input, 'utf8'));
246
+ const model = listModels(home).find(item => item.id === values.model);
247
+ const pack = packArgument(values.pack);
248
+ if (model !== undefined && backendOf(model.package_format) !== readLock(pack).backend)
249
+ throw new Error(`${model.id} does not run on ${pack}`);
250
+ const engine = new LocalEngine(home);
251
+ try {
252
+ const started = performance.now();
253
+ const loaded = await engine.warm({ pack, model: values.model });
254
+ const loadedAt = performance.now();
255
+ const results = await engine.judge({ pack, model: values.model }, inputs);
256
+ console.log(JSON.stringify({ loaded, load_ms: Math.round(loadedAt - started), judge_ms: Math.round(performance.now() - loadedAt), results }, null, 2));
257
+ return 0;
258
+ }
259
+ finally {
260
+ engine.stop();
261
+ }
262
+ }
263
+ function packArgument(value) {
264
+ if (!isPackId(value))
265
+ throw new Error(`unknown pack ${value}; known: ${PACK_IDS.join(', ')}`);
266
+ return value;
267
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The cloud backend: OpenVulcan's decision endpoints, as VulcanCode uses them.
3
+ *
4
+ * The signed-in device asks its auth host for the models it may use (`edge-models`; the server
5
+ * decides, nothing is hard-coded) and, per model, for a short-lived access grant (`edge-access`):
6
+ * an EdDSA token valid on every endpoint of that model, the account's site and the endpoint list.
7
+ * Calls go straight to one endpoint's `POST /v1/judge` with `Authorization: Bearer` and
8
+ * `X-OpenVulcan-Site`.
9
+ *
10
+ * The endpoint is chosen by measurement, never per request: each endpoint's public `/v1/status` is
11
+ * fetched three times in a row, endpoint after endpoint, at start, every 10 minutes and at most
12
+ * every 30 seconds after a failure. The score is the median round trip plus the reported median
13
+ * compute time (round trip alone when any eligible endpoint has not measured compute yet), and a
14
+ * new endpoint wins only when it scores below 80% of the current one. A user lock always uses its
15
+ * endpoint and never fails over. A request that fails is an error, never an allow, and is never
16
+ * retried on another endpoint.
17
+ */
18
+ import { type JudgeInput, type ModelResult } from '../protocol.js';
19
+ import { type AccountSource, type Site } from '@openvulcan/openvulcan-account';
20
+ export interface EdgeEndpoint {
21
+ readonly endpoint_id: string;
22
+ readonly endpoint: string;
23
+ readonly priority: number;
24
+ readonly online: boolean;
25
+ }
26
+ export interface EdgeGrant {
27
+ readonly model_id: string;
28
+ readonly site: Site | string;
29
+ readonly endpoints: readonly EdgeEndpoint[];
30
+ readonly access_token: string;
31
+ readonly expires_at: string;
32
+ }
33
+ /** One endpoint's `/v1/status` answer, reduced to what selection reads. */
34
+ export interface EndpointStatus {
35
+ readonly endpoint_id: string;
36
+ readonly model_id: string;
37
+ readonly region?: string;
38
+ readonly sites: readonly string[];
39
+ readonly ready: boolean;
40
+ readonly compute_ms: {
41
+ readonly p50: number;
42
+ readonly p95: number;
43
+ } | null;
44
+ }
45
+ /** One probe of one endpoint. */
46
+ export interface ProbeResult {
47
+ readonly endpoint_id: string;
48
+ /** Lower median round trip of the successful attempts, undefined when all failed. */
49
+ readonly rtt_ms?: number;
50
+ readonly status?: EndpointStatus;
51
+ /** Why it cannot be used, when it cannot. */
52
+ readonly excluded?: 'unreachable' | 'wrong_endpoint' | 'wrong_model' | 'site_not_served' | 'not_ready';
53
+ /** The last failure of an unreachable endpoint. */
54
+ readonly error?: string;
55
+ readonly at: number;
56
+ }
57
+ /** One row of the endpoint table, as hosts show it. */
58
+ export interface EndpointRow {
59
+ readonly endpointId: string;
60
+ readonly endpoint: string;
61
+ readonly priority: number;
62
+ readonly online: boolean;
63
+ readonly region?: string;
64
+ readonly ready?: boolean;
65
+ readonly acceptsSite?: boolean;
66
+ readonly rttMs?: number;
67
+ readonly computeP50Ms?: number;
68
+ readonly scoreMs?: number;
69
+ readonly error?: string;
70
+ }
71
+ /** The endpoint table of the current model. */
72
+ export interface EndpointSnapshot {
73
+ readonly modelId?: string;
74
+ readonly site?: string;
75
+ readonly mode: 'auto' | 'locked';
76
+ readonly lockedEndpointId?: string;
77
+ readonly selectedEndpointId?: string;
78
+ readonly probedAtMs?: number;
79
+ readonly nextProbeAtMs?: number;
80
+ /** Why there is no table: no sign-in, no model, or the grant was refused. */
81
+ readonly error?: string;
82
+ readonly endpoints: readonly EndpointRow[];
83
+ }
84
+ /** The sample calls a request test can send, besides a custom command. */
85
+ export declare const TEST_PRESETS: readonly ["ordinary", "destructive", "database_reset", "oversized"];
86
+ export type TestPreset = typeof TEST_PRESETS[number];
87
+ export type TestInput = {
88
+ readonly kind: 'preset';
89
+ readonly preset: TestPreset;
90
+ } | {
91
+ readonly kind: 'custom';
92
+ readonly command: string;
93
+ };
94
+ /** One timed request of a request test. Times are milliseconds; absent when not reported. */
95
+ export interface TestAttempt {
96
+ readonly requestId: string;
97
+ readonly totalMs: number;
98
+ readonly networkMs?: number;
99
+ readonly endpointMs?: number;
100
+ readonly nodeMs?: number;
101
+ readonly queueMs?: number;
102
+ readonly computeMs?: number;
103
+ readonly httpStatus?: number;
104
+ readonly tokenRenewed: boolean;
105
+ readonly answer?: JudgeAnswer;
106
+ readonly errorCode?: string;
107
+ readonly error?: string;
108
+ }
109
+ export interface TestResult {
110
+ readonly modelId: string;
111
+ readonly endpointId: string;
112
+ readonly endpoint: string;
113
+ readonly site: string;
114
+ readonly renderedChars: number;
115
+ readonly attempts: readonly TestAttempt[];
116
+ readonly succeeded: number;
117
+ readonly minMs?: number;
118
+ readonly medianMs?: number;
119
+ readonly maxMs?: number;
120
+ }
121
+ /** A checked `/v1/judge` answer. */
122
+ export interface JudgeAnswer {
123
+ readonly region?: string;
124
+ readonly model?: string;
125
+ readonly checkpointSha256: string;
126
+ readonly policyVersion: string;
127
+ readonly results: readonly ModelResult[];
128
+ }
129
+ export declare const PROBE_INTERVAL_MS: number;
130
+ export declare const TEST_ATTEMPTS = 5;
131
+ export declare const MAX_TEST_COMMAND = 65536;
132
+ export declare class EdgeClient {
133
+ private readonly account;
134
+ private readonly fetchImpl;
135
+ private grant;
136
+ private grantKey;
137
+ private grantError;
138
+ private modelList;
139
+ private probes;
140
+ private probedFor;
141
+ private probedAt;
142
+ private lastFailureProbe;
143
+ private selected;
144
+ private probing;
145
+ constructor(account: AccountSource, fetchImpl?: typeof fetch);
146
+ /** The account's credential; an account that cannot supply one means "not signed in". */
147
+ private credential;
148
+ /** Models this account may use, sorted; the first is the default. */
149
+ models(): Promise<string[]>;
150
+ /**
151
+ * The model to use: the saved one, or else the first one OpenVulcan lists for this account.
152
+ * @throws DecisionError when nothing is saved and the account has no model.
153
+ */
154
+ resolveModel(saved: string | undefined): Promise<string>;
155
+ /** The endpoint table of `model` with the last measurements, without measuring. */
156
+ snapshot(model: string | undefined, lock?: string): EndpointSnapshot;
157
+ /** Forget grants, model lists and measurements, after a sign-out or account change. */
158
+ reset(): void;
159
+ /**
160
+ * Measure every endpoint of `model` now and update the selection.
161
+ * @param lock the endpoint id the user locked, which is never replaced.
162
+ */
163
+ probe(model: string, lock?: string): Promise<EndpointSnapshot>;
164
+ /** Measure `model` in the background when its last measurement is older than the interval. */
165
+ refreshIfStale(model: string): void;
166
+ /** Check that `endpointId` serves `model`, for a lock the user is about to save. */
167
+ checkEndpoint(model: string, endpointId: string): Promise<void>;
168
+ /**
169
+ * Judge `inputs` with `model` on the selected (or locked) endpoint.
170
+ * @throws DecisionError for every failure; a failure never carries a decision.
171
+ */
172
+ judge(model: string, inputs: readonly JudgeInput[], lock?: string): Promise<{
173
+ results: ModelResult[];
174
+ endpoint: string;
175
+ }>;
176
+ /**
177
+ * Send {@link TEST_ATTEMPTS} real judgements of one sample call to one endpoint, one after
178
+ * another, and time each. The grant is fetched first so its call is not timed. Nothing changes
179
+ * the selection.
180
+ */
181
+ requestTest(model: string, endpointId: string, input: TestInput): Promise<TestResult>;
182
+ private post;
183
+ private send;
184
+ /** The current grant for `model`, renewed when it is about to expire or `renew` is set. */
185
+ private access;
186
+ private choose;
187
+ private measureOnce;
188
+ private afterFailure;
189
+ private measure;
190
+ private probeEndpoint;
191
+ private compareCompute;
192
+ /** Map account-host failures to decision errors. */
193
+ private call;
194
+ }
195
+ /** The command a request test sends. */
196
+ export declare function testCommand(input: TestInput): string;
197
+ /** The `edge`, `node`, `queue` and `compute` durations of a `Server-Timing` header. */
198
+ export declare function parseServerTiming(header: string | null): Partial<Record<'edge' | 'node' | 'queue' | 'compute', number>>;
199
+ /**
200
+ * The endpoint to use after a measurement: the lowest score (ties by priority, then id), unless
201
+ * the current one is still usable and the best is not 20% better.
202
+ */
203
+ export declare function selectEndpoint(endpoints: readonly EdgeEndpoint[], probes: readonly ProbeResult[], current: string | undefined): string | undefined;
204
+ export declare function parseGrant(value: unknown, model: string): EdgeGrant;
205
+ /** A bare https origin, or http on a loopback address. */
206
+ export declare function isEndpointOrigin(value: string): boolean;
207
+ export declare function parseStatus(value: unknown): EndpointStatus | undefined;
208
+ /** Check a `/v1/judge` answer against the request before anyone acts on it. */
209
+ export declare function parseJudgeAnswer(text: string, requestId: string, count: number): ModelResult[];
210
+ /** {@link parseJudgeAnswer} with the answer's region, model and checkpoint. */
211
+ export declare function parseJudgeEnvelope(text: string, requestId: string, count: number): JudgeAnswer;