@valni/cli-darwin-x64 0.0.0-stage → 0.1.2

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 (236) hide show
  1. package/LICENSE +53 -0
  2. package/package.json +18 -5
  3. package/runtime/LICENSE +53 -0
  4. package/runtime/README.md +674 -0
  5. package/runtime/assets/clankolas.png +0 -0
  6. package/runtime/docs/extensions.md +2910 -0
  7. package/runtime/docs/json.md +82 -0
  8. package/runtime/docs/models.md +540 -0
  9. package/runtime/docs/packages.md +228 -0
  10. package/runtime/docs/rpc.md +1480 -0
  11. package/runtime/docs/sdk.md +1183 -0
  12. package/runtime/docs/session-format.md +422 -0
  13. package/runtime/docs/settings.md +315 -0
  14. package/runtime/docs/shell-aliases.md +13 -0
  15. package/runtime/docs/terminal-setup.md +142 -0
  16. package/runtime/docs/termux.md +127 -0
  17. package/runtime/docs/tmux.md +63 -0
  18. package/runtime/docs/tui.md +927 -0
  19. package/runtime/docs/windows.md +17 -0
  20. package/runtime/examples/README.md +25 -0
  21. package/runtime/examples/extensions/README.md +212 -0
  22. package/runtime/examples/extensions/auto-commit-on-exit.ts +49 -0
  23. package/runtime/examples/extensions/bash-spawn-hook.ts +30 -0
  24. package/runtime/examples/extensions/bookmark.ts +50 -0
  25. package/runtime/examples/extensions/border-status-editor.ts +145 -0
  26. package/runtime/examples/extensions/built-in-tool-renderer.ts +249 -0
  27. package/runtime/examples/extensions/claude-rules.ts +86 -0
  28. package/runtime/examples/extensions/commands.ts +72 -0
  29. package/runtime/examples/extensions/confirm-destructive.ts +59 -0
  30. package/runtime/examples/extensions/custom-footer.ts +64 -0
  31. package/runtime/examples/extensions/custom-header.ts +73 -0
  32. package/runtime/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  33. package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  34. package/runtime/examples/extensions/custom-provider-anthropic/package.json +19 -0
  35. package/runtime/examples/extensions/dirty-repo-guard.ts +56 -0
  36. package/runtime/examples/extensions/doom-overlay/README.md +46 -0
  37. package/runtime/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  38. package/runtime/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  39. package/runtime/examples/extensions/doom-overlay/doom/build.sh +152 -0
  40. package/runtime/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  41. package/runtime/examples/extensions/doom-overlay/doom-component.ts +132 -0
  42. package/runtime/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  43. package/runtime/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  44. package/runtime/examples/extensions/doom-overlay/index.ts +74 -0
  45. package/runtime/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  46. package/runtime/examples/extensions/dynamic-resources/SKILL.md +8 -0
  47. package/runtime/examples/extensions/dynamic-resources/dynamic.json +79 -0
  48. package/runtime/examples/extensions/dynamic-resources/dynamic.md +5 -0
  49. package/runtime/examples/extensions/dynamic-resources/index.ts +15 -0
  50. package/runtime/examples/extensions/dynamic-tools.ts +74 -0
  51. package/runtime/examples/extensions/entry-renderer.ts +41 -0
  52. package/runtime/examples/extensions/event-bus.ts +43 -0
  53. package/runtime/examples/extensions/file-trigger.ts +41 -0
  54. package/runtime/examples/extensions/git-checkpoint.ts +53 -0
  55. package/runtime/examples/extensions/git-merge-and-resolve.ts +115 -0
  56. package/runtime/examples/extensions/github-issue-autocomplete.ts +185 -0
  57. package/runtime/examples/extensions/gondolin/index.ts +531 -0
  58. package/runtime/examples/extensions/gondolin/package-lock.json +185 -0
  59. package/runtime/examples/extensions/gondolin/package.json +19 -0
  60. package/runtime/examples/extensions/hello.ts +26 -0
  61. package/runtime/examples/extensions/hidden-thinking-label.ts +53 -0
  62. package/runtime/examples/extensions/inline-bash.ts +94 -0
  63. package/runtime/examples/extensions/input-transform-streaming.ts +39 -0
  64. package/runtime/examples/extensions/input-transform.ts +43 -0
  65. package/runtime/examples/extensions/interactive-shell.ts +196 -0
  66. package/runtime/examples/extensions/mac-system-theme.ts +47 -0
  67. package/runtime/examples/extensions/message-renderer.ts +59 -0
  68. package/runtime/examples/extensions/minimal-mode.ts +426 -0
  69. package/runtime/examples/extensions/modal-editor.ts +85 -0
  70. package/runtime/examples/extensions/model-status.ts +31 -0
  71. package/runtime/examples/extensions/notify.ts +55 -0
  72. package/runtime/examples/extensions/overlay-qa-tests.ts +1450 -0
  73. package/runtime/examples/extensions/overlay-test.ts +153 -0
  74. package/runtime/examples/extensions/permission-gate.ts +34 -0
  75. package/runtime/examples/extensions/pirate.ts +47 -0
  76. package/runtime/examples/extensions/plan-mode/README.md +66 -0
  77. package/runtime/examples/extensions/plan-mode/index.ts +390 -0
  78. package/runtime/examples/extensions/plan-mode/utils.ts +168 -0
  79. package/runtime/examples/extensions/preset.ts +436 -0
  80. package/runtime/examples/extensions/project-trust.ts +64 -0
  81. package/runtime/examples/extensions/prompt-customizer.ts +97 -0
  82. package/runtime/examples/extensions/protected-paths.ts +30 -0
  83. package/runtime/examples/extensions/provider-payload.ts +18 -0
  84. package/runtime/examples/extensions/question.ts +278 -0
  85. package/runtime/examples/extensions/questionnaire.ts +440 -0
  86. package/runtime/examples/extensions/rainbow-editor.ts +88 -0
  87. package/runtime/examples/extensions/reload-runtime.ts +37 -0
  88. package/runtime/examples/extensions/rpc-demo.ts +118 -0
  89. package/runtime/examples/extensions/sandbox/index.ts +321 -0
  90. package/runtime/examples/extensions/sandbox/package-lock.json +92 -0
  91. package/runtime/examples/extensions/sandbox/package.json +19 -0
  92. package/runtime/examples/extensions/send-user-message.ts +97 -0
  93. package/runtime/examples/extensions/session-name.ts +27 -0
  94. package/runtime/examples/extensions/shutdown-command.ts +63 -0
  95. package/runtime/examples/extensions/snake.ts +343 -0
  96. package/runtime/examples/extensions/space-invaders.ts +560 -0
  97. package/runtime/examples/extensions/ssh.ts +220 -0
  98. package/runtime/examples/extensions/status-line.ts +32 -0
  99. package/runtime/examples/extensions/structured-output.ts +65 -0
  100. package/runtime/examples/extensions/subagent/README.md +175 -0
  101. package/runtime/examples/extensions/subagent/agents/planner.md +37 -0
  102. package/runtime/examples/extensions/subagent/agents/reviewer.md +35 -0
  103. package/runtime/examples/extensions/subagent/agents/scout.md +50 -0
  104. package/runtime/examples/extensions/subagent/agents/worker.md +24 -0
  105. package/runtime/examples/extensions/subagent/agents.ts +126 -0
  106. package/runtime/examples/extensions/subagent/index.ts +1009 -0
  107. package/runtime/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  108. package/runtime/examples/extensions/subagent/prompts/implement.md +10 -0
  109. package/runtime/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  110. package/runtime/examples/extensions/system-prompt-header.ts +17 -0
  111. package/runtime/examples/extensions/tic-tac-toe.ts +1008 -0
  112. package/runtime/examples/extensions/timed-confirm.ts +70 -0
  113. package/runtime/examples/extensions/titlebar-spinner.ts +58 -0
  114. package/runtime/examples/extensions/todo.ts +297 -0
  115. package/runtime/examples/extensions/tool-override.ts +144 -0
  116. package/runtime/examples/extensions/tools.ts +146 -0
  117. package/runtime/examples/extensions/trigger-compact.ts +50 -0
  118. package/runtime/examples/extensions/truncated-tool.ts +195 -0
  119. package/runtime/examples/extensions/widget-placement.ts +9 -0
  120. package/runtime/examples/extensions/with-deps/index.ts +32 -0
  121. package/runtime/examples/extensions/with-deps/package-lock.json +31 -0
  122. package/runtime/examples/extensions/with-deps/package.json +22 -0
  123. package/runtime/examples/extensions/working-indicator.ts +123 -0
  124. package/runtime/examples/extensions/working-message-test.ts +25 -0
  125. package/runtime/examples/rpc-extension-ui.ts +632 -0
  126. package/runtime/examples/sdk/01-minimal.ts +26 -0
  127. package/runtime/examples/sdk/02-custom-model.ts +43 -0
  128. package/runtime/examples/sdk/03-custom-prompt.ts +70 -0
  129. package/runtime/examples/sdk/04-skills.ts +55 -0
  130. package/runtime/examples/sdk/05-tools.ts +48 -0
  131. package/runtime/examples/sdk/06-extensions.ts +94 -0
  132. package/runtime/examples/sdk/07-context-files.ts +42 -0
  133. package/runtime/examples/sdk/08-prompt-templates.ts +51 -0
  134. package/runtime/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  135. package/runtime/examples/sdk/10-settings.ts +53 -0
  136. package/runtime/examples/sdk/11-sessions.ts +52 -0
  137. package/runtime/examples/sdk/12-full-control.ts +71 -0
  138. package/runtime/examples/sdk/13-session-runtime.ts +67 -0
  139. package/runtime/examples/sdk/README.md +140 -0
  140. package/runtime/export-html/ansi-to-html.js +249 -0
  141. package/runtime/export-html/index.js +226 -0
  142. package/runtime/export-html/template.css +1066 -0
  143. package/runtime/export-html/template.html +55 -0
  144. package/runtime/export-html/template.js +1864 -0
  145. package/runtime/export-html/tool-renderer.js +108 -0
  146. package/runtime/export-html/vendor/highlight.min.js +1213 -0
  147. package/runtime/export-html/vendor/marked.min.js +78 -0
  148. package/runtime/extensions/pi-switchboard/.github/workflows/ci.yml +51 -0
  149. package/runtime/extensions/pi-switchboard/LICENSE +22 -0
  150. package/runtime/extensions/pi-switchboard/README.md +85 -0
  151. package/runtime/extensions/pi-switchboard/extensions/switchboard/catalog.ts +183 -0
  152. package/runtime/extensions/pi-switchboard/extensions/switchboard/config.ts +97 -0
  153. package/runtime/extensions/pi-switchboard/extensions/switchboard/connections.ts +386 -0
  154. package/runtime/extensions/pi-switchboard/extensions/switchboard/constants.ts +12 -0
  155. package/runtime/extensions/pi-switchboard/extensions/switchboard/device-auth.ts +127 -0
  156. package/runtime/extensions/pi-switchboard/extensions/switchboard/envelope.ts +199 -0
  157. package/runtime/extensions/pi-switchboard/extensions/switchboard/errors.ts +180 -0
  158. package/runtime/extensions/pi-switchboard/extensions/switchboard/index.ts +125 -0
  159. package/runtime/extensions/pi-switchboard/extensions/switchboard/participant.ts +17 -0
  160. package/runtime/extensions/pi-switchboard/extensions/switchboard/sessionStream.ts +131 -0
  161. package/runtime/extensions/pi-switchboard/extensions/switchboard/toolProxy.ts +136 -0
  162. package/runtime/extensions/pi-switchboard/extensions/switchboard/tools.ts +16 -0
  163. package/runtime/extensions/pi-switchboard/extensions/switchboard/types.ts +37 -0
  164. package/runtime/extensions/pi-switchboard/package.json +30 -0
  165. package/runtime/extensions/pi-switchboard/tsconfig.json +13 -0
  166. package/runtime/extensions/switchboard-local/.github/workflows/ci.yml +80 -0
  167. package/runtime/extensions/switchboard-local/.github/workflows/release.yml +46 -0
  168. package/runtime/extensions/switchboard-local/README.md +98 -0
  169. package/runtime/extensions/switchboard-local/extensions/local/engine.ts +28 -0
  170. package/runtime/extensions/switchboard-local/extensions/local/engines/gguf.ts +324 -0
  171. package/runtime/extensions/switchboard-local/extensions/local/engines/llamaBuild.ts +62 -0
  172. package/runtime/extensions/switchboard-local/extensions/local/engines/mlx.ts +245 -0
  173. package/runtime/extensions/switchboard-local/extensions/local/engines/select.ts +38 -0
  174. package/runtime/extensions/switchboard-local/extensions/local/engines/support.ts +151 -0
  175. package/runtime/extensions/switchboard-local/extensions/local/hf.ts +106 -0
  176. package/runtime/extensions/switchboard-local/extensions/local/index.ts +67 -0
  177. package/runtime/extensions/switchboard-local/extensions/local/nativeStream.ts +602 -0
  178. package/runtime/extensions/switchboard-local/extensions/local/paths.ts +44 -0
  179. package/runtime/extensions/switchboard-local/extensions/local/provider.ts +57 -0
  180. package/runtime/extensions/switchboard-local/extensions/local/store.ts +82 -0
  181. package/runtime/extensions/switchboard-local/extensions/local/ui.ts +60 -0
  182. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/apiTypes.js +74 -0
  183. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/client.js +292 -0
  184. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/deviceAuth.js +155 -0
  185. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/errors.js +95 -0
  186. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/index.js +8 -0
  187. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/messages.js +253 -0
  188. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/models.js +19 -0
  189. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/native.js +886 -0
  190. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/wire.js +85 -0
  191. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/package.json +39 -0
  192. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/package.json +29 -0
  193. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/anthropic.ts +127 -0
  194. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/google.ts +111 -0
  195. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/index.ts +8 -0
  196. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.test.ts +25 -0
  197. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.ts +57 -0
  198. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiGeneric.ts +103 -0
  199. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiPro.ts +89 -0
  200. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/profile.ts +120 -0
  201. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.test.ts +82 -0
  202. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.ts +74 -0
  203. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.google.test.ts +137 -0
  204. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.openai.test.ts +241 -0
  205. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.test.ts +174 -0
  206. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.ts +438 -0
  207. package/runtime/extensions/switchboard-local/package.json +34 -0
  208. package/runtime/extensions/switchboard-local/tsconfig.json +13 -0
  209. package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  210. package/runtime/node_modules/@mariozechner/clipboard/.yarnrc.yml +1 -0
  211. package/runtime/node_modules/@mariozechner/clipboard/Cargo.toml +45 -0
  212. package/runtime/node_modules/@mariozechner/clipboard/README.md +60 -0
  213. package/runtime/node_modules/@mariozechner/clipboard/build.rs +5 -0
  214. package/runtime/node_modules/@mariozechner/clipboard/clipboard.darwin-x64.node +0 -0
  215. package/runtime/node_modules/@mariozechner/clipboard/exp.ts +12 -0
  216. package/runtime/node_modules/@mariozechner/clipboard/index.js +606 -0
  217. package/runtime/node_modules/@mariozechner/clipboard/package.json +56 -0
  218. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/common.rs +307 -0
  219. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/mod.rs +93 -0
  220. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/ios.rs +313 -0
  221. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/macos.rs +471 -0
  222. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/mod.rs +252 -0
  223. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/wayland.rs +431 -0
  224. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/win.rs +712 -0
  225. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/x11.rs +916 -0
  226. package/runtime/node_modules/@mariozechner/clipboard/src/lib.rs +194 -0
  227. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/README.md +3 -0
  228. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/clipboard.darwin-x64.node +0 -0
  229. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/package.json +22 -0
  230. package/runtime/package.json +104 -0
  231. package/runtime/photon_rs_bg.wasm +0 -0
  232. package/runtime/theme/dark.json +87 -0
  233. package/runtime/theme/light.json +86 -0
  234. package/runtime/theme/theme-schema.json +340 -0
  235. package/runtime/valni +4 -0
  236. package/README.md +0 -3
