@valni/cli-darwin-x64 0.0.0-stage → 0.1.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.
Files changed (183) hide show
  1. package/LICENSE +52 -0
  2. package/package.json +18 -5
  3. package/runtime/LICENSE +52 -0
  4. package/runtime/assets/clankolas.png +0 -0
  5. package/runtime/examples/extensions/auto-commit-on-exit.ts +49 -0
  6. package/runtime/examples/extensions/bash-spawn-hook.ts +30 -0
  7. package/runtime/examples/extensions/bookmark.ts +50 -0
  8. package/runtime/examples/extensions/border-status-editor.ts +145 -0
  9. package/runtime/examples/extensions/built-in-tool-renderer.ts +249 -0
  10. package/runtime/examples/extensions/claude-rules.ts +86 -0
  11. package/runtime/examples/extensions/commands.ts +72 -0
  12. package/runtime/examples/extensions/confirm-destructive.ts +59 -0
  13. package/runtime/examples/extensions/custom-footer.ts +64 -0
  14. package/runtime/examples/extensions/custom-header.ts +73 -0
  15. package/runtime/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  16. package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  17. package/runtime/examples/extensions/custom-provider-anthropic/package.json +19 -0
  18. package/runtime/examples/extensions/dirty-repo-guard.ts +56 -0
  19. package/runtime/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  20. package/runtime/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  21. package/runtime/examples/extensions/doom-overlay/doom/build.sh +152 -0
  22. package/runtime/examples/extensions/doom-overlay/doom/doomgeneric_valni.c +72 -0
  23. package/runtime/examples/extensions/doom-overlay/doom-component.ts +132 -0
  24. package/runtime/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  25. package/runtime/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  26. package/runtime/examples/extensions/doom-overlay/index.ts +74 -0
  27. package/runtime/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  28. package/runtime/examples/extensions/dynamic-resources/dynamic.json +78 -0
  29. package/runtime/examples/extensions/dynamic-resources/index.ts +13 -0
  30. package/runtime/examples/extensions/dynamic-tools.ts +74 -0
  31. package/runtime/examples/extensions/entry-renderer.ts +41 -0
  32. package/runtime/examples/extensions/event-bus.ts +43 -0
  33. package/runtime/examples/extensions/file-trigger.ts +41 -0
  34. package/runtime/examples/extensions/git-checkpoint.ts +53 -0
  35. package/runtime/examples/extensions/git-merge-and-resolve.ts +115 -0
  36. package/runtime/examples/extensions/github-issue-autocomplete.ts +185 -0
  37. package/runtime/examples/extensions/hello.ts +26 -0
  38. package/runtime/examples/extensions/hidden-thinking-label.ts +53 -0
  39. package/runtime/examples/extensions/inline-bash.ts +94 -0
  40. package/runtime/examples/extensions/input-transform-streaming.ts +39 -0
  41. package/runtime/examples/extensions/input-transform.ts +43 -0
  42. package/runtime/examples/extensions/interactive-shell.ts +196 -0
  43. package/runtime/examples/extensions/mac-system-theme.ts +47 -0
  44. package/runtime/examples/extensions/message-renderer.ts +59 -0
  45. package/runtime/examples/extensions/minimal-mode.ts +426 -0
  46. package/runtime/examples/extensions/modal-editor.ts +85 -0
  47. package/runtime/examples/extensions/model-status.ts +31 -0
  48. package/runtime/examples/extensions/notify.ts +55 -0
  49. package/runtime/examples/extensions/overlay-qa-tests.ts +1450 -0
  50. package/runtime/examples/extensions/overlay-test.ts +153 -0
  51. package/runtime/examples/extensions/permission-gate.ts +34 -0
  52. package/runtime/examples/extensions/pirate.ts +47 -0
  53. package/runtime/examples/extensions/plan-mode/index.ts +390 -0
  54. package/runtime/examples/extensions/plan-mode/utils.ts +168 -0
  55. package/runtime/examples/extensions/preset.ts +436 -0
  56. package/runtime/examples/extensions/project-trust.ts +64 -0
  57. package/runtime/examples/extensions/prompt-customizer.ts +97 -0
  58. package/runtime/examples/extensions/protected-paths.ts +30 -0
  59. package/runtime/examples/extensions/provider-payload.ts +18 -0
  60. package/runtime/examples/extensions/question.ts +278 -0
  61. package/runtime/examples/extensions/questionnaire.ts +440 -0
  62. package/runtime/examples/extensions/rainbow-editor.ts +88 -0
  63. package/runtime/examples/extensions/reload-runtime.ts +37 -0
  64. package/runtime/examples/extensions/rpc-demo.ts +118 -0
  65. package/runtime/examples/extensions/sandbox/index.ts +321 -0
  66. package/runtime/examples/extensions/sandbox/package-lock.json +92 -0
  67. package/runtime/examples/extensions/sandbox/package.json +19 -0
  68. package/runtime/examples/extensions/send-user-message.ts +97 -0
  69. package/runtime/examples/extensions/session-name.ts +27 -0
  70. package/runtime/examples/extensions/shutdown-command.ts +63 -0
  71. package/runtime/examples/extensions/snake.ts +343 -0
  72. package/runtime/examples/extensions/space-invaders.ts +560 -0
  73. package/runtime/examples/extensions/ssh.ts +220 -0
  74. package/runtime/examples/extensions/status-line.ts +32 -0
  75. package/runtime/examples/extensions/structured-output.ts +65 -0
  76. package/runtime/examples/extensions/subagent/agents.ts +126 -0
  77. package/runtime/examples/extensions/subagent/index.ts +1009 -0
  78. package/runtime/examples/extensions/system-prompt-header.ts +17 -0
  79. package/runtime/examples/extensions/tic-tac-toe.ts +1008 -0
  80. package/runtime/examples/extensions/timed-confirm.ts +70 -0
  81. package/runtime/examples/extensions/titlebar-spinner.ts +58 -0
  82. package/runtime/examples/extensions/todo.ts +297 -0
  83. package/runtime/examples/extensions/tool-override.ts +144 -0
  84. package/runtime/examples/extensions/tools.ts +146 -0
  85. package/runtime/examples/extensions/trigger-compact.ts +50 -0
  86. package/runtime/examples/extensions/truncated-tool.ts +195 -0
  87. package/runtime/examples/extensions/widget-placement.ts +9 -0
  88. package/runtime/examples/extensions/with-deps/index.ts +32 -0
  89. package/runtime/examples/extensions/with-deps/package-lock.json +31 -0
  90. package/runtime/examples/extensions/with-deps/package.json +22 -0
  91. package/runtime/examples/extensions/working-indicator.ts +123 -0
  92. package/runtime/examples/extensions/working-message-test.ts +25 -0
  93. package/runtime/examples/rpc-extension-ui.ts +632 -0
  94. package/runtime/examples/sdk/01-minimal.ts +26 -0
  95. package/runtime/examples/sdk/02-custom-model.ts +43 -0
  96. package/runtime/examples/sdk/03-custom-prompt.ts +70 -0
  97. package/runtime/examples/sdk/04-skills.ts +55 -0
  98. package/runtime/examples/sdk/05-tools.ts +48 -0
  99. package/runtime/examples/sdk/06-extensions.ts +94 -0
  100. package/runtime/examples/sdk/07-context-files.ts +42 -0
  101. package/runtime/examples/sdk/08-prompt-templates.ts +51 -0
  102. package/runtime/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  103. package/runtime/examples/sdk/10-settings.ts +53 -0
  104. package/runtime/examples/sdk/11-sessions.ts +52 -0
  105. package/runtime/examples/sdk/12-full-control.ts +71 -0
  106. package/runtime/examples/sdk/13-session-runtime.ts +67 -0
  107. package/runtime/export-html/ansi-to-html.js +249 -0
  108. package/runtime/export-html/index.js +226 -0
  109. package/runtime/export-html/template.css +1066 -0
  110. package/runtime/export-html/template.html +55 -0
  111. package/runtime/export-html/template.js +1864 -0
  112. package/runtime/export-html/tool-renderer.js +108 -0
  113. package/runtime/export-html/vendor/highlight.min.js +1213 -0
  114. package/runtime/export-html/vendor/marked.min.js +78 -0
  115. package/runtime/extensions/pi-switchboard/.github/workflows/ci.yml +51 -0
  116. package/runtime/extensions/pi-switchboard/LICENSE +22 -0
  117. package/runtime/extensions/pi-switchboard/README.md +85 -0
  118. package/runtime/extensions/pi-switchboard/extensions/switchboard/catalog.ts +183 -0
  119. package/runtime/extensions/pi-switchboard/extensions/switchboard/config.ts +97 -0
  120. package/runtime/extensions/pi-switchboard/extensions/switchboard/connections.ts +386 -0
  121. package/runtime/extensions/pi-switchboard/extensions/switchboard/constants.ts +12 -0
  122. package/runtime/extensions/pi-switchboard/extensions/switchboard/device-auth.ts +127 -0
  123. package/runtime/extensions/pi-switchboard/extensions/switchboard/envelope.ts +199 -0
  124. package/runtime/extensions/pi-switchboard/extensions/switchboard/errors.ts +180 -0
  125. package/runtime/extensions/pi-switchboard/extensions/switchboard/index.ts +125 -0
  126. package/runtime/extensions/pi-switchboard/extensions/switchboard/participant.ts +17 -0
  127. package/runtime/extensions/pi-switchboard/extensions/switchboard/sessionStream.ts +131 -0
  128. package/runtime/extensions/pi-switchboard/extensions/switchboard/toolProxy.ts +136 -0
  129. package/runtime/extensions/pi-switchboard/extensions/switchboard/tools.ts +16 -0
  130. package/runtime/extensions/pi-switchboard/extensions/switchboard/types.ts +37 -0
  131. package/runtime/extensions/pi-switchboard/package.json +30 -0
  132. package/runtime/extensions/pi-switchboard/tsconfig.json +13 -0
  133. package/runtime/extensions/switchboard-local/.github/workflows/ci.yml +80 -0
  134. package/runtime/extensions/switchboard-local/.github/workflows/release.yml +46 -0
  135. package/runtime/extensions/switchboard-local/README.md +98 -0
  136. package/runtime/extensions/switchboard-local/extensions/local/engine.ts +28 -0
  137. package/runtime/extensions/switchboard-local/extensions/local/engines/gguf.ts +324 -0
  138. package/runtime/extensions/switchboard-local/extensions/local/engines/llamaBuild.ts +62 -0
  139. package/runtime/extensions/switchboard-local/extensions/local/engines/mlx.ts +245 -0
  140. package/runtime/extensions/switchboard-local/extensions/local/engines/select.ts +38 -0
  141. package/runtime/extensions/switchboard-local/extensions/local/engines/support.ts +151 -0
  142. package/runtime/extensions/switchboard-local/extensions/local/hf.ts +106 -0
  143. package/runtime/extensions/switchboard-local/extensions/local/index.ts +67 -0
  144. package/runtime/extensions/switchboard-local/extensions/local/nativeStream.ts +602 -0
  145. package/runtime/extensions/switchboard-local/extensions/local/paths.ts +44 -0
  146. package/runtime/extensions/switchboard-local/extensions/local/provider.ts +57 -0
  147. package/runtime/extensions/switchboard-local/extensions/local/store.ts +82 -0
  148. package/runtime/extensions/switchboard-local/extensions/local/ui.ts +60 -0
  149. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/apiTypes.js +74 -0
  150. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/client.js +292 -0
  151. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/deviceAuth.js +155 -0
  152. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/errors.js +95 -0
  153. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/index.js +8 -0
  154. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/messages.js +253 -0
  155. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/models.js +19 -0
  156. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/native.js +886 -0
  157. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/wire.js +85 -0
  158. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/package.json +39 -0
  159. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/package.json +29 -0
  160. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/anthropic.ts +127 -0
  161. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/google.ts +111 -0
  162. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/index.ts +8 -0
  163. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.test.ts +25 -0
  164. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.ts +57 -0
  165. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiGeneric.ts +103 -0
  166. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiPro.ts +89 -0
  167. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/profile.ts +120 -0
  168. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.test.ts +82 -0
  169. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.ts +74 -0
  170. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.google.test.ts +137 -0
  171. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.openai.test.ts +241 -0
  172. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.test.ts +174 -0
  173. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.ts +438 -0
  174. package/runtime/extensions/switchboard-local/package.json +34 -0
  175. package/runtime/extensions/switchboard-local/tsconfig.json +13 -0
  176. package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  177. package/runtime/package.json +98 -0
  178. package/runtime/photon_rs_bg.wasm +0 -0
  179. package/runtime/theme/dark.json +86 -0
  180. package/runtime/theme/light.json +85 -0
  181. package/runtime/theme/theme-schema.json +340 -0
  182. package/runtime/valni +4 -0
  183. package/README.md +0 -3
