refactor(app): extract refcount utility and clean up server sdk context (#29155)

This commit is contained in:
Brendan Allan
2026-05-25 10:00:00 +08:00
committed by GitHub
parent 03bb53c389
commit 9495ecd536
5 changed files with 264 additions and 278 deletions
+5 -3
View File
@@ -8,10 +8,11 @@ import {
getSessionPrefetchPromise, getSessionPrefetchPromise,
setSessionPrefetch, setSessionPrefetch,
} from "./global-sync/session-prefetch" } from "./global-sync/session-prefetch"
import { useServerSync } from "./server-sync" import { createServerSyncContext, useServerSync } from "./server-sync"
import type { Message, OpencodeClient, Part } from "@opencode-ai/sdk/v2/client" import type { Message, OpencodeClient, Part } from "@opencode-ai/sdk/v2/client"
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache" import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
import { diffs as list, message as clean } from "@/utils/diffs" import { diffs as list, message as clean } from "@/utils/diffs"
import { useServerSDK } from "./server-sdk"
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"]) const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
@@ -164,8 +165,9 @@ function setOptimisticRemove(setStore: (...args: unknown[]) => void, input: Opti
}) })
} }
export const createDirSyncContext = (client: OpencodeClient, directory: string) => { export const createDirSyncContext = (directory: string, serverSync: ReturnType<typeof createServerSyncContext>) => {
const serverSync = useServerSync() const serverSDK = useServerSDK()
const client = serverSDK.createClient({ directory, throwOnError: true })
type Child = ReturnType<(typeof serverSync)["child"]> type Child = ReturnType<(typeof serverSync)["child"]>
type Setter = Child[1] type Setter = Child[1]
+1 -1
View File
@@ -6,6 +6,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
init: (props: { directory: string }) => { init: (props: { directory: string }) => {
const serverSDK = useServerSDK() const serverSDK = useServerSDK()
return serverSDK.createDirSyncContext(props.directory) return serverSDK.createDirSdkContext(props.directory)
}, },
}) })
+23 -42
View File
@@ -6,23 +6,20 @@ import { batch, onCleanup, onMount } from "solid-js"
import { createSdkForServer } from "@/utils/server" import { createSdkForServer } from "@/utils/server"
import { useLanguage } from "./language" import { useLanguage } from "./language"
import { usePlatform } from "./platform" import { usePlatform } from "./platform"
import { useServer } from "./server" import { ServerConnection, useServer } from "./server"
import { createRefCountMap } from "@/utils/refcount"
const isAbortError = (error: unknown) => const isAbortError = (error: unknown) =>
error !== null && typeof error === "object" && "name" in error && error.name === "AbortError" error !== null && typeof error === "object" && "name" in error && error.name === "AbortError"
export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({ function createServerSdkContext(server: ServerConnection.Any) {
name: "GlobalSDK",
init: () => {
const language = useLanguage()
const server = useServer()
const platform = usePlatform() const platform = usePlatform()
const abort = new AbortController() const abort = new AbortController()
const eventFetch = (() => { const eventFetch = (() => {
if (!platform.fetch || !server.current) return if (!platform.fetch || !server) return
try { try {
const url = new URL(server.current.http.url) const url = new URL(server.http.url)
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1"
if (url.protocol === "http:" && !loopback) return platform.fetch if (url.protocol === "http:" && !loopback) return platform.fetch
} catch { } catch {
@@ -30,13 +27,10 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
} }
})() })()
const currentServer = server.current
if (!currentServer) throw new Error(language.t("error.serverSDK.noServerAvailable"))
const eventSdk = createSdkForServer({ const eventSdk = createSdkForServer({
signal: abort.signal, signal: abort.signal,
fetch: eventFetch, fetch: eventFetch,
server: currentServer.http, server: server.http,
}) })
const emitter = createGlobalEmitter<{ const emitter = createGlobalEmitter<{
[key: string]: Event [key: string]: Event
@@ -142,7 +136,7 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
if (streamErrorLogged) return if (streamErrorLogged) return
streamErrorLogged = true streamErrorLogged = true
console.error("[global-sdk] event stream error", { console.error("[global-sdk] event stream error", {
url: currentServer.http.url, url: server.http.url,
fetch: eventFetch ? "platform" : "webview", fetch: eventFetch ? "platform" : "webview",
error, error,
}) })
@@ -184,7 +178,7 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
if (!aborted(error) && !streamErrorLogged) { if (!aborted(error) && !streamErrorLogged) {
streamErrorLogged = true streamErrorLogged = true
console.error("[global-sdk] event stream failed", { console.error("[global-sdk] event stream failed", {
url: currentServer.http.url, url: server.http.url,
fetch: eventFetch ? "platform" : "webview", fetch: eventFetch ? "platform" : "webview",
error, error,
}) })
@@ -227,16 +221,13 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
}) })
const sdk = createSdkForServer({ const sdk = createSdkForServer({
server: server.current.http, server: server.http,
fetch: platform.fetch, fetch: platform.fetch,
throwOnError: true, throwOnError: true,
}) })
const dirSyncContexts = new Map<string, ReturnType<typeof createDirSdkContext>>()
const dirSdkContextRefCounts = new Map<string, number>()
return { return {
url: currentServer.http.url, url: server.http.url,
client: sdk, client: sdk,
event: { event: {
on: emitter.on.bind(emitter), on: emitter.on.bind(emitter),
@@ -244,34 +235,26 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
start, start,
}, },
createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) { createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
const s = server.current
if (!s) throw new Error(language.t("error.serverSDK.serverNotAvailable"))
return createSdkForServer({ return createSdkForServer({
server: s.http, server: server.http,
fetch: platform.fetch, fetch: platform.fetch,
...opts, ...opts,
}) })
}, },
createDirSyncContext: (directory: string) => {
onCleanup(() => {
dirSdkContextRefCounts.set(directory, (dirSdkContextRefCounts.get(directory) ?? 0) - 1)
if (dirSdkContextRefCounts.get(directory) === 0) {
dirSyncContexts.delete(directory)
dirSdkContextRefCounts.delete(directory)
} }
}) }
const cached = dirSyncContexts.get(directory) export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({
if (cached) { name: "ServerSDK",
dirSdkContextRefCounts.set(directory, (dirSdkContextRefCounts.get(directory) ?? 0) + 1) init: () => {
return cached const language = useLanguage()
} const server = useServer()
const ctx = createDirSdkContext(directory)
dirSyncContexts.set(directory, ctx)
dirSdkContextRefCounts.set(directory, 1)
return ctx if (!server.current) throw new Error(language.t("error.serverSDK.noServerAvailable"))
}, const sdk = createServerSdkContext(server.current)
return {
...sdk,
createDirSdkContext: createRefCountMap((dir) => createDirSdkContext(dir, sdk)),
} }
}, },
}) })
@@ -280,9 +263,7 @@ type SDKEventMap = {
[key in Event["type"]]: Extract<Event, { type: key }> [key in Event["type"]]: Extract<Event, { type: key }>
} }
function createDirSdkContext(directory: string) { function createDirSdkContext(directory: string, serverSDK: ReturnType<typeof createServerSdkContext>) {
const serverSDK = useServerSDK()
const client = serverSDK.createClient({ const client = serverSDK.createClient({
directory, directory,
throwOnError: true, throwOnError: true,
+13 -36
View File
@@ -29,7 +29,8 @@ import { createRefreshQueue } from "./global-sync/queue"
import { directoryKey } from "./global-sync/utils" import { directoryKey } from "./global-sync/utils"
import { PathKey } from "@/utils/path-key" import { PathKey } from "@/utils/path-key"
import { createDirSyncContext } from "./directory-sync" import { createDirSyncContext } from "./directory-sync"
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context" import { createSimpleContext, NormalizedProviderListResponse } from "@opencode-ai/ui/context"
import { createRefCountMap } from "@/utils/refcount"
type GlobalStore = { type GlobalStore = {
ready: boolean ready: boolean
@@ -72,7 +73,7 @@ function makeQueryOptionsApi(serverSDK: () => OpencodeClient, sdkFor: (dir: Path
} }
export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi> export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi>
function createServerSyncContext() { export function createServerSyncContext() {
const serverSDK = useServerSDK() const serverSDK = useServerSDK()
const language = useLanguage() const language = useLanguage()
const owner = getOwner() const owner = getOwner()
@@ -425,9 +426,6 @@ function createServerSyncContext() {
}, },
})) }))
const dirSyncContexts = new Map<string, ReturnType<typeof createDirSyncContext>>()
const dirSyncContextRefCounts = new Map<string, number>()
return { return {
data: globalStore, data: globalStore,
set, set,
@@ -446,41 +444,20 @@ function createServerSyncContext() {
todo: { todo: {
set: setSessionTodo, set: setSessionTodo,
}, },
createDirSyncContext: (directory: string) => {
onCleanup(() => {
dirSyncContextRefCounts.set(directory, (dirSyncContextRefCounts.get(directory) ?? 0) - 1)
if (dirSyncContextRefCounts.get(directory) === 0) {
dirSyncContexts.delete(directory)
dirSyncContextRefCounts.delete(directory)
} }
}) }
const cached = dirSyncContexts.get(directory) export const { use: useServerSync, provider: ServerSyncProvider } = createSimpleContext({
if (cached) { name: "ServerSync",
dirSyncContextRefCounts.set(directory, (dirSyncContextRefCounts.get(directory) ?? 0) + 1) init: () => {
return cached const sync = createServerSyncContext()
return {
...sync,
createDirSyncContext: createRefCountMap((dir) => createDirSyncContext(dir, sync)),
} }
const ctx = createDirSyncContext(serverSDK.createClient({ directory, throwOnError: true }), directory)
dirSyncContexts.set(directory, ctx)
dirSyncContextRefCounts.set(directory, 1)
return ctx
}, },
} })
}
const ServerSyncContext = createContext<ReturnType<typeof createServerSyncContext>>()
export function ServerSyncProvider(props: ParentProps) {
const value = createServerSyncContext()
return <ServerSyncContext.Provider value={value}>{props.children}</ServerSyncContext.Provider>
}
export function useServerSync() {
const context = useContext(ServerSyncContext)
if (!context) throw new Error("useServerSync must be used within ServerSyncProvider")
return context
}
export function useQueryOptions() { export function useQueryOptions() {
return useServerSync().queryOptions return useServerSync().queryOptions
+26
View File
@@ -0,0 +1,26 @@
import { onCleanup } from "solid-js"
export function createRefCountMap<T>(create: (key: string) => T) {
const items = new Map<string, T>()
const refCounts = new Map<string, number>()
return (key: string) => {
onCleanup(() => {
refCounts.set(key, (refCounts.get(key) ?? 0) - 1)
if (refCounts.get(key) === 0) {
items.delete(key)
refCounts.delete(key)
}
})
const cached = items.get(key)
if (cached) {
refCounts.set(key, (refCounts.get(key) ?? 0) + 1)
return cached
}
const item = create(key)
items.set(key, item)
refCounts.set(key, 1)
return item
}
}