Skip to content

Commit 8af2709

Browse files
authored
Merge branch 'dev' into fix/git-symlink-watcher
2 parents 7f7f4ad + 8feb4a3 commit 8af2709

31 files changed

Lines changed: 544 additions & 215 deletions

File tree

packages/app/src/components/dialog-select-mcp.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { Dialog } from "@opencode-ai/ui/dialog"
66
import { List } from "@opencode-ai/ui/list"
77
import { Switch } from "@opencode-ai/ui/switch"
88
import { useLanguage } from "@/context/language"
9-
import { mcpQueryKey } from "@/context/global-sync"
9+
import { useQueryOptions } from "@/context/global-sync"
10+
import { pathKey } from "@/utils/path-key"
1011

1112
const statusLabels = {
1213
connected: "mcp.status.connected",
@@ -20,6 +21,7 @@ export const DialogSelectMcp: Component = () => {
2021
const sdk = useSDK()
2122
const language = useLanguage()
2223
const queryClient = useQueryClient()
24+
const queryOptions = useQueryOptions()
2325

2426
const items = createMemo(() =>
2527
Object.entries(sync.data.mcp ?? {})
@@ -32,7 +34,7 @@ export const DialogSelectMcp: Component = () => {
3234
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
3335
else await sdk.client.mcp.connect({ name })
3436
},
35-
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
37+
onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))),
3638
}))
3739

3840
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)

packages/app/src/components/prompt-input.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
} from "@/context/prompt"
1717
import { useLayout } from "@/context/layout"
1818
import { useSDK } from "@/context/sdk"
19-
import { useGlobalSDK } from "@/context/global-sdk"
2019
import { useSync } from "@/context/sync"
2120
import { useComments } from "@/context/comments"
2221
import { Button } from "@opencode-ai/ui/button"
@@ -56,7 +55,8 @@ import { PromptDragOverlay } from "./prompt-input/drag-overlay"
5655
import { promptPlaceholder } from "./prompt-input/placeholder"
5756
import { ImagePreview } from "@opencode-ai/ui/image-preview"
5857
import { useQueries } from "@tanstack/solid-query"
59-
import { loadAgentsQuery, loadProvidersQuery } from "@/context/global-sync/bootstrap"
58+
import { useQueryOptions } from "@/context/global-sync"
59+
import { pathKey } from "@/utils/path-key"
6060

6161
interface PromptInputProps {
6262
class?: string
@@ -103,7 +103,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
103103

104104
export const PromptInput: Component<PromptInputProps> = (props) => {
105105
const sdk = useSDK()
106-
const globalSDK = useGlobalSDK()
106+
const queryOptions = useQueryOptions()
107107

108108
const sync = useSync()
109109
const local = useLocal()
@@ -1256,9 +1256,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
12561256

12571257
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
12581258
queries: [
1259-
loadAgentsQuery(sdk.directory, sdk.client),
1260-
loadProvidersQuery(null, globalSDK.client),
1261-
loadProvidersQuery(sdk.directory, sdk.client),
1259+
queryOptions.agents(pathKey(sdk.directory)),
1260+
queryOptions.providers(null),
1261+
queryOptions.providers(pathKey(sdk.directory)),
12621262
],
12631263
}))
12641264

packages/app/src/components/status-popover-body.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { useSDK } from "@/context/sdk"
1515
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
1616
import { useSync } from "@/context/sync"
1717
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
18-
import { mcpQueryKey } from "@/context/global-sync"
18+
import { useQueryOptions } from "@/context/global-sync"
19+
import { pathKey } from "@/utils/path-key"
1920

2021
const pollMs = 10_000
2122