@@ -0,0 +1,151 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { cpSync, readdirSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import type { ChildProcess } from "node:child_process";
5
+
6
+ export function run(
7
+ command: string,
8
+ args: readonly string[],
9
+ options: { cwd?: string; env?: NodeJS.ProcessEnv; signal?: AbortSignal } = {},
10
+ ): Promise<void> {
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(command, [...args], {
13
+ cwd: options.cwd,
14
+ env: options.env,
15
+ stdio: ["ignore", "pipe", "pipe"],
16
+ });
17
+ let stderr = "";
18
+ child.stderr?.on("data", (chunk: Buffer) => {
19
+ stderr = `${stderr}${chunk.toString()}`.slice(-4000);
20
+ });
21
+ const abort = () => child.kill("SIGTERM");
22
+ options.signal?.addEventListener("abort", abort, { once: true });
23
+ child.once("error", (error) => {
24
+ options.signal?.removeEventListener("abort", abort);
25
+ reject(error);
26
+ });
27
+ child.once("close", (code) => {
28
+ options.signal?.removeEventListener("abort", abort);
29
+ if (code === 0) return resolve();
30
+ const tail = stderr.trim().split("\n").slice(-4).join("\n");
31
+ reject(new Error(`${command} exited with code ${code}${tail ? `\n${tail}` : ""}`));
32
+ });
33
+ });
34
+ }
35
+
36
+ export function directorySize(path: string): number {
37
+ let total = 0;
38
+ let entries: string[];
39
+ try {
40
+ entries = readdirSync(path);
41
+ } catch {
42
+ return 0;
43
+ }
44
+ for (const entry of entries) {
45
+ const child = join(path, entry);
46
+ try {
47
+ const stats = statSync(child);
48
+ total += stats.isDirectory() ? directorySize(child) : stats.size;
49
+ } catch {
50
+ continue;
51
+ }
52
+ }
53
+ return total;
54
+ }
55
+
56
+ export function findFile(root: string, name: string): string | undefined {
57
+ let entries: string[];
58
+ try {
59
+ entries = readdirSync(root);
60
+ } catch {
61
+ return undefined;
62
+ }
63
+ for (const entry of entries) {
64
+ const path = join(root, entry);
65
+ let stats;
66
+ try {
67
+ stats = statSync(path);
68
+ } catch {
69
+ continue;
70
+ }
71
+ if (stats.isDirectory()) {
72
+ const found = findFile(path, name);
73
+ if (found !== undefined) return found;
74
+ } else if (entry === name) {
75
+ return path;
76
+ }
77
+ }
78
+ return undefined;
79
+ }
80
+
81
+ export async function healthy(url: string, timeoutMs = 1_000): Promise<boolean> {
82
+ try {
83
+ const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
84
+ return response.ok;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ export function detachChild(child: ChildProcess): void {
91
+ for (const pipe of [child.stderr, child.stdout]) {
92
+ (pipe as { unref?: () => void } | null)?.unref?.();
93
+ }
94
+ child.unref();
95
+ }
96
+
97
+ export function copyDir(from: string, to: string): void {
98
+ cpSync(from, to, { recursive: true });
99
+ }
100
+
101
+ export function killPid(pid: number, signal: NodeJS.Signals): void {
102
+ if (process.platform === "win32") {
103
+ try {
104
+ spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
105
+ } catch {
106
+ return;
107
+ }
108
+ return;
109
+ }
110
+ try {
111
+ process.kill(-pid, signal);
112
+ } catch {
113
+ try {
114
+ process.kill(pid, signal);
115
+ } catch {
116
+ return;
117
+ }
118
+ }
119
+ }
120
+
121
+ export function killOnExit(release: () => number | undefined): void {
122
+ process.on("exit", () => {
123
+ const pid = release();
124
+ if (pid === undefined) return;
125
+ killPid(pid, "SIGKILL");
126
+ });
127
+ }
128
+
129
+ export function watchSpawn(child: ChildProcess): { failure: () => Error | undefined } {
130
+ let failure: Error | undefined;
131
+ child.on("error", (error: Error) => {
132
+ failure = error;
133
+ });
134
+ return { failure: () => failure };
135
+ }
136
+
137
+ export async function stopChild(child: ChildProcess, graceMs: number): Promise<void> {
138
+ if (child.exitCode !== null || child.pid === undefined) return;
139
+ const pid = child.pid;
140
+ killPid(pid, "SIGTERM");
141
+ await new Promise((resolve) => {
142
+ const timer = setTimeout(() => {
143
+ killPid(pid, "SIGKILL");
144
+ resolve(undefined);
145
+ }, graceMs);
146
+ child.once("exit", () => {
147
+ clearTimeout(timer);
148
+ resolve(undefined);
149
+ });
150
+ });
151
+ }
@@ -0,0 +1,106 @@
1
+ const HOSTS = ["huggingface.co", "hf.co"];
2
+
3
+ export function parseRepoId(input: string): string | null {
4
+ let text = input.trim();
5
+ for (const prefix of ["https://", "http://"]) {
6
+ if (text.toLowerCase().startsWith(prefix)) text = text.slice(prefix.length);
7
+ }
8
+ for (const host of HOSTS) {
9
+ if (text.toLowerCase().startsWith(`${host}/`)) text = text.slice(host.length + 1);
10
+ }
11
+ const cut = text.search(/[?#]/u);
12
+ if (cut !== -1) text = text.slice(0, cut);
13
+ text = text.replace(/^\/+|\/+$/gu, "");
14
+ if (text.includes(" ")) return null;
15
+ const parts = text.split("/");
16
+ if (parts.length !== 2) return null;
17
+ if (!parts[0] || !parts[1]) return null;
18
+ return text;
19
+ }
20
+
21
+ export interface ModelRef {
22
+ repoId: string;
23
+ quant?: string;
24
+ }
25
+
26
+ export function parseModelRef(input: string): ModelRef | null {
27
+ const trimmed = input.trim().replace(/\/+$/u, "");
28
+ const colon = trimmed.lastIndexOf(":");
29
+ const slash = trimmed.lastIndexOf("/");
30
+ if (colon > slash && colon !== -1) {
31
+ const repoId = parseRepoId(trimmed.slice(0, colon));
32
+ let quant = trimmed.slice(colon + 1).trim();
33
+ const cut = quant.search(/[?#]/u);
34
+ if (cut !== -1) quant = quant.slice(0, cut);
35
+ quant = quant.replace(/\/+$/u, "").trim();
36
+ if (repoId === null || !quant) return null;
37
+ return { repoId, quant: quant.toUpperCase() };
38
+ }
39
+ const repoId = parseRepoId(trimmed);
40
+ return repoId === null ? null : { repoId };
41
+ }
42
+
43
+ export interface RepoFile {
44
+ path: string;
45
+ size: number;
46
+ }
47
+
48
+ export async function repoFiles(repoId: string, signal?: AbortSignal): Promise<RepoFile[]> {
49
+ const encoded = repoId.split("/").map(encodeURIComponent).join("/");
50
+ const url = `https://huggingface.co/api/models/${encoded}/tree/main?recursive=1`;
51
+ const timeout = AbortSignal.timeout(20_000);
52
+ const response = await fetch(url, { signal: signal ? AbortSignal.any([signal, timeout]) : timeout });
53
+ if (!response.ok) {
54
+ if (response.status === 401 || response.status === 404) {
55
+ throw new Error(`${repoId} was not found on Hugging Face, or it is gated`);
56
+ }
57
+ throw new Error(`Hugging Face returned HTTP ${response.status} for ${repoId}`);
58
+ }
59
+ const payload: unknown = await response.json();
60
+ if (!Array.isArray(payload)) throw new Error("Hugging Face returned an unexpected tree listing");
61
+ return payload.flatMap((value) => {
62
+ if (typeof value !== "object" || value === null) return [];
63
+ const entry = value as { path?: unknown; size?: unknown; type?: unknown };
64
+ if (entry.type !== "file" || typeof entry.path !== "string") return [];
65
+ return [{ path: entry.path, size: typeof entry.size === "number" ? entry.size : 0 }];
66
+ });
67
+ }
68
+
69
+ export function totalBytes(files: readonly RepoFile[]): number {
70
+ return files.reduce((sum, file) => sum + file.size, 0);
71
+ }
72
+
73
+ export function looksLikeMlxRepo(files: readonly RepoFile[]): boolean {
74
+ const names = new Set(files.map((file) => file.path));
75
+ return names.has("config.json") && [...names].some((name) => name.endsWith(".safetensors"));
76
+ }
77
+
78
+ const QUANT_PATTERN =
79
+ /(?:^|[-_.])((?:UD-)?(?:IQ\d(?:_[A-Z0-9]+)+|Q\d(?:_[A-Z0-9]+)+|BF16|F16|F32|MXFP\d(?:_[A-Z0-9]+)*))$/iu;
80
+ const SHARD_PATTERN = /-\d{5}-of-\d{5}$/u;
81
+ const PREFERRED_QUANT = "Q4_K_M";
82
+
83
+ export interface Quantization {
84
+ name: string;
85
+ size: number;
86
+ }
87
+
88
+ export function ggufQuants(files: readonly RepoFile[]): Quantization[] {
89
+ const sizes = new Map<string, number>();
90
+ for (const file of files) {
91
+ const name = file.path.split("/").at(-1) ?? "";
92
+ if (!name.toLowerCase().endsWith(".gguf")) continue;
93
+ if (name.toLowerCase().startsWith("mmproj")) continue;
94
+ const stem = name.slice(0, -5).replace(SHARD_PATTERN, "");
95
+ const quant = stem.match(QUANT_PATTERN)?.[1]?.toUpperCase();
96
+ if (!quant) continue;
97
+ sizes.set(quant, (sizes.get(quant) ?? 0) + file.size);
98
+ }
99
+ return [...sizes]
100
+ .map(([name, size]) => ({ name, size }))
101
+ .sort((left, right) => {
102
+ if (left.name === PREFERRED_QUANT) return -1;
103
+ if (right.name === PREFERRED_QUANT) return 1;
104
+ return left.size - right.size || left.name.localeCompare(right.name);
105
+ });
106
+ }
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
5
+ import { streamLocalModel } from "./nativeStream.ts";
6
+ import type { LocalEngine } from "./engine.ts";
7
+ import { GgufEngine } from "./engines/gguf.ts";
8
+ import { MlxEngine } from "./engines/mlx.ts";
9
+ import { PROVIDER_ID, PROVIDER_NAME, toModelConfigs } from "./provider.ts";
10
+ import { addModel, showMenu } from "./ui.ts";
11
+
12
+ const COMMAND = "switchboard-local";
13
+
14
+ function extensionVersion(): string {
15
+ try {
16
+ const packagePath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
17
+ return (JSON.parse(readFileSync(packagePath, "utf8")) as { version: string }).version;
18
+ } catch {
19
+ return "unknown";
20
+ }
21
+ }
22
+
23
+ async function readyModels(engines: readonly LocalEngine[]): Promise<ReturnType<typeof toModelConfigs>> {
24
+ const listed = await Promise.all(
25
+ engines.map(async (engine) => {
26
+ if (!engine.supported() || !engine.isInstalled()) return [];
27
+ try {
28
+ if (!engine.isRunning()) await engine.ensureReady(() => undefined);
29
+ return await engine.listModels();
30
+ } catch (error) {
31
+ console.error(`switchboard-local: the ${engine.label} engine could not list models`, error);
32
+ return [];
33
+ }
34
+ }),
35
+ );
36
+ return toModelConfigs(listed.flat());
37
+ }
38
+
39
+ export default async function (pi: ExtensionAPI) {
40
+ console.error(`switchboard-local v${extensionVersion()}`);
41
+ const engines: LocalEngine[] = [new MlxEngine(), new GgufEngine()];
42
+
43
+ pi.registerCommand(COMMAND, {
44
+ description: "Download and manage local models",
45
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
46
+ if (args.trim()) {
47
+ await addModel(engines, ctx, args);
48
+ return;
49
+ }
50
+ await showMenu(engines, ctx);
51
+ },
52
+ });
53
+
54
+ pi.on("session_shutdown", async () => {
55
+ await Promise.all(engines.map((engine) => engine.stop()));
56
+ });
57
+
58
+ pi.registerProvider(PROVIDER_ID, {
59
+ name: PROVIDER_NAME,
60
+ baseUrl: "http://127.0.0.1/v1",
61
+ api: "openai-completions",
62
+ apiKey: "local",
63
+ streamSimple: streamLocalModel,
64
+ models: [],
65
+ refreshModels: async () => await readyModels(engines),
66
+ });
67
+ }