diff --git a/__tests__/api/settings-service.test.ts b/__tests__/api/settings-service.test.ts index a7bb0ce74c..85364c2d55 100644 --- a/__tests__/api/settings-service.test.ts +++ b/__tests__/api/settings-service.test.ts @@ -53,7 +53,7 @@ describe("SettingsService", () => { // Should have normalized settings with derived fields expect(settings.agent).toBe("CodeActAgent"); - expect(settings.llm_model).toBe("openhands/kimi-k3"); + expect(settings.llm_model).toBe("openai/gpt-5.6-sol"); expect(settings.confirmation_mode).toBe(false); expect(settings.security_analyzer).toBe("llm"); }); diff --git a/__tests__/components/features/chat/components/chat-input-llm-profile-picker.test.tsx b/__tests__/components/features/chat/components/chat-input-llm-profile-picker.test.tsx index 6bd0193d89..6544dc9070 100644 --- a/__tests__/components/features/chat/components/chat-input-llm-profile-picker.test.tsx +++ b/__tests__/components/features/chat/components/chat-input-llm-profile-picker.test.tsx @@ -101,27 +101,29 @@ describe("ChatInputLlmProfilePicker", () => { profiles: [ { name: "Free", - model: "openhands/glm-5.2", + model: "openhands/deepseek-v4-flash", base_url: null, api_key_set: true, }, ], currentProfileName: "Free", - currentProfileModel: "openhands/glm-5.2", + currentProfileModel: "openhands/deepseek-v4-flash", }), ); renderWithProviders(); fireEvent.click(screen.getByTestId("chat-input-llm-profile")); - expect(screen.getByText("OpenHands GLM-5.2 (free)")).toBeInTheDocument(); + expect( + screen.getByText("OpenHands DeepSeek V4 Flash (free)"), + ).toBeInTheDocument(); }); it("labels a free OpenHands route in the read-only profile menu", () => { useChatInputLlmProfileStateMock.mockReturnValue( state({ canSwitchProfile: false, - currentProfileModel: "openhands/glm-5.2", + currentProfileModel: "openhands/deepseek-v4-flash", }), ); @@ -130,7 +132,7 @@ describe("ChatInputLlmProfilePicker", () => { expect( screen.getByTestId("chat-input-llm-profile-current"), - ).toHaveTextContent("OpenHands GLM-5.2 (free)"); + ).toHaveTextContent("OpenHands DeepSeek V4 Flash (free)"); }); it("links to the LLM profiles settings page", () => { diff --git a/__tests__/components/features/chat/model-messages.test.tsx b/__tests__/components/features/chat/model-messages.test.tsx index 0ffb0f4d42..699e346072 100644 --- a/__tests__/components/features/chat/model-messages.test.tsx +++ b/__tests__/components/features/chat/model-messages.test.tsx @@ -110,7 +110,7 @@ describe("ModelMessages", () => { useModelStore.getState().show(CONVERSATION_ID, "event-1", [ { name: "free", - model: "openhands/glm-5.2", + model: "openhands/deepseek-v4-flash", base_url: null, api_key_set: true, }, @@ -129,7 +129,7 @@ describe("ModelMessages", () => { ); expect( - screen.getByText(/model:\s+OpenHands GLM-5\.2 \(free\)/), + screen.getByText(/model:\s+OpenHands DeepSeek V4 Flash \(free\)/), ).toBeInTheDocument(); }); }); diff --git a/__tests__/components/features/conversation-panel/conversation-card.test.tsx b/__tests__/components/features/conversation-panel/conversation-card.test.tsx index 2d415cb8b5..55cf7f5473 100644 --- a/__tests__/components/features/conversation-panel/conversation-card.test.tsx +++ b/__tests__/components/features/conversation-panel/conversation-card.test.tsx @@ -198,8 +198,12 @@ describe("ConversationCard", () => { const model = screen.getByTestId("conversation-card-agent-chip"); const tags = screen.getByTestId("conversation-card-tag-chips"); - expect(repo.compareDocumentPosition(model) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); - expect(model.compareDocumentPosition(tags) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect( + repo.compareDocumentPosition(model) & Node.DOCUMENT_POSITION_FOLLOWING, + ).toBeTruthy(); + expect( + model.compareDocumentPosition(tags) & Node.DOCUMENT_POSITION_FOLLOWING, + ).toBeTruthy(); }); it("renders the workspace folder name when no repository is selected", () => { @@ -985,10 +989,7 @@ describe("ConversationCard", () => { const chip = screen.getByTestId("conversation-card-agent-chip"); expect(chip).toHaveTextContent("Claude Opus (1M)"); - expect(chip).toHaveAttribute( - "title", - "Claude Code · Claude Opus (1M)", - ); + expect(chip).toHaveAttribute("title", "Claude Code · Claude Opus (1M)"); }); it("falls back to the provider display name for an ACP conversation with no model", () => { @@ -1098,7 +1099,7 @@ describe("ConversationCard", () => { ); const chip = screen.getByTestId("conversation-card-agent-chip"); - expect(chip).toHaveTextContent("OpenHands GLM-5.2 (free)"); + expect(chip).toHaveTextContent("glm-5.2"); expect(chip).toHaveAttribute("title", "openhands/glm-5.2"); }); diff --git a/__tests__/components/modals/settings/model-selector-openhands.test.tsx b/__tests__/components/modals/settings/model-selector-openhands.test.tsx index 5aeb348a11..523f6d05c7 100644 --- a/__tests__/components/modals/settings/model-selector-openhands.test.tsx +++ b/__tests__/components/modals/settings/model-selector-openhands.test.tsx @@ -69,31 +69,25 @@ describe("ModelSelector — OpenHands provider display", () => { it("makes clear which OpenHands models are free", async () => { const user = userEvent.setup(); - renderWithQuery(); + renderWithQuery(); await waitFor(() => { - expect( - screen.getByTestId("openhands-free-models-note"), - ).toHaveTextContent("openhands/glm-5.2"); }); expect(screen.getByTestId("openhands-free-models-note")).toHaveTextContent( "openhands/deepseek-v4-flash", ); - expect(screen.getByTestId("openhands-free-models-note")).toHaveTextContent( - "openhands/minimax-m2.7", - ); expect(screen.getByTestId("selected-free-model-badge")).toHaveTextContent( "Free", ); await user.click(screen.getByLabelText("LLM$MODEL")); - expect(screen.getAllByText("Free")).toHaveLength(4); - expect(screen.getByLabelText("LLM$MODEL")).toHaveValue("glm-5.2"); + expect(screen.getAllByText("Free")).toHaveLength(2); + expect(screen.getByLabelText("LLM$MODEL")).toHaveValue("deepseek-v4-flash"); - await user.click(screen.getByText("minimax-m2.7")); + await user.click(screen.getByRole("option", { name: /deepseek-v4-flash/ })); - expect(screen.getByLabelText("LLM$MODEL")).toHaveValue("minimax-m2.7"); + expect(screen.getByLabelText("LLM$MODEL")).toHaveValue("deepseek-v4-flash"); expect(screen.getByTestId("selected-free-model-badge")).toHaveTextContent( "Free", ); diff --git a/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx b/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx index eca4cadb61..93732de9a2 100644 --- a/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx +++ b/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx @@ -344,12 +344,12 @@ describe("LlmSettingsLocalView", () => { // Should be in create view expect(screen.getByTestId("profile-name-input")).toBeInTheDocument(); - // The profile name is auto-derived from the prefilled free default model. + // The profile name is auto-derived from the prefilled default model. const nameInput = screen.getByTestId("profile-name-input"); - expect(nameInput).toHaveValue("kimi-k3"); + expect(nameInput).toHaveValue("gpt-5.6-sol"); expect(screen.getByTestId("mock-basic-model-input")).toHaveValue( - "openhands/kimi-k3", + "openai/gpt-5.6-sol", ); }); @@ -363,7 +363,7 @@ describe("LlmSettingsLocalView", () => { // The profile name starts from the free-model derived default. const nameInput = screen.getByTestId("profile-name-input"); - expect(nameInput).toHaveValue("kimi-k3"); + expect(nameInput).toHaveValue("gpt-5.6-sol"); // Go back to list await user.click(screen.getByTestId("back-to-profiles")); @@ -375,7 +375,7 @@ describe("LlmSettingsLocalView", () => { // The profile name should return to the free-model derived default // again (fresh form). const freshNameInput = screen.getByTestId("profile-name-input"); - expect(freshNameInput).toHaveValue("kimi-k3"); + expect(freshNameInput).toHaveValue("gpt-5.6-sol"); }); it("does not carry over values from edit mode to create mode", async () => { @@ -390,7 +390,7 @@ describe("LlmSettingsLocalView", () => { // Should be in create view with the free-model derived profile name. const nameInput = screen.getByTestId("profile-name-input"); - expect(nameInput).toHaveValue("kimi-k3"); + expect(nameInput).toHaveValue("gpt-5.6-sol"); // The key "new-profile" should be used, ensuring a fresh form mount // that doesn't inherit any existing profile data diff --git a/__tests__/components/settings/llm-profiles/profile-row.test.tsx b/__tests__/components/settings/llm-profiles/profile-row.test.tsx index ef91c719ef..6be24c66d6 100644 --- a/__tests__/components/settings/llm-profiles/profile-row.test.tsx +++ b/__tests__/components/settings/llm-profiles/profile-row.test.tsx @@ -56,14 +56,13 @@ describe("ProfileRow", () => { render( , ); - expect(screen.getByText("OpenHands GLM-5.2 (free)")).toHaveAttribute( - "title", - "openhands/glm-5.2", - ); + expect( + screen.getByText("OpenHands DeepSeek V4 Flash (free)"), + ).toHaveAttribute("title", "openhands/deepseek-v4-flash"); }); it("does not display model when null", () => { diff --git a/__tests__/hooks/chat/use-slash-command.test.ts b/__tests__/hooks/chat/use-slash-command.test.ts index 7c06d7cbdf..116fafec48 100644 --- a/__tests__/hooks/chat/use-slash-command.test.ts +++ b/__tests__/hooks/chat/use-slash-command.test.ts @@ -156,7 +156,7 @@ describe("useSlashCommand", () => { }, { name: "free", - model: "openhands/glm-5.2", + model: "openhands/deepseek-v4-flash", base_url: null, api_key_set: true, }, @@ -180,7 +180,7 @@ describe("useSlashCommand", () => { expect( result.current.filteredItems.find((i) => i.command === "/model free") ?.skill.content, - ).toBe("Switch to OpenHands GLM-5.2 (free)"); + ).toBe("Switch to OpenHands DeepSeek V4 Flash (free)"); }); it("filters saved LLM profile suggestions by profile name or model", () => { diff --git a/__tests__/utils/format-model-name.test.ts b/__tests__/utils/format-model-name.test.ts index 015177edfc..66b4f15ec8 100644 --- a/__tests__/utils/format-model-name.test.ts +++ b/__tests__/utils/format-model-name.test.ts @@ -17,10 +17,7 @@ describe("formatNativeModelName", () => { it("labels only configured OpenHands free-model routes as free", () => { expect(Object.keys(FREE_OPENHANDS_MODELS)).toEqual([ - "openhands/kimi-k3", - "openhands/glm-5.2", "openhands/deepseek-v4-flash", - "openhands/minimax-m2.7", ]); for (const [model, label] of Object.entries(FREE_OPENHANDS_MODELS)) { @@ -39,9 +36,7 @@ describe("formatNativeModelName", () => { }); it("keeps free OpenHands labels on native conversation chips", () => { - expect(formatNativeModelName("openhands/glm-5.2")).toBe( - FREE_OPENHANDS_MODELS["openhands/glm-5.2"], - ); + expect(formatNativeModelName("openhands/glm-5.2")).toBe("glm-5.2"); expect(formatNativeModelName("openhands/deepseek-v4-flash")).toBe( FREE_OPENHANDS_MODELS["openhands/deepseek-v4-flash"], ); diff --git a/specs/llm-defaults.md b/specs/llm-defaults.md index 4d10b1b45b..a5acdc1095 100644 --- a/specs/llm-defaults.md +++ b/specs/llm-defaults.md @@ -3,7 +3,8 @@ --- ### LLD-001: Frontend always sends its chosen default model -- [x] When the agent-server returns an absent or empty `llm.model` (e.g. because the user has never saved settings), the frontend adapter shall substitute `DEFAULT_SETTINGS.llm_model` (`"openhands/kimi-k3"`) before sending the conversation-start request. + +- [x] When the agent-server returns an absent or empty `llm.model` (e.g. because the user has never saved settings), the frontend adapter shall substitute `DEFAULT_SETTINGS.llm_model` (`"openai/gpt-5.6-sol"`) before sending the conversation-start request. - [x] The frontend shall never rely on the agent-server SDK's own default model (`gpt-5.5`); it shall always send an explicit model value. - [x] Whitespace-only model strings shall be treated as absent and fall back to the default. - [x] The same guard applies when LLM settings arrive via `encryptedAgentSettings` (the conversation-start encrypted payload path). diff --git a/src/components/features/onboarding/steps/setup-llm-step.tsx b/src/components/features/onboarding/steps/setup-llm-step.tsx index bf2452bb3f..b7be1d5644 100644 --- a/src/components/features/onboarding/steps/setup-llm-step.tsx +++ b/src/components/features/onboarding/steps/setup-llm-step.tsx @@ -16,16 +16,12 @@ interface SetupLlmStepProps { } /** - * Pre-fills the LLM form with the OpenHands provider's free default model - * (`openhands/kimi-k3`), matching `DEFAULT_SETTINGS.llm_model`. The OpenHands - * provider is the agent the user just selected, so the onboarding override - * keeps the LLM provider aligned with that choice rather than silently - * switching to OpenAI. Canvas stores provider-qualified LiteLLM model ids, - * so the override uses this OpenHands-prefixed model id, and keeping it as an - * explicit override marks the model dirty so the Next button persists the - * suggested default immediately. + * Pre-fills the LLM form with the OpenAI GPT-5.6 Sol default + * (`openai/gpt-5.6-sol`), matching `DEFAULT_SETTINGS.llm_model`. The explicit + * override marks the model dirty so the Next button persists the suggested + * default immediately. */ -export const ONBOARDING_DEFAULT_LLM_MODEL = "openhands/kimi-k3"; +export const ONBOARDING_DEFAULT_LLM_MODEL = "openai/gpt-5.6-sol"; /** * Step 2: embed the LLM settings form. The screen runs in `embedded` diff --git a/src/i18n/translation.json b/src/i18n/translation.json index 976c8dca56..d77bd974e5 100644 --- a/src/i18n/translation.json +++ b/src/i18n/translation.json @@ -30583,21 +30583,21 @@ "uk": "Налаштуйте свій LLM" }, "ONBOARDING$LLM_SUBTITLE": { - "en": "Pick a provider and model. We've pre-selected OpenHands GLM-5.2.", - "ja": "プロバイダーとモデルを選択してください。OpenHands GLM-5.2 を事前に選択しています。", - "zh-CN": "选择提供商和模型。我们已为你预先选择 OpenHands GLM-5.2。", - "zh-TW": "選擇供應商與模型。我們已預先選好 OpenHands GLM-5.2。", - "ko-KR": "공급자와 모델을 선택하세요. 기본값으로 OpenHands GLM-5.2가 선택되어 있습니다.", - "no": "Velg en leverandør og modell. Vi har forhåndsvalgt OpenHands GLM-5.2.", - "ar": "اختر مزوّدًا ونموذجًا. لقد اخترنا مسبقًا OpenHands GLM-5.2.", - "de": "Wählen Sie Anbieter und Modell. Wir haben OpenHands GLM-5.2 vorausgewählt.", - "fr": "Choisissez un fournisseur et un modèle. Nous avons présélectionné OpenHands GLM-5.2.", - "it": "Scegli un provider e un modello. Abbiamo preselezionato OpenHands GLM-5.2.", - "pt": "Escolha um provedor e modelo. Já selecionamos o OpenHands GLM-5.2.", - "es": "Elige un proveedor y modelo. Hemos preseleccionado OpenHands GLM-5.2.", - "ca": "Trieu un proveïdor i model. Hem preseleccionat OpenHands GLM-5.2.", - "tr": "Bir sağlayıcı ve model seçin. OpenHands GLM-5.2'i önceden seçtik.", - "uk": "Виберіть провайдера та модель. OpenHands GLM-5.2 уже обрано." + "en": "Pick a provider and model. We've pre-selected OpenAI GPT-5.6 Sol.", + "ja": "プロバイダーとモデルを選択してください。OpenAI GPT-5.6 Sol を事前に選択しています。", + "zh-CN": "选择提供商和模型。我们已为你预先选择 OpenAI GPT-5.6 Sol。", + "zh-TW": "選擇供應商與模型。我們已預先選好 OpenAI GPT-5.6 Sol。", + "ko-KR": "공급자와 모델을 선택하세요. 기본값으로 OpenAI GPT-5.6 Sol가 선택되어 있습니다.", + "no": "Velg en leverandør og modell. Vi har forhåndsvalgt OpenAI GPT-5.6 Sol.", + "ar": "اختر مزوّدًا ونموذجًا. لقد اخترنا مسبقًا OpenAI GPT-5.6 Sol.", + "de": "Wählen Sie Anbieter und Modell. Wir haben OpenAI GPT-5.6 Sol vorausgewählt.", + "fr": "Choisissez un fournisseur et un modèle. Nous avons présélectionné OpenAI GPT-5.6 Sol.", + "it": "Scegli un provider e un modello. Abbiamo preselezionato OpenAI GPT-5.6 Sol.", + "pt": "Escolha um provedor e modelo. Já selecionamos o OpenAI GPT-5.6 Sol.", + "es": "Elige un proveedor y modelo. Hemos preseleccionado OpenAI GPT-5.6 Sol.", + "ca": "Trieu un proveïdor i model. Hem preseleccionat OpenAI GPT-5.6 Sol.", + "tr": "Bir sağlayıcı ve model seçin. OpenAI GPT-5.6 Sol'i önceden seçtik.", + "uk": "Виберіть провайдера та модель. OpenAI GPT-5.6 Sol уже обрано." }, "ONBOARDING$HELLO_TITLE": { "en": "Say hello 👋", diff --git a/src/mocks/settings-handlers.ts b/src/mocks/settings-handlers.ts index 7b87e2189b..7e831b5ae8 100644 --- a/src/mocks/settings-handlers.ts +++ b/src/mocks/settings-handlers.ts @@ -610,7 +610,8 @@ const MOCK_MODELS = [ "openhands/claude-sonnet-4-5-20250929", "openhands/claude-haiku-4-5-20251001", "openhands/claude-opus-4-5-20251101", - "openhands/kimi-k3", + "openai/gpt-5.6-sol", + "openhands/deepseek-v4-flash", "openhands/glm-5.2", "sambanova/Meta-Llama-3.1-8B-Instruct", ]; @@ -624,7 +625,8 @@ const MOCK_VERIFIED_MODELS = new Set([ "openai/gpt-5.5", "openhands/claude-opus-4-5-20251101", "openhands/claude-sonnet-4-5-20250929", - "openhands/kimi-k3", + "openai/gpt-5.6-sol", + "openhands/deepseek-v4-flash", "openhands/glm-5.2", ]); @@ -755,7 +757,7 @@ export const SETTINGS_HANDLERS = [ "claude-sonnet-4-5-20250929", ], verified_providers: MOCK_VERIFIED_PROVIDERS, - default_model: "openhands/kimi-k3", + default_model: "openai/gpt-5.6-sol", }), ), diff --git a/src/services/settings.ts b/src/services/settings.ts index 9f850f271e..f1a55332b3 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -3,7 +3,7 @@ import { Settings } from "#/types/settings"; export const LATEST_SETTINGS_VERSION = 5; export const DEFAULT_SETTINGS: Settings = { - llm_model: "openhands/kimi-k3", + llm_model: "openai/gpt-5.6-sol", llm_base_url: "", agent: "CodeActAgent", language: "en", @@ -37,7 +37,7 @@ export const DEFAULT_SETTINGS: Settings = { agent_kind: "openhands", agent: "CodeActAgent", llm: { - model: "openhands/kimi-k3", + model: "openai/gpt-5.6-sol", }, condenser: { enabled: true, diff --git a/src/utils/format-model-name.ts b/src/utils/format-model-name.ts index b17e4461d8..8be17b076f 100644 --- a/src/utils/format-model-name.ts +++ b/src/utils/format-model-name.ts @@ -1,10 +1,7 @@ export const FREE_MODEL_BADGE_LABEL = "Free"; export const FREE_OPENHANDS_MODELS = { - "openhands/kimi-k3": "OpenHands Kimi K3 (free)", - "openhands/glm-5.2": "OpenHands GLM-5.2 (free)", "openhands/deepseek-v4-flash": "OpenHands DeepSeek V4 Flash (free)", - "openhands/minimax-m2.7": "OpenHands MiniMax M2.7 (free)", } as const; export const FREE_OPENHANDS_MODEL_IDS = Object.keys(FREE_OPENHANDS_MODELS); diff --git a/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts b/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts index 9dcf8390e9..4ddc292ba3 100644 --- a/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts +++ b/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts @@ -61,10 +61,11 @@ test.describe("onboarding recent regressions", () => { }); // Regression coverage for #1077 / PR #1089: first-run LLM setup - // should default users to the OpenHands provider, matching the agent - // selected on the previous step. + // should default users to the configured default provider and model. - test("defaults the LLM setup step to OpenHands GLM-5.2", async ({ page }) => { + test("defaults the LLM setup step to OpenAI GPT-5.6 Sol", async ({ + page, + }) => { await showOnboarding(page, { apiKey: SESSION_API_KEY, beforeGoto: async () => { @@ -94,18 +95,18 @@ test.describe("onboarding recent regressions", () => { await expect( providerInput, - "first-run onboarding should default to the OpenHands provider", - ).toHaveValue("OpenHands", { timeout: 10_000 }); + "first-run onboarding should default to the OpenAI provider", + ).toHaveValue("OpenAI", { timeout: 10_000 }); // The model input displays the model ID without the provider prefix. await expect( modelInput, - "first-run onboarding should default to Kimi K3", - ).toHaveValue("kimi-k3", { + "first-run onboarding should default to GPT-5.6 Sol", + ).toHaveValue("gpt-5.6-sol", { timeout: 10_000, }); await expect( page.getByTestId("openhands-account-help"), - "OpenHands account helper should be visible for OpenHands defaults", - ).toBeVisible(); + "OpenHands account helper should be hidden for OpenAI defaults", + ).toBeHidden(); }); }); diff --git a/tests/e2e/support/onboarding-helpers.ts b/tests/e2e/support/onboarding-helpers.ts index 188be10d9b..dcd0986b2d 100644 --- a/tests/e2e/support/onboarding-helpers.ts +++ b/tests/e2e/support/onboarding-helpers.ts @@ -36,7 +36,7 @@ export async function routeOnboardingLlmCatalog(page: Page) { "anthropic/claude-opus-4-8", "openai/gpt-5.5", "openhands/claude-opus-4-5-20251101", - "openhands/kimi-k3", + "openai/gpt-5.6-sol", "openhands/glm-5.2", ], }),