@@ -139,13 +140,14 @@ const useMcpToggleMutation = () => {
139140
const sdk = useSDK()
140141
const language = useLanguage()
141142
const queryClient = useQueryClient()
143+
const queryOptions = useQueryOptions()
142144

143145
return useMutation(() => ({
144146
mutationFn: async (name: string) => {
145147
const status = sync.data.mcp[name]
146148
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
147149
},
148-
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
150+
onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))),
149151
onError: (err) => {
150152
showToast({
151153
variant: "error",

packages/app/src/context/global-sync.tsx

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import {
1818
bootstrapDirectory,
1919
bootstrapGlobal,
2020
clearProviderRev,
21+
loadAgentsQuery,
2122
loadGlobalConfigQuery,
2223
loadPathQuery,
24+
loadProjectsQuery,
2325
loadProvidersQuery,
2426
} from "./global-sync/bootstrap"
2527
import { createChildStoreManager } from "./global-sync/child-store"
@@ -33,6 +35,7 @@ import { formatServerError } from "@/utils/server-errors"
3335
import { queryOptions, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
3436
import { createRefreshQueue } from "./global-sync/queue"
3537
import { directoryKey } from "./global-sync/utils"
38+
import { PathKey } from "@/utils/path-key"
3639

3740
type GlobalStore = {
3841
ready: boolean
@@ -48,24 +51,33 @@ type GlobalStore = {
4851
reload: undefined | "pending" | "complete"
4952
}
5053

51-
export const loadSessionsQueryKey = (directory: string) => [directory, "loadSessions"] as const
52-
53-
export const mcpQueryKey = (directory: string) => [directory, "mcp"] as const
54-
5554
export const loadMcpQuery = (directory: string, sdk: OpencodeClient) =>
5655
queryOptions({
57-
queryKey: mcpQueryKey(directory),
56+
queryKey: [directory, "mcp"] as const,
5857
queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
5958
})
6059

61-
export const lspQueryKey = (directory: string) => [directory, "lsp"] as const
62-
6360
export const loadLspQuery = (directory: string, sdk: OpencodeClient) =>
6461
queryOptions({
65-
queryKey: lspQueryKey(directory),
62+
queryKey: [directory, "lsp"] as const,
6663
queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
6764
})
6865

66+
function makeQueryOptionsApi(globalSDK: () => OpencodeClient, sdkFor: (dir: PathKey) => OpencodeClient) {
67+
return {
68+
globalConfig: () => loadGlobalConfigQuery(globalSDK()),
69+
projects: () => loadProjectsQuery(globalSDK()),
70+
providers: (directory: PathKey | null) =>
71+
loadProvidersQuery(directory, directory === null ? globalSDK() : sdkFor(directory)),
72+
path: (directory: PathKey | null) => loadPathQuery(directory, directory === null ? globalSDK() : sdkFor(directory)),
73+
agents: (directory: PathKey) => loadAgentsQuery(directory, sdkFor(directory)),
74+
mcp: (directory: PathKey) => loadMcpQuery(directory, sdkFor(directory)),
75+
lsp: (directory: PathKey) => loadLspQuery(directory, sdkFor(directory)),
76+
sessions: (directory: PathKey) => ({ queryKey: [directory, "loadSessions"] as const }),
77+
}
78+
}
79+
export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi>
80+
6981
function createGlobalSync() {
7082
const globalSDK = useGlobalSDK()
7183
const language = useLanguage()
@@ -77,12 +89,22 @@ function createGlobalSync() {
7789
const sessionLoads = new Map<string, Promise<void>>()
7890
const sessionMeta = new Map<string, { limit: number }>()
7991

92+
const sdkFor = (directory: string) => {
93+
const key = directoryKey(directory)
94+
const cached = sdkCache.get(key)
95+
if (cached) return cached
96+
const sdk = globalSDK.createClient({
97+
directory,
98+
throwOnError: true,
99+
})
100+
sdkCache.set(key, sdk)
101+
return sdk
102+
}
103+
104+
const queryOptionsApi = makeQueryOptionsApi(() => globalSDK.client, sdkFor)
105+
80106
const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
81-
queries: [
82-
loadGlobalConfigQuery(globalSDK.client),
83-
loadProvidersQuery(null, globalSDK.client),
84-
loadPathQuery(null, globalSDK.client),
85-
],
107+
queries: [queryOptionsApi.globalConfig(), queryOptionsApi.providers(null), queryOptionsApi.path(null)],
86108
}))
87109

88110
const [globalStore, setGlobalStore] = createStore<GlobalStore>({
@@ -181,18 +203,6 @@ function createGlobalSync() {
181203
bootstrapInstance,
182204
})
183205

184-
const sdkFor = (directory: string) => {
185-
const key = directoryKey(directory)
186-
const cached = sdkCache.get(key)
187-
if (cached) return cached
188-
const sdk = globalSDK.createClient({
189-
directory,
190-
throwOnError: true,
191-
})
192-
sdkCache.set(key, sdk)
193-
return sdk
194-
}
195-
196206
const children = createChildStoreManager({
197207
owner,
198208
isBooting: (directory) => booting.has(directory),
@@ -209,7 +219,7 @@ function createGlobalSync() {
209219
clearSessionPrefetchDirectory(key)
210220
},
211221
translate: language.t,
212-
getSdk: sdkFor,
222+
queryOptions: queryOptionsApi,
213223
global: {
214224
provider: globalStore.provider,
215225
},
@@ -239,7 +249,7 @@ function createGlobalSync() {
239249
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
240250
const promise = queryClient
241251
.fetchQuery({
242-
queryKey: loadSessionsQueryKey(key),
252+
...queryOptionsApi.sessions(key),
243253
queryFn: () =>
244254
loadRootSessionsWithFallback({
245255
directory,
@@ -368,7 +378,7 @@ function createGlobalSync() {
368378
setSessionTodo,
369379
vcsCache: children.vcsCache.get(key),
370380
loadLsp: () => {
371-
void queryClient.fetchQuery(loadLspQuery(key, sdkFor(directory)))
381+
void queryClient.fetchQuery(queryOptionsApi.lsp(key))
372382
},
373383
})
374384
})
@@ -426,6 +436,7 @@ function createGlobalSync() {
426436
},
427437
child: children.child,
428438
peek: children.peek,
439+
queryOptions: queryOptionsApi,
429440
// bootstrap,
430441
updateConfig: updateConfigMutation.mutateAsync,
431442
project: projectApi,
@@ -447,3 +458,7 @@ export function useGlobalSync() {
447458
if (!context) throw new Error("useGlobalSync must be used within GlobalSyncProvider")
448459
return context
449460
}
461+
462+
export function useQueryOptions() {
463+
return useGlobalSync().queryOptions
464+
}

packages/app/src/context/global-sync/child-store.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe("createChildStoreManager", () => {
2222
onBootstrap() {},
2323
onDispose() {},
2424
translate: (key) => key,
25-
getSdk: () => null!,
25+
queryOptions: {} as any,
2626
global: { provider: null! },
2727
})
2828

packages/app/src/context/global-sync/child-store.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
22
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
33
import { Persist, persisted } from "@/utils/persist"
4-
import type { OpencodeClient, ProviderListResponse, VcsInfo } from "@opencode-ai/sdk/v2/client"
4+
import type { ProviderListResponse, VcsInfo } from "@opencode-ai/sdk/v2/client"
55
import {
66
DIR_IDLE_TTL_MS,
77
MAX_DIR_STORES,
@@ -15,8 +15,7 @@ import {
1515
} from "./types"
1616
import { canDisposeDirectory, pickDirectoriesToEvict } from "./eviction"
1717
import { useQueries } from "@tanstack/solid-query"
18-
import { loadPathQuery, loadProvidersQuery } from "./bootstrap"
19-
import { loadLspQuery, loadMcpQuery } from "../global-sync"
18+
import { QueryOptionsApi } from "../global-sync"
2019
import { directoryKey, type DirectoryKey } from "./utils"
2120

2221
export function createChildStoreManager(input: {
@@ -26,7 +25,7 @@ export function createChildStoreManager(input: {
2625
onBootstrap: (directory: string) => void
2726
onDispose: (directory: string) => void
2827
translate: (key: string, vars?: Record<string, string | number>) => string
29-
getSdk: (directory: string) => OpencodeClient
28+
queryOptions: QueryOptionsApi
3029
global: {
3130
provider: ProviderListResponse
3231
}
@@ -171,17 +170,15 @@ export function createChildStoreManager(input: {
171170

172171
const init = () =>
173172
createRoot((dispose) => {
174-
const sdk = input.getSdk(directory)
175-
176173
const initialMeta = meta[0].value
177174
const initialIcon = icon[0].value
178175

179176
const [pathQuery, mcpQuery, lspQuery, providerQuery] = useQueries(() => ({
180177
queries: [
181-
loadPathQuery(key, sdk),
182-
loadMcpQuery(key, sdk),
183-
loadLspQuery(key, sdk),
184-
loadProvidersQuery(key, sdk),
178+
input.queryOptions.path(key),
179+
input.queryOptions.mcp(key),
180+
input.queryOptions.lsp(key),
181+
input.queryOptions.providers(key),
185182
],
186183
}))
187184

packages/app/src/context/local.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const migrate = (value: unknown) => {
4444
}
4545

4646
const clone = (value: State | undefined) => {
47-
if (!value) return undefined
47+
if (!value) return
4848
return {
4949
...value,
5050
model: value.model ? { ...value.model } : undefined,
@@ -104,7 +104,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
104104

105105
const pickAgent = (name: string | undefined) => {
106106
const items = list()
107-
if (items.length === 0) return undefined
107+
if (items.length === 0) return
108108
return items.find((item) => item.name === name) ?? items[0]
109109
}
110110

@@ -227,14 +227,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
227227
() => agent.current()?.model,
228228
fallback,
229229
)
230-
if (!item) return undefined
230+
if (!item) return
231231
return models.find(item)
232232
}
233233

234234
const configured = () => {
235235
const item = agent.current()
236236
const model = current()
237-
if (!item || !model) return undefined
237+
if (!item || !model) return
238238
return getConfiguredAgentVariant({
239239
agent: { model: item.model, variant: item.variant },
240240
model: { providerID: model.provider.id, modelID: model.id, variants: model.variants },
@@ -314,11 +314,16 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
314314
configured,
315315
selected,
316316
current() {
317-
return resolveModelVariant({
317+
const resolved = resolveModelVariant({
318318
variants: this.list(),
319319
selected: this.selected(),
320320
configured: this.configured(),
321321
})
322+
if (resolved) return resolved
323+
const model = current()
324+
if (!model) return
325+
const saved = models.variant.get({ providerID: model.provider.id, modelID: model.id })
326+
if (saved && this.list().includes(saved)) return saved
322327
},
323328
list() {
324329
const item = current()
@@ -335,6 +340,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
335340
variant: value ?? null,
336341
})
337342
write({ variant: value ?? null })
343+
if (model) {
344+
models.variant.set({ providerID: model.provider.id, modelID: model.id }, value ?? undefined)
345+
}
338346
})
339347
},
340348
cycle() {

0 commit comments

Comments
 (0)