feat(vscode): add thinking effort toggle to prompt input
Adds a variant cycle button to the VS Code extension's prompt input that lets users toggle the thinking effort level (e.g. high/max) for models that support reasoning, matching the TUI app behaviour. - Extend ProviderModel types to expose variants and capabilities.reasoning - Thread variant through the full send-message pipeline (webview → extension → CLI) - Persist variant selections via VS Code globalState - Render a thinking toggle button next to the model selector (only visible when the selected model supports reasoning variants) Closes #172
This commit is contained in:
@@ -285,6 +285,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
message.providerID,
|
||||
message.modelID,
|
||||
message.agent,
|
||||
message.variant,
|
||||
files,
|
||||
)
|
||||
break
|
||||
@@ -439,6 +440,23 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
case "telemetry":
|
||||
TelemetryProxy.capture(message.event, message.properties)
|
||||
break
|
||||
case "persistVariant": {
|
||||
const stored =
|
||||
this.extensionContext?.globalState.get<Record<string, string | undefined>>("variantSelections") ?? {}
|
||||
if (message.value === undefined) {
|
||||
delete stored[message.key]
|
||||
} else {
|
||||
stored[message.key] = message.value
|
||||
}
|
||||
await this.extensionContext?.globalState.update("variantSelections", stored)
|
||||
break
|
||||
}
|
||||
case "requestVariants": {
|
||||
const variants =
|
||||
this.extensionContext?.globalState.get<Record<string, string | undefined>>("variantSelections") ?? {}
|
||||
this.postMessage({ type: "variantsLoaded", variants })
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1002,6 +1020,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
providerID?: string,
|
||||
modelID?: string,
|
||||
agent?: string,
|
||||
variant?: string,
|
||||
files?: Array<{ mime: string; url: string }>,
|
||||
): Promise<void> {
|
||||
if (!this.httpClient) {
|
||||
@@ -1057,6 +1076,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
providerID,
|
||||
modelID,
|
||||
agent,
|
||||
variant,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[Kilo New] KiloProvider: Failed to send message:", error)
|
||||
|
||||
@@ -214,7 +214,7 @@ export class HttpClient {
|
||||
sessionId: string,
|
||||
parts: Array<{ type: "text"; text: string } | { type: "file"; mime: string; url: string }>,
|
||||
directory: string,
|
||||
options?: { providerID?: string; modelID?: string; agent?: string },
|
||||
options?: { providerID?: string; modelID?: string; agent?: string; variant?: string },
|
||||
): Promise<void> {
|
||||
const body: Record<string, unknown> = { parts }
|
||||
if (options?.providerID && options?.modelID) {
|
||||
@@ -224,6 +224,9 @@ export class HttpClient {
|
||||
if (options?.agent) {
|
||||
body.agent = options.agent
|
||||
}
|
||||
if (options?.variant) {
|
||||
body.variant = options.variant
|
||||
}
|
||||
|
||||
await this.request<void>("POST", `/session/${sessionId}/message`, body, { directory, allowEmpty: true })
|
||||
}
|
||||
|
||||
@@ -139,6 +139,8 @@ export interface ProviderModel {
|
||||
latest?: boolean
|
||||
// Actual shape returned by the server (Provider.Model)
|
||||
limit?: { context: number; input?: number; output: number }
|
||||
variants?: Record<string, Record<string, unknown>>
|
||||
capabilities?: { reasoning: boolean }
|
||||
}
|
||||
|
||||
// Provider definition
|
||||
|
||||
@@ -314,6 +314,18 @@ export const PromptInput: Component = () => {
|
||||
<div class="prompt-input-hint-selectors">
|
||||
<ModeSwitcher />
|
||||
<ModelSelector />
|
||||
<Show when={session.variantList().length > 0}>
|
||||
<Tooltip value={language.t("command.model.variant.cycle")} placement="top">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
onClick={() => session.cycleVariant()}
|
||||
aria-label={language.t("command.model.variant.cycle")}
|
||||
>
|
||||
{session.currentVariant() ?? language.t("common.default")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="prompt-input-hint-actions">
|
||||
<Show
|
||||
|
||||
@@ -48,6 +48,7 @@ interface SessionStore {
|
||||
todos: Record<string, TodoItem[]> // sessionID -> todos
|
||||
modelSelections: Record<string, ModelSelection> // sessionID -> model
|
||||
agentSelections: Record<string, string> // sessionID -> agent name
|
||||
variantSelections: Record<string, string | undefined> // "providerID/modelID" -> variant name
|
||||
}
|
||||
|
||||
interface SessionContextValue {
|
||||
@@ -105,6 +106,11 @@ interface SessionContextValue {
|
||||
setSessionModel: (sessionID: string, providerID: string, modelID: string) => void
|
||||
setSessionAgent: (sessionID: string, name: string) => void
|
||||
|
||||
// Thinking variant for the selected model
|
||||
variantList: () => string[]
|
||||
currentVariant: () => string | undefined
|
||||
cycleVariant: () => void
|
||||
|
||||
// Actions
|
||||
sendMessage: (text: string, providerID?: string, modelID?: string, files?: FileAttachment[]) => void
|
||||
abort: () => void
|
||||
@@ -177,6 +183,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
todos: {},
|
||||
modelSelections: {},
|
||||
agentSelections: {},
|
||||
variantSelections: {},
|
||||
})
|
||||
|
||||
// Keep pending selection in sync with provider default until the user
|
||||
@@ -268,6 +275,56 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
clearInterval(agentRetryTimer)
|
||||
})
|
||||
|
||||
// Variant (thinking effort) selection — keyed by "providerID/modelID"
|
||||
const variantKey = (sel: ModelSelection) => `${sel.providerID}/${sel.modelID}`
|
||||
|
||||
const variantList = () => {
|
||||
const sel = selected()
|
||||
if (!sel) return []
|
||||
const model = provider.findModel(sel)
|
||||
if (!model?.variants) return []
|
||||
return Object.keys(model.variants)
|
||||
}
|
||||
|
||||
const currentVariant = () => {
|
||||
const sel = selected()
|
||||
if (!sel) return undefined
|
||||
const key = variantKey(sel)
|
||||
const stored = store.variantSelections[key]
|
||||
const list = variantList()
|
||||
if (stored && list.includes(stored)) return stored
|
||||
return undefined
|
||||
}
|
||||
|
||||
const cycleVariant = () => {
|
||||
const sel = selected()
|
||||
if (!sel) return
|
||||
const list = variantList()
|
||||
if (list.length === 0) return
|
||||
const key = variantKey(sel)
|
||||
const current = store.variantSelections[key]
|
||||
const next = (() => {
|
||||
if (!current || !list.includes(current)) return list[0]
|
||||
const idx = list.indexOf(current)
|
||||
if (idx === list.length - 1) return undefined
|
||||
return list[idx + 1]
|
||||
})()
|
||||
setStore("variantSelections", key, next)
|
||||
vscode.postMessage({ type: "persistVariant", key, value: next })
|
||||
}
|
||||
|
||||
// Load persisted variants from extension globalState
|
||||
const unsubVariants = vscode.onMessage((message: ExtensionMessage) => {
|
||||
if (message.type !== "variantsLoaded") return
|
||||
for (const [k, v] of Object.entries(message.variants)) {
|
||||
setStore("variantSelections", k, v)
|
||||
}
|
||||
})
|
||||
|
||||
vscode.postMessage({ type: "requestVariants" })
|
||||
|
||||
onCleanup(unsubVariants)
|
||||
|
||||
// Handle messages from extension
|
||||
onMount(() => {
|
||||
const unsubscribe = vscode.onMessage((message: ExtensionMessage) => {
|
||||
@@ -631,6 +688,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
providerID,
|
||||
modelID,
|
||||
agent,
|
||||
variant: currentVariant(),
|
||||
files,
|
||||
})
|
||||
}
|
||||
@@ -866,6 +924,9 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
},
|
||||
allMessages,
|
||||
allParts,
|
||||
variantList,
|
||||
currentVariant,
|
||||
cycleVariant,
|
||||
sendMessage,
|
||||
abort,
|
||||
compact,
|
||||
|
||||
@@ -215,6 +215,8 @@ export interface ProviderModel {
|
||||
latest?: boolean
|
||||
// Actual shape returned by the server (Provider.Model)
|
||||
limit?: { context: number; input?: number; output: number }
|
||||
variants?: Record<string, Record<string, unknown>>
|
||||
capabilities?: { reasoning: boolean }
|
||||
}
|
||||
|
||||
export interface Provider {
|
||||
@@ -607,6 +609,12 @@ export interface AgentManagerMultiVersionProgressMessage {
|
||||
groupId?: string
|
||||
}
|
||||
|
||||
// Stored variant selections loaded from extension globalState (extension → webview)
|
||||
export interface VariantsLoadedMessage {
|
||||
type: "variantsLoaded"
|
||||
variants: Record<string, string | undefined>
|
||||
}
|
||||
|
||||
// Request webview to send initial prompt to a newly created session (extension → webview)
|
||||
export interface AgentManagerSendInitialMessage {
|
||||
type: "agentManager.sendInitialMessage"
|
||||
@@ -663,6 +671,7 @@ export type ExtensionMessage =
|
||||
| AgentManagerSendInitialMessage
|
||||
| SetChatBoxMessage
|
||||
| TriggerTaskMessage
|
||||
| VariantsLoadedMessage
|
||||
|
||||
// ============================================
|
||||
// Messages FROM webview TO extension
|
||||
@@ -680,6 +689,7 @@ export interface SendMessageRequest {
|
||||
providerID?: string
|
||||
modelID?: string
|
||||
agent?: string
|
||||
variant?: string
|
||||
files?: FileAttachment[]
|
||||
}
|
||||
|
||||
@@ -947,6 +957,18 @@ export interface SetSessionsCollapsedRequest {
|
||||
collapsed: boolean
|
||||
}
|
||||
|
||||
// Variant persistence (webview → extension)
|
||||
export interface PersistVariantRequest {
|
||||
type: "persistVariant"
|
||||
key: string
|
||||
value: string | undefined
|
||||
}
|
||||
|
||||
// Request stored variants from extension (webview → extension)
|
||||
export interface RequestVariantsMessage {
|
||||
type: "requestVariants"
|
||||
}
|
||||
|
||||
export type WebviewMessage =
|
||||
| SendMessageRequest
|
||||
| AbortRequest
|
||||
@@ -999,6 +1021,8 @@ export type WebviewMessage =
|
||||
| CreateMultiVersionRequest
|
||||
| SetTabOrderRequest
|
||||
| SetSessionsCollapsedRequest
|
||||
| PersistVariantRequest
|
||||
| RequestVariantsMessage
|
||||
|
||||
// ============================================
|
||||
// VS Code API type
|
||||
|
||||
Reference in New Issue
Block a user