@@ -0,0 +1,82 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { createServer } from "node:net";
3
+ import { managedDir, stateFile } from "./paths.ts";
4
+
5
+ export interface State {
6
+ mlxPort?: number;
7
+ mlxPid?: number;
8
+ ggufPort?: number;
9
+ ggufPid?: number;
10
+ ggufBuild?: string;
11
+ ggufAccelerator?: string;
12
+ mlxOwners?: number[];
13
+ ggufOwners?: number[];
14
+ }
15
+
16
+ export type OwnerKey = "mlxOwners" | "ggufOwners";
17
+
18
+ export function isAlive(pid: number): boolean {
19
+ try {
20
+ process.kill(pid, 0);
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ export function claimOwnership(key: OwnerKey, pid: number): void {
28
+ const owners = (readState()[key] ?? []).filter(isAlive);
29
+ if (!owners.includes(pid)) owners.push(pid);
30
+ updateState({ [key]: owners } as Partial<State>);
31
+ }
32
+
33
+ export function releaseOwnership(key: OwnerKey, pid: number): number[] {
34
+ const remaining = (readState()[key] ?? []).filter((owner) => owner !== pid && isAlive(owner));
35
+ updateState({ [key]: remaining } as Partial<State>);
36
+ return remaining;
37
+ }
38
+
39
+ export function readState(): State {
40
+ try {
41
+ const parsed: unknown = JSON.parse(readFileSync(stateFile(), "utf8"));
42
+ if (typeof parsed !== "object" || parsed === null) return {};
43
+ return parsed as State;
44
+ } catch {
45
+ return {};
46
+ }
47
+ }
48
+
49
+ export function writeState(next: State): void {
50
+ mkdirSync(managedDir(), { recursive: true });
51
+ writeFileSync(stateFile(), `${JSON.stringify(next, null, "\t")}\n`, "utf8");
52
+ }
53
+
54
+ export function updateState(patch: Partial<State>): State {
55
+ const next = { ...readState(), ...patch };
56
+ writeState(next);
57
+ return next;
58
+ }
59
+
60
+ export async function freePort(): Promise<number> {
61
+ return await new Promise((resolve, reject) => {
62
+ const server = createServer();
63
+ server.once("error", reject);
64
+ server.listen(0, "127.0.0.1", () => {
65
+ const address = server.address();
66
+ if (address === null || typeof address === "string") {
67
+ server.close(() => reject(new Error("could not reserve a local port")));
68
+ return;
69
+ }
70
+ const { port } = address;
71
+ server.close(() => resolve(port));
72
+ });
73
+ });
74
+ }
75
+
76
+ export async function portIsFree(port: number): Promise<boolean> {
77
+ return await new Promise((resolve) => {
78
+ const server = createServer();
79
+ server.once("error", () => resolve(false));
80
+ server.listen(port, "127.0.0.1", () => server.close(() => resolve(true)));
81
+ });
82
+ }
@@ -0,0 +1,60 @@
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import type { LocalEngine, Progress } from "./engine.ts";
3
+ import { chooseEngine } from "./engines/select.ts";
4
+ import { displayName } from "./provider.ts";
5
+
6
+ const STATUS_KEY = "switchboard-local";
7
+ const ADD_LABEL = "Add a model…";
8
+ const CLOSE_LABEL = "Close";
9
+
10
+ function bar(fraction: number): string {
11
+ const filled = Math.round(fraction * 20);
12
+ return `${"█".repeat(filled)}${"░".repeat(20 - filled)} ${Math.round(fraction * 100)}%`;
13
+ }
14
+
15
+ function render(progress: Progress): string {
16
+ return progress.fraction === undefined ? progress.label : `${progress.label} ${bar(progress.fraction)}`;
17
+ }
18
+
19
+ async function describe(engine: LocalEngine): Promise<string> {
20
+ if (!engine.supported()) return `${engine.label}: not available here`;
21
+ if (!engine.isInstalled()) return `${engine.label}: not installed`;
22
+ if (!engine.isRunning()) return `${engine.label}: installed`;
23
+ const models = await engine.listModels().catch(() => []);
24
+ return `${engine.label}: ${models.length} ready`;
25
+ }
26
+
27
+ export async function statusLine(engines: readonly LocalEngine[]): Promise<string> {
28
+ return (await Promise.all(engines.map(describe))).join(" · ");
29
+ }
30
+
31
+ export async function addModel(
32
+ engines: readonly LocalEngine[],
33
+ ctx: ExtensionCommandContext,
34
+ input: string,
35
+ ): Promise<void> {
36
+ const onProgress = (progress: Progress) => ctx.ui.setStatus(STATUS_KEY, render(progress));
37
+ try {
38
+ const { engine, reference } = await chooseEngine(engines, input);
39
+ await engine.ensureReady(onProgress);
40
+ await engine.addModel(reference, onProgress);
41
+ ctx.ui.setStatus(STATUS_KEY, undefined);
42
+ ctx.ui.notify(`${displayName(reference)} is ready on ${engine.label}. Pick it with /model.`, "info");
43
+ } catch (error) {
44
+ ctx.ui.setStatus(STATUS_KEY, undefined);
45
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
46
+ }
47
+ }
48
+
49
+ export async function showMenu(engines: readonly LocalEngine[], ctx: ExtensionCommandContext): Promise<void> {
50
+ const usable = engines.filter((engine) => engine.supported());
51
+ if (usable.length === 0) {
52
+ ctx.ui.notify(engines.map((engine) => engine.unsupportedReason()).join(" "), "warning");
53
+ return;
54
+ }
55
+ const choice = await ctx.ui.select(`Switchboard Local — ${await statusLine(engines)}`, [ADD_LABEL, CLOSE_LABEL]);
56
+ if (choice !== ADD_LABEL) return;
57
+ const entry = await ctx.ui.input("Hugging Face model", "unsloth/Qwen3-30B-A3B-Instruct-GGUF:Q4_K_M");
58
+ if (!entry || !entry.trim()) return;
59
+ await addModel(engines, ctx, entry);
60
+ }
@@ -0,0 +1,74 @@
1
+ import * as wire from "./wire.js";
2
+ export const MICRO_CENTS_PER_USD = 100_000_000;
3
+ export function usdFromMicroCents(microCents) {
4
+ return microCents / MICRO_CENTS_PER_USD;
5
+ }
6
+ export const UsageRecord = {
7
+ fromWire(value) {
8
+ const fields = wire.mapping(value, "usage_record");
9
+ return {
10
+ id: wire.integer(fields, "id", "usage_record"),
11
+ at: wire.integer(fields, "at", "usage_record"),
12
+ end_user_id: wire.optionalString(fields, "end_user_id", "usage_record"),
13
+ model_id: wire.string(fields, "model_id", "usage_record"),
14
+ provider_id: wire.string(fields, "provider_id", "usage_record"),
15
+ prompt_tokens: wire.integer(fields, "prompt_tokens", "usage_record"),
16
+ completion_tokens: wire.integer(fields, "completion_tokens", "usage_record"),
17
+ cache_creation_tokens: wire.integer(fields, "cache_creation_tokens", "usage_record"),
18
+ cache_read_tokens: wire.integer(fields, "cache_read_tokens", "usage_record"),
19
+ reasoning_tokens: wire.integer(fields, "reasoning_tokens", "usage_record"),
20
+ cost_micro_cents: wire.integer(fields, "cost_micro_cents", "usage_record"),
21
+ };
22
+ },
23
+ };
24
+ export const UsagePage = {
25
+ fromWire(value) {
26
+ const fields = wire.mapping(value, "usage_page");
27
+ return {
28
+ company_id: wire.string(fields, "company_id", "usage_page"),
29
+ records: wire.array(fields, "records", "usage_page").map((item) => UsageRecord.fromWire(item)),
30
+ next_before_at: wire.optionalInteger(fields, "next_before_at", "usage_page"),
31
+ next_before_id: wire.optionalInteger(fields, "next_before_id", "usage_page"),
32
+ };
33
+ },
34
+ };
35
+ export const PickerModel = {
36
+ fromWire(value) {
37
+ const fields = wire.mapping(value, "picker_model");
38
+ return {
39
+ id: wire.string(fields, "id", "picker_model"),
40
+ display_name: wire.string(fields, "display_name", "picker_model"),
41
+ context_window: wire.integer(fields, "context_window", "picker_model"),
42
+ max_output_tokens: wire.optionalInteger(fields, "max_output_tokens", "picker_model"),
43
+ input_formats: wire.stringArray(fields, "input_formats", "picker_model"),
44
+ capabilities: wire.stringArray(fields, "capabilities", "picker_model"),
45
+ wire_format: wire.string(fields, "wire_format", "picker_model"),
46
+ };
47
+ },
48
+ };
49
+ export const ModelsPage = {
50
+ fromWire(value) {
51
+ const fields = wire.mapping(value, "models_page");
52
+ return {
53
+ models: wire.array(fields, "models", "models_page").map((item) => PickerModel.fromWire(item)),
54
+ };
55
+ },
56
+ };
57
+ export const BalancePage = {
58
+ fromWire(value) {
59
+ const fields = wire.mapping(value, "balance_page");
60
+ return {
61
+ company_id: wire.string(fields, "company_id", "balance_page"),
62
+ balance_micro_cents: wire.integer(fields, "balance_micro_cents", "balance_page"),
63
+ };
64
+ },
65
+ };
66
+ export const ErrorEnvelope = {
67
+ fromWire(value) {
68
+ const fields = wire.mapping(value, "error_envelope");
69
+ return {
70
+ code: wire.string(fields, "code", "error_envelope"),
71
+ error: wire.string(fields, "error", "error_envelope"),
72
+ };
73
+ },
74
+ };
@@ -0,0 +1,292 @@
1
+ import { ModelsPage, UsagePage } from "./apiTypes.js";
2
+ import { DecodingFailedError, EncodingFailedError, MissingAPIKeyError, ServerError, StreamError, SwitchboardError, TransportError, serverErrorFor, } from "./errors.js";
3
+ import * as native from "./native.js";
4
+ export const DEFAULT_BASE_URL = "https://switchboard.valni.app";
5
+ export const DEFAULT_TIMEOUT_MILLISECONDS = 600_000;
6
+ const INFERENCE_PATH = "/v1/switchboard/inference";
7
+ const MODELS_PATH = "/v1/models";
8
+ const USAGE_PATH = "/v1/switchboard/usage";
9
+ const MAX_ERROR_BODY_BYTES = 64 * 1024;
10
+ const SSE_DATA_PREFIX = "data:";
11
+ const RESPONSES_FAILED_CODE = "response.failed";
12
+ const RESPONSES_FAILED_DEFAULT_MESSAGE = "The response failed";
13
+ const RESPONSES_ERROR_CODE = "error";
14
+ function encodeRouterBody(router) {
15
+ let encoded;
16
+ try {
17
+ encoded = JSON.stringify(router.toWire());
18
+ }
19
+ catch (error) {
20
+ throw new EncodingFailedError(error);
21
+ }
22
+ if (encoded === undefined) {
23
+ throw new EncodingFailedError(new TypeError("router body is not JSON-encodable"));
24
+ }
25
+ return encoded;
26
+ }
27
+ function decodeJson(payload, decode) {
28
+ try {
29
+ return decode(JSON.parse(payload));
30
+ }
31
+ catch (error) {
32
+ throw new DecodingFailedError(error);
33
+ }
34
+ }
35
+ function responseDecoderFor(body) {
36
+ if (body instanceof native.AnthropicMessagesRequest)
37
+ return native.AnthropicMessageResponse.fromWire;
38
+ if (body instanceof native.OpenAIChatRequest)
39
+ return native.OpenAIChatResponse.fromWire;
40
+ if (body instanceof native.OpenAIResponsesRequest)
41
+ return native.OpenAIResponsesResponse.fromWire;
42
+ throw new EncodingFailedError(new TypeError("unsupported router body type"));
43
+ }
44
+ function streamEventParserFor(body) {
45
+ if (body instanceof native.AnthropicMessagesRequest)
46
+ return native.AnthropicStream.parse;
47
+ if (body instanceof native.OpenAIChatRequest)
48
+ return native.OpenAIChatChunk.fromWire;
49
+ if (body instanceof native.OpenAIResponsesRequest)
50
+ return native.OpenAIResponsesStream.parse;
51
+ throw new EncodingFailedError(new TypeError("unsupported router body type"));
52
+ }
53
+ function streamErrorFromEvent(event) {
54
+ if (event instanceof native.AnthropicStream.Error) {
55
+ return new StreamError(event.error_type, event.message);
56
+ }
57
+ if (event instanceof native.OpenAIResponsesStream.ResponseFailed) {
58
+ const errorWire = event.response["error"];
59
+ const message = errorWire !== null && typeof errorWire === "object" && !Array.isArray(errorWire)
60
+ ? errorWire["message"]
61
+ : null;
62
+ return new StreamError(RESPONSES_FAILED_CODE, typeof message === "string" ? message : RESPONSES_FAILED_DEFAULT_MESSAGE);
63
+ }
64
+ if (event instanceof native.OpenAIResponsesStream.Error) {
65
+ return new StreamError(RESPONSES_ERROR_CODE, event.message);
66
+ }
67
+ return null;
68
+ }
69
+ function decodeErrorEnvelope(bodyText) {
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(bodyText);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
78
+ return null;
79
+ const envelope = parsed;
80
+ const code = envelope["code"];
81
+ const message = envelope["error"];
82
+ if (typeof code !== "string" || typeof message !== "string")
83
+ return null;
84
+ const optionalStrings = {};
85
+ for (const key of ["model", "provider", "fault", "requestId"]) {
86
+ const value = envelope[key];
87
+ if (value !== null && value !== undefined && typeof value !== "string")
88
+ return null;
89
+ optionalStrings[key] = typeof value === "string" ? value : null;
90
+ }
91
+ const optionalIntegers = {};
92
+ for (const key of ["spentMicros", "capMicros", "retryAfterSeconds"]) {
93
+ const value = envelope[key];
94
+ if (value !== null && value !== undefined && (typeof value !== "number" || !Number.isInteger(value)))
95
+ return null;
96
+ optionalIntegers[key] = typeof value === "number" ? value : null;
97
+ }
98
+ return {
99
+ code,
100
+ message,
101
+ context: {
102
+ model: optionalStrings["model"] ?? null,
103
+ provider: optionalStrings["provider"] ?? null,
104
+ fault: optionalStrings["fault"] ?? null,
105
+ requestId: optionalStrings["requestId"] ?? null,
106
+ spentMicros: optionalIntegers["spentMicros"] ?? null,
107
+ capMicros: optionalIntegers["capMicros"] ?? null,
108
+ retryAfterSeconds: optionalIntegers["retryAfterSeconds"] ?? null,
109
+ },
110
+ };
111
+ }
112
+ function serverErrorFromBody(status, bodyText) {
113
+ if (bodyText.length === 0) {
114
+ return new ServerError(status, null, "Empty response body", null);
115
+ }
116
+ const envelope = decodeErrorEnvelope(bodyText);
117
+ if (envelope !== null) {
118
+ return serverErrorFor(status, envelope.code, envelope.message, envelope.context);
119
+ }
120
+ return new ServerError(status, null, bodyText, null);
121
+ }
122
+ async function collectBoundedBody(response) {
123
+ const body = response.body;
124
+ if (body === null)
125
+ return "";
126
+ const reader = body.getReader();
127
+ const collected = [];
128
+ let total = 0;
129
+ while (total < MAX_ERROR_BODY_BYTES) {
130
+ const { done, value } = await reader.read();
131
+ if (done)
132
+ break;
133
+ const remaining = MAX_ERROR_BODY_BYTES - total;
134
+ const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value;
135
+ collected.push(chunk);
136
+ total += chunk.byteLength;
137
+ }
138
+ await reader.cancel().catch(() => undefined);
139
+ const merged = new Uint8Array(total);
140
+ let offset = 0;
141
+ for (const chunk of collected) {
142
+ merged.set(chunk, offset);
143
+ offset += chunk.byteLength;
144
+ }
145
+ return new TextDecoder().decode(merged);
146
+ }
147
+ function ssePayload(line) {
148
+ if (!line.startsWith(SSE_DATA_PREFIX))
149
+ return null;
150
+ const payload = line.slice(SSE_DATA_PREFIX.length).trim();
151
+ if (payload.length === 0 || payload === native.DONE_SENTINEL)
152
+ return null;
153
+ return payload;
154
+ }
155
+ function isSuccess(status) {
156
+ return status >= 200 && status < 300;
157
+ }
158
+ async function* sseLines(body) {
159
+ const decoder = new TextDecoder();
160
+ let buffered = "";
161
+ for await (const chunk of body) {
162
+ buffered += decoder.decode(chunk, { stream: true });
163
+ const lines = buffered.split(/\r\n|\r|\n/);
164
+ buffered = lines.pop() ?? "";
165
+ yield* lines;
166
+ }
167
+ buffered += decoder.decode();
168
+ if (buffered.length > 0) {
169
+ yield buffered;
170
+ }
171
+ }
172
+ export class Client {
173
+ apiKey;
174
+ baseUrl;
175
+ timeoutMilliseconds;
176
+ fetchImplementation;
177
+ constructor(apiKey, options = {}) {
178
+ this.apiKey = apiKey;
179
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
180
+ this.timeoutMilliseconds = options.timeoutMilliseconds ?? DEFAULT_TIMEOUT_MILLISECONDS;
181
+ this.fetchImplementation = options.fetch ?? globalThis.fetch.bind(globalThis);
182
+ }
183
+ async inference(router) {
184
+ const decode = responseDecoderFor(router.kind);
185
+ const bodyText = await this.post(INFERENCE_PATH, encodeRouterBody(router));
186
+ return decodeJson(bodyText, decode);
187
+ }
188
+ async inferenceResponse(router) {
189
+ const body = encodeRouterBody(router);
190
+ let response;
191
+ try {
192
+ response = await this.fetchImplementation(`${this.baseUrl}${INFERENCE_PATH}`, {
193
+ method: "POST",
194
+ headers: this.postHeaders(),
195
+ body,
196
+ signal: AbortSignal.timeout(this.timeoutMilliseconds),
197
+ });
198
+ }
199
+ catch (error) {
200
+ throw new TransportError(error);
201
+ }
202
+ if (!isSuccess(response.status)) {
203
+ throw serverErrorFromBody(response.status, await collectBoundedBody(response));
204
+ }
205
+ return response;
206
+ }
207
+ async *streamInference(router) {
208
+ const streamingRouter = router.streaming();
209
+ const parse = streamEventParserFor(streamingRouter.kind);
210
+ const response = await this.inferenceResponse(streamingRouter);
211
+ if (response.body === null) {
212
+ throw new TransportError(new Error("Response body is missing"));
213
+ }
214
+ try {
215
+ for await (const line of sseLines(response.body)) {
216
+ const payload = ssePayload(line);
217
+ if (payload === null)
218
+ continue;
219
+ const event = decodeJson(payload, parse);
220
+ if (event === null)
221
+ continue;
222
+ const streamError = streamErrorFromEvent(event);
223
+ if (streamError !== null)
224
+ throw streamError;
225
+ yield event;
226
+ }
227
+ }
228
+ catch (error) {
229
+ if (error instanceof SwitchboardError)
230
+ throw error;
231
+ throw new TransportError(error);
232
+ }
233
+ }
234
+ async models() {
235
+ const bodyText = await this.get(MODELS_PATH);
236
+ return decodeJson(bodyText, (value) => ModelsPage.fromWire(value).models);
237
+ }
238
+ async usage(query = {}) {
239
+ const parameters = new URLSearchParams();
240
+ if (query.limit !== undefined)
241
+ parameters.set("limit", String(query.limit));
242
+ if (query.beforeAt !== undefined)
243
+ parameters.set("before_at", String(query.beforeAt));
244
+ if (query.beforeId !== undefined)
245
+ parameters.set("before_id", String(query.beforeId));
246
+ if (query.since !== undefined)
247
+ parameters.set("since", String(query.since));
248
+ if (query.until !== undefined)
249
+ parameters.set("until", String(query.until));
250
+ if (query.userId !== undefined)
251
+ parameters.set("user_id", query.userId);
252
+ const queryText = parameters.size > 0 ? `?${parameters.toString()}` : "";
253
+ const bodyText = await this.get(`${USAGE_PATH}${queryText}`);
254
+ return decodeJson(bodyText, UsagePage.fromWire);
255
+ }
256
+ async get(path) {
257
+ return this.request(path, { method: "GET", headers: this.getHeaders() });
258
+ }
259
+ async post(path, body) {
260
+ return this.request(path, { method: "POST", headers: this.postHeaders(), body });
261
+ }
262
+ async request(path, init) {
263
+ let response;
264
+ try {
265
+ response = await this.fetchImplementation(`${this.baseUrl}${path}`, {
266
+ ...init,
267
+ signal: AbortSignal.timeout(this.timeoutMilliseconds),
268
+ });
269
+ }
270
+ catch (error) {
271
+ throw new TransportError(error);
272
+ }
273
+ if (!isSuccess(response.status)) {
274
+ throw serverErrorFromBody(response.status, await collectBoundedBody(response));
275
+ }
276
+ try {
277
+ return await response.text();
278
+ }
279
+ catch (error) {
280
+ throw new TransportError(error);
281
+ }
282
+ }
283
+ getHeaders() {
284
+ if (this.apiKey.length === 0) {
285
+ throw new MissingAPIKeyError();
286
+ }
287
+ return { Authorization: `Bearer ${this.apiKey}` };
288
+ }
289
+ postHeaders() {
290
+ return { ...this.getHeaders(), "Content-Type": "application/json" };
291
+ }
292
+ }
@@ -0,0 +1,155 @@
1
+ import { hostname } from "node:os";
2
+ import { DeviceAuthError, TransportError } from "./errors.js";
3
+ import * as wire from "./wire.js";
4
+ export const DEFAULT_AUTH_BASE_URL = "https://api.valni.app";
5
+ const DEVICE_AUTHORIZE_PATH = "/v1/device/authorize";
6
+ const DEVICE_TOKEN_PATH = "/v1/device/token";
7
+ const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
8
+ const REFRESH_TOKEN_GRANT = "refresh_token";
9
+ const MILLISECONDS_PER_SECOND = 1000;
10
+ const SLOW_DOWN_EXTRA_SECONDS = 5;
11
+ const ERROR_AUTHORIZATION_PENDING = "authorization_pending";
12
+ const ERROR_SLOW_DOWN = "slow_down";
13
+ const ERROR_ACCESS_DENIED = "access_denied";
14
+ const ERROR_EXPIRED_TOKEN = "expired_token";
15
+ function resolveFetch(options) {
16
+ return options.fetch ?? globalThis.fetch.bind(globalThis);
17
+ }
18
+ function resolveAuthBaseUrl(options) {
19
+ return (options.authBaseUrl ?? DEFAULT_AUTH_BASE_URL).replace(/\/+$/, "");
20
+ }
21
+ function sleep(milliseconds, signal) {
22
+ return new Promise((resolve, reject) => {
23
+ if (signal?.aborted) {
24
+ reject(new DeviceAuthError("cancelled", "Sign-in cancelled"));
25
+ return;
26
+ }
27
+ const timer = setTimeout(() => {
28
+ signal?.removeEventListener("abort", onAbort);
29
+ resolve();
30
+ }, milliseconds);
31
+ function onAbort() {
32
+ clearTimeout(timer);
33
+ reject(new DeviceAuthError("cancelled", "Sign-in cancelled"));
34
+ }
35
+ signal?.addEventListener("abort", onAbort, { once: true });
36
+ });
37
+ }
38
+ async function postJson(fetchImplementation, url, body) {
39
+ try {
40
+ return await fetchImplementation(url, {
41
+ method: "POST",
42
+ headers: { "Content-Type": "application/json" },
43
+ body: JSON.stringify(body),
44
+ });
45
+ }
46
+ catch (error) {
47
+ throw new TransportError(error);
48
+ }
49
+ }
50
+ async function decodeTokenResponse(response) {
51
+ let parsed;
52
+ try {
53
+ parsed = await response.json();
54
+ }
55
+ catch {
56
+ throw new DeviceAuthError("protocol", `Switchboard sign-in returned an unreadable token response (HTTP ${response.status})`);
57
+ }
58
+ const fields = wire.mapping(parsed, "device_token");
59
+ return {
60
+ accessToken: wire.optionalString(fields, "access_token", "device_token"),
61
+ refreshToken: wire.optionalString(fields, "refresh_token", "device_token"),
62
+ expiresInSeconds: wire.optionalInteger(fields, "expires_in", "device_token"),
63
+ endUserId: wire.optionalString(fields, "end_user_id", "device_token"),
64
+ errorCode: wire.optionalString(fields, "error", "device_token"),
65
+ };
66
+ }
67
+ function toCredentials(token) {
68
+ if (token.accessToken === null ||
69
+ token.refreshToken === null ||
70
+ token.expiresInSeconds === null ||
71
+ token.endUserId === null) {
72
+ throw new DeviceAuthError("incomplete_token", "Switchboard sign-in returned an incomplete token response");
73
+ }
74
+ return {
75
+ access: token.accessToken,
76
+ refresh: token.refreshToken,
77
+ expires: Date.now() + token.expiresInSeconds * MILLISECONDS_PER_SECOND,
78
+ endUserId: token.endUserId,
79
+ };
80
+ }
81
+ export async function deviceLogin(callbacks, options) {
82
+ const fetchImplementation = resolveFetch(options);
83
+ const authBaseUrl = resolveAuthBaseUrl(options);
84
+ const authorizeResponse = await postJson(fetchImplementation, `${authBaseUrl}${DEVICE_AUTHORIZE_PATH}`, {
85
+ client_id: options.clientId,
86
+ device_label: options.deviceLabel ?? hostname(),
87
+ });
88
+ if (!authorizeResponse.ok) {
89
+ throw new DeviceAuthError("protocol", `Switchboard device authorization failed (HTTP ${authorizeResponse.status})`);
90
+ }
91
+ let authorizationWire;
92
+ try {
93
+ authorizationWire = await authorizeResponse.json();
94
+ }
95
+ catch {
96
+ throw new DeviceAuthError("protocol", "Switchboard device authorization returned an unreadable response");
97
+ }
98
+ const fields = wire.mapping(authorizationWire, "device_authorization");
99
+ const deviceCode = wire.string(fields, "device_code", "device_authorization");
100
+ const userCode = wire.string(fields, "user_code", "device_authorization");
101
+ const verificationUriComplete = wire.string(fields, "verification_uri_complete", "device_authorization");
102
+ const expiresInSeconds = wire.integer(fields, "expires_in", "device_authorization");
103
+ const intervalSeconds = wire.integer(fields, "interval", "device_authorization");
104
+ callbacks.onDeviceCode({
105
+ userCode,
106
+ verificationUri: verificationUriComplete,
107
+ intervalSeconds,
108
+ expiresInSeconds,
109
+ });
110
+ const deadline = Date.now() + expiresInSeconds * MILLISECONDS_PER_SECOND;
111
+ let pollIntervalSeconds = intervalSeconds;
112
+ while (true) {
113
+ await sleep(pollIntervalSeconds * MILLISECONDS_PER_SECOND, callbacks.signal);
114
+ if (Date.now() > deadline) {
115
+ throw new DeviceAuthError("expired_token", "The sign-in code expired. Start the sign-in again.");
116
+ }
117
+ const pollResponse = await postJson(fetchImplementation, `${authBaseUrl}${DEVICE_TOKEN_PATH}`, {
118
+ grant_type: DEVICE_CODE_GRANT,
119
+ device_code: deviceCode,
120
+ client_id: options.clientId,
121
+ });
122
+ const token = await decodeTokenResponse(pollResponse);
123
+ if (token.accessToken !== null) {
124
+ callbacks.onProgress?.("Signed in to Switchboard");
125
+ return toCredentials(token);
126
+ }
127
+ if (token.errorCode === ERROR_AUTHORIZATION_PENDING)
128
+ continue;
129
+ if (token.errorCode === ERROR_SLOW_DOWN) {
130
+ pollIntervalSeconds += SLOW_DOWN_EXTRA_SECONDS;
131
+ continue;
132
+ }
133
+ if (token.errorCode === ERROR_ACCESS_DENIED) {
134
+ throw new DeviceAuthError("access_denied", "Sign-in was denied in the browser.");
135
+ }
136
+ if (token.errorCode === ERROR_EXPIRED_TOKEN) {
137
+ throw new DeviceAuthError("expired_token", "The sign-in code expired. Start the sign-in again.");
138
+ }
139
+ throw new DeviceAuthError("protocol", `Switchboard sign-in failed: ${token.errorCode ?? pollResponse.status}`);
140
+ }
141
+ }
142
+ export async function deviceRefresh(credentials, options) {
143
+ const fetchImplementation = resolveFetch(options);
144
+ const authBaseUrl = resolveAuthBaseUrl(options);
145
+ const response = await postJson(fetchImplementation, `${authBaseUrl}${DEVICE_TOKEN_PATH}`, {
146
+ grant_type: REFRESH_TOKEN_GRANT,
147
+ refresh_token: credentials.refresh,
148
+ client_id: options.clientId,
149
+ });
150
+ const token = await decodeTokenResponse(response);
151
+ if (token.accessToken === null) {
152
+ throw new DeviceAuthError("expired_token", "Switchboard session expired. Sign in again.");
153
+ }
154
+ return toCredentials(token);
155
+ }