import { type ModelCapabilities, type OpenCodeSettings, type ServerProviderModel, type ServerProviderSkill, } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import * as Data from "effect/Data"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import { createModelCapabilities } from "@t3tools/shared/model"; import { compareSemverVersions } from "@t3tools/shared/semver"; import { buildServerProvider, nonEmptyTrimmed, parseGenericCliVersion, providerModelsFromSettings, type ServerProviderDraft, } from "../providerSnapshot.ts"; import { OpenCodeRuntime, openCodeRuntimeErrorDetail, type OpenCodeInventory, } from "../opencodeRuntime.ts"; import type { Agent, ProviderListResponse } from "@opencode-ai/sdk/v2"; const OPENCODE_PRESENTATION = { displayName: "OpenCode", showInteractionModeToggle: false, } as const; const MINIMUM_OPENCODE_VERSION = "1.14.19"; class OpenCodeProbeError extends Data.TaggedError("OpenCodeProbeError")<{ readonly cause: unknown; readonly detail: string; }> {} function normalizeProbeMessage(message: string): string | undefined { const trimmed = message.trim(); if (trimmed.length === 0) { return undefined; } if ( trimmed === "An error occurred in Effect.tryPromise" || trimmed === "An error occurred in Effect.try" ) { return undefined; } return trimmed; } function normalizedErrorMessage(cause: unknown): string | undefined { if (cause instanceof OpenCodeProbeError) { return normalizeProbeMessage(cause.detail); } if (!(cause instanceof Error)) { return undefined; } return normalizeProbeMessage(cause.message); } function formatOpenCodeProbeError(input: { readonly cause: unknown; readonly isExternalServer: boolean; readonly serverUrl: string; }): { readonly installed: boolean; readonly message: string } { const detail = normalizedErrorMessage(input.cause); const lower = detail?.toLowerCase() ?? ""; if (input.isExternalServer) { if ( lower.includes("401") || lower.includes("403") || lower.includes("unauthorized") || lower.includes("forbidden") ) { return { installed: true, message: "OpenCode server rejected authentication. Check the server URL and password.", }; } if ( lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("fetch failed") || lower.includes("networkerror") || lower.includes("timed out") || lower.includes("timeout") || lower.includes("socket hang up") ) { return { installed: true, message: `Couldn't reach the configured OpenCode server at ${input.serverUrl}. Check that the server is running and the URL is correct.`, }; } return { installed: true, message: detail ?? "Failed to connect to the configured OpenCode server.", }; } if (lower.includes("enoent") || lower.includes("notfound")) { return { installed: false, message: "OpenCode CLI (`opencode`) is not installed or not on PATH.", }; } if (lower.includes("quarantine")) { return { installed: true, message: "macOS is blocking the OpenCode binary (quarantine). Run `xattr -d com.apple.quarantine $(which opencode)` to fix this.", }; } if (lower.includes("invalid code signature") || lower.includes("corrupted")) { return { installed: true, message: "macOS killed the OpenCode process due to an invalid code signature. The binary may be corrupted — try reinstalling OpenCode.", }; } return { installed: true, message: detail ? `Failed to execute OpenCode CLI health check: ${detail}` : "Failed to execute OpenCode CLI health check.", }; } function titleCaseSlug(value: string): string { const segments: Array = []; for (const segment of value.split(/[-_/]+/)) { if (segment.length > 0) { segments.push(segment.charAt(0).toUpperCase() + segment.slice(1)); } } return segments.join(" "); } function inferDefaultVariant( providerID: string, variants: ReadonlyArray, ): string | undefined { if (variants.length === 1) { return variants[0]; } if (providerID === "anthropic" || providerID.startsWith("google")) { return variants.includes("high") ? "high" : undefined; } if (providerID === "openai" || providerID === "opencode") { return variants.includes("medium") ? "medium" : variants.includes("high") ? "high" : undefined; } return undefined; } function inferDefaultAgent(agents: ReadonlyArray): string | undefined { return agents.find((agent) => agent.name === "build")?.name ?? agents[0]?.name ?? undefined; } const DEFAULT_OPENCODE_MODEL_CAPABILITIES: ModelCapabilities = createModelCapabilities({ optionDescriptors: [], }); function openCodeCapabilitiesForModel(input: { readonly providerID: string; readonly model: ProviderListResponse["all"][number]["models"][string]; readonly agents: ReadonlyArray; }): ModelCapabilities { const variantValues = Object.keys(input.model.variants ?? {}); const defaultVariant = inferDefaultVariant(input.providerID, variantValues); const variantOptions = variantValues.map((value) => defaultVariant === value ? { id: value, label: titleCaseSlug(value), isDefault: true as const } : { id: value, label: titleCaseSlug(value) }, ); const primaryAgents = input.agents.filter( (agent) => !agent.hidden && (agent.mode === "primary" || agent.mode === "all"), ); const defaultAgent = inferDefaultAgent(primaryAgents); const agentOptions = primaryAgents.map((agent) => defaultAgent === agent.name ? { id: agent.name, label: titleCaseSlug(agent.name), isDefault: true as const } : { id: agent.name, label: titleCaseSlug(agent.name) }, ); return createModelCapabilities({ optionDescriptors: [ ...(variantOptions.length > 0 ? [ { id: "variant", label: "Variant", type: "select" as const, options: variantOptions, ...(defaultVariant ? { currentValue: defaultVariant } : {}), }, ] : []), ...(agentOptions.length > 0 ? [ { id: "agent", label: "Agent", type: "select" as const, options: agentOptions, ...(defaultAgent ? { currentValue: defaultAgent } : {}), }, ] : []), ], }); } function flattenOpenCodeModels(input: OpenCodeInventory): ReadonlyArray { const connected = new Set(input.providerList.connected); const models: Array = []; for (const provider of input.providerList.all) { if (!connected.has(provider.id)) { continue; } for (const model of Object.values(provider.models)) { const name = nonEmptyTrimmed(model.name); if (!name) { continue; } const subProvider = nonEmptyTrimmed(provider.name); models.push({ slug: `${provider.id}/${model.id}`, name, ...(subProvider ? { subProvider } : {}), isCustom: false, capabilities: openCodeCapabilitiesForModel({ providerID: provider.id, model, agents: input.agents, }), }); } } return models.toSorted((left, right) => left.name.localeCompare(right.name)); } function trimOptional(value: string | null | undefined): string | undefined { const trimmed = value?.trim(); return trimmed && trimmed.length > 0 ? trimmed : undefined; } function flattenOpenCodeSkills(input: OpenCodeInventory): ReadonlyArray { const skills: ServerProviderSkill[] = []; for (const skill of input.skills ?? []) { const name = trimOptional(skill.name); const path = trimOptional(skill.location); if (!name || !path) { continue; } const description = trimOptional(skill.description); skills.push({ name, path, enabled: true, ...(description ? { description, shortDescription: description } : {}), }); } return skills.toSorted((left, right) => left.name.localeCompare(right.name)); } export const makePendingOpenCodeProvider = ( openCodeSettings: OpenCodeSettings, ): Effect.Effect => Effect.gen(function* () { const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); const models = providerModelsFromSettings( [], openCodeSettings.customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES, ); if (!openCodeSettings.enabled) { return buildServerProvider({ presentation: OPENCODE_PRESENTATION, enabled: false, checkedAt, models, probe: { installed: false, version: null, status: "warning", auth: { status: "unknown" }, message: openCodeSettings.serverUrl.trim().length > 0 ? "OpenCode is disabled in T3 Code settings. A server URL is configured." : "OpenCode is disabled in T3 Code settings.", }, }); } return buildServerProvider({ presentation: OPENCODE_PRESENTATION, enabled: true, checkedAt, models, probe: { installed: false, version: null, status: "warning", auth: { status: "unknown" }, message: "OpenCode provider status has not been checked in this session yet.", }, }); }); export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatus")(function* ( openCodeSettings: OpenCodeSettings, cwd: string, environment?: NodeJS.ProcessEnv, ): Effect.fn.Return { const openCodeRuntime = yield* OpenCodeRuntime; const resolvedEnvironment = environment ?? process.env; const checkedAt = DateTime.formatIso(yield* DateTime.now); const customModels = openCodeSettings.customModels; const isExternalServer = openCodeSettings.serverUrl.trim().length > 0; const fallback = (cause: unknown, version: string | null = null) => { const failure = formatOpenCodeProbeError({ cause, isExternalServer, serverUrl: openCodeSettings.serverUrl, }); return buildServerProvider({ presentation: OPENCODE_PRESENTATION, enabled: openCodeSettings.enabled, checkedAt, models: providerModelsFromSettings([], customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES), probe: { installed: failure.installed, version, status: "error", auth: { status: "unknown" }, message: failure.message, }, }); }; if (!openCodeSettings.enabled) { return buildServerProvider({ presentation: OPENCODE_PRESENTATION, enabled: false, checkedAt, models: providerModelsFromSettings([], customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES), probe: { installed: false, version: null, status: "warning", auth: { status: "unknown" }, message: isExternalServer ? "OpenCode is disabled in T3 Code settings. A server URL is configured." : "OpenCode is disabled in T3 Code settings.", }, }); } let version: string | null = null; if (!isExternalServer) { const versionExit = yield* Effect.exit( openCodeRuntime .runOpenCodeCommand({ binaryPath: openCodeSettings.binaryPath, args: ["--version"], environment: resolvedEnvironment, }) .pipe( Effect.mapError( (cause) => new OpenCodeProbeError({ cause, detail: openCodeRuntimeErrorDetail(cause) }), ), ), ); if (versionExit._tag === "Failure") { return fallback(Cause.squash(versionExit.cause)); } version = parseGenericCliVersion(versionExit.value.stdout) ?? null; if (!version) { return fallback( new Error( `Unable to determine OpenCode version from \`opencode --version\` output. T3 Code requires OpenCode v${MINIMUM_OPENCODE_VERSION} or newer.`, ), null, ); } if (compareSemverVersions(version, MINIMUM_OPENCODE_VERSION) < 0) { return buildServerProvider({ presentation: OPENCODE_PRESENTATION, enabled: openCodeSettings.enabled, checkedAt, models: providerModelsFromSettings([], customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES), probe: { installed: true, version, status: "error", auth: { status: "unknown" }, message: `OpenCode v${version} is too old. Upgrade to v${MINIMUM_OPENCODE_VERSION} or newer.`, }, }); } } const inventoryExit = yield* Effect.exit( (isExternalServer ? Effect.scoped( Effect.gen(function* () { const server = yield* openCodeRuntime.connectToOpenCodeServer({ binaryPath: openCodeSettings.binaryPath, serverUrl: openCodeSettings.serverUrl, environment: resolvedEnvironment, }); return yield* openCodeRuntime.loadOpenCodeInventory( openCodeRuntime.createOpenCodeSdkClient({ baseUrl: server.url, directory: cwd, ...(openCodeSettings.serverPassword ? { serverPassword: openCodeSettings.serverPassword } : {}), }), ); }), ) : openCodeRuntime.loadInventoryFromCli({ binaryPath: openCodeSettings.binaryPath, cwd, environment: resolvedEnvironment, }) ).pipe( Effect.mapError( (cause) => new OpenCodeProbeError({ cause, detail: openCodeRuntimeErrorDetail(cause) }), ), ), ); if (inventoryExit._tag === "Failure") { return fallback(Cause.squash(inventoryExit.cause), version); } const models = providerModelsFromSettings( flattenOpenCodeModels(inventoryExit.value), customModels, DEFAULT_OPENCODE_MODEL_CAPABILITIES, ); const skills = flattenOpenCodeSkills(inventoryExit.value); const connectedCount = inventoryExit.value.providerList.connected.length; return buildServerProvider({ presentation: OPENCODE_PRESENTATION, enabled: true, checkedAt, models, skills, probe: { installed: true, version, status: connectedCount > 0 ? "ready" : "warning", auth: { status: connectedCount > 0 ? "authenticated" : "unknown", type: "opencode", }, message: connectedCount > 0 ? `${connectedCount} upstream provider${connectedCount === 1 ? "" : "s"} connected through ${isExternalServer ? "the configured OpenCode server" : "OpenCode"}.` : isExternalServer ? "Connected to the configured OpenCode server, but it did not report any connected upstream providers." : "OpenCode is available, but it did not report any connected upstream providers.", }, }); });