소스 검색

fix(tui): restore legacy sync consumers (#31908)

Dax 2 달 전
부모
커밋
567d6ed1a3

+ 50 - 45
packages/tui/src/component/dialog-model.tsx

@@ -1,17 +1,17 @@
 import { createMemo, createSignal } from "solid-js"
 import { useLocal } from "../context/local"
-import { map, pipe, filter, sortBy, take } from "remeda"
+import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
 import { DialogSelect } from "../ui/dialog-select"
 import { useDialog } from "../ui/dialog"
 import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
 import { DialogVariant } from "./dialog-variant"
 import * as fuzzysort from "fuzzysort"
 import { useConnected } from "./use-connected"
-import { useData } from "../context/data"
+import { useSync } from "../context/sync"
 
 export function DialogModel(props: { providerID?: string }) {
   const local = useLocal()
-  const data = useData()
+  const sync = useSync()
   const dialog = useDialog()
   const [query, setQuery] = createSignal("")
 
@@ -29,21 +29,19 @@ export function DialogModel(props: { providerID?: string }) {
     function toOptions(items: typeof favorites, category: string) {
       if (!showSections) return []
       return items.flatMap((item) => {
-        const provider = data.location.provider.list()?.find((provider) => provider.id === item.providerID)
+        const provider = sync.data.provider.find((provider) => provider.id === item.providerID)
         if (!provider) return []
-        const model = data.location.model
-          .list()
-          ?.find((model) => model.providerID === item.providerID && model.id === item.modelID)
+        const model = provider.models[item.modelID]
         if (!model) return []
         return [
           {
             key: item,
             value: { providerID: provider.id, modelID: model.id },
-            title: model.name,
+            title: model.name ?? item.modelID,
             description: provider.name,
             category,
             disabled: provider.id === "opencode" && model.id.includes("-nano"),
-            footer: model.cost[0]?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
+            footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
             onSelect: () => {
               onSelect(provider.id, model.id)
             },
@@ -61,43 +59,50 @@ export function DialogModel(props: { providerID?: string }) {
     )
 
     const providerOptions = pipe(
-      data.location.model.list() ?? [],
-      filter((model) => model.status !== "deprecated"),
-      filter((model) => (props.providerID ? model.providerID === props.providerID : true)),
+      sync.data.provider,
       sortBy(
-        (model) => model.providerID !== "opencode",
-        (model) => data.location.provider.list()?.find((provider) => provider.id === model.providerID)?.name ?? "",
-        [(model) => model.time.released, "desc"],
+        (provider) => provider.id !== "opencode",
+        (provider) => provider.name,
+      ),
+      flatMap((provider) =>
+        pipe(
+          provider.models,
+          entries(),
+          filter(([_, info]) => info.status !== "deprecated"),
+          filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
+          map(([model, info]) => ({
+            value: { providerID: provider.id, modelID: model },
+            title: info.name ?? model,
+            releaseDate: info.release_date,
+            description: favorites.some((item) => item.providerID === provider.id && item.modelID === model)
+              ? "(Favorite)"
+              : undefined,
+            category: connected() ? provider.name : undefined,
+            disabled: provider.id === "opencode" && model.includes("-nano"),
+            footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
+            onSelect() {
+              onSelect(provider.id, model)
+            },
+          })),
+          filter((option) => {
+            if (!showSections) return true
+            if (
+              favorites.some(
+                (item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID,
+              )
+            )
+              return false
+            if (
+              recents.some(
+                (item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID,
+              )
+            )
+              return false
+            return true
+          }),
+          (options) => sortModelOptions(options, props.providerID !== undefined),
+        ),
       ),
-      map((model) => ({
-        value: { providerID: model.providerID, modelID: model.id },
-        title: model.name,
-        releaseDate: model.time.released,
-        description: favorites.some((item) => item.providerID === model.providerID && item.modelID === model.id)
-          ? "(Favorite)"
-          : undefined,
-        category: connected()
-          ? data.location.provider.list()?.find((provider) => provider.id === model.providerID)?.name
-          : undefined,
-        disabled: !model.enabled || (model.providerID === "opencode" && model.id.includes("-nano")),
-        footer: model.cost[0]?.input === 0 && model.providerID === "opencode" ? "Free" : undefined,
-        onSelect() {
-          onSelect(model.providerID, model.id)
-        },
-      })),
-      filter((option) => {
-        if (!showSections) return true
-        if (
-          favorites.some((item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID)
-        )
-          return false
-        if (
-          recents.some((item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID)
-        )
-          return false
-        return true
-      }),
-      (options) => sortModelOptions(options, props.providerID !== undefined),
     )
 
     const popularProviders = !connected()
@@ -122,7 +127,7 @@ export function DialogModel(props: { providerID?: string }) {
   })
 
   const provider = createMemo(() =>
-    props.providerID ? data.location.provider.list()?.find((item) => item.id === props.providerID) : null,
+    props.providerID ? sync.data.provider.find((item) => item.id === props.providerID) : null,
   )
 
   const title = createMemo(() => {

+ 1 - 1
packages/tui/src/component/dialog-workspace-create.tsx

@@ -132,7 +132,7 @@ export async function warpWorkspaceSession(input: {
 
   input.project.workspace.set(input.workspaceID)
 
-  await input.sync.bootstrap()
+  await input.sync.bootstrap({ fatal: false }).catch(() => undefined)
 
   const dir = input.project.instance.directory() || input.sync.path.directory
   if (dir) {

+ 1 - 1
packages/tui/src/component/dialog-workspace-list.tsx

@@ -82,7 +82,7 @@ export function DialogWorkspaceList() {
       route.navigate({ type: "home" })
     }
     await project.workspace.sync()
-    await sync.bootstrap()
+    await sync.bootstrap({ fatal: false }).catch(() => undefined)
     setRemoving(undefined)
   }
 

+ 11 - 12
packages/tui/src/component/prompt/autocomplete.tsx

@@ -317,9 +317,10 @@ export function Autocomplete(props: {
       const { lineRange, baseQuery } = extractLineRange(query ?? "")
 
       // Get files from SDK
-      const result = await sdk.client.find.files({
+      const result = await sdk.client.v2.fs.find({
         query: baseQuery,
-        workspace: project.workspace.current(),
+        limit: "20",
+        location: { workspace: project.workspace.current() },
       })
 
       const options: AutocompleteOption[] = []
@@ -329,15 +330,13 @@ export function Autocomplete(props: {
       if (!result.error && result.data) {
         const width = props.anchor().width - 4
         options.push(
-          ...result.data.map((item): AutocompleteOption => {
-            const { filename, url, part } = createFilePart(item, lineRange)
-
-            const isDir = item.endsWith("/")
+          ...result.data.data.map((item): AutocompleteOption => {
+            const { filename, url, part } = createFilePart(item.path, lineRange)
             return {
               display: Locale.truncateMiddle(filename, width),
               value: filename,
-              isDirectory: isDir,
-              path: item,
+              isDirectory: item.type === "directory",
+              path: item.path,
               onSelect: () => {
                 insertPart(filename, part)
               },
@@ -390,15 +389,15 @@ export function Autocomplete(props: {
   })
 
   const agents = createMemo(() => {
-    return (data.location.agent.list() ?? [])
+    return sync.data.agent
       .filter((agent) => !agent.hidden && agent.mode !== "primary")
       .map(
         (agent): AutocompleteOption => ({
-          display: "@" + agent.id,
+          display: "@" + agent.name,
           onSelect: () => {
-            insertPart(agent.id, {
+            insertPart(agent.name, {
               type: "agent",
-              name: agent.id,
+              name: agent.name,
               source: {
                 start: 0,
                 end: 0,

+ 7 - 3
packages/tui/src/component/use-connected.tsx

@@ -1,7 +1,11 @@
 import { createMemo } from "solid-js"
-import { useData } from "../context/data"
+import { useSync } from "../context/sync"
 
 export function useConnected() {
-  const data = useData()
-  return createMemo(() => (data.location.provider.list() ?? []).some((provider) => provider.enabled !== false))
+  const sync = useSync()
+  return createMemo(() =>
+    sync.data.provider.some((provider) =>
+      provider.id !== "opencode" || Object.values(provider.models).some((model) => model.cost?.input !== 0),
+    ),
+  )
 }

+ 18 - 27
packages/tui/src/context/local.tsx

@@ -12,7 +12,6 @@ import { readJson, writeJsonAtomic } from "../util/persistence"
 import { useTheme } from "./theme"
 import { useToast } from "../ui/toast"
 import { useRoute } from "./route"
-import { useData } from "./data"
 
 export type LocalTheme = {
   secondary: RGBA
@@ -52,17 +51,15 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
   name: "Local",
   init: () => {
     const sync = useSync()
-    const data = useData()
     const sdk = useSDK()
     const toast = useToast()
     const theme = useTheme().theme
     const route = useRoute()
     const paths = useTuiPaths()
-    const providers = createMemo(() => data.location.provider.list() ?? [])
-    const models = createMemo(() => data.location.model.list() ?? [])
 
     function isModelValid(model: { providerID: string; modelID: string }) {
-      return models().some((item) => item.providerID === model.providerID && item.id === model.modelID && item.enabled)
+      const provider = sync.data.provider.find((item) => item.id === model.providerID)
+      return !!provider?.models[model.modelID]
     }
 
     function getFirstValidModel(...modelFns: (() => { providerID: string; modelID: string } | undefined)[]) {
@@ -74,18 +71,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
     }
 
     function createAgent() {
-      const all = createMemo(() =>
-        (data.location.agent.list() ?? []).map((agent) => ({
-          ...agent,
-          name: agent.id,
-          native: false,
-          model: agent.model
-            ? { providerID: agent.model.providerID, modelID: agent.model.id, variant: agent.model.variant }
-            : undefined,
-        })),
-      )
-      const agents = createMemo(() => all().filter((agent) => agent.mode !== "subagent" && !agent.hidden))
-      const visibleAgents = createMemo(() => all().filter((agent) => !agent.hidden))
+      const agents = createMemo(() => sync.data.agent.filter((agent) => agent.mode !== "subagent" && !agent.hidden))
+      const visibleAgents = createMemo(() => sync.data.agent.filter((agent) => !agent.hidden))
       const [agentStore, setAgentStore] = createStore({
         current: undefined as string | undefined,
       })
@@ -231,13 +218,15 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
           }
         }
 
-        const model = models().find(
-          (item) => item.enabled && providers().some((provider) => provider.id === item.providerID),
-        )
+        const provider = sync.data.provider[0]
+        if (!provider) return undefined
+        const defaultModel = sync.data.provider_default[provider.id]
+        const firstModel = Object.values(provider.models)[0]
+        const model = defaultModel ?? firstModel?.id
         if (!model) return undefined
         return {
-          providerID: model.providerID,
-          modelID: model.id,
+          providerID: provider.id,
+          modelID: model,
         }
       })
 
@@ -272,12 +261,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
               reasoning: false,
             }
           }
-          const provider = providers().find((item) => item.id === value.providerID)
-          const info = models().find((item) => item.providerID === value.providerID && item.id === value.modelID)
+          const provider = sync.data.provider.find((item) => item.id === value.providerID)
+          const info = provider?.models[value.modelID]
           return {
             provider: provider?.name ?? value.providerID,
             model: info?.name ?? value.modelID,
-            reasoning: false,
+            reasoning: info?.capabilities?.reasoning ?? false,
           }
         }),
         cycle(direction: 1 | -1) {
@@ -383,8 +372,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
           list() {
             const m = currentModel()
             if (!m) return []
-            const info = models().find((item) => item.providerID === m.providerID && item.id === m.modelID)
-            return info?.variants.map((variant) => variant.id) ?? []
+            const provider = sync.data.provider.find((item) => item.id === m.providerID)
+            const info = provider?.models[m.modelID]
+            if (!info?.variants) return []
+            return Object.keys(info.variants)
           },
           set(value: string | undefined) {
             const m = currentModel()

+ 4 - 5
packages/tui/src/feature-plugins/home/footer.tsx

@@ -4,20 +4,19 @@ import { createMemo, Match, Show, Switch } from "solid-js"
 import { abbreviateHome } from "../../runtime"
 import { useTuiPaths } from "../../context/runtime"
 import { useHomeSessionDestination } from "../../routes/home/session-destination"
-import { useData } from "../../context/data"
 
 const id = "internal:home-footer"
 
 function Directory(props: { api: TuiPluginApi }) {
   const theme = () => props.api.theme.current
   const destination = useHomeSessionDestination()
-  const data = useData()
   const paths = useTuiPaths()
   const dir = createMemo(() => {
     const selected = destination?.destination()
-    const directory = !selected || selected.type === "new" ? data.location.default().directory : selected.directory
-    const out = abbreviateHome(directory, paths.home)
-    const branch = directory === (props.api.state.path.directory || paths.cwd) ? props.api.state.vcs?.branch : undefined
+    if (!selected || selected.type === "new") return
+    const out = abbreviateHome(selected.directory, paths.home)
+    const branch =
+      selected.directory === (props.api.state.path.directory || paths.cwd) ? props.api.state.vcs?.branch : undefined
     if (branch) return out + ":" + branch
     return out
   })

+ 5 - 3
packages/tui/src/routes/home/session-destination.tsx

@@ -7,7 +7,8 @@ import {
   type ParentProps,
   type Setter,
 } from "solid-js"
-import { useData } from "../../context/data"
+import { useSync } from "../../context/sync"
+import { useTuiPaths } from "../../context/runtime"
 
 export type HomeSessionDestination = { type: "directory"; directory: string; subdirectory: boolean } | { type: "new" }
 
@@ -20,10 +21,11 @@ type Context = {
 const HomeSessionDestinationContext = createContext<Context>()
 
 export function HomeSessionDestinationProvider(props: ParentProps) {
-  const data = useData()
+  const sync = useSync()
+  const paths = useTuiPaths()
   const [selected, setDestination] = createSignal<HomeSessionDestination>()
   const destination = createMemo<HomeSessionDestination>(
-    () => selected() ?? { type: "directory", directory: data.location.default().directory, subdirectory: false },
+    () => selected() ?? { type: "directory", directory: sync.path.directory || paths.cwd, subdirectory: false },
   )
   return (
     <HomeSessionDestinationContext.Provider