Răsfoiți Sursa

fix(app): use current default model (#38603)

Brendan Allan 1 zi în urmă
părinte
comite
941e71dbbb

+ 5 - 0
packages/app/src/context/global-sync/utils.test.ts

@@ -103,6 +103,7 @@ describe("normalizeProviderList", () => {
     )
 
     expect(result.connected).toEqual(["openai"])
+    expect(result.defaultModel).toEqual({ providerID: "openai", modelID: "gpt-5" })
     expect(result.default).toEqual({ openai: "gpt-5" })
     expect(result.all.get("openai")?.models["gpt-old"]).toBeUndefined()
     expect(result.all.get("openai")?.models["gpt-5"]).toMatchObject({
@@ -113,6 +114,10 @@ describe("normalizeProviderList", () => {
       variants: { high: {} },
     })
   })
+
+  test("preserves an empty current default", () => {
+    expect(normalizeProviderList([] as ProviderListOutput["data"], [], null).defaultModel).toBeNull()
+  })
 })
 
 describe("directoryKey", () => {

+ 1 - 0
packages/app/src/context/global-sync/utils.ts

@@ -139,6 +139,7 @@ export function normalizeProviderList(
   return {
     all,
     connected: providers.map((provider) => provider.id),
+    defaultModel: defaultModel ? { providerID: defaultModel.providerID, modelID: defaultModel.id } : null,
     default: Object.fromEntries(
       providers.flatMap((provider) => {
         const model =

+ 3 - 4
packages/app/src/context/local.tsx

@@ -6,6 +6,7 @@ import { createStore } from "solid-js/store"
 import { useModels } from "@/context/models"
 import { useSettings } from "@/context/settings"
 import { useProviders } from "@/hooks/use-providers"
+import { resolveDefaultModel } from "@/hooks/provider-catalog"
 import { Persist, persisted } from "@/utils/persist"
 import { hasCustomAgent, resolveAgent } from "./local-agent"
 import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
@@ -149,10 +150,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
     })
 
     const configuredModel = () => {
-      const configured = sync().data.config.model
-      if (!configured) return
-      const [providerID, modelID] = configured.split("/")
-      const model = { providerID, modelID }
+      const model = resolveDefaultModel(providers.defaultModel(), sync().data.config.model)
+      if (!model) return
       if (validModel(model)) return model
     }
 

+ 19 - 1
packages/app/src/hooks/provider-catalog.test.ts

@@ -1,6 +1,6 @@
 import { expect, test } from "bun:test"
 import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
-import { selectProviderCatalog } from "./provider-catalog"
+import { resolveDefaultModel, selectProviderCatalog } from "./provider-catalog"
 
 const catalog = (id: string): NormalizedProviderListResponse => ({
   all: new Map([[id, { id, name: id, source: "api", env: [], options: {}, models: {} }]]),
@@ -57,3 +57,21 @@ test("falls back to the global catalog for route consumers", () => {
     }),
   ).toBe(global)
 })
+
+test("uses the current server default model", () => {
+  expect(resolveDefaultModel({ providerID: "openai", modelID: "gpt-5" }, "anthropic/claude")).toEqual({
+    providerID: "openai",
+    modelID: "gpt-5",
+  })
+})
+
+test("does not use legacy config when the current server has no default", () => {
+  expect(resolveDefaultModel(null, "anthropic/claude")).toBeUndefined()
+})
+
+test("uses config for legacy servers", () => {
+  expect(resolveDefaultModel(undefined, "anthropic/claude")).toEqual({
+    providerID: "anthropic",
+    modelID: "claude",
+  })
+})

+ 10 - 0
packages/app/src/hooks/provider-catalog.ts

@@ -25,3 +25,13 @@ export function selectProviderCatalog(input: ProviderCatalogInput) {
   if (input.explicit) return emptyProviderCatalog
   return input.global
 }
+
+export function resolveDefaultModel(
+  current: NormalizedProviderListResponse["defaultModel"],
+  legacy: string | undefined,
+) {
+  if (current !== undefined) return current ?? undefined
+  if (!legacy) return undefined
+  const [providerID, modelID] = legacy.split("/")
+  return { providerID, modelID }
+}

+ 1 - 0
packages/app/src/hooks/use-providers.ts

@@ -41,6 +41,7 @@ export function useProviders(directory: Accessor<string | undefined>) {
   return {
     all: () => providers().all,
     default: () => providers().default,
+    defaultModel: () => providers().defaultModel,
     popular: () =>
       pipe(
         providers().all,

+ 3 - 4
packages/app/src/pages/session/composer/prompt-model-selection.ts

@@ -6,6 +6,7 @@ import { usePrompt } from "@/context/prompt"
 import { useSDK } from "@/context/sdk"
 import { useSync } from "@/context/sync"
 import { useProviders } from "@/hooks/use-providers"
+import { resolveDefaultModel } from "@/hooks/provider-catalog"
 
 export function createPromptModelSelection(input: { agent: () => { model?: ModelKey; variant?: string } | undefined }) {
   const sdk = useSDK()
@@ -21,10 +22,8 @@ export function createPromptModelSelection(input: { agent: () => { model?: Model
   }
 
   const configured = () => {
-    const value = sync().data.config.model
-    if (!value) return
-    const [providerID, modelID] = value.split("/")
-    const model = { providerID, modelID }
+    const model = resolveDefaultModel(providers.defaultModel(), sync().data.config.model)
+    if (!model) return
     if (valid(model)) return model
   }
 

+ 4 - 0
packages/session-ui/src/context/data.tsx

@@ -5,6 +5,10 @@ import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
 
 export type NormalizedProviderListResponse = {
   all: Map<string, Provider>
+  defaultModel?: {
+    providerID: string
+    modelID: string
+  } | null
   default: {
     [key: string]: string
   }