Files
opencode-mobile/src/stores/catalog.ts
Dzianis Vauchok 0ef3dd37b6 fix: use directory-aware client for delete, rename, command, permissions, questions
- sessions.ts: deleteSession looks up session.directory and uses clientFor()
  instead of bare client, so cross-project deletes hit the right server path
- index.tsx: submitRename uses clientForDirectory(session.directory) so renames
  work for sessions not owned by the active project directory
- index.tsx: handleSwitchDirectory calls catalog.load() after a directory switch
  so agents/commands/providers refresh for the new project
- session/[id].tsx: introduce sessionClient (directory-aware) and use it in
  handlePermissionReply, handleQuestionReply, handleQuestionReject, slash-command
  send, and the refreshPending bootstrap call; also add 'directory' to useEffect deps
- catalog.ts: remove debug console.log
2026-05-26 08:29:35 +00:00

130 lines
3.9 KiB
TypeScript

import { create } from "zustand"
import { useConnections } from "./connections"
import type { Agent, Command } from "../lib/sdk"
export interface ProviderModel {
id: string
name: string
reasoning: boolean
attachment: boolean
limit?: { context: number; output: number }
}
export interface Provider {
id: string
name: string
connected: boolean
models: ProviderModel[]
}
interface ModelSelection {
providerID: string
modelID: string
}
interface CatalogState {
agents: Agent[]
commands: Command[]
providers: Provider[]
defaults: Record<string, string>
// Current selections
agent: string // agent name, e.g. "build"
model: ModelSelection | null
loaded: boolean
// Actions
load: () => Promise<void>
setAgent: (name: string) => void
setModel: (selection: ModelSelection | null) => void
cycleAgent: (direction?: 1 | -1) => void
}
export const useCatalog = create<CatalogState>((set, get) => ({
agents: [],
commands: [],
providers: [],
defaults: {},
agent: "",
model: null,
loaded: false,
load: async () => {
const client = useConnections.getState().client
if (!client) return
const [agentResult, commandResult, providerResult] = await Promise.all([
client.agent.list().catch(() => [] as Agent[]),
client.command.list().catch(() => [] as Command[]),
client.provider.list().catch(() => null),
])
const agents = Array.isArray(agentResult) ? agentResult : []
const commands = Array.isArray(commandResult) ? commandResult : []
// Parse provider response: { all: [...], default: {...}, connected: [...] }
const raw = providerResult
const connected = new Set(Array.isArray(raw?.connected) ? raw.connected : [])
const defaults = raw?.default || {}
const providers: Provider[] = Array.isArray(raw?.all)
? raw.all
.filter((p) => connected.has(p.id))
.map((p) => ({
id: p.id,
name: p.name || p.id,
connected: connected.has(p.id),
models: Object.values(p.models || {})
.filter((m) => m.status !== "deprecated")
.map((m) => ({
id: m.id,
name: m.name || m.id,
reasoning: m.reasoning ?? false,
attachment: m.attachment ?? false,
limit: m.limit,
})),
}))
.filter((p) => p.models.length > 0)
: []
// Filter out hidden agents
const visible = agents.filter((a) => !a.hidden)
// Default agent
const current = get().agent
const agent = current && visible.some((a) => a.name === current) ? current : visible[0]?.name || "build"
// Default model: use default agent's model, or first connected provider's default model
const existing = get().model
const fallback = (() => {
const defaultAgent = visible[0]
if (defaultAgent?.model) return defaultAgent.model
for (const p of providers) {
const defaultModelID = defaults[p.id]
const match = defaultModelID ? p.models.find((m) => m.id === defaultModelID) : p.models[0]
if (match) return { providerID: p.id, modelID: match.id }
}
return null
})()
const model = existing || fallback
set({ agents: visible, commands, providers, defaults, agent, model, loaded: true })
},
setAgent: (name) => {
const match = get().agents.find((a) => a.name === name)
if (!match) return
const model = match.model || get().model
set({ agent: name, model })
},
setModel: (selection) => set({ model: selection }),
cycleAgent: (direction = 1) => {
const { agents, agent } = get()
const primary = agents.filter((a) => a.mode === "primary" || a.mode === "all")
if (primary.length < 2) return
const idx = primary.findIndex((a) => a.name === agent)
const next = (idx + direction + primary.length) % primary.length
get().setAgent(primary[next].name)
},
}))