"use client"; import { useState, useEffect } from "react"; import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components"; import Image from "next/image"; export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, apiKeys, activeProviders, cloudEnabled, initialStatus }) { const [status, setStatus] = useState(initialStatus || null); const [checking, setChecking] = useState(false); const [applying, setApplying] = useState(false); const [restoring, setRestoring] = useState(false); const [message, setMessage] = useState(null); const [showInstallGuide, setShowInstallGuide] = useState(false); const [selectedApiKey, setSelectedApiKey] = useState(""); const [selectedModel, setSelectedModel] = useState(""); const [subagentModel, setSubagentModel] = useState(""); const [modalOpen, setModalOpen] = useState(false); const [subagentModalOpen, setSubagentModalOpen] = useState(false); const [modelAliases, setModelAliases] = useState({}); const [showManualConfigModal, setShowManualConfigModal] = useState(false); const [customBaseUrl, setCustomBaseUrl] = useState(""); useEffect(() => { if (apiKeys?.length > 0 && !selectedApiKey) { setSelectedApiKey(apiKeys[0].key); } }, [apiKeys, selectedApiKey]); useEffect(() => { if (initialStatus) setStatus(initialStatus); }, [initialStatus]); useEffect(() => { if (isExpanded && !status) { checkStatus(); fetchModelAliases(); } if (isExpanded) fetchModelAliases(); }, [isExpanded]); // Sync model and subagent model from existing config useEffect(() => { if (status?.config?.model?.startsWith("9router/")) { setSelectedModel(status.config.model.replace("9router/", "")); } // Parse subagent settings from agent.explorer if exists if (status?.config?.agent?.explorer?.model?.startsWith("9router/")) { setSubagentModel(status.config.agent.explorer.model.replace("9router/", "")); } }, [status]); const fetchModelAliases = async () => { try { const res = await fetch("/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { console.log("Error fetching model aliases:", error); } }; const getConfigStatus = () => { if (!status?.installed) return null; if (!status.config) return "not_configured"; const url = status.config?.provider?.["9router"]?.options?.baseURL || ""; const isLocal = url.includes("localhost") || url.includes("127.0.0.1"); return status.has9Router && (isLocal || url.includes(baseUrl)) ? "configured" : status.has9Router ? "other" : "not_configured"; }; const configStatus = getConfigStatus(); const getEffectiveBaseUrl = () => { const url = customBaseUrl || baseUrl; return url.endsWith("/v1") ? url : `${url}/v1`; }; const getDisplayUrl = () => customBaseUrl || `${baseUrl}/v1`; const checkStatus = async () => { setChecking(true); try { const res = await fetch("/api/cli-tools/opencode-settings"); const data = await res.json(); setStatus(data); } catch (error) { setStatus({ installed: false, error: error.message }); } finally { setChecking(false); } }; const handleApply = async () => { setApplying(true); setMessage(null); try { const keyToUse = (selectedApiKey && selectedApiKey.trim()) ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); const res = await fetch("/api/cli-tools/opencode-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, model: selectedModel, subagentModel: subagentModel || selectedModel }), }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings applied successfully!" }); checkStatus(); } else { setMessage({ type: "error", text: data.error || "Failed to apply settings" }); } } catch (error) { setMessage({ type: "error", text: error.message }); } finally { setApplying(false); } }; const handleReset = async () => { setRestoring(true); setMessage(null); try { const res = await fetch("/api/cli-tools/opencode-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); setSelectedModel(""); setSubagentModel(""); checkStatus(); } else { setMessage({ type: "error", text: data.error || "Failed to reset settings" }); } } catch (error) { setMessage({ type: "error", text: error.message }); } finally { setRestoring(false); } }; const getManualConfigs = () => { const keyToUse = (selectedApiKey && selectedApiKey.trim()) ? selectedApiKey : (!cloudEnabled ? "sk_9router" : ""); const effectiveSubagentModel = subagentModel || selectedModel; return [{ filename: "~/.config/opencode/opencode.json", content: JSON.stringify({ provider: { "9router": { npm: "@ai-sdk/openai-compatible", options: { baseURL: getEffectiveBaseUrl(), apiKey: keyToUse }, models: { [selectedModel || "provider/model-id"]: { name: selectedModel || "provider/model-id" }, [effectiveSubagentModel]: { name: effectiveSubagentModel } }, }, }, model: `9router/${selectedModel || "provider/model-id"}`, agent: { explorer: { description: "Fast explorer subagent for codebase exploration", mode: "subagent", model: `9router/${effectiveSubagentModel}` } } }, null, 2), }]; }; return (
{tool.name} { e.target.style.display = "none"; }} />

{tool.name}

{configStatus === "configured" && Connected} {configStatus === "not_configured" && Not configured} {configStatus === "other" && Other}

{tool.description}

expand_more
{isExpanded && (
{checking && (
progress_activity Checking OpenCode CLI...
)} {!checking && status && !status.installed && (
warning

OpenCode CLI not installed

Please install OpenCode CLI to use auto-apply feature.

{showInstallGuide && (

Installation Guide

macOS / Linux:

npm install -g opencode-ai

After installation, run opencode to verify.

)}
)} {!checking && status?.installed && ( <>
{/* Current base URL */} {status?.config?.provider?.["9router"]?.options?.baseURL && (
Current arrow_forward {status.config.provider["9router"].options.baseURL}
)} {/* Base URL */}
Base URL arrow_forward setCustomBaseUrl(e.target.value)} placeholder="https://.../v1" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> {customBaseUrl && customBaseUrl !== `${baseUrl}/v1` && ( )}
{/* API Key */}
API Key arrow_forward {apiKeys.length > 0 ? ( ) : ( {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
{/* Model */}
Model arrow_forward setSelectedModel(e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> {selectedModel && }
{/* Subagent Model */}
Subagent Model arrow_forward setSubagentModel(e.target.value)} placeholder={selectedModel || "provider/model-id (defaults to main model)"} className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> {subagentModel && ( )}
{message && (
{message.type === "success" ? "check_circle" : "error"} {message.text}
)}
)}
)} setModalOpen(false)} onSelect={(model) => { setSelectedModel(model.value); // Auto-set subagent model if not set if (!subagentModel) { setSubagentModel(model.value); } setModalOpen(false); }} selectedModel={selectedModel} activeProviders={activeProviders} modelAliases={modelAliases} title="Select Model for OpenCode" /> setSubagentModalOpen(false)} onSelect={(model) => { setSubagentModel(model.value); setSubagentModalOpen(false); }} selectedModel={subagentModel} activeProviders={activeProviders} modelAliases={modelAliases} title="Select Subagent Model for OpenCode" /> setShowManualConfigModal(false)} title="OpenCode - Manual Configuration" configs={getManualConfigs()} />
); }