/** * OpenCodeDriver — `ProviderDriver` for the OpenCode runtime. * * Mirrors the Codex / Claude drivers: a plain value whose `create()` * bundles `snapshot` / `adapter` / `textGeneration` closures over the * per-instance `OpenCodeSettings`. * * Two instances with different `serverUrl`s therefore talk to independent * OpenCode servers; when no `serverUrl` is set, the adapter + text-generation * shares spin up their own scoped child processes, and those child * processes are released when the registry scope closes. * * @module provider/Drivers/OpenCodeDriver */ import { OpenCodeSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; import { Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; import { ChildProcessSpawner } from "effect/unstable/process"; import { makeOpenCodeTextGeneration } from "../../textGeneration/OpenCodeTextGeneration.ts"; import { ServerConfig } from "../../config.ts"; import { ProviderDriverError } from "../Errors.ts"; import { makeOpenCodeAdapter } from "../Layers/OpenCodeAdapter.ts"; import { checkOpenCodeProviderStatus, makePendingOpenCodeProvider, } from "../Layers/OpenCodeProvider.ts"; import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; import { makeManagedServerProvider } from "../makeManagedServerProvider.ts"; import { OpenCodeRuntime } from "../opencodeRuntime.ts"; import { defaultProviderContinuationIdentity, type ProviderDriver, type ProviderInstance, } from "../ProviderDriver.ts"; import type { ServerProviderDraft } from "../providerSnapshot.ts"; import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts"; const DRIVER_KIND = ProviderDriverKind.make("opencode"); const SNAPSHOT_REFRESH_INTERVAL = Duration.minutes(5); export type OpenCodeDriverEnv = | ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | OpenCodeRuntime | Path.Path | ProviderEventLoggers | ServerConfig; const withInstanceIdentity = (input: { readonly instanceId: ProviderInstance["instanceId"]; readonly displayName: string | undefined; readonly accentColor: string | undefined; readonly continuationGroupKey: string; }) => (snapshot: ServerProviderDraft): ServerProvider => ({ ...snapshot, instanceId: input.instanceId, driver: DRIVER_KIND, ...(input.displayName ? { displayName: input.displayName } : {}), ...(input.accentColor ? { accentColor: input.accentColor } : {}), continuation: { groupKey: input.continuationGroupKey }, }); export const OpenCodeDriver: ProviderDriver = { driverKind: DRIVER_KIND, metadata: { displayName: "OpenCode", supportsMultipleInstances: true, }, configSchema: OpenCodeSettings, defaultConfig: (): OpenCodeSettings => Schema.decodeSync(OpenCodeSettings)({}), create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => Effect.gen(function* () { const openCodeRuntime = yield* OpenCodeRuntime; const serverConfig = yield* ServerConfig; const eventLoggers = yield* ProviderEventLoggers; const processEnv = mergeProviderInstanceEnvironment(environment); const continuationIdentity = defaultProviderContinuationIdentity({ driverKind: DRIVER_KIND, instanceId, }); const stampIdentity = withInstanceIdentity({ instanceId, displayName, accentColor, continuationGroupKey: continuationIdentity.continuationKey, }); const effectiveConfig = { ...config, enabled } satisfies OpenCodeSettings; const adapter = yield* makeOpenCodeAdapter(effectiveConfig, { instanceId, environment: processEnv, ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}), }); const textGeneration = yield* makeOpenCodeTextGeneration(effectiveConfig, processEnv); const checkProvider = checkOpenCodeProviderStatus( effectiveConfig, serverConfig.cwd, processEnv, ).pipe(Effect.map(stampIdentity), Effect.provideService(OpenCodeRuntime, openCodeRuntime)); const snapshot = yield* makeManagedServerProvider({ getSettings: Effect.succeed(effectiveConfig), streamSettings: Stream.never, haveSettingsChanged: () => false, initialSnapshot: (settings) => stampIdentity(makePendingOpenCodeProvider(settings)), checkProvider, refreshInterval: SNAPSHOT_REFRESH_INTERVAL, }).pipe( Effect.mapError( (cause) => new ProviderDriverError({ driver: DRIVER_KIND, instanceId, detail: `Failed to build OpenCode snapshot: ${cause.message ?? String(cause)}`, cause, }), ), ); return { instanceId, driverKind: DRIVER_KIND, continuationIdentity, displayName, accentColor, enabled, snapshot, adapter, textGeneration, } satisfies ProviderInstance; }), };