diff --git a/__tests__/hooks/use-llm-configured.test.tsx b/__tests__/hooks/use-llm-configured.test.tsx index 1489de7a7b..c6c18c4229 100644 --- a/__tests__/hooks/use-llm-configured.test.tsx +++ b/__tests__/hooks/use-llm-configured.test.tsx @@ -126,6 +126,47 @@ describe("useLlmConfigured (local, agent-profile-driven)", () => { expect(result.current.isConfigured).toBe(true); }); + it("is configured for the local seeded `default` agent profile when its ref is unauthenticated (matches the agent_settings launch)", () => { + // #16193: "B" EXISTS (so the stale-ref fallback above doesn't fire) but has + // no key. `default` launches via `agent_settings` on the active LLM "A". + useLlmProfilesMock.mockReturnValue( + llmProfiles("A", [ + { name: "A", api_key_set: true }, + { name: "B", api_key_set: false }, + ]), + ); + useActiveAgentProfileMock.mockReturnValue({ + activeProfile: { + agent_kind: "openhands", + llm_profile_ref: "B", + name: "default", + }, + }); + + const { result } = renderHook(() => useLlmConfigured(), { wrapper }); + expect(result.current.isConfigured).toBe(true); + }); + + it("is NOT configured for the local seeded `default` profile when the active LLM profile has no key either", () => { + // The fallback must still validate: nothing here is usable. + useLlmProfilesMock.mockReturnValue( + llmProfiles("A", [ + { name: "A", api_key_set: false }, + { name: "B", api_key_set: false }, + ]), + ); + useActiveAgentProfileMock.mockReturnValue({ + activeProfile: { + agent_kind: "openhands", + llm_profile_ref: "B", + name: "default", + }, + }); + + const { result } = renderHook(() => useLlmConfigured(), { wrapper }); + expect(result.current.isConfigured).toBe(false); + }); + it("is configured for an ACP agent profile regardless of LLM keys", () => { useLlmProfilesMock.mockReturnValue( llmProfiles("default", [{ name: "default", api_key_set: false }]), diff --git a/src/hooks/use-llm-configured.ts b/src/hooks/use-llm-configured.ts index 128dbfd2c0..8ce0145777 100644 --- a/src/hooks/use-llm-configured.ts +++ b/src/hooks/use-llm-configured.ts @@ -6,6 +6,7 @@ import { useActiveBackend } from "#/contexts/active-backend-context"; import { useActiveAgentProfile } from "#/hooks/use-active-agent-profile"; import { isSettingsPageHidden } from "#/utils/settings-utils"; import ProfilesService from "#/api/profiles-service/profiles-service.api"; +import { WELL_KNOWN_DEFAULT_AGENT_PROFILE_NAME } from "#/api/agent-profiles-service/agent-profiles-service.api"; import { CONFIG_CACHE_OPTIONS, LLM_PROFILES_QUERY_KEYS, @@ -69,8 +70,15 @@ export function useLlmConfigured(): LlmConfiguredResult { // The LLM that will actually power the next conversation is the profile the // active AGENT profile references (`llm_profile_ref`) — conversations launch // from the agent profile, not the standalone "active LLM profile". + // Except the local seeded `default` profile: `useCreateConversation` launches + // it via `agent_settings` on the ACTIVE LLM, not the seed's ref, so gating on + // that ref blocks launches that would have succeeded (#16193). const referencedLlmProfileName = - activeAgentProfile?.agent_kind === "openhands" + activeAgentProfile?.agent_kind === "openhands" && + !( + isLocal && + activeAgentProfile.name === WELL_KNOWN_DEFAULT_AGENT_PROFILE_NAME + ) ? activeAgentProfile.llm_profile_ref : undefined; const referencedProfile = referencedLlmProfileName