Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/tui/src/config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export const Info = Schema.Struct({
scroll_acceleration: Schema.optional(ScrollAcceleration),
diff_style: Schema.optional(DiffStyle),
mouse: Schema.optional(Schema.Boolean).annotate({ description: "Enable or disable mouse capture (default: true)" }),
share_plan_build_model: Schema.optional(Schema.Boolean).annotate({
description: "Share manually selected models between the Plan and Build agents (default: false)",
}),
})
export type Info = Schema.Schema.Type<typeof Info>

Expand Down
31 changes: 28 additions & 3 deletions packages/tui/src/context/local.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useTheme } from "./theme"
import { useToast } from "../ui/toast"
import { useRoute } from "./route"
import { usePermission } from "./permission"
import { useTuiConfig } from "../config"

export type LocalTheme = {
secondary: RGBA
Expand Down Expand Up @@ -48,6 +49,25 @@ export function recentModels(
.map((item) => ({ providerID: item.providerID, modelID: item.modelID }))
}

export function selectModel<T extends Record<string, { providerID: string; modelID: string }>>(
models: T,
agent: string,
model: { providerID: string; modelID: string },
sharePlanBuild: boolean,
) {
if (!sharePlanBuild || (agent !== "plan" && agent !== "build")) {
return {
...models,
[agent]: model,
}
}
return {
...models,
plan: model,
build: model,
}
}

export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
name: "Local",
init: () => {
Expand All @@ -60,6 +80,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const args = useArgs()
const event = useEvent()
const permission = usePermission()
const tuiConfig = useTuiConfig()

function isModelValid(model: { providerID: string; modelID: string }) {
const provider = sync.data.provider.find((item) => item.id === model.providerID)
Expand Down Expand Up @@ -285,7 +306,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (!val) return
const a = agent.current()
if (!a) return
setModelStore("model", a.name, { ...val })
setModelStore("model", (models) =>
selectModel(models, a.name, { ...val }, !!tuiConfig.share_plan_build_model),
)
},
cycleFavorite(direction: 1 | -1) {
const favorites = modelStore.favorite.filter((item) => isModelValid(item))
Expand Down Expand Up @@ -313,7 +336,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (!next) return
const a = agent.current()
if (!a) return
setModelStore("model", a.name, { ...next })
setModelStore("model", (models) =>
selectModel(models, a.name, { ...next }, !!tuiConfig.share_plan_build_model),
)
setModelStore("recent", recentModels(next, modelStore.recent))
save()
},
Expand All @@ -329,7 +354,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
const a = agent.current()
if (!a) return
setModelStore("model", a.name, model)
setModelStore("model", (models) => selectModel(models, a.name, model, !!tuiConfig.share_plan_build_model))
if (options?.recent) {
setModelStore("recent", recentModels(model, modelStore.recent))
save()
Expand Down
64 changes: 63 additions & 1 deletion packages/tui/test/context/local.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"
import { parseModel, recentModels } from "../../src/context/local"
import { parseModel, recentModels, selectModel } from "../../src/context/local"

test("parses model IDs containing slashes", () => {
expect(parseModel("provider/family/model")).toEqual({
Expand All @@ -20,3 +20,65 @@ test("moves a model to the front, deduplicates, and limits recents", () => {
...recent.slice(6, 10),
])
})

test("keeps model selection per agent by default", () => {
const selected = { providerID: "provider", modelID: "selected" }

expect(
selectModel(
{
plan: { providerID: "provider", modelID: "plan" },
build: { providerID: "provider", modelID: "build" },
},
"plan",
selected,
false,
),
).toEqual({
plan: selected,
build: { providerID: "provider", modelID: "build" },
})
})

test("shares model selection between plan and build when enabled", () => {
const custom = { providerID: "provider", modelID: "custom" }
const selected = { providerID: "provider", modelID: "selected" }

expect(
selectModel(
{
plan: { providerID: "provider", modelID: "plan" },
build: { providerID: "provider", modelID: "build" },
custom,
},
"build",
selected,
true,
),
).toEqual({
plan: selected,
build: selected,
custom,
})
})

test("keeps custom agent model selection independent", () => {
const selected = { providerID: "provider", modelID: "selected" }

expect(
selectModel(
{
plan: { providerID: "provider", modelID: "plan" },
build: { providerID: "provider", modelID: "build" },
custom: { providerID: "provider", modelID: "custom" },
},
"custom",
selected,
true,
),
).toEqual({
plan: { providerID: "provider", modelID: "plan" },
build: { providerID: "provider", modelID: "build" },
custom: selected,
})
})
Loading