/** * 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<OpenCodeSettings, OpenCodeDriverEnv> = { 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<OpenCodeSettings>({ 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; }),};
/**
* 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<OpenCodeSettings, OpenCodeDriverEnv> = {
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<OpenCodeSettings>({
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;
}),
};