fix(app): separate provider lifetimes and reactive ownership (#33739)
This commit is contained in:
parent
dc569b5a5f
commit
cfd75d62fe
37 changed files with 838 additions and 274 deletions
59
packages/app/src/hooks/provider-catalog.test.ts
Normal file
59
packages/app/src/hooks/provider-catalog.test.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { expect, test } from "bun:test"
|
||||
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
import { selectProviderCatalog } from "./provider-catalog"
|
||||
|
||||
const catalog = (id: string): NormalizedProviderListResponse => ({
|
||||
all: new Map([[id, { id, name: id, source: "api", env: [], options: {}, models: {} }]]),
|
||||
connected: [id],
|
||||
default: { [id]: `${id}-model` },
|
||||
})
|
||||
|
||||
test("selects the ready catalog for an explicit directory", () => {
|
||||
const directory = catalog("directory")
|
||||
|
||||
expect(
|
||||
selectProviderCatalog({
|
||||
explicit: true,
|
||||
directory: "/repo",
|
||||
catalog: { ready: true, providers: directory },
|
||||
}),
|
||||
).toBe(directory)
|
||||
})
|
||||
|
||||
test("returns an empty catalog while an explicit directory is unresolved", () => {
|
||||
expect(selectProviderCatalog({ explicit: true })).toEqual({ all: new Map(), connected: [], default: {} })
|
||||
expect(
|
||||
selectProviderCatalog({
|
||||
explicit: true,
|
||||
directory: "/repo",
|
||||
catalog: { ready: false, providers: catalog("directory") },
|
||||
}),
|
||||
).toEqual({ all: new Map(), connected: [], default: {} })
|
||||
})
|
||||
|
||||
test("uses the route catalog when it is ready", () => {
|
||||
const directory = catalog("directory")
|
||||
|
||||
expect(
|
||||
selectProviderCatalog({
|
||||
explicit: false,
|
||||
directory: "/repo",
|
||||
catalog: { ready: true, providers: directory },
|
||||
global: catalog("global"),
|
||||
}),
|
||||
).toBe(directory)
|
||||
})
|
||||
|
||||
test("falls back to the global catalog for route consumers", () => {
|
||||
const global = catalog("global")
|
||||
|
||||
expect(selectProviderCatalog({ explicit: false, global })).toBe(global)
|
||||
expect(
|
||||
selectProviderCatalog({
|
||||
explicit: false,
|
||||
directory: "/repo",
|
||||
catalog: { ready: false, providers: catalog("directory") },
|
||||
global,
|
||||
}),
|
||||
).toBe(global)
|
||||
})
|
||||
27
packages/app/src/hooks/provider-catalog.ts
Normal file
27
packages/app/src/hooks/provider-catalog.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
|
||||
const emptyProviderCatalog: NormalizedProviderListResponse = { all: new Map(), connected: [], default: {} }
|
||||
|
||||
type DirectoryCatalog = {
|
||||
ready: boolean
|
||||
providers: NormalizedProviderListResponse
|
||||
}
|
||||
|
||||
type ProviderCatalogInput =
|
||||
| {
|
||||
explicit: true
|
||||
directory?: string
|
||||
catalog?: DirectoryCatalog
|
||||
}
|
||||
| {
|
||||
explicit: false
|
||||
directory?: string
|
||||
catalog?: DirectoryCatalog
|
||||
global: NormalizedProviderListResponse
|
||||
}
|
||||
|
||||
export function selectProviderCatalog(input: ProviderCatalogInput) {
|
||||
if (input.directory && input.catalog?.ready) return input.catalog.providers
|
||||
if (input.explicit) return emptyProviderCatalog
|
||||
return input.global
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@ import { useServerSync } from "@/context/server-sync"
|
|||
import { decode64 } from "@/utils/base64"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { Iterable, pipe } from "effect"
|
||||
import { createMemo } from "solid-js"
|
||||
import type { Accessor } from "solid-js"
|
||||
import { selectProviderCatalog } from "./provider-catalog"
|
||||
|
||||
export const popularProviders = [
|
||||
"opencode",
|
||||
|
|
@ -16,16 +17,25 @@ export const popularProviders = [
|
|||
]
|
||||
const popularProviderSet = new Set(popularProviders)
|
||||
|
||||
export function useProviders() {
|
||||
export function useProviders(directory?: Accessor<string | undefined>) {
|
||||
const serverSync = useServerSync()
|
||||
const params = useParams()
|
||||
const dir = createMemo(() => decode64(params.dir) ?? "")
|
||||
const dir = () => (directory ? directory() : decode64(params.dir))
|
||||
const providers = () => {
|
||||
if (dir()) {
|
||||
const [projectStore] = serverSync().child(dir())
|
||||
if (projectStore.provider_ready) return projectStore.provider
|
||||
}
|
||||
return serverSync().data.provider
|
||||
const value = dir()
|
||||
const projectStore = value ? serverSync().child(value)[0] : undefined
|
||||
if (directory)
|
||||
return selectProviderCatalog({
|
||||
explicit: true,
|
||||
directory: value,
|
||||
catalog: projectStore && { ready: projectStore.provider_ready, providers: projectStore.provider },
|
||||
})
|
||||
return selectProviderCatalog({
|
||||
explicit: false,
|
||||
directory: value,
|
||||
catalog: projectStore && { ready: projectStore.provider_ready, providers: projectStore.provider },
|
||||
global: serverSync().data.provider,
|
||||
})
|
||||
}
|
||||
return {
|
||||
all: () => providers().all,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue