Skip to content
Closed
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
18 changes: 17 additions & 1 deletion packages/opencode/src/cli/cmd/tui/context/local.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createStore } from "solid-js/store"
import { batch, createEffect, createMemo } from "solid-js"
import { createStore } from "solid-js/store"
import { useSync } from "@tui/context/sync"
import { useTheme } from "@tui/context/theme"
import { uniqueBy } from "remeda"
Expand All @@ -12,6 +12,13 @@ import { Provider } from "@/provider/provider"
import { useArgs } from "./args"
import { useSDK } from "./sdk"
import { RGBA } from "@opentui/core"
import type { SessionPref } from "./session-pref"

type SessionPrefStore = {
get(sessionID: string): SessionPref | undefined
setAgent(sessionID: string, agent: string): void
setModel(sessionID: string, model: { providerID: string; modelID: string }): void
}

export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
name: "Local",
Expand All @@ -20,6 +27,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const sdk = useSDK()
const toast = useToast()

const pref: SessionPrefStore = {
get() {
return undefined
},
setAgent() {},
setModel() {},
}

function isModelValid(model: { providerID: string; modelID: string }) {
const provider = sync.data.provider.find((x) => x.id === model.providerID)
return !!provider?.models[model.modelID]
Expand Down Expand Up @@ -333,6 +348,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
model,
agent,
mcp,
pref,
}
return result
},
Expand Down
24 changes: 24 additions & 0 deletions packages/opencode/src/cli/cmd/tui/context/session-pref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import path from "path"
import { Global } from "@/global"

export type SessionPref = {
agent?: string
model?: {
providerID: string
modelID: string
}
}

const prefPath = process.env.OPENCODE_SESSION_PREF_PATH ?? path.join(Global.Path.state, "session-pref.json")
const file = Bun.file(prefPath)

export async function readPrefs(): Promise<Record<string, SessionPref>> {
return file
.json()
.then((data) => (data && typeof data === "object" ? (data as Record<string, SessionPref>) : {}))
.catch(() => ({}))
}

export async function writePrefs(prefs: Record<string, SessionPref>) {
await Bun.write(file, JSON.stringify(prefs, null, 2))
}
20 changes: 18 additions & 2 deletions packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export function Session() {
const route = useRouteData("session")
const { navigate } = useRoute()
const sync = useSync()
const local = useLocal()
const kv = useKV()
const { theme } = useTheme()
const promptRef = usePromptRef()
Expand Down Expand Up @@ -163,6 +164,23 @@ export function Session() {
const toast = useToast()
const sdk = useSDK()

createEffect(() => {
if (!sync.data.provider.length) return
if (!sync.data.agent.length) return
if (!local.model.ready) return
const last = messages().at(-1)
if (!last) return
const pref = last as { agent?: string; model?: { providerID: string; modelID: string } }
const agent = pref.agent
if (agent && agent !== local.agent.current().name) local.agent.set(agent)
const model = pref.model
if (model) {
const current = local.model.current()
const same = current && current.providerID === model.providerID && current.modelID === model.modelID
if (!same) local.model.set(model)
}
})

// Auto-navigate to whichever session currently needs permission input
createEffect(() => {
const currentSession = session()
Expand Down Expand Up @@ -218,8 +236,6 @@ export function Session() {
}, 50)
}

const local = useLocal()

function moveChild(direction: number) {
const parentID = session()?.parentID ?? session()?.id
let children = sync.data.session
Expand Down
33 changes: 33 additions & 0 deletions packages/opencode/test/tui/session-pref.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, test } from "bun:test"
import os from "os"
import path from "path"
import fs from "fs/promises"

describe("session pref storage", () => {
test("persists and reads per-session selections", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-pref-"))
process.env.OPENCODE_SESSION_PREF_PATH = path.join(dir, "prefs.json")

const pref = await import("../../src/cli/cmd/tui/context/session-pref")
const data = {
sessionA: {
agent: "Agent Alpha",
model: { providerID: "p1", modelID: "m1" },
},
sessionB: {
agent: "Agent Beta",
},
}

await pref.writePrefs(data)
const result = await pref.readPrefs()

expect(result.sessionA.agent).toBe("Agent Alpha")
expect(result.sessionA.model?.providerID).toBe("p1")
expect(result.sessionB.agent).toBe("Agent Beta")
expect(result.sessionB.model).toBeUndefined()

delete process.env.OPENCODE_SESSION_PREF_PATH
await fs.rm(dir, { recursive: true, force: true })
})
})