feat(model): add provider fast mode toggle
This commit is contained in:
parent
0befa1e57e
commit
e73ec6d2d7
19 changed files with 337 additions and 9 deletions
|
|
@ -1023,6 +1023,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
})
|
||||
|
||||
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
||||
const fast = createMemo(() => local.model.fast.available())
|
||||
const fastLabel = createMemo(() =>
|
||||
language.t(local.model.fast.current() ? "command.model.fast.disable" : "command.model.fast.enable"),
|
||||
)
|
||||
const accepting = createMemo(() => {
|
||||
const id = params.id
|
||||
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
|
||||
|
|
@ -1534,6 +1538,25 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
/>
|
||||
</TooltipKeybind>
|
||||
</div>
|
||||
<Show when={fast()}>
|
||||
<Tooltip placement="top" gutter={8} value={fastLabel()}>
|
||||
<Button
|
||||
data-action="prompt-fast"
|
||||
variant="ghost"
|
||||
onClick={() => local.model.fast.toggle()}
|
||||
class="h-7 px-2 shrink-0 text-13-medium"
|
||||
classList={{
|
||||
"text-text-base": !local.model.fast.current(),
|
||||
"text-icon-warning-base bg-surface-warning-base": local.model.fast.current(),
|
||||
}}
|
||||
style={control()}
|
||||
aria-label={fastLabel()}
|
||||
aria-pressed={local.model.fast.current()}
|
||||
>
|
||||
{language.t("command.model.fast.label")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<TooltipKeybind
|
||||
placement="top"
|
||||
gutter={8}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export type FollowupDraft = {
|
|||
agent: string
|
||||
model: { providerID: string; modelID: string }
|
||||
variant?: string
|
||||
fast?: boolean
|
||||
}
|
||||
|
||||
type FollowupSendInput = {
|
||||
|
|
@ -88,6 +89,7 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||
agent: input.draft.agent,
|
||||
model: `${input.draft.model.providerID}/${input.draft.model.modelID}`,
|
||||
variant: input.draft.variant,
|
||||
fast: input.draft.fast,
|
||||
parts: images.map((attachment) => ({
|
||||
id: Identifier.ascending("part"),
|
||||
type: "file" as const,
|
||||
|
|
@ -122,6 +124,7 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||
agent: input.draft.agent,
|
||||
model: input.draft.model,
|
||||
variant: input.draft.variant,
|
||||
fast: input.draft.fast,
|
||||
}
|
||||
|
||||
const add = () =>
|
||||
|
|
@ -156,6 +159,7 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||
messageID,
|
||||
parts: requestParts,
|
||||
variant: input.draft.variant,
|
||||
fast: input.draft.fast,
|
||||
})
|
||||
return true
|
||||
} catch (err) {
|
||||
|
|
@ -297,6 +301,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||
const currentModel = local.model.current()
|
||||
const currentAgent = local.agent.current()
|
||||
const variant = local.model.variant.current()
|
||||
const fast = local.model.fast.current()
|
||||
if (!currentModel || !currentAgent) {
|
||||
showToast({
|
||||
title: language.t("prompt.toast.modelAgentRequired.title"),
|
||||
|
|
@ -398,6 +403,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||
agent,
|
||||
model,
|
||||
variant,
|
||||
fast,
|
||||
}
|
||||
|
||||
const clearInput = () => {
|
||||
|
|
@ -461,6 +467,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||
agent,
|
||||
model: `${model.providerID}/${model.modelID}`,
|
||||
variant,
|
||||
fast,
|
||||
parts: images.map((attachment) => ({
|
||||
id: Identifier.ascending("part"),
|
||||
type: "file" as const,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useProviders } from "@/hooks/use-providers"
|
|||
import { modelEnabled, modelProbe } from "@/testing/model-selection"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
|
||||
import * as Fast from "./model-fast"
|
||||
import { useSDK } from "./sdk"
|
||||
import { useSync } from "./sync"
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ type State = {
|
|||
agent?: string
|
||||
model?: ModelKey
|
||||
variant?: string | null
|
||||
fast?: boolean
|
||||
}
|
||||
|
||||
type Saved = {
|
||||
|
|
@ -79,10 +81,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
current?: string
|
||||
draft?: State
|
||||
last?: {
|
||||
type: "agent" | "model" | "variant"
|
||||
type: "agent" | "model" | "variant" | "fast"
|
||||
agent?: string
|
||||
model?: ModelKey | null
|
||||
variant?: string | null
|
||||
fast?: boolean
|
||||
}
|
||||
}>({
|
||||
current: list()[0]?.name,
|
||||
|
|
@ -191,11 +194,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
agent: item.name,
|
||||
model: item.model,
|
||||
variant: item.variant ?? null,
|
||||
fast: scope()?.fast,
|
||||
})
|
||||
const next = {
|
||||
agent: item.name,
|
||||
model: item.model,
|
||||
variant: item.variant,
|
||||
fast: scope()?.fast,
|
||||
} satisfies State
|
||||
const session = id()
|
||||
if (session) {
|
||||
|
|
@ -249,6 +254,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
agent: agent.current()?.name,
|
||||
model: model ? { providerID: model.provider.id, modelID: model.id } : undefined,
|
||||
variant: selected(),
|
||||
fast: !!scope()?.fast && Fast.enabled(model),
|
||||
} satisfies State
|
||||
}
|
||||
|
||||
|
|
@ -296,6 +302,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
agent: agent.current()?.name,
|
||||
model: item ?? null,
|
||||
variant: selected(),
|
||||
fast: model.fast.current(),
|
||||
})
|
||||
write({ model: item })
|
||||
if (!item) return
|
||||
|
|
@ -333,6 +340,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
agent: agent.current()?.name,
|
||||
model: model ? { providerID: model.provider.id, modelID: model.id } : null,
|
||||
variant: value ?? null,
|
||||
fast: !!scope()?.fast && Fast.enabled(model),
|
||||
})
|
||||
write({ variant: value ?? null })
|
||||
})
|
||||
|
|
@ -349,6 +357,34 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
)
|
||||
},
|
||||
},
|
||||
fast: {
|
||||
selected() {
|
||||
return scope()?.fast === true
|
||||
},
|
||||
current() {
|
||||
return this.selected() && this.available()
|
||||
},
|
||||
available() {
|
||||
return Fast.enabled(current())
|
||||
},
|
||||
set(value: boolean) {
|
||||
if (value && !this.available()) return
|
||||
const model = current()
|
||||
batch(() => {
|
||||
setStore("last", {
|
||||
type: "fast",
|
||||
agent: agent.current()?.name,
|
||||
model: model ? { providerID: model.provider.id, modelID: model.id } : null,
|
||||
variant: selected(),
|
||||
fast: value,
|
||||
})
|
||||
write({ fast: value || undefined })
|
||||
})
|
||||
},
|
||||
toggle() {
|
||||
this.set(!this.current())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const result = {
|
||||
|
|
@ -372,7 +408,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
handoff.set(handoffKey(dir, session), next)
|
||||
setStore("draft", undefined)
|
||||
},
|
||||
restore(msg: { sessionID: string; agent: string; model: ModelKey; variant?: string }) {
|
||||
restore(msg: { sessionID: string; agent: string; model: ModelKey; variant?: string; fast?: boolean }) {
|
||||
const session = id()
|
||||
if (!session) return
|
||||
if (msg.sessionID !== session) return
|
||||
|
|
@ -383,6 +419,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
agent: msg.agent,
|
||||
model: msg.model,
|
||||
variant: msg.variant ?? null,
|
||||
fast: msg.fast === true,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
@ -405,6 +442,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
}
|
||||
: undefined,
|
||||
variant: result.model.variant.current() ?? null,
|
||||
fast: result.model.fast.current(),
|
||||
selected: result.model.variant.selected(),
|
||||
configured: result.model.variant.configured(),
|
||||
pick: scope(),
|
||||
|
|
|
|||
28
packages/app/src/context/model-fast.ts
Normal file
28
packages/app/src/context/model-fast.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
type Model = {
|
||||
id: string
|
||||
provider: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
function lower(model: Model) {
|
||||
return model.id.toLowerCase()
|
||||
}
|
||||
|
||||
export function kind(model: Model | undefined) {
|
||||
if (!model) return
|
||||
const id = lower(model)
|
||||
if (
|
||||
model.provider.id === "anthropic" &&
|
||||
(id.includes("claude-opus-4-6") || id.includes("claude-opus-4.6") || id.includes("opus-4-6"))
|
||||
) {
|
||||
return "claude"
|
||||
}
|
||||
if (model.provider.id === "openai" && id.includes("gpt-5.4")) {
|
||||
return "codex"
|
||||
}
|
||||
}
|
||||
|
||||
export function enabled(model: Model | undefined) {
|
||||
return !!kind(model)
|
||||
}
|
||||
|
|
@ -67,6 +67,10 @@ export const dict = {
|
|||
"command.agent.cycle.description": "Switch to the next agent",
|
||||
"command.agent.cycle.reverse": "Cycle agent backwards",
|
||||
"command.agent.cycle.reverse.description": "Switch to the previous agent",
|
||||
"command.model.fast.label": "Fast",
|
||||
"command.model.fast.enable": "Enable fast mode",
|
||||
"command.model.fast.disable": "Disable fast mode",
|
||||
"command.model.fast.description": "Toggle provider fast mode for supported Claude and Codex models",
|
||||
"command.model.variant.cycle": "Cycle thinking effort",
|
||||
"command.model.variant.cycle.description": "Switch to the next effort level",
|
||||
"command.prompt.mode.shell": "Shell",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
|
|||
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { resetSessionModel, syncSessionModel } from "./session-model-helpers"
|
||||
|
||||
const message = (input?: Partial<Pick<UserMessage, "agent" | "model" | "variant">>) =>
|
||||
const message = (input?: Partial<Pick<UserMessage, "agent" | "model" | "variant" | "fast">>) =>
|
||||
({
|
||||
id: "msg",
|
||||
sessionID: "session",
|
||||
|
|
@ -31,6 +31,24 @@ describe("syncSessionModel", () => {
|
|||
|
||||
expect(calls).toEqual([message({ variant: "high" })])
|
||||
})
|
||||
|
||||
test("restores fast mode from the last message", () => {
|
||||
const calls: unknown[] = []
|
||||
|
||||
syncSessionModel(
|
||||
{
|
||||
session: {
|
||||
restore(value) {
|
||||
calls.push(value)
|
||||
},
|
||||
reset() {},
|
||||
},
|
||||
},
|
||||
message({ fast: true }),
|
||||
)
|
||||
|
||||
expect(calls).toEqual([message({ fast: true })])
|
||||
})
|
||||
})
|
||||
|
||||
describe("resetSessionModel", () => {
|
||||
|
|
|
|||
|
|
@ -353,6 +353,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||
slash: "model",
|
||||
onSelect: () => dialog.show(() => <DialogSelectModel model={local.model} />),
|
||||
}),
|
||||
modelCommand({
|
||||
id: "model.fast.toggle",
|
||||
title: language.t(local.model.fast.current() ? "command.model.fast.disable" : "command.model.fast.enable"),
|
||||
description: language.t("command.model.fast.description"),
|
||||
slash: "fast",
|
||||
disabled: !local.model.fast.available(),
|
||||
onSelect: () => local.model.fast.toggle(),
|
||||
}),
|
||||
mcpCommand({
|
||||
id: "mcp.toggle",
|
||||
title: language.t("command.mcp.toggle"),
|
||||
|
|
|
|||
|
|
@ -7,20 +7,23 @@ type State = {
|
|||
agent?: string
|
||||
model?: ModelKey | null
|
||||
variant?: string | null
|
||||
fast?: boolean
|
||||
}
|
||||
|
||||
export type ModelProbeState = {
|
||||
dir?: string
|
||||
sessionID?: string
|
||||
last?: {
|
||||
type: "agent" | "model" | "variant"
|
||||
type: "agent" | "model" | "variant" | "fast"
|
||||
agent?: string
|
||||
model?: ModelKey | null
|
||||
variant?: string | null
|
||||
fast?: boolean
|
||||
}
|
||||
agent?: string
|
||||
model?: (ModelKey & { name?: string }) | undefined
|
||||
variant?: string | null
|
||||
fast?: boolean
|
||||
selected?: string | null
|
||||
configured?: string
|
||||
pick?: State
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue