From 214b8da6c6101ad704a32088cb5ddb6bf0e2d4bb Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Tue, 16 Jun 2026 16:54:18 -0400 Subject: [PATCH] fix(vscode): allow agent-only switch links --- .changeset/switch-model-agent-links.md | 2 +- .../code-with-ai/agents/model-selection.md | 31 ++++++++++++------- packages/kilo-vscode/src/KiloProvider.ts | 7 +++-- .../src/kilo-provider/switch-link.ts | 8 ++--- .../tests/unit/switch-link.test.ts | 17 ++++++---- .../webview-ui/src/context/session.tsx | 15 ++++----- .../src/types/messages/extension-messages.ts | 2 +- 7 files changed, 49 insertions(+), 33 deletions(-) diff --git a/.changeset/switch-model-agent-links.md b/.changeset/switch-model-agent-links.md index 3ce3b3f770f..0aad2e12d12 100644 --- a/.changeset/switch-model-agent-links.md +++ b/.changeset/switch-model-agent-links.md @@ -2,4 +2,4 @@ "kilo-code": minor --- -Support VS Code switch links that select a Kilo model and optional agent. +Support VS Code switch links that select a Kilo model, agent, or both. diff --git a/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md b/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md index 971b74257c5..5e070441d24 100644 --- a/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md +++ b/packages/kilo-docs/pages/code-with-ai/agents/model-selection.md @@ -142,19 +142,21 @@ The model selection is remembered per mode across sessions. For details on configuring subagent models, see [Custom Subagents](/docs/customize/custom-subagents). -## Selecting a Model via a Link (VS Code) +## Selecting a Model or Agent via a Link (VS Code) -The VS Code extension supports a `vscode://` protocol handler that lets you open VS Code and automatically select a specific model — no manual picker interaction required. This is useful for sharing model recommendations, launching a specific model tier from a web page, or switching quickly to a newly announced model. +The VS Code extension supports a `vscode://` protocol handler that lets you open VS Code and automatically select a model, an agent, or both — no manual picker interaction required. This is useful for sharing model recommendations, launching a specific model tier from a web page, or switching quickly to a preferred agent. ### URL Format +Include at least one of the `model` or `agent` parameters: + ``` -vscode://kilocode.kilo-code/kilocode/switch?model=[&agent=] +vscode://kilocode.kilo-code/kilocode/switch?model= +vscode://kilocode.kilo-code/kilocode/switch?agent= +vscode://kilocode.kilo-code/kilocode/switch?model=&agent= ``` -Replace `` with the Kilo Gateway model ID you want to select (e.g. `kilo-auto/free`). Only models available in the Kilo Gateway catalog are accepted; the link is silently ignored if the model ID is not found. - -The optional `agent` parameter switches to a visible primary agent before selecting the model. Use the agent's ID, such as `code` or `plan`, rather than its display name. `mode` is also accepted as an alias for `agent`. +Replace `` with a Kilo Gateway model ID such as `kilo-auto/free`. Replace `` with a visible primary agent ID such as `code` or `plan`, rather than its display name. `mode` is also accepted as an alias for `agent`. ### Example: Auto Free @@ -164,7 +166,13 @@ To open Kilo Code and switch to the [Auto Free](/docs/code-with-ai/agents/auto-m vscode://kilocode.kilo-code/kilocode/switch?model=kilo-auto%2Ffree ``` -To switch to Plan at the same time, add the optional agent: +To switch only to Plan and use its normal model selection, specify the agent without a model: + +``` +vscode://kilocode.kilo-code/kilocode/switch?agent=plan +``` + +To select both at the same time, include both parameters: ``` vscode://kilocode.kilo-code/kilocode/switch?model=kilo-auto%2Ffree&agent=plan @@ -176,11 +184,12 @@ URL-encode the `/` in model IDs as `%2F` when embedding this URL in HTML links o ### How It Works -- **VS Code open**: the Kilo sidebar is focused and the model is selected in the active session immediately. -- **VS Code closed**: VS Code launches, then applies the model selection once the extension is ready. -- The model is validated against the current Kilo Gateway model catalog before being applied. If the model ID is not found in the catalog, the deep link is silently ignored. +- **VS Code open**: the Kilo sidebar is focused and the linked selection is applied to the active session immediately. +- **VS Code closed**: VS Code launches, then applies the selection once the extension is ready. +- When `model` is provided, it must identify a model in the current Kilo Gateway catalog. Invalid or unavailable models cause the deep link to be ignored. - When `agent` or `mode` is provided, it must identify a visible primary agent. Invalid or unavailable agents cause the deep link to be ignored. -- The agent is selected before the model so the linked model applies to that agent. The selection follows the same precedence as using the pickers: it updates the active session, or the next session when no session is active. It does **not** change your configured defaults in settings. +- An agent-only link uses the model that would normally be selected for that agent. When both parameters are present, the agent is selected first so the linked model applies to it. +- The selection follows the same precedence as using the pickers: it updates the active session, or the next session when no session is active. It does **not** change your configured defaults in settings. ### Sharing and Embedding diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 7b69cc09717..9ed1cbadd14 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -301,7 +301,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private configWarningsShown = false /** Cached notificationsLoaded payload */ private cachedNotificationsMessage: unknown = null - private pendingKiloModel: { modelID: string; agent?: string } | null = null + private pendingKiloModel: { modelID?: string; agent?: string } | null = null private pendingReviewComments: { comments: unknown[]; autoSend: boolean }[] = [] private readyResolvers: (() => void)[] = [] private promptRecoveryQueued = false @@ -716,8 +716,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.postMessage({ type: "openCloudSession", sessionId }) } - public selectKiloModel(modelID: string, agent?: string): void { - this.pendingKiloModel = { modelID, ...(agent && { agent }) } + public selectKiloModel(modelID?: string, agent?: string): void { + if (!modelID && !agent) return + this.pendingKiloModel = { ...(modelID && { modelID }), ...(agent && { agent }) } this.flushPendingKiloModel() } diff --git a/packages/kilo-vscode/src/kilo-provider/switch-link.ts b/packages/kilo-vscode/src/kilo-provider/switch-link.ts index 5e05b32fc8e..6c046b55339 100644 --- a/packages/kilo-vscode/src/kilo-provider/switch-link.ts +++ b/packages/kilo-vscode/src/kilo-provider/switch-link.ts @@ -4,9 +4,9 @@ export function parseSwitchLink(path: string, query: string) { if (!paths.has(path)) return const params = new URLSearchParams(query) - const modelID = params.get("model") - if (!modelID) return - + const modelID = params.get("model") || undefined const agent = params.get("agent") || params.get("mode") || undefined - return { modelID, ...(agent && { agent }) } + if (!modelID && !agent) return + + return { ...(modelID && { modelID }), ...(agent && { agent }) } } diff --git a/packages/kilo-vscode/tests/unit/switch-link.test.ts b/packages/kilo-vscode/tests/unit/switch-link.test.ts index d8bd01b6768..31ec3c426d5 100644 --- a/packages/kilo-vscode/tests/unit/switch-link.test.ts +++ b/packages/kilo-vscode/tests/unit/switch-link.test.ts @@ -8,16 +8,21 @@ describe("parseSwitchLink", () => { }) }) - it("includes an optional agent", () => { + it("parses an agent without a model", () => { + expect(parseSwitchLink("/kilocode/switch", "agent=plan")).toEqual({ + agent: "plan", + }) + }) + + it("parses a model and agent together", () => { expect(parseSwitchLink("/kilocode/switch", "model=kilo-auto%2Ffree&agent=plan")).toEqual({ modelID: "kilo-auto/free", agent: "plan", }) }) - it("accepts mode as an agent alias", () => { - expect(parseSwitchLink("/kilocode/switch", "model=kilo-auto%2Ffree&mode=code")).toEqual({ - modelID: "kilo-auto/free", + it("accepts mode as an agent alias without a model", () => { + expect(parseSwitchLink("/kilocode/switch", "mode=code")).toEqual({ agent: "code", }) }) @@ -35,8 +40,8 @@ describe("parseSwitchLink", () => { }) }) - it("rejects unsupported routes and missing models", () => { + it("rejects unsupported routes and empty selections", () => { expect(parseSwitchLink("/kilocode/other", "model=kilo-auto%2Ffree")).toBeUndefined() - expect(parseSwitchLink("/kilocode/switch", "agent=plan")).toBeUndefined() + expect(parseSwitchLink("/kilocode/switch", "")).toBeUndefined() }) }) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 7b04f165949..01788f5d667 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -369,7 +369,7 @@ export const SessionProvider: ParentComponent = (props) => { const [allAgents, setAllAgents] = createSignal([]) const [defaultAgent, setDefaultAgent] = createSignal("code") const [pendingKiloModel, setPendingKiloModel] = createSignal<{ - modelID: string + modelID?: string agent?: string after: number } | null>(null) @@ -602,9 +602,10 @@ export const SessionProvider: ParentComponent = (props) => { } } - function selectKiloModel(modelID: string, agent?: string) { - setPendingKiloModel({ modelID, ...(agent && { agent }), after: catalog() }) - vscode.postMessage({ type: "requestProviders" }) + function selectKiloModel(modelID?: string, agent?: string) { + if (!modelID && !agent) return + setPendingKiloModel({ ...(modelID && { modelID }), ...(agent && { agent }), after: catalog() }) + if (modelID) vscode.postMessage({ type: "requestProviders" }) } const unsubKiloModel = vscode.onMessage((message: ExtensionMessage) => { @@ -618,9 +619,9 @@ export const SessionProvider: ParentComponent = (props) => { createEffect(() => { const pending = pendingKiloModel() - if (!pending || agents().length === 0 || catalog() <= pending.after) return + if (!pending || agents().length === 0 || (pending.modelID && catalog() <= pending.after)) return setPendingKiloModel(null) - if (!provider.providers()[KILO_PROVIDER_ID]?.models[pending.modelID]) { + if (pending.modelID && !provider.providers()[KILO_PROVIDER_ID]?.models[pending.modelID]) { console.warn("[Kilo New] Ignoring unavailable Kilo catalog model:", pending.modelID) return } @@ -629,7 +630,7 @@ export const SessionProvider: ParentComponent = (props) => { return } if (pending.agent) selectAgent(pending.agent) - selectModel(KILO_PROVIDER_ID, pending.modelID) + if (pending.modelID) selectModel(KILO_PROVIDER_ID, pending.modelID) }) function promptAgent(sessionID?: string) { diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts index 36ee675353b..7debeff4782 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts @@ -241,7 +241,7 @@ export interface OpenCloudSessionMessage { export interface SelectKiloModelMessage { type: "selectKiloModel" - modelID: string + modelID?: string agent?: string }