import { useCallback, useEffect, useState } from "react"; import type { AgentProfile, GatewayError, LocalModelServerConfig, OpenCodeConfig, OpenCodeProviderCatalogEntry, } from "@/domain"; import { useGateways } from "@/app/di"; import { Button, IconButton, Input, cn } from "@/shared"; import { ModelServerSelect } from "@/features/model-servers"; import { defaultOpenCodeConfig, type ProfileErrors, } from "./profile"; /** A small caption above a control. */ function Caption({ children }: { children: React.ReactNode }) { return {children}; } function describeError(e: unknown): string { if (e && typeof e === "object" && "message" in e) { return String((e as GatewayError).message); } return String(e); } /** View-model for the OpenCode cloud-provider catalogue (ticket #92). */ export interface OpenCodeProviderCatalog { providers: OpenCodeProviderCatalogEntry[] | null; loading: boolean; error: string | null; reload: () => void; } /** * Loads the static OpenCode cloud-provider catalogue once for the whole form * surface, so Cloud OpenCode rows share the same provider/model options. */ export function useOpenCodeProviderCatalog(): OpenCodeProviderCatalog { const { profile } = useGateways(); const [providers, setProviders] = useState( null, ); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { setProviders(await profile.listOpenCodeProviders()); } catch (e) { setProviders(null); setError(describeError(e)); } finally { setLoading(false); } }, [profile]); useEffect(() => { void load(); }, [load]); return { providers, loading, error, reload: () => void load() }; } /** * Segmented control choosing whether an OpenCode profile runs against the local * `llama.cpp` endpoint or a cloud provider from the OpenCode registry. */ export function OpenCodeModeFields({ profile, errors, servers, providerCatalog, onChange, }: { profile: AgentProfile; errors: ProfileErrors; servers: LocalModelServerConfig[]; providerCatalog: OpenCodeProviderCatalog; onChange: (p: AgentProfile) => void; }) { const [mode, setMode] = useState<"local" | "cloud">( profile.opencodeProvider ? "cloud" : "local", ); function selectMode(next: "local" | "cloud") { setMode(next); if (next === "local" && profile.opencodeProvider) { onChange({ ...profile, opencode: profile.opencode ?? defaultOpenCodeConfig(), opencodeProvider: undefined, }); } } return (
{( [ { id: "local", label: "Local (llama.cpp)" }, { id: "cloud", label: "Provider cloud" }, ] as const ).map((seg) => { const active = mode === seg.id; return ( ); })}
{mode === "local" && ( )} {mode === "cloud" && ( )}
); } /** Field-keyed validation errors for the Cloud sub-form, surfaced on submit. */ interface CloudFieldErrors { providerId?: string; model?: string; apiKey?: string; npm?: string; baseUrl?: string; } /** Sentinel `