@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.
- package/LICENSE +53 -0
- package/package.json +18 -5
- package/runtime/LICENSE +53 -0
- package/runtime/README.md +674 -0
- package/runtime/assets/clankolas.png +0 -0
- package/runtime/docs/extensions.md +2910 -0
- package/runtime/docs/json.md +82 -0
- package/runtime/docs/models.md +540 -0
- package/runtime/docs/packages.md +228 -0
- package/runtime/docs/rpc.md +1480 -0
- package/runtime/docs/sdk.md +1183 -0
- package/runtime/docs/session-format.md +422 -0
- package/runtime/docs/settings.md +315 -0
- package/runtime/docs/shell-aliases.md +13 -0
- package/runtime/docs/terminal-setup.md +142 -0
- package/runtime/docs/termux.md +127 -0
- package/runtime/docs/tmux.md +63 -0
- package/runtime/docs/tui.md +927 -0
- package/runtime/docs/windows.md +17 -0
- package/runtime/examples/README.md +25 -0
- package/runtime/examples/extensions/README.md +212 -0
- package/runtime/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/runtime/examples/extensions/bash-spawn-hook.ts +30 -0
- package/runtime/examples/extensions/bookmark.ts +50 -0
- package/runtime/examples/extensions/border-status-editor.ts +145 -0
- package/runtime/examples/extensions/built-in-tool-renderer.ts +249 -0
- package/runtime/examples/extensions/claude-rules.ts +86 -0
- package/runtime/examples/extensions/commands.ts +72 -0
- package/runtime/examples/extensions/confirm-destructive.ts +59 -0
- package/runtime/examples/extensions/custom-footer.ts +64 -0
- package/runtime/examples/extensions/custom-header.ts +73 -0
- package/runtime/examples/extensions/custom-provider-anthropic/index.ts +604 -0
- package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/runtime/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/runtime/examples/extensions/dirty-repo-guard.ts +56 -0
- package/runtime/examples/extensions/doom-overlay/README.md +46 -0
- package/runtime/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/runtime/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/runtime/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/runtime/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/runtime/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/runtime/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/runtime/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/runtime/examples/extensions/doom-overlay/index.ts +74 -0
- package/runtime/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/runtime/examples/extensions/dynamic-resources/SKILL.md +8 -0
- package/runtime/examples/extensions/dynamic-resources/dynamic.json +79 -0
- package/runtime/examples/extensions/dynamic-resources/dynamic.md +5 -0
- package/runtime/examples/extensions/dynamic-resources/index.ts +15 -0
- package/runtime/examples/extensions/dynamic-tools.ts +74 -0
- package/runtime/examples/extensions/entry-renderer.ts +41 -0
- package/runtime/examples/extensions/event-bus.ts +43 -0
- package/runtime/examples/extensions/file-trigger.ts +41 -0
- package/runtime/examples/extensions/git-checkpoint.ts +53 -0
- package/runtime/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/runtime/examples/extensions/github-issue-autocomplete.ts +185 -0
- package/runtime/examples/extensions/gondolin/index.ts +531 -0
- package/runtime/examples/extensions/gondolin/package-lock.json +185 -0
- package/runtime/examples/extensions/gondolin/package.json +19 -0
- package/runtime/examples/extensions/hello.ts +26 -0
- package/runtime/examples/extensions/hidden-thinking-label.ts +53 -0
- package/runtime/examples/extensions/inline-bash.ts +94 -0
- package/runtime/examples/extensions/input-transform-streaming.ts +39 -0
- package/runtime/examples/extensions/input-transform.ts +43 -0
- package/runtime/examples/extensions/interactive-shell.ts +196 -0
- package/runtime/examples/extensions/mac-system-theme.ts +47 -0
- package/runtime/examples/extensions/message-renderer.ts +59 -0
- package/runtime/examples/extensions/minimal-mode.ts +426 -0
- package/runtime/examples/extensions/modal-editor.ts +85 -0
- package/runtime/examples/extensions/model-status.ts +31 -0
- package/runtime/examples/extensions/notify.ts +55 -0
- package/runtime/examples/extensions/overlay-qa-tests.ts +1450 -0
- package/runtime/examples/extensions/overlay-test.ts +153 -0
- package/runtime/examples/extensions/permission-gate.ts +34 -0
- package/runtime/examples/extensions/pirate.ts +47 -0
- package/runtime/examples/extensions/plan-mode/README.md +66 -0
- package/runtime/examples/extensions/plan-mode/index.ts +390 -0
- package/runtime/examples/extensions/plan-mode/utils.ts +168 -0
- package/runtime/examples/extensions/preset.ts +436 -0
- package/runtime/examples/extensions/project-trust.ts +64 -0
- package/runtime/examples/extensions/prompt-customizer.ts +97 -0
- package/runtime/examples/extensions/protected-paths.ts +30 -0
- package/runtime/examples/extensions/provider-payload.ts +18 -0
- package/runtime/examples/extensions/question.ts +278 -0
- package/runtime/examples/extensions/questionnaire.ts +440 -0
- package/runtime/examples/extensions/rainbow-editor.ts +88 -0
- package/runtime/examples/extensions/reload-runtime.ts +37 -0
- package/runtime/examples/extensions/rpc-demo.ts +118 -0
- package/runtime/examples/extensions/sandbox/index.ts +321 -0
- package/runtime/examples/extensions/sandbox/package-lock.json +92 -0
- package/runtime/examples/extensions/sandbox/package.json +19 -0
- package/runtime/examples/extensions/send-user-message.ts +97 -0
- package/runtime/examples/extensions/session-name.ts +27 -0
- package/runtime/examples/extensions/shutdown-command.ts +63 -0
- package/runtime/examples/extensions/snake.ts +343 -0
- package/runtime/examples/extensions/space-invaders.ts +560 -0
- package/runtime/examples/extensions/ssh.ts +220 -0
- package/runtime/examples/extensions/status-line.ts +32 -0
- package/runtime/examples/extensions/structured-output.ts +65 -0
- package/runtime/examples/extensions/subagent/README.md +175 -0
- package/runtime/examples/extensions/subagent/agents/planner.md +37 -0
- package/runtime/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/runtime/examples/extensions/subagent/agents/scout.md +50 -0
- package/runtime/examples/extensions/subagent/agents/worker.md +24 -0
- package/runtime/examples/extensions/subagent/agents.ts +126 -0
- package/runtime/examples/extensions/subagent/index.ts +1009 -0
- package/runtime/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/runtime/examples/extensions/subagent/prompts/implement.md +10 -0
- package/runtime/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/runtime/examples/extensions/system-prompt-header.ts +17 -0
- package/runtime/examples/extensions/tic-tac-toe.ts +1008 -0
- package/runtime/examples/extensions/timed-confirm.ts +70 -0
- package/runtime/examples/extensions/titlebar-spinner.ts +58 -0
- package/runtime/examples/extensions/todo.ts +297 -0
- package/runtime/examples/extensions/tool-override.ts +144 -0
- package/runtime/examples/extensions/tools.ts +146 -0
- package/runtime/examples/extensions/trigger-compact.ts +50 -0
- package/runtime/examples/extensions/truncated-tool.ts +195 -0
- package/runtime/examples/extensions/widget-placement.ts +9 -0
- package/runtime/examples/extensions/with-deps/index.ts +32 -0
- package/runtime/examples/extensions/with-deps/package-lock.json +31 -0
- package/runtime/examples/extensions/with-deps/package.json +22 -0
- package/runtime/examples/extensions/working-indicator.ts +123 -0
- package/runtime/examples/extensions/working-message-test.ts +25 -0
- package/runtime/examples/rpc-extension-ui.ts +632 -0
- package/runtime/examples/sdk/01-minimal.ts +26 -0
- package/runtime/examples/sdk/02-custom-model.ts +43 -0
- package/runtime/examples/sdk/03-custom-prompt.ts +70 -0
- package/runtime/examples/sdk/04-skills.ts +55 -0
- package/runtime/examples/sdk/05-tools.ts +48 -0
- package/runtime/examples/sdk/06-extensions.ts +94 -0
- package/runtime/examples/sdk/07-context-files.ts +42 -0
- package/runtime/examples/sdk/08-prompt-templates.ts +51 -0
- package/runtime/examples/sdk/09-api-keys-and-oauth.ts +34 -0
- package/runtime/examples/sdk/10-settings.ts +53 -0
- package/runtime/examples/sdk/11-sessions.ts +52 -0
- package/runtime/examples/sdk/12-full-control.ts +71 -0
- package/runtime/examples/sdk/13-session-runtime.ts +67 -0
- package/runtime/examples/sdk/README.md +140 -0
- package/runtime/export-html/ansi-to-html.js +249 -0
- package/runtime/export-html/index.js +226 -0
- package/runtime/export-html/template.css +1066 -0
- package/runtime/export-html/template.html +55 -0
- package/runtime/export-html/template.js +1864 -0
- package/runtime/export-html/tool-renderer.js +108 -0
- package/runtime/export-html/vendor/highlight.min.js +1213 -0
- package/runtime/export-html/vendor/marked.min.js +78 -0
- package/runtime/extensions/pi-switchboard/.github/workflows/ci.yml +51 -0
- package/runtime/extensions/pi-switchboard/LICENSE +22 -0
- package/runtime/extensions/pi-switchboard/README.md +85 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/catalog.ts +183 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/config.ts +97 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/connections.ts +386 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/constants.ts +12 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/device-auth.ts +127 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/envelope.ts +199 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/errors.ts +180 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/index.ts +125 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/participant.ts +17 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/sessionStream.ts +131 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/toolProxy.ts +136 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/tools.ts +16 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/types.ts +37 -0
- package/runtime/extensions/pi-switchboard/package.json +30 -0
- package/runtime/extensions/pi-switchboard/tsconfig.json +13 -0
- package/runtime/extensions/switchboard-local/.github/workflows/ci.yml +80 -0
- package/runtime/extensions/switchboard-local/.github/workflows/release.yml +46 -0
- package/runtime/extensions/switchboard-local/README.md +98 -0
- package/runtime/extensions/switchboard-local/extensions/local/engine.ts +28 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/gguf.ts +324 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/llamaBuild.ts +62 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/mlx.ts +245 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/select.ts +38 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/support.ts +151 -0
- package/runtime/extensions/switchboard-local/extensions/local/hf.ts +106 -0
- package/runtime/extensions/switchboard-local/extensions/local/index.ts +67 -0
- package/runtime/extensions/switchboard-local/extensions/local/nativeStream.ts +602 -0
- package/runtime/extensions/switchboard-local/extensions/local/paths.ts +44 -0
- package/runtime/extensions/switchboard-local/extensions/local/provider.ts +57 -0
- package/runtime/extensions/switchboard-local/extensions/local/store.ts +82 -0
- package/runtime/extensions/switchboard-local/extensions/local/ui.ts +60 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/apiTypes.js +74 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/client.js +292 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/deviceAuth.js +155 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/errors.js +95 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/index.js +8 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/messages.js +253 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/models.js +19 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/native.js +886 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/wire.js +85 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/package.json +39 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/package.json +29 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/anthropic.ts +127 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/google.ts +111 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/index.ts +8 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.test.ts +25 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.ts +57 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiGeneric.ts +103 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiPro.ts +89 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/profile.ts +120 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.test.ts +82 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.ts +74 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.google.test.ts +137 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.openai.test.ts +241 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.test.ts +174 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.ts +438 -0
- package/runtime/extensions/switchboard-local/package.json +34 -0
- package/runtime/extensions/switchboard-local/tsconfig.json +13 -0
- package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/runtime/node_modules/@mariozechner/clipboard/.yarnrc.yml +1 -0
- package/runtime/node_modules/@mariozechner/clipboard/Cargo.toml +45 -0
- package/runtime/node_modules/@mariozechner/clipboard/README.md +60 -0
- package/runtime/node_modules/@mariozechner/clipboard/build.rs +5 -0
- package/runtime/node_modules/@mariozechner/clipboard/clipboard.darwin-x64.node +0 -0
- package/runtime/node_modules/@mariozechner/clipboard/exp.ts +12 -0
- package/runtime/node_modules/@mariozechner/clipboard/index.js +606 -0
- package/runtime/node_modules/@mariozechner/clipboard/package.json +56 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/common.rs +307 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/mod.rs +93 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/ios.rs +313 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/macos.rs +471 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/mod.rs +252 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/wayland.rs +431 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/win.rs +712 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/x11.rs +916 -0
- package/runtime/node_modules/@mariozechner/clipboard/src/lib.rs +194 -0
- package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/README.md +3 -0
- package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/clipboard.darwin-x64.node +0 -0
- package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/package.json +22 -0
- package/runtime/package.json +104 -0
- package/runtime/photon_rs_bg.wasm +0 -0
- package/runtime/theme/dark.json +87 -0
- package/runtime/theme/light.json +86 -0
- package/runtime/theme/theme-schema.json +340 -0
- package/runtime/valni +4 -0
- package/README.md +0 -3
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { resolveBaseUrl, resolveSessionId } from "./config.ts";
|
|
3
|
+
import { PROVIDER_ID, spawnedConnectionId } from "./constants.ts";
|
|
4
|
+
|
|
5
|
+
const MCP_PATH = "/mcp";
|
|
6
|
+
const DISCOVER_TIMEOUT_MS = 10_000;
|
|
7
|
+
const SESSION_HEADER = "X-Switchboard-Session";
|
|
8
|
+
const CONNECTION_TOOLS = new Set(["send_message", "close_automation_connection"]);
|
|
9
|
+
const CONNECTION_ID_PARAM = "connection_id";
|
|
10
|
+
|
|
11
|
+
function hasConnectionId(record: Record<string, unknown>): boolean {
|
|
12
|
+
const existing = record[CONNECTION_ID_PARAM];
|
|
13
|
+
return typeof existing === "string" && existing.trim() !== "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function withSpawnedConnectionId(name: string, params: unknown, connectionId: string | null): unknown {
|
|
17
|
+
if (!CONNECTION_TOOLS.has(name) || connectionId === null) return params;
|
|
18
|
+
if (params === null || typeof params !== "object" || Array.isArray(params)) return params;
|
|
19
|
+
const record = params as Record<string, unknown>;
|
|
20
|
+
if (hasConnectionId(record)) return params;
|
|
21
|
+
return { ...record, [CONNECTION_ID_PARAM]: connectionId };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AdvertisedTool {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
parameters: Record<string, unknown>;
|
|
28
|
+
version?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function discoverTools(baseUrl: string, bearer: string): Promise<AdvertisedTool[]> {
|
|
32
|
+
let response: Response;
|
|
33
|
+
try {
|
|
34
|
+
response = await fetch(`${baseUrl}${MCP_PATH}`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
|
37
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
|
|
38
|
+
signal: AbortSignal.timeout(DISCOVER_TIMEOUT_MS),
|
|
39
|
+
});
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
if (!response.ok) return [];
|
|
44
|
+
const body = (await response.json().catch(() => null)) as {
|
|
45
|
+
result?: { tools?: Array<{ name?: unknown; description?: unknown; inputSchema?: unknown }> };
|
|
46
|
+
} | null;
|
|
47
|
+
const tools = body?.result?.tools;
|
|
48
|
+
if (!Array.isArray(tools)) return [];
|
|
49
|
+
const advertised: AdvertisedTool[] = [];
|
|
50
|
+
for (const tool of tools) {
|
|
51
|
+
if (typeof tool.name !== "string" || tool.name.length === 0) continue;
|
|
52
|
+
advertised.push({
|
|
53
|
+
name: tool.name,
|
|
54
|
+
description: typeof tool.description === "string" ? tool.description : "",
|
|
55
|
+
parameters:
|
|
56
|
+
tool.inputSchema !== null && typeof tool.inputSchema === "object" && !Array.isArray(tool.inputSchema)
|
|
57
|
+
? (tool.inputSchema as Record<string, unknown>)
|
|
58
|
+
: {},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return advertised;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function textResult(text: string, details: unknown): AgentToolResult<unknown> {
|
|
65
|
+
return { content: [{ type: "text", text }], details };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function toAgentContent(content: unknown): AgentToolResult<unknown>["content"] {
|
|
69
|
+
if (!Array.isArray(content)) return [{ type: "text", text: JSON.stringify(content) }];
|
|
70
|
+
const items: AgentToolResult<unknown>["content"] = [];
|
|
71
|
+
for (const raw of content) {
|
|
72
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
73
|
+
const item = raw as { type?: unknown; text?: unknown; data?: unknown; mimeType?: unknown };
|
|
74
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
75
|
+
items.push({ type: "text", text: item.text });
|
|
76
|
+
} else if (item.type === "image" && typeof item.data === "string") {
|
|
77
|
+
items.push({ type: "image", data: item.data, mimeType: typeof item.mimeType === "string" ? item.mimeType : "image/png" });
|
|
78
|
+
} else {
|
|
79
|
+
items.push({ type: "text", text: JSON.stringify(raw) });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return items.length > 0 ? items : [{ type: "text", text: JSON.stringify(content) }];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function resolveInvokeToken(ctx: ExtensionContext): Promise<string | null> {
|
|
86
|
+
let resolution: Awaited<ReturnType<ExtensionContext["modelRegistry"]["getProviderAuth"]>>;
|
|
87
|
+
try {
|
|
88
|
+
resolution = await ctx.modelRegistry.getProviderAuth(PROVIDER_ID);
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return resolution?.auth.apiKey ?? null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function makeProxyExecute(name: string) {
|
|
96
|
+
return async (
|
|
97
|
+
_toolCallId: string,
|
|
98
|
+
params: unknown,
|
|
99
|
+
signal: AbortSignal | undefined,
|
|
100
|
+
_onUpdate: unknown,
|
|
101
|
+
ctx: ExtensionContext,
|
|
102
|
+
): Promise<AgentToolResult<unknown>> => {
|
|
103
|
+
const token = await resolveInvokeToken(ctx);
|
|
104
|
+
if (!token) return textResult(`Not signed in to Switchboard — cannot run ${name}. Run /login in pi, or set SWITCHBOARD_API_KEY for key-based use.`, null);
|
|
105
|
+
const headers: Record<string, string> = { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${token}` };
|
|
106
|
+
const sessionId = resolveSessionId();
|
|
107
|
+
if (sessionId !== null) headers[SESSION_HEADER] = sessionId;
|
|
108
|
+
|
|
109
|
+
const args = withSpawnedConnectionId(name, params, spawnedConnectionId());
|
|
110
|
+
let response: Response;
|
|
111
|
+
try {
|
|
112
|
+
response = await fetch(`${resolveBaseUrl()}${MCP_PATH}`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers,
|
|
115
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name, arguments: args } }),
|
|
116
|
+
signal,
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return textResult(`Could not reach Switchboard to run ${name}: ${String(error)}`, null);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const envelope = (await response.json().catch(() => null)) as {
|
|
123
|
+
result?: { content?: unknown; isError?: boolean };
|
|
124
|
+
error?: { message?: unknown; code?: unknown };
|
|
125
|
+
} | null;
|
|
126
|
+
if (envelope?.error) {
|
|
127
|
+
const message = typeof envelope.error.message === "string" ? envelope.error.message : `Tool ${name} failed.`;
|
|
128
|
+
const code = typeof envelope.error.code === "number" || typeof envelope.error.code === "string" ? ` [${envelope.error.code}]` : "";
|
|
129
|
+
return textResult(`${message}${code}`, envelope);
|
|
130
|
+
}
|
|
131
|
+
if (!response.ok || !envelope?.result) {
|
|
132
|
+
return textResult(`Tool ${name} failed (HTTP ${response.status}).`, envelope);
|
|
133
|
+
}
|
|
134
|
+
return { content: toAgentContent(envelope.result.content), details: envelope.result };
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { type AdvertisedTool, makeProxyExecute } from "./toolProxy.ts";
|
|
4
|
+
|
|
5
|
+
export function registerServerTools(pi: ExtensionAPI, tools: AdvertisedTool[]): void {
|
|
6
|
+
for (const tool of tools) {
|
|
7
|
+
if (!tool.name) continue;
|
|
8
|
+
pi.registerTool({
|
|
9
|
+
name: tool.name,
|
|
10
|
+
label: tool.name,
|
|
11
|
+
description: tool.description,
|
|
12
|
+
parameters: Type.Unsafe(tool.parameters),
|
|
13
|
+
execute: makeProxyExecute(tool.name),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
3
|
+
export const KIND_TO_API = {
|
|
4
|
+
anthropic: "anthropic-messages",
|
|
5
|
+
openai_generic: "openai-completions",
|
|
6
|
+
openai_pro: "openai-responses",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type SwitchboardKind = keyof typeof KIND_TO_API;
|
|
10
|
+
|
|
11
|
+
export interface KindProfile {
|
|
12
|
+
model: string;
|
|
13
|
+
maxTokensCeiling?: number;
|
|
14
|
+
vision?: boolean;
|
|
15
|
+
thinking?: { modes: string[] } | false;
|
|
16
|
+
reasoningEffort?: string[] | false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ModelRecord {
|
|
20
|
+
id: string;
|
|
21
|
+
kind: Record<string, KindProfile>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ModelRecordPrice {
|
|
25
|
+
input_micro_cents_per_mtok: number;
|
|
26
|
+
output_micro_cents_per_mtok: number;
|
|
27
|
+
cached_input_micro_cents_per_mtok: number | null;
|
|
28
|
+
effective_at: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ModelsPage {
|
|
32
|
+
models: ModelRecord[];
|
|
33
|
+
prices: Record<string, ModelRecordPrice>;
|
|
34
|
+
context_windows?: Record<string, number>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RegistryModel = Model<(typeof KIND_TO_API)[SwitchboardKind]>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-switchboard",
|
|
3
|
+
"version": "0.11.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Route the pi coding agent through Switchboard: one prepaid balance, spend controls, and a ledger for every model in the catalog",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package"
|
|
8
|
+
],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/Valni-Labs/pi-switchboard"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22.19.0"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "npm run typecheck && node --test \"extensions/switchboard/tests/*.test.ts\"",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./extensions"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@earendil-works/pi-ai": "0.82.1",
|
|
27
|
+
"@earendil-works/pi-coding-agent": "0.82.1",
|
|
28
|
+
"typescript": "5.9.3"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2023",
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
|
+
"allowImportingTsExtensions": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["extensions/**/*.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
typecheck-test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
persist-credentials: false
|
|
21
|
+
|
|
22
|
+
- name: Setup Node.js
|
|
23
|
+
uses: actions/setup-node@v4
|
|
24
|
+
with:
|
|
25
|
+
node-version: 22
|
|
26
|
+
cache: npm
|
|
27
|
+
|
|
28
|
+
- name: Verify dependency token
|
|
29
|
+
env:
|
|
30
|
+
TOKEN: ${{ secrets.VALNI_REPO_READ_TOKEN }}
|
|
31
|
+
run: |
|
|
32
|
+
if [ -z "$TOKEN" ]; then
|
|
33
|
+
echo "::error::VALNI_REPO_READ_TOKEN is empty or not in scope for this repository"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
# Both are pulled by the lockfile, and a fine-grained token can hold one without the other.
|
|
37
|
+
for repo in switchboard-ts SwitchboardNative; do
|
|
38
|
+
if ! git ls-remote "https://oauth2:${TOKEN}@github.com/Valni-Labs/${repo}.git" HEAD > /dev/null 2>&1; then
|
|
39
|
+
echo "::error::VALNI_REPO_READ_TOKEN cannot read Valni-Labs/${repo}"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
- name: Install dependencies
|
|
45
|
+
env:
|
|
46
|
+
TOKEN: ${{ secrets.VALNI_REPO_READ_TOKEN }}
|
|
47
|
+
run: |
|
|
48
|
+
# Keep the credential in a throwaway config for this step only. Later steps run
|
|
49
|
+
# code from the pull request, which must never be able to read the token.
|
|
50
|
+
CONFIG="$(mktemp)"
|
|
51
|
+
export GIT_CONFIG_GLOBAL="$CONFIG"
|
|
52
|
+
trap 'rm -f "$CONFIG"' EXIT
|
|
53
|
+
git config --global url."https://oauth2:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/"
|
|
54
|
+
git config --global --add url."https://oauth2:${TOKEN}@github.com/".insteadOf "git@github.com:"
|
|
55
|
+
npm ci --ignore-scripts
|
|
56
|
+
|
|
57
|
+
- name: Confirm no credential survived the install
|
|
58
|
+
run: |
|
|
59
|
+
if [ -f "$HOME/.gitconfig" ] && grep -q "oauth2:" "$HOME/.gitconfig"; then
|
|
60
|
+
echo "::error::a token was left in $HOME/.gitconfig"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
- name: Typecheck and test
|
|
65
|
+
run: npm test
|
|
66
|
+
|
|
67
|
+
- name: Confirm the extension loads in pi
|
|
68
|
+
run: |
|
|
69
|
+
npx pi --no-extensions --extension ./extensions/local/index.ts --list-models 2> >(tee /tmp/pi-stderr.txt >&2)
|
|
70
|
+
# The version line is the reliable signal. Stock pi swallows extension load
|
|
71
|
+
# errors on --list-models, so its absence is how a broken extension shows up.
|
|
72
|
+
if ! grep -q "switchboard-local v" /tmp/pi-stderr.txt; then
|
|
73
|
+
echo "::error::the extension never ran; it printed no version line"
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
# Hosts that do report load failures, such as valni-cli, are caught here too.
|
|
77
|
+
if grep -q "Failed to load extension" /tmp/pi-stderr.txt; then
|
|
78
|
+
echo "::error::the extension failed to load in pi"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
release:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
|
|
17
|
+
- name: Read the version
|
|
18
|
+
id: version
|
|
19
|
+
run: echo "tag=v$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
|
|
20
|
+
|
|
21
|
+
- name: Refuse to release a tag that already exists
|
|
22
|
+
run: |
|
|
23
|
+
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
|
|
24
|
+
echo "::error::${{ steps.version.outputs.tag }} already exists. Bump the version in package.json first."
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
- name: Refuse to release a commit CI has not passed
|
|
29
|
+
env:
|
|
30
|
+
GH_TOKEN: ${{ github.token }}
|
|
31
|
+
run: |
|
|
32
|
+
STATE="$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/check-runs" \
|
|
33
|
+
--jq '[.check_runs[] | select(.name == "typecheck-test") | .conclusion] | first // "missing"')"
|
|
34
|
+
if [ "$STATE" != "success" ]; then
|
|
35
|
+
echo "::error::CI on ${{ github.sha }} is '${STATE}', not success"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
- name: Tag and publish the release
|
|
40
|
+
env:
|
|
41
|
+
GH_TOKEN: ${{ github.token }}
|
|
42
|
+
run: |
|
|
43
|
+
gh release create "${{ steps.version.outputs.tag }}" \
|
|
44
|
+
--target "${{ github.sha }}" \
|
|
45
|
+
--title "${{ steps.version.outputs.tag }}" \
|
|
46
|
+
--generate-notes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# switchboard-local
|
|
2
|
+
|
|
3
|
+
Local models in the pi model picker. You paste a Hugging Face link, it downloads, and the model
|
|
4
|
+
appears in `/model` at $0. No account, no network at inference time.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
pi install https://github.com/Valni-Labs/switchboard-local
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
That follows `main`. To pin to a release instead:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
pi install https://github.com/Valni-Labs/switchboard-local@v0.2.0
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Use it
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
/switchboard-local
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Pick **Add a model…** and paste a link or a bare `owner/repository`. Or skip the menu:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
/switchboard-local huggingface.co/mlx-community/Llama-3.2-1B-Instruct-4bit
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The first model you add also installs the engine. That happens once and takes a few minutes.
|
|
29
|
+
Afterwards `/model` lists every local model you have, alongside your cloud models.
|
|
30
|
+
|
|
31
|
+
## What it installs
|
|
32
|
+
|
|
33
|
+
Everything it manages lives in `~/.switchboard-local/`, outside the package, so `pi update` never
|
|
34
|
+
wipes it.
|
|
35
|
+
|
|
36
|
+
| Path | What |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `~/.switchboard-local/mlx-venv` | Python environment plus `mlx-lm` |
|
|
39
|
+
| `~/.switchboard-local/llama` | llama.cpp build and GGUF weights |
|
|
40
|
+
| `~/.switchboard-local/state.json` | Ports, engine process ids, llama.cpp build |
|
|
41
|
+
|
|
42
|
+
MLX weights go to the standard Hugging Face cache (`~/.cache/huggingface/hub`), so anything you
|
|
43
|
+
already downloaded is picked up automatically.
|
|
44
|
+
|
|
45
|
+
## Engines
|
|
46
|
+
|
|
47
|
+
Two engines, picked for you. Paste any model and the right one runs it.
|
|
48
|
+
|
|
49
|
+
| Engine | Format | Machines | Acceleration |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| MLX | safetensors, such as `mlx-community/*` | Apple Silicon | Metal |
|
|
52
|
+
| GGUF | `.gguf`, such as `unsloth/*-GGUF` | macOS, Windows, Linux | Metal, Vulkan, CPU |
|
|
53
|
+
|
|
54
|
+
MLX exists only on Apple Silicon, so GGUF is what covers Windows and Linux. The extension reads
|
|
55
|
+
the repository and routes to whichever engine can run it, and says so plainly when neither can.
|
|
56
|
+
|
|
57
|
+
For GGUF you may name a quantization, or leave it out and get `Q4_K_M` when it exists, else the
|
|
58
|
+
smallest:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
/switchboard-local unsloth/Qwen3-4B-Instruct-2507-GGUF:Q4_K_M
|
|
62
|
+
/switchboard-local unsloth/Qwen3-4B-Instruct-2507-GGUF
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The llama.cpp build is downloaded for you. On Windows and Linux it uses the Vulkan build, which
|
|
66
|
+
gives GPU speed on NVIDIA, AMD and Intel without a CUDA install, and falls back to CPU if Vulkan
|
|
67
|
+
will not start.
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
pi is Node and cannot load a model into itself, so each engine runs as a child process bound to
|
|
72
|
+
`127.0.0.1`, and every downloaded model is registered against the engine that holds it. pi's own
|
|
73
|
+
OpenAI client does the talking, which is why streaming, native tool calls and token counts all
|
|
74
|
+
work without any protocol code here.
|
|
75
|
+
|
|
76
|
+
Nothing is sent anywhere. The only network traffic is downloading the model from Hugging Face.
|
|
77
|
+
|
|
78
|
+
- Models cost `$0.00` and are listed straight from the engine, so the picker always matches disk.
|
|
79
|
+
- The provider declares a local credential, so local models need no login and work signed out.
|
|
80
|
+
- Engines start on demand and stop when pi exits, including runs with no session. Each runs in its
|
|
81
|
+
own process group, so a stop takes its workers with it and never holds pi open.
|
|
82
|
+
- Several pi sessions share one engine rather than loading a model twice. The engine is stopped by
|
|
83
|
+
the last session using it, so quitting one window never pulls a model out from under another.
|
|
84
|
+
|
|
85
|
+
## Not yet
|
|
86
|
+
|
|
87
|
+
- `$0` usage events into the Switchboard ledger. Inference would still stay on the machine; only
|
|
88
|
+
the token count would be reported afterwards.
|
|
89
|
+
- Reasoning models are listed without a thinking level, so pi offers no thinking-level selector.
|
|
90
|
+
|
|
91
|
+
## Develop
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
npm test
|
|
95
|
+
pi -ne -e ./extensions/local/index.ts --list-models
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`-ne` disables other extensions so you are testing this one alone.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface Progress {
|
|
2
|
+
label: string;
|
|
3
|
+
fraction?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface LocalModelInfo {
|
|
7
|
+
id: string;
|
|
8
|
+
engine: LocalEngineId;
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
contextWindow: number;
|
|
11
|
+
vision?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type LocalEngineId = "mlx" | "gguf";
|
|
15
|
+
|
|
16
|
+
export interface LocalEngine {
|
|
17
|
+
readonly id: LocalEngineId;
|
|
18
|
+
readonly label: string;
|
|
19
|
+
supported(): boolean;
|
|
20
|
+
unsupportedReason(): string;
|
|
21
|
+
isInstalled(): boolean;
|
|
22
|
+
isRunning(): boolean;
|
|
23
|
+
ensureReady(onProgress: (progress: Progress) => void, signal?: AbortSignal): Promise<void>;
|
|
24
|
+
baseUrl(): string;
|
|
25
|
+
addModel(repoId: string, onProgress: (progress: Progress) => void, signal?: AbortSignal): Promise<void>;
|
|
26
|
+
listModels(signal?: AbortSignal): Promise<LocalModelInfo[]>;
|
|
27
|
+
stop(): Promise<void>;
|
|
28
|
+
}
|