From e62161bc5b53389a462d066836d47d25df0f4134 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 8 Oct 2026 18:30:57 -0700 Subject: [PATCH] refactor(provider-opencode): move OpenCode into its own provider package (#17345) Co-authored-by: Claude Opus 5.5 --- apps/mobile/package.json | 1 + apps/mobile/src/components/ProviderIcon.tsx | 9 --- apps/mobile/src/lib/providerClients.ts | 3 +- apps/server/package.json | 4 +- apps/server/src/cli/theme.ts | 2 +- apps/server/src/device/LocalDeviceHost.ts | 2 +- apps/server/src/keybindings.ts | 2 +- apps/server/src/mcp/OrchestratorMcpService.ts | 2 +- .../AcpRegistryOrchestratorV2.live.test.ts | 4 +- .../orchestration-v2/Adapters/AcpAdapterV2.ts | 2 +- .../Adapters/ClaudeAdapterV2.ts | 2 +- .../Adapters/CodexAdapterV2.ts | 2 +- .../Adapters/CursorAdapterV2.ts | 2 +- .../Adapters/OpenCode2AdapterV2.test.ts | 7 +- .../Adapters/OpenCode2AdapterV2.testkit.ts | 42 +++-------- .../Adapters/OpenCodeAdapterV2.testkit.ts | 19 ++--- .../CursorOrchestratorV2.live.test.ts | 4 +- .../GrokOrchestratorV2.live.test.ts | 4 +- ...penCode2OrchestratorV2.integration.test.ts | 2 +- .../OpenCode2OrchestratorV2.live.test.ts | 6 +- .../src/orchestration-v2/Orchestrator.ts | 2 +- .../SubagentProjection.test.ts | 2 +- .../builtInProviderAdapterDrivers.ts | 2 +- apps/server/src/provider/ModelManifest.ts | 2 +- .../OpenCode2Client.events.test.ts | 4 +- .../provider/ProviderInstanceRegistry.test.ts | 8 +-- .../src/provider/ProviderRegistry.test.ts | 4 +- .../src/provider/acp/AcpNativeLogging.ts | 2 +- .../acp/AcpRegistryAuthenticationState.ts | 2 +- apps/server/src/provider/builtInDrivers.ts | 2 +- .../src/provider/providerStatusCache.ts | 2 +- apps/server/src/server.ts | 17 ++++- apps/server/src/serverRuntimeState.ts | 2 +- apps/server/src/serverSettings.test.ts | 2 +- apps/server/src/serverSettings.ts | 2 +- apps/server/src/telemetry/Identify.ts | 2 +- .../OpenCode2TextGeneration.test.ts | 10 ++- apps/server/src/usage/UsageService.ts | 2 +- apps/web/package.json | 1 + apps/web/src/components/Icons.tsx | 26 ------- apps/web/src/components/chat/ChatComposer.tsx | 2 +- .../components/chat/ProviderInstanceIcon.tsx | 12 +--- .../components/chat/ProviderPackageIcon.tsx | 2 + .../components/settings/providerDriverMeta.ts | 8 +-- docs/internals/adding-a-provider.md | 5 +- docs/internals/providers.md | 8 +-- package.json | 2 +- packages/contracts/src/settings.ts | 52 -------------- packages/provider-core/package.json | 8 +++ .../src/server/nativeProtocolLogging.ts | 0 .../src/server/subagentProjection.ts | 0 packages/provider-opencode/package.json | 68 ++++++++++++++++++ packages/provider-opencode/src/client.ts | 24 +++++++ packages/provider-opencode/src/server.ts | 8 +++ .../server/OpenCodeRuntime.cliParsers.test.ts | 43 +++++------- .../OpenCodeRuntime.environment.test.ts | 2 +- .../server/OpenCodeRuntime.inventory.test.ts | 2 +- .../OpenCodeRuntime.permissions.test.ts | 10 +-- .../src/server/OpenCodeRuntime.ts | 4 +- .../src/server}/OpenCodeServerLedger.test.ts | 2 +- .../src/server}/OpenCodeServerLedger.ts | 26 +++---- .../src/server}/OpenCodeServerOwner.test.ts | 2 +- .../src/server}/OpenCodeServerOwner.ts | 4 +- .../src/server/adapter.test.ts | 63 +++++++++-------- .../provider-opencode/src/server/adapter.ts | 64 ++++++++--------- .../src/server/driver.test.ts | 30 +++----- .../provider-opencode/src/server/driver.ts | 62 ++++++++--------- .../src/server/probeResponses.fixture.ts | 0 .../src/server/status.test.ts | 14 ++-- .../provider-opencode/src/server/status.ts | 6 +- .../src/server/textGeneration.test.ts | 27 +++----- .../src/server/textGeneration.ts | 15 ++-- .../provider-opencode/src/server/toolItems.ts | 0 .../src/server/usageLimits.ts | 0 .../server/v2}/OpenCode2Client.live.test.ts | 0 .../src/server/v2}/OpenCode2Client.test.ts | 0 .../src/server/v2}/OpenCode2Client.ts | 2 +- .../server/v2}/OpenCode2Server.live.test.ts | 2 +- .../src/server/v2}/OpenCode2Server.test.ts | 2 +- .../src/server/v2}/OpenCode2Server.ts | 14 ++-- .../src/server/v2/adapter.ts | 69 +++++++++---------- .../src/server/v2/textGeneration.ts | 20 +++--- .../src/server/versionProbe.test.ts | 6 +- .../src/server/versionProbe.ts | 2 +- packages/provider-opencode/src/settings.ts | 66 ++++++++++++++++++ packages/provider-opencode/src/testing.ts | 13 ++++ packages/provider-opencode/tsconfig.json | 7 ++ packages/shared/package.json | 4 ++ .../shared}/src/atomicWrite.test.ts | 0 .../shared}/src/atomicWrite.ts | 0 pnpm-lock.yaml | 61 +++++++++++++--- scripts/release-smoke.ts | 1 + 92 files changed, 575 insertions(+), 486 deletions(-) rename apps/server/src/provider/{opencode2 => }/OpenCode2Client.events.test.ts (97%) rename apps/server/src/provider/NativeProtocolLogging.ts => packages/provider-core/src/server/nativeProtocolLogging.ts (100%) rename apps/server/src/orchestration-v2/SubagentProjection.ts => packages/provider-core/src/server/subagentProjection.ts (100%) create mode 100644 packages/provider-opencode/package.json create mode 100644 packages/provider-opencode/src/client.ts create mode 100644 packages/provider-opencode/src/server.ts rename apps/server/src/provider/opencodeRuntime.cliParsers.test.ts => packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts (89%) rename apps/server/src/provider/opencodeRuntime.environment.test.ts => packages/provider-opencode/src/server/OpenCodeRuntime.environment.test.ts (99%) rename apps/server/src/provider/opencodeRuntime.inventory.test.ts => packages/provider-opencode/src/server/OpenCodeRuntime.inventory.test.ts (99%) rename apps/server/src/provider/opencodeRuntime.permissions.test.ts => packages/provider-opencode/src/server/OpenCodeRuntime.permissions.test.ts (89%) rename apps/server/src/provider/opencodeRuntime.ts => packages/provider-opencode/src/server/OpenCodeRuntime.ts (99%) rename {apps/server/src/provider => packages/provider-opencode/src/server}/OpenCodeServerLedger.test.ts (99%) rename {apps/server/src/provider => packages/provider-opencode/src/server}/OpenCodeServerLedger.ts (95%) rename {apps/server/src/provider => packages/provider-opencode/src/server}/OpenCodeServerOwner.test.ts (99%) rename {apps/server/src/provider => packages/provider-opencode/src/server}/OpenCodeServerOwner.ts (98%) rename apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts => packages/provider-opencode/src/server/adapter.test.ts (98%) rename apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts => packages/provider-opencode/src/server/adapter.ts (98%) rename apps/server/src/provider/Drivers/OpenCodeDriver.test.ts => packages/provider-opencode/src/server/driver.test.ts (90%) rename apps/server/src/provider/Drivers/OpenCodeDriver.ts => packages/provider-opencode/src/server/driver.ts (92%) rename apps/server/src/provider/testFixtures/opencodeProbeResponses.ts => packages/provider-opencode/src/server/probeResponses.fixture.ts (100%) rename apps/server/src/provider/OpenCodeProvider.test.ts => packages/provider-opencode/src/server/status.test.ts (98%) rename apps/server/src/provider/OpenCodeProvider.ts => packages/provider-opencode/src/server/status.ts (99%) rename apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts => packages/provider-opencode/src/server/textGeneration.test.ts (96%) rename apps/server/src/textGeneration/OpenCodeTextGeneration.ts => packages/provider-opencode/src/server/textGeneration.ts (95%) rename apps/server/src/orchestration-v2/Adapters/OpenCodeToolItems.ts => packages/provider-opencode/src/server/toolItems.ts (100%) rename apps/server/src/provider/openCodeUsageLimits.ts => packages/provider-opencode/src/server/usageLimits.ts (100%) rename {apps/server/src/provider/opencode2 => packages/provider-opencode/src/server/v2}/OpenCode2Client.live.test.ts (100%) rename {apps/server/src/provider/opencode2 => packages/provider-opencode/src/server/v2}/OpenCode2Client.test.ts (100%) rename {apps/server/src/provider/opencode2 => packages/provider-opencode/src/server/v2}/OpenCode2Client.ts (99%) rename {apps/server/src/provider/opencode2 => packages/provider-opencode/src/server/v2}/OpenCode2Server.live.test.ts (99%) rename {apps/server/src/provider/opencode2 => packages/provider-opencode/src/server/v2}/OpenCode2Server.test.ts (99%) rename {apps/server/src/provider/opencode2 => packages/provider-opencode/src/server/v2}/OpenCode2Server.ts (94%) rename apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.ts => packages/provider-opencode/src/server/v2/adapter.ts (98%) rename apps/server/src/textGeneration/OpenCode2TextGeneration.ts => packages/provider-opencode/src/server/v2/textGeneration.ts (92%) rename apps/server/src/provider/opencodeVersionProbe.test.ts => packages/provider-opencode/src/server/versionProbe.test.ts (98%) rename apps/server/src/provider/opencodeVersionProbe.ts => packages/provider-opencode/src/server/versionProbe.ts (99%) create mode 100644 packages/provider-opencode/src/settings.ts create mode 100644 packages/provider-opencode/src/testing.ts create mode 100644 packages/provider-opencode/tsconfig.json rename {apps/server => packages/shared}/src/atomicWrite.test.ts (100%) rename {apps/server => packages/shared}/src/atomicWrite.ts (100%) diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 08307ad65c2ac6c5c048b57aa9ae85d2feb690b1..02ba1b20ae859c7a7dd833f2241fc67cd3a1e78c 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -71,6 +71,7 @@ "@t3tools/mobile-terminal-native": "file:./modules/t3-terminal", "@t3tools/provider-core": "workspace:*", "@t3tools/provider-muse": "workspace:*", + "@t3tools/provider-opencode": "workspace:*", "@t3tools/provider-pi": "workspace:*", "@t3tools/shared": "workspace:*", "@tabler/icons-react-native": "^3.44.0", diff --git a/apps/mobile/src/components/ProviderIcon.tsx b/apps/mobile/src/components/ProviderIcon.tsx index 0645e673981817cdd1b4eae9340d43c9f9949b45..dda6e6f3b544c2bbe559a2c3c1f5a906b36a2a7b 100644 --- a/apps/mobile/src/components/ProviderIcon.tsx +++ b/apps/mobile/src/components/ProviderIcon.tsx @@ -138,15 +138,6 @@ export function ProviderIcon(props: ProviderIconProps) { ); } - if (props.provider === "opencode") { - return ( - - - - - ); - } - // codex (and unknown drivers) return ( diff --git a/apps/mobile/src/lib/providerClients.ts b/apps/mobile/src/lib/providerClients.ts index 2d3ec1224992afbee93798b67c5865208f17a392..11f7e13a3a78dcf6f44ea15cf350deee24b4745a 100644 --- a/apps/mobile/src/lib/providerClients.ts +++ b/apps/mobile/src/lib/providerClients.ts @@ -1,10 +1,11 @@ import { isProviderDriverKind } from "@t3tools/contracts"; import { makeProviderClientRegistry } from "@t3tools/provider-core/client"; import { museClient } from "@t3tools/provider-muse/client"; +import { openCodeClient } from "@t3tools/provider-opencode/client"; import { piClient } from "@t3tools/provider-pi/client"; /** The provider client definitions this mobile build ships. */ -const providerClients = makeProviderClientRegistry([museClient, piClient]); +const providerClients = makeProviderClientRegistry([museClient, openCodeClient, piClient]); /** The client definition for a driver kind, or `undefined` for drivers drawn by hand. */ export function getProviderClient(driver: string | null | undefined) { diff --git a/apps/server/package.json b/apps/server/package.json index bb776149e4cd55099e572c832342ef2642029ab3..11f202120f85a6d19160255608e90e8c08d7b334 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -38,11 +38,9 @@ "@napi-rs/keyring": "^1.3.0", "@noble/hashes": "catalog:", "@opencode-ai/sdk": "^1.3.15", - "@opencode/client": "2.0.23", - "@opencode/protocol": "2.0.23", - "@opencode/schema": "2.0.23", "@t3tools/provider-core": "workspace:*", "@t3tools/provider-muse": "workspace:*", + "@t3tools/provider-opencode": "workspace:*", "@t3tools/provider-pi": "workspace:*", "diff": "8.0.3", "effect": "catalog:", diff --git a/apps/server/src/cli/theme.ts b/apps/server/src/cli/theme.ts index 13f451b42363d6d9b26513dc0121e436f630fe9a..2c05c00f9a4e366c30cc90b6289483f40f93a315 100644 --- a/apps/server/src/cli/theme.ts +++ b/apps/server/src/cli/theme.ts @@ -35,7 +35,7 @@ import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import { Argument, Command, Flag } from "effect/cli"; -import { writeFileStringAtomically } from "../atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import * as ServerConfig from "../config.ts"; import { MAX_THEME_FILE_BYTES, diff --git a/apps/server/src/device/LocalDeviceHost.ts b/apps/server/src/device/LocalDeviceHost.ts index 65ddf6895742ecdd9eff0d2537981a4639d1d7ce..58179151877c25376458b4b27a20cd42fcf32b10 100644 --- a/apps/server/src/device/LocalDeviceHost.ts +++ b/apps/server/src/device/LocalDeviceHost.ts @@ -47,7 +47,7 @@ import { HttpClient } from "effect/http"; import * as ChildProcess from "effect/process/ChildProcess"; import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; -import { writeFileStringAtomically } from "../atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import * as ServerConfig from "../config.ts"; import * as ProcessRunner from "../processRunner.ts"; import * as DeviceHost from "./DeviceHost.ts"; diff --git a/apps/server/src/keybindings.ts b/apps/server/src/keybindings.ts index 3697b62cb403d4500d64927d660777057be3be2c..332aa7134bd0dd8e9f198efa580bb63982aeb9a8 100644 --- a/apps/server/src/keybindings.ts +++ b/apps/server/src/keybindings.ts @@ -41,7 +41,7 @@ import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import * as Semaphore from "effect/Semaphore"; import * as ServerConfig from "./config.ts"; -import { writeFileStringAtomically } from "./atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson"; import { DEFAULT_KEYBINDINGS, diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index 78d4ee48e66645e040c6de3c9cfa9a03d1d278a1..b218a76d1ab5033f14e63b65ac1cfc2a642c3185 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -75,7 +75,7 @@ import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterReg import { subagentResultForRun, delegatedTaskProgress, -} from "../orchestration-v2/SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; import { DispatchModeLimit, type DispatchModeRefusal, diff --git a/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts index 2eb06f7570529863e79a2c4e4576e07dcda6d787..4f2ea3e2f3d61e0d6d9c52b0a8c55058e4094690 100644 --- a/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts @@ -30,8 +30,8 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; import * as ModelManifest from "../provider/ModelManifest.ts"; import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts"; import * as ProviderEventLoggers from "../provider/ProviderEventLoggers.ts"; -import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; -import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; import * as ServerSettings from "../serverSettings.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts index 90255fe75a78701fe5842be70694d55122a4fb43..03a5c629b66302c3dcf3d02971a57fea2de3b6e3 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts @@ -119,7 +119,7 @@ import { makeSubagentChildThread, makeSubagentConversationArtifacts, subagentThreadTitle, -} from "../SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; export const ACP_PROTOCOL = "acp.ndjson-jsonrpc" as const; diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 50fab3e191dba7f5e4255b5f0375c1add9faf867..4cd45b86df7f9e6ca747782ab901915959d92ad3 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -140,7 +140,7 @@ import { makeSubagentChildThread, makeSubagentConversationArtifacts, subagentThreadTitle, -} from "../SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; export const CLAUDE_PROVIDER = ProviderDriverKind.make("claudeAgent"); export const CLAUDE_AGENT_SDK_QUERY_PROTOCOL = "claude-agent-sdk.query" as const; diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index 5318971e89d4e7997209d65f8d4d4ec58d848d07..c8b98828939027d814389dc068db5b48b8b687fa 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -165,7 +165,7 @@ import { makeSubagentChildThread, makeSubagentConversationArtifacts, subagentThreadTitle, -} from "../SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; const CODEX_PROVIDER = ProviderDriverKind.make("codex"); export const CODEX_DRIVER_KIND = CODEX_PROVIDER; diff --git a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts index 53a1d260cc838d199857a293e5ee75722bb13a5a..273564e786c4949dd90b0814471accf62ea601d7 100644 --- a/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts @@ -73,7 +73,7 @@ import { makeSubagentChildThread, makeSubagentConversationArtifacts, subagentThreadTitle, -} from "../SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; import * as CursorAgentSdk from "./CursorAgentSdk.ts"; export { cursorSdkModelSelection } from "../../provider/cursorSdkModel.ts"; diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts index 96b28d5e082e8ece871184103f64cb3289ecc53c..ff87c347641e5f6be84e47ac57330fddaecf7d5a 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts @@ -46,8 +46,11 @@ import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import type { ProviderContinuationRequest } from "@t3tools/provider-core/server/continuationRequests"; import * as ProviderContinuationRequests from "@t3tools/provider-core/server/continuationRequests"; -import { OPENCODE_PROVIDER } from "./OpenCodeAdapterV2.ts"; -import { OPENCODE_2_STILL_STOPPING, t3McpServerName } from "./OpenCode2AdapterV2.ts"; +import { + OPENCODE_2_STILL_STOPPING, + OPENCODE_PROVIDER, + t3McpServerName, +} from "@t3tools/provider-opencode/testing"; import { openCode2ReplayRuntime } from "./OpenCode2AdapterV2.testkit.ts"; const SESSION = "ses_f148ca2deffeJcwCnRQtb0YFNX"; diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts index 466349cc37ff73e2ecdac5b882cd345b93132768..2b009799cc5343cc5d8a45a4acd44fcb1aafe802 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts @@ -20,22 +20,19 @@ import * as HttpClientError from "effect/http/HttpClientError"; import * as HttpClientResponse from "effect/http/HttpClientResponse"; import * as UrlParams from "effect/http/UrlParams"; -import * as ServerConfig from "../../config.ts"; -import * as OpenCode2Client from "../../provider/opencode2/OpenCode2Client.ts"; -import * as OpenCode2Server from "../../provider/opencode2/OpenCode2Server.ts"; +import * as OpenCode2Client from "@t3tools/provider-opencode/server/v2/OpenCode2Client"; +import * as OpenCode2Server from "@t3tools/provider-opencode/server/v2/OpenCode2Server"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts"; import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate"; -import { - makeReplayServerConfig, - type OrchestratorV2ProviderReplayHarness, -} from "../testkit/ProviderReplayHarness.ts"; -import { OPENCODE_PROVIDER } from "./OpenCodeAdapterV2.ts"; +import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts"; +import { layerTestProviderHost } from "@t3tools/provider-testing/host"; +import { OPENCODE_PROVIDER } from "@t3tools/provider-opencode/testing"; import { OpenCodeReplayController, OpenCodeReplayTranscriptDecodeError, } from "./OpenCodeAdapterV2.testkit.ts"; -import * as OpenCode2AdapterV2 from "./OpenCode2AdapterV2.ts"; +import * as OpenCode2Adapter from "@t3tools/provider-opencode/server/v2/adapter"; export const OPENCODE2_HTTP_PROTOCOL = "opencode2-http.sse" as const; const BASE_URL = "http://opencode2.replay"; @@ -265,15 +262,12 @@ const makeReplayAdapter = ( ) => Effect.gen(function* () { const server = yield* replayServer(transcript, options); - return yield* OpenCode2AdapterV2.make(ProviderInstanceId.make("opencode")).pipe( + return yield* OpenCode2Adapter.make(ProviderInstanceId.make("opencode")).pipe( Effect.provideService(OpenCode2Server.OpenCode2Server, server), ); }); -const layerReplayServerConfig = (scenario: string) => - Layer.effect(ServerConfig.ServerConfig, makeReplayServerConfig(scenario).pipe(Effect.orDie)).pipe( - Layer.provide(NodeServices.layer), - ); +const layerReplayHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)); function layerRegistry( transcript: OpenCode2ReplayTranscript, @@ -283,15 +277,7 @@ function layerRegistry( makeReplayAdapter(transcript, { external: true, ...options }).pipe( Effect.map((adapter) => ProviderAdapterRegistry.layerFromAdapters([adapter])), ), - ).pipe( - Layer.provide( - Layer.mergeAll( - layerReplayServerConfig(transcript.scenario), - IdAllocator.layer, - NodeServices.layer, - ), - ), - ); + ).pipe(Layer.provide(Layer.mergeAll(layerReplayHost, IdAllocator.layer, NodeServices.layer))); } /** @@ -327,15 +313,7 @@ export const openCode2ReplayRuntime = ( cwd: "/work/opencode2", }, }); - }).pipe( - Effect.provide( - Layer.mergeAll( - layerReplayServerConfig("opencode2_adapter"), - IdAllocator.layer, - NodeServices.layer, - ), - ), - ); + }).pipe(Effect.provide(Layer.mergeAll(layerReplayHost, IdAllocator.layer, NodeServices.layer))); export const OpenCode2OrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness< OpenCode2ReplayTranscript, diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts index aaf50f24a6ac4f32aa2c669e19ca06f90ea56c11..36d0e35ddcede3f369e2167c06f8e99e0ad48043 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts @@ -6,22 +6,19 @@ import * as Duration from "effect/Duration"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; -import * as ServerConfig from "../../config.ts"; -import * as OpenCodeRuntime from "../../provider/opencodeRuntime.ts"; +import { OpenCodeAdapterV2Driver } from "@t3tools/provider-opencode/server"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import { layerTestProviderHost } from "@t3tools/provider-testing/host"; import * as ProviderEventLoggers from "../../provider/ProviderEventLoggers.ts"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver"; import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts"; -import { - makeReplayServerConfig, - type OrchestratorV2ProviderReplayHarness, -} from "../testkit/ProviderReplayHarness.ts"; +import { type OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts"; import { OPENCODE_DEFAULT_INSTANCE_ID, OPENCODE_PROVIDER, OPENCODE_SDK_PROTOCOL, - OpenCodeAdapterV2Driver, -} from "./OpenCodeAdapterV2.ts"; +} from "@t3tools/provider-opencode/testing"; const OPENCODE_SDK_REPLAY_PROTOCOL = OPENCODE_SDK_PROTOCOL; @@ -457,10 +454,6 @@ function layerOpenCodeReplayRuntime(transcript: OpenCodeSdkReplayTranscript) { } function layerOpenCodeProviderAdapterRegistryReplay(transcript: OpenCodeSdkReplayTranscript) { - const layerServerConfig = Layer.effect( - ServerConfig.ServerConfig, - makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie), - ).pipe(Layer.provide(NodeServices.layer)); return ProviderAdapterRegistry.layerFromDrivers({ drivers: [OpenCodeAdapterV2Driver], configMap: { @@ -473,7 +466,7 @@ function layerOpenCodeProviderAdapterRegistryReplay(transcript: OpenCodeSdkRepla Layer.provide( Layer.mergeAll( layerOpenCodeReplayRuntime(transcript), - layerServerConfig, + layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)), NodeServices.layer, IdAllocator.layer, Layer.succeed( diff --git a/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts index d6dc569db2b684b7a5ace7e4074e46cb30c405ea..1c74ee18d7a925016f9d1f436449d7d55936c08e 100644 --- a/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts @@ -30,8 +30,8 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; import * as ModelManifest from "../provider/ModelManifest.ts"; import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts"; import * as ProviderEventLoggers from "../provider/ProviderEventLoggers.ts"; -import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; -import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; import * as ServerSettings from "../serverSettings.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; diff --git a/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts index b6b5eee97a0d99bc530a46d9da448c761d8015f0..15d29d30f8acd5eee121103d8f1aed08c6cbbd72 100644 --- a/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts @@ -30,8 +30,8 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; import * as ModelManifest from "../provider/ModelManifest.ts"; import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts"; import * as ProviderEventLoggers from "../provider/ProviderEventLoggers.ts"; -import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; -import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; import * as ServerSettings from "../serverSettings.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; diff --git a/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts b/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts index 873ca407a7012a9f1827759a782817875d04cb23..3b9fa218d052172847a9c38808de417e623abf30 100644 --- a/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts +++ b/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts @@ -27,7 +27,7 @@ import { OPENCODE2_HTTP_PROTOCOL, OpenCode2OrchestratorReplayHarness, } from "./Adapters/OpenCode2AdapterV2.testkit.ts"; -import { OPENCODE_PROVIDER } from "./Adapters/OpenCodeAdapterV2.ts"; +import { OPENCODE_PROVIDER } from "@t3tools/provider-opencode/testing"; import { provideDeterministicTestRuntime } from "./testkit/DeterministicRuntime.ts"; import type { OrchestratorV2ScenarioStep } from "./testkit/OrchestratorScenario.ts"; import { runOrchestratorV2ProviderReplayScenario } from "./testkit/ProviderReplayHarness.ts"; diff --git a/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.live.test.ts index 9f046abba25f3dde4656c5a962e084e65b356a3c..55d93ded6e6e74a6d90171b836cdc3825efb9f4e 100644 --- a/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.live.test.ts @@ -55,9 +55,9 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; import * as ModelManifest from "../provider/ModelManifest.ts"; import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts"; import * as ProviderEventLoggers from "../provider/ProviderEventLoggers.ts"; -import * as OpenCode2Client from "../provider/opencode2/OpenCode2Client.ts"; -import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; -import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts"; +import * as OpenCode2Client from "@t3tools/provider-opencode/server/v2/OpenCode2Client"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; import * as ServerSettings from "../serverSettings.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts index 54127e20bc7a375a491d02d3d56a3a243ba4c27a..c85f11c568350e52a261804c45703fb49e03c3ee 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.ts @@ -121,7 +121,7 @@ import { subagentResultForRun, delegatedTaskProgress, subagentThreadTitle, -} from "./SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; import { forkableSourceRunStatusError, isForkableSourceRunStatus, diff --git a/apps/server/src/orchestration-v2/SubagentProjection.test.ts b/apps/server/src/orchestration-v2/SubagentProjection.test.ts index e2691e08e0db6680099c3957821cbab6ae9f4def..59eca69cf35a17dcb1686e1b72a35f2d4ba2e6ea 100644 --- a/apps/server/src/orchestration-v2/SubagentProjection.test.ts +++ b/apps/server/src/orchestration-v2/SubagentProjection.test.ts @@ -21,7 +21,7 @@ import { delegatedTaskProgress, subagentResultForRun, makeSubagentConversationArtifacts, -} from "./SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; import { emptyProjection } from "./ProjectionStore.ts"; diff --git a/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts b/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts index 8a6490ef798fc4d4c32028bf854611a56250c290..3e33d5b2de417ec349ba7410d45098f4fe00da98 100644 --- a/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts +++ b/apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts @@ -17,7 +17,7 @@ import { GrokAdapterV2Driver, type GrokAdapterV2DriverEnv } from "./Adapters/Gro import { OpenCodeAdapterV2Driver, type OpenCodeAdapterV2DriverEnv, -} from "./Adapters/OpenCodeAdapterV2.ts"; +} from "@t3tools/provider-opencode/server"; import { PiAdapterV2Driver, type PiAdapterV2DriverEnv } from "@t3tools/provider-pi/server"; import type { AnyProviderAdapterDriver } from "@t3tools/provider-core/server/adapterDriver"; diff --git a/apps/server/src/provider/ModelManifest.ts b/apps/server/src/provider/ModelManifest.ts index 2f0f65cf5390378795c54ea892d73b213fe8ce15..37c2bbec91b0a235ab1547cbae9e61ddba23e1a8 100644 --- a/apps/server/src/provider/ModelManifest.ts +++ b/apps/server/src/provider/ModelManifest.ts @@ -33,7 +33,7 @@ import * as Schema from "effect/Schema"; import * as Semaphore from "effect/Semaphore"; import { HttpClient, HttpClientResponse } from "effect/http"; -import { writeFileStringAtomically } from "../atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import { ServerConfig } from "../config.ts"; import * as ServerSettings from "../serverSettings.ts"; import { hasValidClaudeManifestAdapters } from "./ClaudeModelManifest.ts"; diff --git a/apps/server/src/provider/opencode2/OpenCode2Client.events.test.ts b/apps/server/src/provider/OpenCode2Client.events.test.ts similarity index 97% rename from apps/server/src/provider/opencode2/OpenCode2Client.events.test.ts rename to apps/server/src/provider/OpenCode2Client.events.test.ts index 0c4ae966e2d0140d761d519d334954bee48dc13a..25f246d89acb3594977529c717589652369c7d15 100644 --- a/apps/server/src/provider/opencode2/OpenCode2Client.events.test.ts +++ b/apps/server/src/provider/OpenCode2Client.events.test.ts @@ -10,10 +10,10 @@ import { TestClock } from "effect/testing"; import { HttpClient, HttpClientResponse } from "effect/http"; import { describe } from "vite-plus/test"; -import * as OpenCode2Client from "./OpenCode2Client.ts"; +import * as OpenCode2Client from "@t3tools/provider-opencode/server/v2/OpenCode2Client"; const RECORDING = new URL( - "../../orchestration-v2/testkit/fixtures/opencode2_simple/opencode_transcript.ndjson", + "../orchestration-v2/testkit/fixtures/opencode2_simple/opencode_transcript.ndjson", import.meta.url, ); const Entry = Schema.Struct({ type: Schema.String, frame: Schema.optional(Schema.Unknown) }); diff --git a/apps/server/src/provider/ProviderInstanceRegistry.test.ts b/apps/server/src/provider/ProviderInstanceRegistry.test.ts index 8050e426dd6c20eafd5366dd01d73ff9c8914b0d..5c1bbb1658223dd4bcbb6e1d46ad136a2ecddeec 100644 --- a/apps/server/src/provider/ProviderInstanceRegistry.test.ts +++ b/apps/server/src/provider/ProviderInstanceRegistry.test.ts @@ -33,7 +33,6 @@ import { type CodexSettings, type CursorSettings, type GrokSettings, - type OpenCodeSettings, ProviderDriverKind, type ProviderInstanceConfigMap, ProviderInstanceId, @@ -56,10 +55,11 @@ import { ClaudeDriver, type ClaudeDriverEnv } from "./Drivers/ClaudeDriver.ts"; import { CodexDriver, type CodexDriverEnv } from "./Drivers/CodexDriver.ts"; import { CursorDriver, type CursorDriverEnv } from "./Drivers/CursorDriver.ts"; import { GrokDriver, type GrokDriverEnv } from "./Drivers/GrokDriver.ts"; -import { OpenCodeDriver, type OpenCodeDriverEnv } from "./Drivers/OpenCodeDriver.ts"; +import { OpenCodeDriver, type OpenCodeDriverEnv } from "@t3tools/provider-opencode/server"; +import type { OpenCodeSettings } from "@t3tools/provider-opencode/settings"; import * as ModelManifest from "./ModelManifest.ts"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; -import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts"; import * as ProviderEventLoggers from "./ProviderEventLoggers.ts"; import { makeProviderInstanceRegistry } from "./ProviderInstanceRegistry.ts"; diff --git a/apps/server/src/provider/ProviderRegistry.test.ts b/apps/server/src/provider/ProviderRegistry.test.ts index 733bb59280cfbc793022f5acd7e95b2f754db707..b76e079fc38a11f7280c2e6000de556296625b54 100644 --- a/apps/server/src/provider/ProviderRegistry.test.ts +++ b/apps/server/src/provider/ProviderRegistry.test.ts @@ -45,8 +45,8 @@ import * as AntigravityInstallation from "./AntigravityInstallation.ts"; import * as ModelManifest from "./ModelManifest.ts"; import { applyProviderCompatibility } from "./providerCompatibility.ts"; import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; -import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; import * as ProviderEventLoggers from "./ProviderEventLoggers.ts"; import * as ProviderInstanceRegistryHydration from "./ProviderInstanceRegistryHydration.ts"; import * as ServerConfig from "../config.ts"; diff --git a/apps/server/src/provider/acp/AcpNativeLogging.ts b/apps/server/src/provider/acp/AcpNativeLogging.ts index c909f5a007b8198def306c582e2ff19b25c5b724..a510c5f88b5a3f12ff05a213ca95710c0eec8a6e 100644 --- a/apps/server/src/provider/acp/AcpNativeLogging.ts +++ b/apps/server/src/provider/acp/AcpNativeLogging.ts @@ -10,7 +10,7 @@ import type { EventNdjsonLogger } from "../EventNdjsonLogger.ts"; import { structuralProtocolMethod, summarizeNativeProtocolPayload, -} from "../NativeProtocolLogging.ts"; +} from "@t3tools/provider-core/server/nativeProtocolLogging"; import type * as AcpSessionRuntime from "./AcpSessionRuntime.ts"; const transientProtocolUpdates = new Set(["agent_message_chunk", "agent_thought_chunk"]); diff --git a/apps/server/src/provider/acp/AcpRegistryAuthenticationState.ts b/apps/server/src/provider/acp/AcpRegistryAuthenticationState.ts index dee416d776cae92518c2130dd0d490bffb382442..986ed6d0ca72784cedb2ee49a66b773f51e6aaa1 100644 --- a/apps/server/src/provider/acp/AcpRegistryAuthenticationState.ts +++ b/apps/server/src/provider/acp/AcpRegistryAuthenticationState.ts @@ -12,7 +12,7 @@ import * as Ref from "effect/Ref"; import * as Schema from "effect/Schema"; import * as Semaphore from "effect/Semaphore"; -import { writeFileStringAtomically } from "../../atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; const decodeState = Schema.decodeUnknownEffect( Schema.fromJsonString(Schema.Struct({ binding: Schema.String, authenticated: Schema.Boolean })), diff --git a/apps/server/src/provider/builtInDrivers.ts b/apps/server/src/provider/builtInDrivers.ts index 1690a998c5f4016117d9a6bb154e06e402bcb418..8cfcc007e9c16fbca591dadb9953830f610d5ad3 100644 --- a/apps/server/src/provider/builtInDrivers.ts +++ b/apps/server/src/provider/builtInDrivers.ts @@ -26,7 +26,7 @@ import { ClaudeDriver, type ClaudeDriverEnv } from "./Drivers/ClaudeDriver.ts"; import { CodexDriver, type CodexDriverEnv } from "./Drivers/CodexDriver.ts"; import { CursorDriver, type CursorDriverEnv } from "./Drivers/CursorDriver.ts"; import { GrokDriver, type GrokDriverEnv } from "./Drivers/GrokDriver.ts"; -import { OpenCodeDriver, type OpenCodeDriverEnv } from "./Drivers/OpenCodeDriver.ts"; +import { OpenCodeDriver, type OpenCodeDriverEnv } from "@t3tools/provider-opencode/server"; import { MuseDriver, type MuseDriverEnv } from "@t3tools/provider-muse/server"; import { PiDriver, type PiDriverEnv } from "@t3tools/provider-pi/server"; import type { AnyProviderDriver } from "@t3tools/provider-core/server/driver"; diff --git a/apps/server/src/provider/providerStatusCache.ts b/apps/server/src/provider/providerStatusCache.ts index a4b260f43bf62d224316d8b4c0d9cdc091ec8113..ea9e7ff9ce7a8f7820dcfebb8b9cf98c0bb45e3d 100644 --- a/apps/server/src/provider/providerStatusCache.ts +++ b/apps/server/src/provider/providerStatusCache.ts @@ -9,7 +9,7 @@ import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; -import { writeFileStringAtomically } from "../atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; const decodeProviderStatusCache = Schema.decodeUnknownEffect( Schema.fromJsonString(ServerProviderSchema), diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index b6c0d99848ae8457df3903d3cb5e30ec52b0a1d8..f91976f38ece92999306a05dab03e7a69f635810 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -43,8 +43,8 @@ import * as ProviderEventIngestor from "./orchestration-v2/ProviderEventIngestor import * as ModelManifest from "./provider/ModelManifest.ts"; import * as ResetCreditCoordinator from "./provider/resetCreditCoordinator.ts"; import * as ProviderEventLoggers from "./provider/ProviderEventLoggers.ts"; -import * as OpenCodeRuntime from "./provider/opencodeRuntime.ts"; -import * as OpenCodeServerLedger from "./provider/OpenCodeServerLedger.ts"; +import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; +import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; import * as AcpRegistryCatalog from "./provider/AcpRegistryCatalog.ts"; import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; import * as CheckpointStore from "./checkpointing/CheckpointStore.ts"; @@ -614,7 +614,18 @@ const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe( // the rewritten registry reads snapshots off the instance registry and // no longer transitively provides it. Exposing it at the runtime level // keeps a single Live for all opencode consumers. - Layer.provideMerge(OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layer))), + Layer.provideMerge( + OpenCodeRuntime.layer.pipe( + Layer.provide( + Layer.unwrap( + Effect.gen(function* () { + const config = yield* ServerConfig.ServerConfig; + return OpenCodeServerLedger.layer({ stateDir: config.stateDir }); + }), + ), + ), + ), + ), Layer.provideMerge(layerWorkspace), Layer.provideMerge(ProjectEnrichmentService.layer), Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, layerProjectFaviconResolver)), diff --git a/apps/server/src/serverRuntimeState.ts b/apps/server/src/serverRuntimeState.ts index 26535ae348a2d60b320dbc947ab264000ce194d9..8e565b46e26b7e1c32b90aeae9811551dc645e57 100644 --- a/apps/server/src/serverRuntimeState.ts +++ b/apps/server/src/serverRuntimeState.ts @@ -4,7 +4,7 @@ import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; -import { writeFileStringAtomically } from "./atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import type * as ServerConfig from "./config.ts"; import { formatHostForUrl, isWildcardHost } from "./startupAccess.ts"; diff --git a/apps/server/src/serverSettings.test.ts b/apps/server/src/serverSettings.test.ts index 1611b7dfddb9520f2222912dda1148cf22114d38..4444a7851dffca7c862ed945342d6ef486895ba0 100644 --- a/apps/server/src/serverSettings.test.ts +++ b/apps/server/src/serverSettings.test.ts @@ -28,7 +28,7 @@ import * as SqlClient from "effect/sql/SqlClient"; import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; import * as ServerConfig from "./config.ts"; import * as SqlitePersistence from "./persistence/Sqlite.ts"; -import { writeFileStringAtomically } from "./atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import * as ServerSettingsModule from "./serverSettings.ts"; import { resolveProviderInstanceTerminalEnvironment } from "./terminal/Manager.ts"; diff --git a/apps/server/src/serverSettings.ts b/apps/server/src/serverSettings.ts index 0695f673650d9422fbc7de13786c2e0be4541f87..7536a74d6e02cc56f1123eedec70c472a10731db 100644 --- a/apps/server/src/serverSettings.ts +++ b/apps/server/src/serverSettings.ts @@ -53,7 +53,7 @@ import * as Semaphore from "effect/Semaphore"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import * as SqlClient from "effect/sql/SqlClient"; -import { writeFileStringAtomically } from "./atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import { resolveSymlinkTarget } from "@t3tools/shared/symlink"; import * as ServerConfig from "./config.ts"; import { type DeepPartial, deepMerge } from "@t3tools/shared/Struct"; diff --git a/apps/server/src/telemetry/Identify.ts b/apps/server/src/telemetry/Identify.ts index dcb59d8bb3fbe6cee448749301a075a762fcfeb0..4009625d57525cfacd64fe62626b16d90ef35991 100644 --- a/apps/server/src/telemetry/Identify.ts +++ b/apps/server/src/telemetry/Identify.ts @@ -8,7 +8,7 @@ import * as Path from "effect/Path"; import * as PlatformError from "effect/PlatformError"; import * as Schema from "effect/Schema"; -import { writeFileStringAtomically } from "../atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import * as ServerConfig from "../config.ts"; /** diff --git a/apps/server/src/textGeneration/OpenCode2TextGeneration.test.ts b/apps/server/src/textGeneration/OpenCode2TextGeneration.test.ts index eaed9ea6abd6a36a9d53aa17470ca71c71cd448a..424764cd8f377ab8befecfd3e41270d3fdaacf1e 100644 --- a/apps/server/src/textGeneration/OpenCode2TextGeneration.test.ts +++ b/apps/server/src/textGeneration/OpenCode2TextGeneration.test.ts @@ -4,15 +4,13 @@ import { ProviderInstanceId } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import * as ServerConfig from "../config.ts"; +import { layerTestProviderHost } from "@t3tools/provider-testing/host"; import * as OpenCode2AdapterV2Testkit from "../orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts"; -import * as OpenCode2Server from "../provider/opencode2/OpenCode2Server.ts"; -import * as OpenCode2TextGeneration from "./OpenCode2TextGeneration.ts"; +import * as OpenCode2Server from "@t3tools/provider-opencode/server/v2/OpenCode2Server"; +import * as OpenCode2TextGeneration from "@t3tools/provider-opencode/server/v2/textGeneration"; import { OPENCODE2_TITLE_GENERATION } from "./OpenCode2TextGeneration.fixture.ts"; -const layer = ServerConfig.ServerConfig.layerTest(process.cwd(), { - prefix: "t3code-opencode2-text-generation-test-", -}).pipe(Layer.provideMerge(NodeServices.layer)); +const layer = layerTestProviderHost().pipe(Layer.provideMerge(NodeServices.layer)); it.layer(layer)("OpenCode2TextGeneration", (it) => { it.effect("generates a title in a temporary session on the free tier and removes it", () => diff --git a/apps/server/src/usage/UsageService.ts b/apps/server/src/usage/UsageService.ts index 7b4269287f4bd56497a3e90eb308e8c3c75137b7..76bdf21f377acf9d1918de1286e01dee385ddcda 100644 --- a/apps/server/src/usage/UsageService.ts +++ b/apps/server/src/usage/UsageService.ts @@ -52,7 +52,7 @@ import * as Schema from "effect/Schema"; import * as Semaphore from "effect/Semaphore"; import { HttpClient, HttpClientResponse } from "effect/http"; -import { writeFileStringAtomically } from "../atomicWrite.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import * as ServerConfig from "../config.ts"; import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion"; import * as ServerSettings from "../serverSettings.ts"; diff --git a/apps/web/package.json b/apps/web/package.json index 63a5f5c2ad357b62aa031ba5a4f78b563a9a1c7b..372c956ec04ea06c17a98e5bdaafc940c6d65def 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -30,6 +30,7 @@ "@t3tools/contracts": "workspace:*", "@t3tools/provider-core": "workspace:*", "@t3tools/provider-muse": "workspace:*", + "@t3tools/provider-opencode": "workspace:*", "@t3tools/provider-pi": "workspace:*", "@t3tools/shared": "workspace:*", "@tanstack/react-pacer": "^0.19.4", diff --git a/apps/web/src/components/Icons.tsx b/apps/web/src/components/Icons.tsx index 7f38d65f2a14e426134172d89be89d8cc4778193..be008bac333089870dd2640de1099c4155b2f2d1 100644 --- a/apps/web/src/components/Icons.tsx +++ b/apps/web/src/components/Icons.tsx @@ -681,32 +681,6 @@ export const AntigravityIcon: Icon = (props) => ( ); -export const OpenCodeIcon: Icon = (props) => ( - - - - - - - - - - - - - -); - export const ACPRegistryIcon: Icon = ({ className, ...props }) => ( > = { [ProviderDriverKind.make("codex")]: OpenAI, [ProviderDriverKind.make("claudeAgent")]: ClaudeAI, - [ProviderDriverKind.make("opencode")]: OpenCodeIcon, [ProviderDriverKind.make("cursor")]: CursorIcon, [ProviderDriverKind.make("grok")]: GrokIcon, [ProviderDriverKind.make("antigravity")]: AntigravityIcon, @@ -36,7 +27,6 @@ const PROVIDER_TEXT_COLOR_BY_PROVIDER: Partial` package, with - **Package exports.** `./settings` holds the instance settings schema, built with `makeProviderSettingsSchema` from contracts. `./client` exports a `ProviderClientDefinition` (label, settings schema, plain-data icon, badge) and must stay browser- and React Native-safe. - `./server` exports the `ProviderDriver` and its adapter driver. Settings live in - `providerInstances`; new providers are off by default. + `./server` exports the `ProviderDriver` and its adapter driver. `./testing` exposes internals + the server's replay testkits drive; testkits that need the orchestrator stay in the server. + Settings live in `providerInstances`; new providers are off by default. - **Package boundaries.** Server code imports only `@t3tools/provider-core/server/*`, contracts, and shared. It reaches the server through `ProviderHost` (paths, settings, background demand, attachments), never through `apps/server`. Tests use `@t3tools/provider-testing`. diff --git a/docs/internals/providers.md b/docs/internals/providers.md index b8542cbbef38b07782417e772c361811bc386981..6de371a95084745b046d12595c48e1ca24a5a44a 100644 --- a/docs/internals/providers.md +++ b/docs/internals/providers.md @@ -17,12 +17,12 @@ directory must not share one T3 MCP entry. - **1.x** uses one T3-managed chat server per thread, so threads cannot replace each other's connection. Catalog and text-generation work can share the - [instance-owned helper](../../apps/server/src/provider/OpenCodeServerOwner.ts), which closes - after an idle period. See the [1.x adapter](../../apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts). + [instance-owned helper](../../packages/provider-opencode/src/server/OpenCodeServerOwner.ts), which closes + after an idle period. See the [1.x adapter](../../packages/provider-opencode/src/server/adapter.ts). - **2.x** serves every directory from one - [server per instance](../../apps/server/src/provider/opencode2/OpenCode2Server.ts). Each thread + [server per instance](../../packages/provider-opencode/src/server/v2/OpenCode2Server.ts). Each thread registers its own `t3-code-` MCP entry, and session permission rules deny every other - thread's entry. See the [2.x adapter](../../apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.ts). + thread's entry. See the [2.x adapter](../../packages/provider-opencode/src/server/v2/adapter.ts). External OpenCode servers remain externally owned and can require an external restart to pick up configuration changes. OpenCode stores "always" approval grants for the whole project. Automatic diff --git a/package.json b/package.json index a345b307273db1b89d716eb8830e526229d88d9e..e261ffedfb3509af6d12ab3f16eb00e1aa35f9aa 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "tc": "vp run -r --concurrency-limit 2 typecheck", "lint": "vp lint --report-unused-disable-directives", "knip": "knip --preprocessor ./scripts/knip-schemas.ts", - "knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints", + "knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --workspace packages/provider-opencode --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints", "knip:production": "knip --production --preprocessor ./scripts/knip-schemas.ts", "lint:mobile": "node scripts/mobile-native-static-check.ts", "test": "vp run -r test", diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index e62f4ff578b791a07ef161e328c00bbf02c57ccc..a302412f179e77931910b91d45fea8fcc49a328d 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -907,58 +907,6 @@ export const AcpRegistrySettings = makeProviderSettingsSchema( ); export type AcpRegistrySettings = typeof AcpRegistrySettings.Type; -export const OpenCodeSettings = makeProviderSettingsSchema( - { - // Off by default (like Cursor and Grok): the binding is not yet stable - // enough to probe on every install. Users opt in from Settings. - enabled: Schema.Boolean.pipe( - Schema.withDecodingDefault(Effect.succeed(false)), - Schema.annotateKey({ providerSettingsForm: { hidden: true } }), - ), - binaryPath: makeBinaryPathSetting("opencode").pipe( - Schema.annotateKey({ - title: "Binary path", - description: "Path to the OpenCode binary.", - providerSettingsForm: { - placeholder: "opencode", - clearWhenEmpty: "omit", - }, - }), - ), - serverUrl: TrimmedString.pipe( - Schema.withDecodingDefault(Effect.succeed("")), - Schema.annotateKey({ - title: "Server URL", - description: "Leave blank to let T3 Code spawn the server when needed.", - providerSettingsForm: { - placeholder: "http://127.0.0.1:4096", - clearWhenEmpty: "omit", - }, - }), - ), - serverPassword: TrimmedString.pipe( - Schema.withDecodingDefault(Effect.succeed("")), - Schema.annotateKey({ - title: "Server password", - description: "Stored in plain text on disk.", - providerSettingsForm: { - control: "password", - placeholder: "Optional", - clearWhenEmpty: "omit", - }, - }), - ), - customModels: Schema.Array(CustomModelSetting).pipe( - Schema.withDecodingDefault(Effect.succeed([])), - Schema.annotateKey({ providerSettingsForm: { hidden: true } }), - ), - }, - { - order: ["binaryPath", "serverUrl", "serverPassword"], - }, -); -export type OpenCodeSettings = typeof OpenCodeSettings.Type; - /** * A read-only quota source outside this environment's provider CLIs. The * only kind today is a CLIProxyAPI hub, whose management API reports the diff --git a/packages/provider-core/package.json b/packages/provider-core/package.json index fa21d1f2efeec0a8e3ad17749ee2556b435f1f05..a733491676b758b464df8e530f4a8a3422c3c265 100644 --- a/packages/provider-core/package.json +++ b/packages/provider-core/package.json @@ -75,6 +75,10 @@ "types": "./src/server/mcpToolPresentation.ts", "import": "./src/server/mcpToolPresentation.ts" }, + "./server/nativeProtocolLogging": { + "types": "./src/server/nativeProtocolLogging.ts", + "import": "./src/server/nativeProtocolLogging.ts" + }, "./server/notification": { "types": "./src/server/notification.ts", "import": "./src/server/notification.ts" @@ -127,6 +131,10 @@ "types": "./src/server/snapshotSettings.ts", "import": "./src/server/snapshotSettings.ts" }, + "./server/subagentProjection": { + "types": "./src/server/subagentProjection.ts", + "import": "./src/server/subagentProjection.ts" + }, "./server/textGeneration": { "types": "./src/server/textGeneration.ts", "import": "./src/server/textGeneration.ts" diff --git a/apps/server/src/provider/NativeProtocolLogging.ts b/packages/provider-core/src/server/nativeProtocolLogging.ts similarity index 100% rename from apps/server/src/provider/NativeProtocolLogging.ts rename to packages/provider-core/src/server/nativeProtocolLogging.ts diff --git a/apps/server/src/orchestration-v2/SubagentProjection.ts b/packages/provider-core/src/server/subagentProjection.ts similarity index 100% rename from apps/server/src/orchestration-v2/SubagentProjection.ts rename to packages/provider-core/src/server/subagentProjection.ts diff --git a/packages/provider-opencode/package.json b/packages/provider-opencode/package.json new file mode 100644 index 0000000000000000000000000000000000000000..9aa836a69a6d1c99b08542ea88997e1c710bebbd --- /dev/null +++ b/packages/provider-opencode/package.json @@ -0,0 +1,68 @@ +{ + "name": "@t3tools/provider-opencode", + "private": true, + "type": "module", + "exports": { + "./client": { + "types": "./src/client.ts", + "import": "./src/client.ts" + }, + "./settings": { + "types": "./src/settings.ts", + "import": "./src/settings.ts" + }, + "./server": { + "types": "./src/server.ts", + "import": "./src/server.ts" + }, + "./testing": { + "types": "./src/testing.ts", + "import": "./src/testing.ts" + }, + "./server/OpenCodeRuntime": { + "types": "./src/server/OpenCodeRuntime.ts", + "import": "./src/server/OpenCodeRuntime.ts" + }, + "./server/OpenCodeServerLedger": { + "types": "./src/server/OpenCodeServerLedger.ts", + "import": "./src/server/OpenCodeServerLedger.ts" + }, + "./server/v2/adapter": { + "types": "./src/server/v2/adapter.ts", + "import": "./src/server/v2/adapter.ts" + }, + "./server/v2/OpenCode2Client": { + "types": "./src/server/v2/OpenCode2Client.ts", + "import": "./src/server/v2/OpenCode2Client.ts" + }, + "./server/v2/OpenCode2Server": { + "types": "./src/server/v2/OpenCode2Server.ts", + "import": "./src/server/v2/OpenCode2Server.ts" + }, + "./server/v2/textGeneration": { + "types": "./src/server/v2/textGeneration.ts", + "import": "./src/server/v2/textGeneration.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "vp test run --config ../../vite.config.ts --dir ." + }, + "dependencies": { + "@opencode-ai/sdk": "^1.3.15", + "@opencode/client": "2.0.23", + "@opencode/protocol": "2.0.23", + "@opencode/schema": "2.0.23", + "@t3tools/contracts": "workspace:*", + "@t3tools/provider-core": "workspace:*", + "@t3tools/shared": "workspace:*", + "effect": "catalog:" + }, + "devDependencies": { + "@effect/platform-node": "catalog:", + "@effect/vitest": "catalog:", + "@t3tools/provider-testing": "workspace:*", + "@types/node": "catalog:", + "vite-plus": "catalog:" + } +} diff --git a/packages/provider-opencode/src/client.ts b/packages/provider-opencode/src/client.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c5e2d165db0ff3cec93a94347f0f9f90a53a035 --- /dev/null +++ b/packages/provider-opencode/src/client.ts @@ -0,0 +1,24 @@ +/** + * OpenCode's client definition: settings form, label, and glyph. + * Browser- and React Native-safe. + * + * @module provider-opencode/client + */ +import { ProviderDriverKind } from "@t3tools/contracts"; +import { defineProviderClient } from "@t3tools/provider-core/client"; + +import { OpenCodeSettings } from "./settings.ts"; + +export const openCodeClient = defineProviderClient({ + driverKind: ProviderDriverKind.make("opencode"), + label: "OpenCode", + settingsSchema: OpenCodeSettings, + icon: { + viewBox: "0 0 32 40", + fill: { light: "#211E1E", dark: "#F1ECEC" }, + paths: [ + { d: "M24 32H8V16H24V32Z", fill: { light: "#CFCECD", dark: "#4B4646" } }, + { d: "M24 8H8V32H24V8ZM32 40H0V0H32V40Z" }, + ], + }, +}); diff --git a/packages/provider-opencode/src/server.ts b/packages/provider-opencode/src/server.ts new file mode 100644 index 0000000000000000000000000000000000000000..a9e0e2653cc0821bb44421bc025c57fa1205f8e6 --- /dev/null +++ b/packages/provider-opencode/src/server.ts @@ -0,0 +1,8 @@ +/** + * OpenCode's server entry: the driver the server registers, its adapter + * driver, and the runtime layer the driver needs from the server. + * + * @module provider-opencode/server + */ +export { OpenCodeDriver, type OpenCodeDriverEnv } from "./server/driver.ts"; +export { OpenCodeAdapterV2Driver, type OpenCodeAdapterV2DriverEnv } from "./server/adapter.ts"; diff --git a/apps/server/src/provider/opencodeRuntime.cliParsers.test.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts similarity index 89% rename from apps/server/src/provider/opencodeRuntime.cliParsers.test.ts rename to packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts index c66a2f36a2073e923148b3c3fabbd3116a215e65..499d7d5fee7368c24d2e415479bd4775cf6582fc 100644 --- a/apps/server/src/provider/opencodeRuntime.cliParsers.test.ts +++ b/packages/provider-opencode/src/server/OpenCodeRuntime.cliParsers.test.ts @@ -2,12 +2,7 @@ import * as NodeAssert from "node:assert/strict"; import { describe, it } from "vite-plus/test"; -import { - parseAgentListCliOutput, - parseModelsCliOutput, - parseSkillsCliOutput, - toOpenCodeFileParts, -} from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; describe("parseModelsCliOutput", () => { it("parses a single model from a single provider", () => { @@ -27,7 +22,7 @@ describe("parseModelsCliOutput", () => { }), ].join("\n"); - const result = parseModelsCliOutput(stdout); + const result = OpenCodeRuntime.parseModelsCliOutput(stdout); NodeAssert.equal(result.providers.size, 1); NodeAssert.equal(result.connected.length, 1); NodeAssert.equal(result.connected[0], "anthropic"); @@ -55,7 +50,7 @@ describe("parseModelsCliOutput", () => { JSON.stringify({ id: "gpt-4o", providerID: "openai", name: "GPT-4o" }), ].join("\n"); - const result = parseModelsCliOutput(stdout); + const result = OpenCodeRuntime.parseModelsCliOutput(stdout); NodeAssert.equal(result.providers.size, 2); NodeAssert.equal(result.connected.length, 2); NodeAssert.equal([...result.connected].sort().join(","), "anthropic,openai"); @@ -64,7 +59,7 @@ describe("parseModelsCliOutput", () => { }); it("handles empty input", () => { - const result = parseModelsCliOutput(""); + const result = OpenCodeRuntime.parseModelsCliOutput(""); NodeAssert.equal(result.providers.size, 0); NodeAssert.equal(result.connected.length, 0); }); @@ -77,7 +72,7 @@ describe("parseModelsCliOutput", () => { JSON.stringify({ id: "claude-haiku-4-5", providerID: "anthropic", name: "Haiku 4.5" }), ].join("\n"); - const result = parseModelsCliOutput(stdout); + const result = OpenCodeRuntime.parseModelsCliOutput(stdout); NodeAssert.equal(result.providers.size, 1); const provider = result.providers.get("anthropic")!; NodeAssert.equal(Object.keys(provider.models).length, 1); @@ -90,7 +85,7 @@ describe("parseModelsCliOutput", () => { JSON.stringify({ id: "claude-sonnet-4-5", providerID: "anthropic", name: "Sonnet" }) + "\r\n"; - const result = parseModelsCliOutput(stdout); + const result = OpenCodeRuntime.parseModelsCliOutput(stdout); NodeAssert.equal(result.providers.size, 1); NodeAssert.ok(result.providers.get("anthropic")!.models["claude-sonnet-4-5"]); }); @@ -122,7 +117,7 @@ describe("parseModelsCliOutput", () => { }), ].join("\n"); - const result = parseModelsCliOutput(stdout); + const result = OpenCodeRuntime.parseModelsCliOutput(stdout); const model = result.providers.get("opencode")!.models["gpt-5.4"]!; NodeAssert.ok(model); NodeAssert.ok(model.capabilities); @@ -145,7 +140,7 @@ describe("parseModelsCliOutput", () => { }), ].join("\n"); - const result = parseModelsCliOutput(stdout); + const result = OpenCodeRuntime.parseModelsCliOutput(stdout); NodeAssert.equal(result.providers.size, 1); NodeAssert.deepEqual([...result.connected], ["openrouter"]); const provider = result.providers.get("openrouter")!; @@ -164,7 +159,7 @@ describe("parseAgentListCliOutput", () => { " " + JSON.stringify([{ permission: "*", action: "allow", pattern: "*" }]), ].join("\n"); - const result = parseAgentListCliOutput(stdout); + const result = OpenCodeRuntime.parseAgentListCliOutput(stdout); NodeAssert.equal(result.length, 1); NodeAssert.equal(result[0]!.name, "build"); NodeAssert.equal(result[0]!.mode, "primary"); @@ -181,7 +176,7 @@ describe("parseAgentListCliOutput", () => { " " + JSON.stringify([{ permission: "edit", action: "ask", pattern: "*.md" }]), ].join("\n"); - const result = parseAgentListCliOutput(stdout); + const result = OpenCodeRuntime.parseAgentListCliOutput(stdout); NodeAssert.equal(result.length, 3); NodeAssert.equal(result[0]!.name, "build"); NodeAssert.equal(result[0]!.mode, "primary"); @@ -192,7 +187,7 @@ describe("parseAgentListCliOutput", () => { }); it("handles empty input", () => { - const result = parseAgentListCliOutput(""); + const result = OpenCodeRuntime.parseAgentListCliOutput(""); NodeAssert.equal(result.length, 0); }); @@ -204,7 +199,7 @@ describe("parseAgentListCliOutput", () => { " " + JSON.stringify([{ permission: "read", action: "allow", pattern: "*" }]), ].join("\n"); - const result = parseAgentListCliOutput(stdout); + const result = OpenCodeRuntime.parseAgentListCliOutput(stdout); NodeAssert.equal(result.length, 1); NodeAssert.equal(result[0]!.name, "explore"); }); @@ -221,7 +216,7 @@ describe("parseAgentListCliOutput", () => { ]; const stdout = ["build (primary)", " " + JSON.stringify(permissions)].join("\n"); - const result = parseAgentListCliOutput(stdout); + const result = OpenCodeRuntime.parseAgentListCliOutput(stdout); NodeAssert.equal(result.length, 1); NodeAssert.equal(result[0]!.permission.length, 3); NodeAssert.equal(result[0]!.permission[0]!.action, "allow"); @@ -236,7 +231,7 @@ describe("parseAgentListCliOutput", () => { " " + JSON.stringify([{ permission: "edit", action: "ask", pattern: "*.ts" }]), ].join("\n"); - const result = parseAgentListCliOutput(stdout); + const result = OpenCodeRuntime.parseAgentListCliOutput(stdout); NodeAssert.equal(result.length, 2); NodeAssert.equal(result[0]!.name, "code reviewer"); NodeAssert.equal(result[0]!.mode, "subagent"); @@ -252,7 +247,7 @@ describe("parseAgentListCliOutput", () => { " " + JSON.stringify([{ permission: "*", action: "allow", pattern: "*" }]), ].join("\n"); - const result = parseAgentListCliOutput(stdout); + const result = OpenCodeRuntime.parseAgentListCliOutput(stdout); NodeAssert.equal(result[0]!.hidden, true); NodeAssert.equal(result[1]!.hidden, false); }); @@ -260,7 +255,7 @@ describe("parseAgentListCliOutput", () => { describe("parseSkillsCliOutput", () => { it("parses only skill metadata from the CLI JSON output", () => { - const result = parseSkillsCliOutput( + const result = OpenCodeRuntime.parseSkillsCliOutput( JSON.stringify([ { name: "review-pr", @@ -281,7 +276,7 @@ describe("parseSkillsCliOutput", () => { }); it("degrades malformed output to an empty skill list", () => { - NodeAssert.deepEqual(parseSkillsCliOutput("not json"), []); + NodeAssert.deepEqual(OpenCodeRuntime.parseSkillsCliOutput("not json"), []); }); }); @@ -295,7 +290,7 @@ describe("toOpenCodeFileParts", () => { }); it("sends supported images, text, and PDFs natively and skips what models reject", () => { - const parts = toOpenCodeFileParts({ + const parts = OpenCodeRuntime.toOpenCodeFileParts({ attachments: [ attachment("application/pdf"), attachment("text/markdown"), @@ -320,7 +315,7 @@ describe("toOpenCodeFileParts", () => { }); it("keeps folded clipboard text on the lazy path fallback", () => { - const parts = toOpenCodeFileParts({ + const parts = OpenCodeRuntime.toOpenCodeFileParts({ attachments: [ { ...attachment("text/plain"), diff --git a/apps/server/src/provider/opencodeRuntime.environment.test.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.environment.test.ts similarity index 99% rename from apps/server/src/provider/opencodeRuntime.environment.test.ts rename to packages/provider-opencode/src/server/OpenCodeRuntime.environment.test.ts index 9b382347dcd6dd9afb68d706cb57776aeaf428bb..fdd1ae3319bd934b790ca50ea2727a9dbf682903 100644 --- a/apps/server/src/provider/opencodeRuntime.environment.test.ts +++ b/packages/provider-opencode/src/server/OpenCodeRuntime.environment.test.ts @@ -15,7 +15,7 @@ import * as TestClock from "effect/testing/TestClock"; import { FetchHttpClient, HttpClient } from "effect/http"; import { describe, expect, it } from "vite-plus/test"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts"; describe("resolveOpenCodeConfigContent", () => { diff --git a/apps/server/src/provider/opencodeRuntime.inventory.test.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.inventory.test.ts similarity index 99% rename from apps/server/src/provider/opencodeRuntime.inventory.test.ts rename to packages/provider-opencode/src/server/OpenCodeRuntime.inventory.test.ts index ad29f73c8d6d101d63887a9ea4fbff32f9f4c01a..29e302c134f1d48a6f3d3f9352ea1c4683a0ecb7 100644 --- a/apps/server/src/provider/opencodeRuntime.inventory.test.ts +++ b/packages/provider-opencode/src/server/OpenCodeRuntime.inventory.test.ts @@ -15,7 +15,7 @@ import { HostProcessPlatform, } from "@t3tools/shared/hostProcess"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts"; const layerTest = OpenCodeRuntime.layer.pipe( diff --git a/apps/server/src/provider/opencodeRuntime.permissions.test.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.permissions.test.ts similarity index 89% rename from apps/server/src/provider/opencodeRuntime.permissions.test.ts rename to packages/provider-opencode/src/server/OpenCodeRuntime.permissions.test.ts index a6ae1fbe04376939c4f4dedcb8345e1f4531b5c4..afa6da63f94cf4660e613cfedaf835a0b7db64b7 100644 --- a/apps/server/src/provider/opencodeRuntime.permissions.test.ts +++ b/packages/provider-opencode/src/server/OpenCodeRuntime.permissions.test.ts @@ -3,15 +3,15 @@ import * as NodeAssert from "node:assert/strict"; import * as RegExpUtils from "effect/RegExp"; import { describe, it } from "vite-plus/test"; -import { buildOpenCodePermissionRules, toOpenCodePermissionReply } from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; function actionFor( - runtimeMode: Parameters[0], + runtimeMode: Parameters[0], permission: string, target = "*", ) { // OpenCode uses the last matching rule. Its wildcards match directory separators. - return buildOpenCodePermissionRules(runtimeMode).findLast( + return OpenCodeRuntime.buildOpenCodePermissionRules(runtimeMode).findLast( (rule) => (rule.permission === "*" || rule.permission === permission) && new RegExp(`^${RegExpUtils.escape(rule.pattern).replaceAll("\\*", ".*")}$`, "s").test(target), @@ -69,7 +69,7 @@ describe("buildOpenCodePermissionRules", () => { }); it("allows everything only under full access", () => { - NodeAssert.deepEqual(buildOpenCodePermissionRules("full-access"), [ + NodeAssert.deepEqual(OpenCodeRuntime.buildOpenCodePermissionRules("full-access"), [ { permission: "*", pattern: "*", action: "allow" }, { permission: "external_directory", pattern: "*", action: "allow" }, ]); @@ -84,6 +84,6 @@ describe("toOpenCodePermissionReply", () => { ["decline", "reject"], ["cancel", "reject"], ] as const)("maps %s to %s", (decision, reply) => { - NodeAssert.equal(toOpenCodePermissionReply(decision), reply); + NodeAssert.equal(OpenCodeRuntime.toOpenCodePermissionReply(decision), reply); }); }); diff --git a/apps/server/src/provider/opencodeRuntime.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.ts similarity index 99% rename from apps/server/src/provider/opencodeRuntime.ts rename to packages/provider-opencode/src/server/OpenCodeRuntime.ts index 979cacc75b145897770e3381992e2465f34e0539..935294575057d062c82f32852a00a9007aa5a9b7 100644 --- a/apps/server/src/provider/opencodeRuntime.ts +++ b/packages/provider-opencode/src/server/OpenCodeRuntime.ts @@ -31,7 +31,7 @@ import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup"; -import { isWindowsCommandNotFound } from "../processRunner.ts"; +import { isWindowsCommandNotFound } from "@t3tools/provider-core/server/snapshotProbe"; import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts"; import { collectStreamAsString } from "@t3tools/provider-core/server/snapshotProbe"; import * as NetService from "@t3tools/shared/Net"; @@ -1103,7 +1103,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () { }); export class OpenCodeRuntime extends Context.Service()( - "t3/provider/opencodeRuntime", + "@t3tools/provider-opencode/server/OpenCodeRuntime", ) {} export const layer = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe( diff --git a/apps/server/src/provider/OpenCodeServerLedger.test.ts b/packages/provider-opencode/src/server/OpenCodeServerLedger.test.ts similarity index 99% rename from apps/server/src/provider/OpenCodeServerLedger.test.ts rename to packages/provider-opencode/src/server/OpenCodeServerLedger.test.ts index 9f1b297b816d915e73cfcdf6a994300623f9646e..a805572e8aac4d01daa03c74c0d2632e7bdde981 100644 --- a/apps/server/src/provider/OpenCodeServerLedger.test.ts +++ b/packages/provider-opencode/src/server/OpenCodeServerLedger.test.ts @@ -14,7 +14,7 @@ import * as Path from "effect/Path"; import * as Scope from "effect/Scope"; import { ChildProcessSpawner } from "effect/process"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts"; const SERVE_ARGS = ["serve", "--hostname=127.0.0.1", "--port=4096"]; diff --git a/apps/server/src/provider/OpenCodeServerLedger.ts b/packages/provider-opencode/src/server/OpenCodeServerLedger.ts similarity index 95% rename from apps/server/src/provider/OpenCodeServerLedger.ts rename to packages/provider-opencode/src/server/OpenCodeServerLedger.ts index 8243e37f048135ea5ed4c3d721a327bac990b9d5..0c721cf6ffd52c531f4d93f6fa7c68266cf2fab7 100644 --- a/apps/server/src/provider/OpenCodeServerLedger.ts +++ b/packages/provider-opencode/src/server/OpenCodeServerLedger.ts @@ -8,8 +8,7 @@ import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; -import { writeFileStringAtomically } from "../atomicWrite.ts"; -import * as ServerConfig from "../config.ts"; +import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup"; const ProcessIdentity = Schema.Struct({ pid: Schema.Int, startTime: Schema.String }); @@ -57,7 +56,7 @@ export class OpenCodeServerLedger extends Context.Service< readonly args: ReadonlyArray; }) => Effect.Effect>; } ->()("t3/provider/OpenCodeServerLedger") {} +>()("@t3tools/provider-opencode/server/OpenCodeServerLedger") {} const ENTRY_DIRECTORY = "opencode-servers"; const ENTRY_FILE = /^\d+\.json$/; @@ -321,16 +320,17 @@ export const make = Effect.fn("OpenCodeServerLedger.make")(function* (input: { return { track, reapOrphans }; }); -export const layer = Layer.effect( - OpenCodeServerLedger, - Effect.gen(function* () { - const config = yield* ServerConfig.ServerConfig; - const ledger = yield* make({ stateDir: config.stateDir }); - // Reaping waits for orphans to exit, so it must not hold up startup. - yield* ledger.reapOrphans.pipe(Effect.forkScoped); - return OpenCodeServerLedger.of({ track: ledger.track }); - }), -); +/** Tracks servers under `stateDir`, the server's own state directory. */ +export const layer = (input: { readonly stateDir: string }) => + Layer.effect( + OpenCodeServerLedger, + Effect.gen(function* () { + const ledger = yield* make(input); + // Reaping waits for orphans to exit, so it must not hold up startup. + yield* ledger.reapOrphans.pipe(Effect.forkScoped); + return OpenCodeServerLedger.of({ track: ledger.track }); + }), + ); /** Records nothing. For tests that start OpenCode servers without a state directory. */ export const layerTest = Layer.succeed( diff --git a/apps/server/src/provider/OpenCodeServerOwner.test.ts b/packages/provider-opencode/src/server/OpenCodeServerOwner.test.ts similarity index 99% rename from apps/server/src/provider/OpenCodeServerOwner.test.ts rename to packages/provider-opencode/src/server/OpenCodeServerOwner.test.ts index e789e2bec7191639d04c6f9f4fe266a93dc728bc..f1f9881fdc293d81c410a2a7f015524eb3cb4646 100644 --- a/apps/server/src/provider/OpenCodeServerOwner.test.ts +++ b/packages/provider-opencode/src/server/OpenCodeServerOwner.test.ts @@ -7,7 +7,7 @@ import * as Ref from "effect/Ref"; import * as TestClock from "effect/testing/TestClock"; import { expect } from "vite-plus/test"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts"; const unusedRuntimeMethod = () => diff --git a/apps/server/src/provider/OpenCodeServerOwner.ts b/packages/provider-opencode/src/server/OpenCodeServerOwner.ts similarity index 98% rename from apps/server/src/provider/OpenCodeServerOwner.ts rename to packages/provider-opencode/src/server/OpenCodeServerOwner.ts index 7b8999213673b68e6a9c688b9c0d3407daf21d96..05d65dba377dcc21b8219c9e09b9ddafa6d0ad37 100644 --- a/apps/server/src/provider/OpenCodeServerOwner.ts +++ b/packages/provider-opencode/src/server/OpenCodeServerOwner.ts @@ -6,7 +6,7 @@ import * as Layer from "effect/Layer"; import * as Scope from "effect/Scope"; import * as Semaphore from "effect/Semaphore"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; const OPENCODE_SERVER_IDLE_TTL = "30 seconds"; @@ -24,7 +24,7 @@ export class OpenCodeServerOwner extends Context.Service< use: (server: OpenCodeRuntime.OpenCodeServerProcess) => Effect.Effect, ) => Effect.Effect; } ->()("t3/provider/OpenCodeServerOwner") {} +>()("@t3tools/provider-opencode/server/OpenCodeServerOwner") {} /** Owns the lazy local OpenCode server shared by one provider instance. */ export const make = Effect.fn("OpenCodeServerOwner.make")(function* (input: { diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts b/packages/provider-opencode/src/server/adapter.test.ts similarity index 98% rename from apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts rename to packages/provider-opencode/src/server/adapter.test.ts index c52447fafaeb3a7400aed17f5d334eef3df961be..9f80cdbead986173ebccdb296a78ff7352f19c86 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts +++ b/packages/provider-opencode/src/server/adapter.test.ts @@ -4,7 +4,6 @@ import type { OpencodeClient, ToolPart } from "@opencode-ai/sdk/v2"; import { CheckpointId, NodeId, - OpenCodeSettings, ProjectId, ProviderInstanceId, ProviderSessionId, @@ -17,6 +16,7 @@ import { type OrchestrationV2ProviderThread, type OrchestrationV2ProviderTurn, } from "@t3tools/contracts"; +import { OpenCodeSettings } from "../settings.ts"; import * as Effect from "effect/Effect"; import * as Clock from "effect/Clock"; import * as DateTime from "effect/DateTime"; @@ -31,9 +31,10 @@ import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; -import * as ServerConfig from "../../config.ts"; -import type { EventNdjsonLogger } from "../../provider/EventNdjsonLogger.ts"; -import type { OpenCodeRuntimeShape } from "../../provider/opencodeRuntime.ts"; +import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; +import { layerTestProviderHost } from "@t3tools/provider-testing/host"; +import type * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; +import type * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import { @@ -48,8 +49,8 @@ import { makeOpenCodeAdapterV2, OPENCODE_PROVIDER, reconcileOpenCodePromptAdmissionStatus, -} from "./OpenCodeAdapterV2.ts"; -import { ProviderAdapterV2RuntimePolicy } from "@t3tools/provider-core/server/ProviderAdapter"; +} from "./adapter.ts"; +import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; const encodeUnknownJson = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Unknown)); const OPEN_CODE_TEST_SETTINGS = Schema.decodeSync(OpenCodeSettings)({ @@ -105,10 +106,10 @@ function asyncEventStream() { const OPENCODE_TEST_SETTINGS = Schema.decodeUnknownSync(OpenCodeSettings)({}); function runtimePolicy( - runtimeMode: ProviderAdapterV2RuntimePolicy["runtimeMode"], - override: Partial = {}, -): ProviderAdapterV2RuntimePolicy { - return ProviderAdapterV2RuntimePolicy.make({ + runtimeMode: ProviderAdapter.ProviderAdapterV2RuntimePolicy["runtimeMode"], + override: Partial = {}, +): ProviderAdapter.ProviderAdapterV2RuntimePolicy { + return ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ runtimeMode, interactionMode: "default", cwd: null, @@ -163,12 +164,12 @@ const makeOpenCodeRuntimeHarness = Effect.fn("makeOpenCodeRuntimeHarness")(funct runtime: { connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }), createOpenCodeSdkClient: () => client, - } as unknown as OpenCodeRuntimeShape, + } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape, idAllocator, - serverConfig: { - cwd: "/workspace", - attachmentsDir: "/tmp/attachments", - } as ServerConfig.ServerConfig["Service"], + host: { + paths: { cwd: "/workspace" }, + resolveAttachmentPath: () => null, + } as unknown as ProviderHost.ProviderHostShape, }); const runtime = yield* adapter.openSession({ threadId, @@ -1495,12 +1496,12 @@ describe("OpenCodeAdapterV2", () => { connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }), createOpenCodeSdkClient: () => client, - } as unknown as OpenCodeRuntimeShape, + } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape, idAllocator, - serverConfig: { - cwd: "/workspace", - attachmentsDir: "/tmp/attachments", - } as ServerConfig.ServerConfig["Service"], + host: { + paths: { cwd: "/workspace" }, + resolveAttachmentPath: () => null, + } as unknown as ProviderHost.ProviderHostShape, }); const threadId = ThreadId.make("thread-opencode-admission-race"); const providerSessionId = ProviderSessionId.make("session-opencode-admission-race"); @@ -1790,12 +1791,12 @@ describe("OpenCodeAdapterV2", () => { connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }), createOpenCodeSdkClient: () => client, - } as unknown as OpenCodeRuntimeShape, + } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape, idAllocator, - serverConfig: { - cwd: "/workspace", - attachmentsDir: "/tmp/attachments", - } as ServerConfig.ServerConfig["Service"], + host: { + paths: { cwd: "/workspace" }, + resolveAttachmentPath: () => null, + } as unknown as ProviderHost.ProviderHostShape, }); const threadId = ThreadId.make("thread-opencode-initial-stop"); const providerSessionId = ProviderSessionId.make("session-opencode-initial-stop"); @@ -2387,7 +2388,7 @@ describe("OpenCodeAdapterV2", () => { Effect.gen(function* () { const idAllocator = yield* IdAllocator.IdAllocatorV2; const records: Array = []; - const nativeEventLogger: EventNdjsonLogger = { + const nativeEventLogger: ProviderEventLoggers.EventNdjsonLogger = { filePath: "/tmp/provider-native.ndjson", write: (event) => Effect.sync(() => void records.push(event)), close: () => Effect.void, @@ -2419,7 +2420,7 @@ describe("OpenCodeAdapterV2", () => { it.effect("adopts the handed-over provider thread identity on session create", () => Effect.gen(function* () { const idAllocator = yield* IdAllocator.IdAllocatorV2; - const serverConfig = yield* ServerConfig.ServerConfig; + const host = yield* ProviderHost.ProviderHost; let createCount = 0; const createInputs: Array = []; const fakeClient = { @@ -2447,7 +2448,7 @@ describe("OpenCodeAdapterV2", () => { }, } as unknown as OpencodeClient; const unused = (operation: string) => () => Effect.die(`${operation} is not used`); - const runtime: OpenCodeRuntimeShape = { + const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = { startOpenCodeServerProcess: unused("startOpenCodeServerProcess"), connectToOpenCodeServer: () => Effect.succeed({ @@ -2472,7 +2473,7 @@ describe("OpenCodeAdapterV2", () => { environment: {}, runtime, idAllocator, - serverConfig, + host, }); const session = yield* adapter.openSession({ threadId, @@ -2527,9 +2528,7 @@ describe("OpenCodeAdapterV2", () => { Effect.provide( Layer.mergeAll( IdAllocator.layer, - ServerConfig.layerTest(process.cwd(), { - prefix: "t3-opencode-v2-adapter-", - }).pipe(Layer.provide(NodeServices.layer)), + layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)), ), ), ), diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts b/packages/provider-opencode/src/server/adapter.ts similarity index 98% rename from apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts rename to packages/provider-opencode/src/server/adapter.ts index c746bec2180e61fef52195da1d78bdc9a526d682..0fa85f3be6d49cfc505401de54f77a81578b42ab 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts +++ b/packages/provider-opencode/src/server/adapter.ts @@ -15,7 +15,6 @@ import { causeErrorTag } from "@t3tools/shared/observability"; import { defaultInstanceIdForDriver, type ModelSelection, - type OpenCodeSettings, type OrchestrationV2AppThread, type OrchestrationV2ConversationMessage, type OrchestrationV2ExecutionNode, @@ -29,7 +28,6 @@ import { type OrchestrationV2RuntimeRequest, type OrchestrationV2Subagent, type OrchestrationV2TurnItem, - OpenCodeSettings as OpenCodeSettingsSchema, type PlanId, ProviderDriverKind, type ProviderInstanceId, @@ -38,6 +36,7 @@ import { type RuntimeRequestId, type ThreadId, } from "@t3tools/contracts"; +import { OpenCodeSettings } from "../settings.ts"; import * as Cause from "effect/Cause"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; @@ -54,20 +53,18 @@ import * as Semaphore from "effect/Semaphore"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { resolveAttachmentPath } from "../../attachmentStore.ts"; -import * as ServerConfig from "../../config.ts"; +import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; -import type { EventNdjsonLogger } from "../../provider/EventNdjsonLogger.ts"; -import * as ProviderEventLoggers from "../../provider/ProviderEventLoggers.ts"; +import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import { structuralProtocolMethod, summarizeNativeProtocolPayload, -} from "../../provider/NativeProtocolLogging.ts"; +} from "@t3tools/provider-core/server/nativeProtocolLogging"; import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment"; import { t3OrchestrationSystemPrompt } from "@t3tools/provider-core/server/orchestrationInstructions"; import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions"; -import * as OpenCodeRuntime from "../../provider/opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import { makeProviderFailure } from "@t3tools/provider-core/server/failure"; import { turnScopedSelectionTransition } from "@t3tools/provider-core/server/selectionTransition"; @@ -78,15 +75,18 @@ import { type ProviderAdapterDriver, type ProviderAdapterDriverCreateInput, } from "@t3tools/provider-core/server/adapterDriver"; -import { makeSubagentChildThread, subagentThreadTitle } from "../SubagentProjection.ts"; -import { openCodeToolTurnItem } from "./OpenCodeToolItems.ts"; +import { + makeSubagentChildThread, + subagentThreadTitle, +} from "@t3tools/provider-core/server/subagentProjection"; +import { openCodeToolTurnItem } from "./toolItems.ts"; -export { openCodeToolProjectionKind } from "./OpenCodeToolItems.ts"; +export { openCodeToolProjectionKind } from "./toolItems.ts"; export const OPENCODE_PROVIDER = ProviderDriverKind.make("opencode"); export const OPENCODE_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(OPENCODE_PROVIDER); export const OPENCODE_SDK_PROTOCOL = "opencode-sdk.sse" as const; -const DEFAULT_OPENCODE_SETTINGS = Schema.decodeSync(OpenCodeSettingsSchema)({}); +const DEFAULT_OPENCODE_SETTINGS = Schema.decodeSync(OpenCodeSettings)({}); let openCodeMessageIdEpochMillis = -1; let openCodeMessageIdCounter = 0; @@ -440,8 +440,8 @@ export interface OpenCodeAdapterV2Options { readonly environment: NodeJS.ProcessEnv; readonly runtime: OpenCodeRuntime.OpenCodeRuntimeShape; readonly idAllocator: IdAllocator.IdAllocatorV2Shape; - readonly serverConfig: ServerConfig.ServerConfig["Service"]; - readonly nativeEventLogger?: EventNdjsonLogger; + readonly host: ProviderHost.ProviderHostShape; + readonly nativeEventLogger?: ProviderEventLoggers.EventNdjsonLogger; } export interface OpenCodeProtocolLogEvent { @@ -461,7 +461,7 @@ function formatOpenCodeProtocolLogPayload(event: OpenCodeProtocolLogEvent) { } export function makeOpenCodeProtocolLogger(input: { - readonly nativeEventLogger: EventNdjsonLogger | undefined; + readonly nativeEventLogger: ProviderEventLoggers.EventNdjsonLogger | undefined; readonly idAllocator: IdAllocator.IdAllocatorV2Shape; readonly providerInstanceId: ProviderInstanceId; readonly providerSessionId: ProviderSessionId; @@ -947,7 +947,7 @@ function unwrapData(operation: string, result: { readonly data?: A }): NonNul export function makeOpenCodeAdapterV2( options: OpenCodeAdapterV2Options, ): ProviderAdapter.ProviderAdapterV2Shape { - const { idAllocator, runtime, serverConfig } = options; + const { idAllocator, runtime, host } = options; return ProviderAdapter.ProviderAdapterV2.of({ instanceId: options.instanceId, @@ -957,7 +957,7 @@ export function makeOpenCodeAdapterV2( openSession: Effect.fn("OpenCodeAdapterV2.openSession")( function* (input: ProviderAdapter.ProviderAdapterV2OpenSessionInput) { const scope = yield* Effect.scope; - const cwd = input.runtimePolicy.cwd ?? serverConfig.cwd; + const cwd = input.runtimePolicy.cwd ?? host.paths.cwd; const connection = yield* runtime.connectToOpenCodeServer({ binaryPath: options.settings.binaryPath, directory: cwd, @@ -2853,13 +2853,11 @@ export function makeOpenCodeAdapterV2( const text = providerMessageTextWithAttachmentPaths({ text: turnInput.message.text, attachments: turnInput.message.attachments, - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }), + resolveAttachmentPath: host.resolveAttachmentPath, }).trim(); const files = OpenCodeRuntime.toOpenCodeFileParts({ attachments: turnInput.message.attachments, - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }), + resolveAttachmentPath: host.resolveAttachmentPath, }); if (text.length === 0 && files.length === 0) { throw protocolError("OpenCode turns require text or at least one valid attachment"); @@ -3357,19 +3355,11 @@ export function makeOpenCodeAdapterV2( const text = providerMessageTextWithAttachmentPaths({ text: steerInput.message.text, attachments: steerInput.message.attachments, - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ - attachmentsDir: serverConfig.attachmentsDir, - attachment, - }), + resolveAttachmentPath: host.resolveAttachmentPath, }).trim(); const files = OpenCodeRuntime.toOpenCodeFileParts({ attachments: steerInput.message.attachments, - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ - attachmentsDir: serverConfig.attachmentsDir, - attachment, - }), + resolveAttachmentPath: host.resolveAttachmentPath, }); if (text.length === 0 && files.length === 0) { return yield* protocolError("OpenCode steering requires text or an attachment"); @@ -3740,14 +3730,14 @@ export type OpenCodeAdapterV2DriverEnv = | OpenCodeRuntime.OpenCodeRuntime | IdAllocator.IdAllocatorV2 | ProviderEventLoggers.ProviderEventLoggers - | ServerConfig.ServerConfig; + | ProviderHost.ProviderHost; export const OpenCodeAdapterV2Driver: ProviderAdapterDriver< OpenCodeSettings, OpenCodeAdapterV2DriverEnv > = { driverKind: OPENCODE_PROVIDER, - configSchema: OpenCodeSettingsSchema, + configSchema: OpenCodeSettings, defaultConfig: (): OpenCodeSettings => DEFAULT_OPENCODE_SETTINGS, create: Effect.fn("OpenCodeAdapterV2Driver.create")( function* (input: ProviderAdapterDriverCreateInput) { @@ -3755,14 +3745,14 @@ export const OpenCodeAdapterV2Driver: ProviderAdapterDriver< const openCodeRuntime = yield* OpenCodeRuntime.OpenCodeRuntime; const idAllocator = yield* IdAllocator.IdAllocatorV2; const providerEventLoggers = yield* ProviderEventLoggers.ProviderEventLoggers; - const serverConfig = yield* ServerConfig.ServerConfig; + const host = yield* ProviderHost.ProviderHost; return makeOpenCodeAdapterV2({ instanceId: input.instanceId, settings: { ...input.config, enabled: input.enabled }, environment: mergeProviderInstanceEnvironment(input.environment, hostEnvironment), runtime: openCodeRuntime, idAllocator, - serverConfig, + host, ...(providerEventLoggers.native === undefined ? {} : { nativeEventLogger: providerEventLoggers.native }), @@ -3791,14 +3781,14 @@ const layer: Layer.Layer = []; const reachedServer = (operation: string) => @@ -48,18 +46,15 @@ const openCode2Runtime = { connectToOpenCodeServer: () => reachedServer("connect"), } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape; -const layerDeps = Layer.mergeAll( - ServerConfig.layerTest(process.cwd(), { prefix: "t3-opencode-driver-" }), +const layer = Layer.mergeAll( IdAllocator.layer, - ServerSettings.layerTest(), - Layer.mock(BackgroundPolicy.BackgroundPolicy)({}), + layerTestProviderHost(), Layer.succeed( ProviderEventLoggers.ProviderEventLoggers, ProviderEventLoggers.NoOpProviderEventLoggers, ), Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, openCode2Runtime), ).pipe(Layer.provideMerge(NodeServices.layer)); -const layer = ProviderHostLive.layer.pipe(Layer.provideMerge(layerDeps)); const create = (config: Partial, http: HttpClient.HttpClient) => OpenCodeDriver.create({ @@ -213,18 +208,15 @@ const changingRuntime = { startOpenCodeServerProcess: () => reachedServer("start"), connectToOpenCodeServer: () => reachedServer("connect"), } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape; -const layerUpdateDeps = Layer.mergeAll( - ServerConfig.layerTest(process.cwd(), { prefix: "t3-opencode-driver-update-" }), +const layerUpdate = Layer.mergeAll( IdAllocator.layer, - ServerSettings.layerTest(), - Layer.mock(BackgroundPolicy.BackgroundPolicy)({}), + layerTestProviderHost(), Layer.succeed( ProviderEventLoggers.ProviderEventLoggers, ProviderEventLoggers.NoOpProviderEventLoggers, ), Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, changingRuntime), ).pipe(Layer.provideMerge(NodeServices.layer)); -const layerUpdate = ProviderHostLive.layer.pipe(Layer.provideMerge(layerUpdateDeps)); it.layer(layerUpdate)("OpenCodeDriver updates", (it) => { it.effect("never runs the binary for a disabled instance's update check", () => diff --git a/apps/server/src/provider/Drivers/OpenCodeDriver.ts b/packages/provider-opencode/src/server/driver.ts similarity index 92% rename from apps/server/src/provider/Drivers/OpenCodeDriver.ts rename to packages/provider-opencode/src/server/driver.ts index 83347d86a87a6540183f81003a3fe4576c4778e9..9c37ab2abd48a0d0e1d18bbd2e4abbfbefe768f7 100644 --- a/apps/server/src/provider/Drivers/OpenCodeDriver.ts +++ b/packages/provider-opencode/src/server/driver.ts @@ -12,7 +12,8 @@ * * @module provider/Drivers/OpenCodeDriver */ -import { OpenCodeSettings, ProviderDriverKind } from "@t3tools/contracts"; +import { ProviderDriverKind } from "@t3tools/contracts"; +import { OpenCodeSettings } from "../settings.ts"; import * as Crypto from "effect/Crypto"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -24,16 +25,15 @@ import * as Stream from "effect/Stream"; import { HttpClient } from "effect/http"; import { ChildProcessSpawner } from "effect/process"; -import * as OpenCode2TextGeneration from "../../textGeneration/OpenCode2TextGeneration.ts"; -import { makeOpenCodeTextGeneration } from "../../textGeneration/OpenCodeTextGeneration.ts"; -import { ProviderHost } from "@t3tools/provider-core/server/ProviderHost"; -import * as ServerConfig from "../../config.ts"; -import * as OpenCodeAdapterV2 from "../../orchestration-v2/Adapters/OpenCodeAdapterV2.ts"; -import * as OpenCode2AdapterV2 from "../../orchestration-v2/Adapters/OpenCode2AdapterV2.ts"; -import type { ProviderAdapterV2Shape } from "@t3tools/provider-core/server/ProviderAdapter"; -import type { TextGeneration } from "../../textGeneration/TextGeneration.ts"; -import { ProviderDriverError } from "../Errors.ts"; -import { readOpenCodeGoUsageLimits } from "../openCodeUsageLimits.ts"; +import * as OpenCode2TextGeneration from "./v2/textGeneration.ts"; +import { makeOpenCodeTextGeneration } from "./textGeneration.ts"; +import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; +import * as OpenCodeAdapterV2 from "./adapter.ts"; +import * as OpenCode2AdapterV2 from "./v2/adapter.ts"; +import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; +import type { ProviderTextGeneration } from "@t3tools/provider-core/server/textGeneration"; +import { ProviderDriverError } from "@t3tools/provider-core/server/errors"; +import { readOpenCodeGoUsageLimits } from "./usageLimits.ts"; import { checkOpenCodeProviderStatus, loadOpenCode2Workspace, @@ -43,17 +43,17 @@ import { openCode2SkillsToServerProviderSkills, openCodeSkillsToServerProviderSkills, openCodeCommandsToServerProviderSlashCommands, -} from "../OpenCodeProvider.ts"; +} from "./status.ts"; import { makeManagedServerProvider } from "@t3tools/provider-core/server/managedProvider"; -import * as OpenCodeRuntime from "../opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import { makeOpenCodeRuntimeProbe, probeOpenCodeRuntime, type ProbedOpenCode, -} from "../opencodeVersionProbe.ts"; -import * as OpenCodeServerOwner from "../OpenCodeServerOwner.ts"; -import * as OpenCode2Client from "../opencode2/OpenCode2Client.ts"; -import * as OpenCode2Server from "../opencode2/OpenCode2Server.ts"; +} from "./versionProbe.ts"; +import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts"; +import * as OpenCode2Client from "./v2/OpenCode2Client.ts"; +import * as OpenCode2Server from "./v2/OpenCode2Server.ts"; import { defaultProviderContinuationIdentity, type ProviderDriver, @@ -123,9 +123,9 @@ function byOpenCodeRuntime( */ function selectOpenCodeRuntimeAdapter(input: { readonly probe: OpenCodeRuntimeProbe; - readonly v1: ProviderAdapterV2Shape; - readonly v2: ProviderAdapterV2Shape; -}): ProviderAdapterV2Shape { + readonly v1: ProviderAdapter.ProviderAdapterV2Shape; + readonly v2: ProviderAdapter.ProviderAdapterV2Shape; +}): ProviderAdapter.ProviderAdapterV2Shape { const pick = (probed: Effect.Effect) => byOpenCodeRuntime(probed, { v1: Effect.succeed(input.v1), v2: Effect.succeed(input.v2) }); const hot = pick(Effect.map(input.probe.lastSuccess, Option.getOrUndefined)); @@ -143,9 +143,9 @@ function selectOpenCodeRuntimeAdapter(input: { /** Text generation runs on the server the instance's probe detected, each in its own protocol. */ function selectOpenCodeRuntimeTextGeneration( probe: OpenCodeRuntimeProbe, - v1: TextGeneration["Service"], - v2: TextGeneration["Service"], -): TextGeneration["Service"] { + v1: ProviderTextGeneration, + v2: ProviderTextGeneration, +): ProviderTextGeneration { return { generateCommitMessage: (input) => byOpenCodeRuntime(probe.get, { @@ -172,14 +172,13 @@ function selectOpenCodeRuntimeTextGeneration( export type OpenCodeDriverEnv = | OpenCodeAdapterV2.OpenCodeAdapterV2DriverEnv - | ProviderHost + | ProviderHost.ProviderHost | ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | FileSystem.FileSystem | HttpClient.HttpClient | OpenCodeRuntime.OpenCodeRuntime - | Path.Path - | ServerConfig.ServerConfig; + | Path.Path; export const OpenCodeDriver: ProviderDriver = { driverKind: DRIVER_KIND, @@ -195,10 +194,9 @@ export const OpenCodeDriver: ProviderDriver const fileSystem = yield* FileSystem.FileSystem; const pathService = yield* Path.Path; const openCodeRuntime = yield* OpenCodeRuntime.OpenCodeRuntime; - const serverConfig = yield* ServerConfig.ServerConfig; const httpClient = yield* HttpClient.HttpClient; const crypto = yield* Crypto.Crypto; - const host = yield* ProviderHost; + const host = yield* ProviderHost.ProviderHost; const processEnv = mergeProviderInstanceEnvironment(environment); const continuationIdentity = defaultProviderContinuationIdentity({ driverKind: DRIVER_KIND, @@ -279,7 +277,7 @@ export const OpenCodeDriver: ProviderDriver binaryPath: effectiveConfig.binaryPath, serverUrl: effectiveConfig.serverUrl, serverPassword: effectiveConfig.serverPassword, - directory: serverConfig.cwd, + directory: host.paths.cwd, environment: processEnv, }).pipe( Effect.provideService(OpenCodeRuntime.OpenCodeRuntime, openCodeRuntime), @@ -299,7 +297,7 @@ export const OpenCodeDriver: ProviderDriver }); const loadOpenCode2Models = yield* makeOpenCode2ModelLoader( openCode2Server.withConnection((connection) => - connection.client.model.list({ location: { directory: serverConfig.cwd } }).pipe( + connection.client.model.list({ location: { directory: host.paths.cwd } }).pipe( Effect.map((models) => models.data), Effect.mapError( (cause) => @@ -350,7 +348,7 @@ export const OpenCodeDriver: ProviderDriver ); const serverOwner = yield* OpenCodeServerOwner.make({ binaryPath: effectiveConfig.binaryPath, - directory: serverConfig.cwd, + directory: host.paths.cwd, ...(effectiveConfig.serverPassword ? { serverPassword: effectiveConfig.serverPassword } : {}), @@ -370,7 +368,7 @@ export const OpenCodeDriver: ProviderDriver { provider: checkOpenCodeProviderStatus( effectiveConfig, - serverConfig.cwd, + host.paths.cwd, runtimeProbe.refresh, loadOpenCode2Models, ), diff --git a/apps/server/src/provider/testFixtures/opencodeProbeResponses.ts b/packages/provider-opencode/src/server/probeResponses.fixture.ts similarity index 100% rename from apps/server/src/provider/testFixtures/opencodeProbeResponses.ts rename to packages/provider-opencode/src/server/probeResponses.fixture.ts diff --git a/apps/server/src/provider/OpenCodeProvider.test.ts b/packages/provider-opencode/src/server/status.test.ts similarity index 98% rename from apps/server/src/provider/OpenCodeProvider.test.ts rename to packages/provider-opencode/src/server/status.test.ts index 1341e851a80cba39aea9e6f1c5b74f4f998194ef..f062330ad733e05e71068ef7d3d12340bbfde2f2 100644 --- a/apps/server/src/provider/OpenCodeProvider.test.ts +++ b/packages/provider-opencode/src/server/status.test.ts @@ -12,9 +12,8 @@ import * as TestClock from "effect/testing/TestClock"; import { HttpClient, HttpClientResponse } from "effect/http"; import { beforeEach } from "vite-plus/test"; -import { OpenCodeSettings } from "@t3tools/contracts"; -import * as ServerConfig from "../config.ts"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import { OpenCodeSettings } from "../settings.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts"; import { checkOpenCodeProviderStatus, @@ -25,14 +24,14 @@ import { openCode2CommandsToServerProviderSlashCommands, openCode2SkillsToServerProviderSkills, openCodeCommandsToServerProviderSlashCommands, -} from "./OpenCodeProvider.ts"; -import { readOpenCodeGoUsageLimits } from "./openCodeUsageLimits.ts"; -import { probeOpenCodeRuntime } from "./opencodeVersionProbe.ts"; +} from "./status.ts"; +import { readOpenCodeGoUsageLimits } from "./usageLimits.ts"; +import { probeOpenCodeRuntime } from "./versionProbe.ts"; import { OPENCODE_1_RESPONSES, OPENCODE_2_RESPONSES, replayOpenCodeServer, -} from "./testFixtures/opencodeProbeResponses.ts"; +} from "./probeResponses.fixture.ts"; const decodeOpenCodeSettings = Schema.decodeSync(OpenCodeSettings); const DEFAULT_VERSION_STDOUT = "opencode 1.14.19\n"; @@ -315,7 +314,6 @@ it("keeps native and MCP commands while preserving compaction and separate skill }); const layerTest = Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, OpenCodeRuntimeTestDouble).pipe( - Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(NodeServices.layer), ); diff --git a/apps/server/src/provider/OpenCodeProvider.ts b/packages/provider-opencode/src/server/status.ts similarity index 99% rename from apps/server/src/provider/OpenCodeProvider.ts rename to packages/provider-opencode/src/server/status.ts index 0b638c47821c70497a9a87bd1c4e54becdd6a247..f89298af01ae61c02aff0ec66886ae58419ee4d7 100644 --- a/apps/server/src/provider/OpenCodeProvider.ts +++ b/packages/provider-opencode/src/server/status.ts @@ -1,10 +1,10 @@ import { type ModelCapabilities, - type OpenCodeSettings, type ServerProviderModel, type ServerProviderSkill, type ServerProviderSlashCommand, } from "@t3tools/contracts"; +import type { OpenCodeSettings } from "../settings.ts"; import * as Cause from "effect/Cause"; import * as Data from "effect/Data"; import * as DateTime from "effect/DateTime"; @@ -20,8 +20,8 @@ import { providerModelsFromSettings, type ServerProviderDraft, } from "@t3tools/provider-core/server/snapshotProbe"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; -import type { ProbedOpenCode } from "./opencodeVersionProbe.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; +import type { ProbedOpenCode } from "./versionProbe.ts"; import type { Agent, ProviderListResponse } from "@opencode-ai/sdk/v2"; import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts"; diff --git a/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts b/packages/provider-opencode/src/server/textGeneration.test.ts similarity index 96% rename from apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts rename to packages/provider-opencode/src/server/textGeneration.test.ts index f5d2aa1d2bbd6438d24cc5577e873cb57d5391a1..db93d27d274740905fc41d4f7ede24e3b8391f4b 100644 --- a/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts +++ b/packages/provider-opencode/src/server/textGeneration.test.ts @@ -1,4 +1,5 @@ -import { OpenCodeSettings, ProviderInstanceId, TextGenerationError } from "@t3tools/contracts"; +import { ProviderInstanceId, TextGenerationError } from "@t3tools/contracts"; +import { OpenCodeSettings } from "../settings.ts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; import * as Duration from "effect/Duration"; @@ -9,11 +10,11 @@ import * as TestClock from "effect/testing/TestClock"; import * as NetService from "@t3tools/shared/Net"; import { beforeEach, expect } from "vite-plus/test"; -import * as ServerConfig from "../config.ts"; -import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; -import * as OpenCodeServerOwner from "../provider/OpenCodeServerOwner.ts"; -import * as OpenCodeTextGeneration from "./OpenCodeTextGeneration.ts"; -import * as TextGeneration from "./TextGeneration.ts"; +import { layerTestProviderHost } from "@t3tools/provider-testing/host"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; +import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts"; +import * as OpenCodeTextGeneration from "./textGeneration.ts"; +import type { ProviderTextGeneration } from "@t3tools/provider-core/server/textGeneration"; const runtimeMock = { state: { @@ -166,11 +167,7 @@ const layerOpenCodeTextGenerationTest = Layer.succeed( OpenCodeRuntime.OpenCodeRuntime, OpenCodeRuntimeTestDouble, ).pipe( - Layer.provideMerge( - ServerConfig.ServerConfig.layerTest(process.cwd(), { - prefix: "t3code-opencode-text-generation-test-", - }), - ), + Layer.provideMerge(layerTestProviderHost()), Layer.provideMerge(NetService.layer), Layer.provideMerge(NodeServices.layer), ); @@ -179,11 +176,7 @@ const layerOpenCodeTextGenerationExistingServerTest = Layer.succeed( OpenCodeRuntime.OpenCodeRuntime, OpenCodeRuntimeTestDouble, ).pipe( - Layer.provideMerge( - ServerConfig.ServerConfig.layerTest(process.cwd(), { - prefix: "t3code-opencode-text-generation-existing-server-test-", - }), - ), + Layer.provideMerge(layerTestProviderHost()), Layer.provideMerge(NetService.layer), Layer.provideMerge(NodeServices.layer), ); @@ -207,7 +200,7 @@ const EXTERNAL_SERVER_WITHOUT_AUTH_OPENCODE_SETTINGS = Schema.decodeSync(OpenCod function withOpenCodeTextGeneration( settings: OpenCodeSettings, - effectFn: (textGeneration: TextGeneration.TextGeneration["Service"]) => Effect.Effect, + effectFn: (textGeneration: ProviderTextGeneration) => Effect.Effect, environment?: NodeJS.ProcessEnv, ) { return Effect.gen(function* () { diff --git a/apps/server/src/textGeneration/OpenCodeTextGeneration.ts b/packages/provider-opencode/src/server/textGeneration.ts similarity index 95% rename from apps/server/src/textGeneration/OpenCodeTextGeneration.ts rename to packages/provider-opencode/src/server/textGeneration.ts index 5ffcf45661852267f5bfe8a9dc05b2ca6047bacf..8a48eaec7b41578cdc3eb5c2059d2d16dd9c4c7d 100644 --- a/apps/server/src/textGeneration/OpenCodeTextGeneration.ts +++ b/packages/provider-opencode/src/server/textGeneration.ts @@ -1,14 +1,14 @@ import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; -import { NonNegativeInt, TextGenerationError, type OpenCodeSettings } from "@t3tools/contracts"; +import { NonNegativeInt, TextGenerationError } from "@t3tools/contracts"; +import type { OpenCodeSettings } from "../settings.ts"; import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; -import * as ServerConfig from "../config.ts"; -import { resolveAttachmentPath } from "../attachmentStore.ts"; +import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; import * as TextGenerationOperations from "@t3tools/provider-core/server/textGenerationOperations"; -import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; -import * as OpenCodeServerOwner from "../provider/OpenCodeServerOwner.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; +import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts"; const OpenCodeTextGenerationOperation = Schema.Literals([ "generateCommitMessage", @@ -152,7 +152,7 @@ function getOpenCodeTextResponse(parts: ReadonlyArray | undefined): str export const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration")(function* ( openCodeSettings: OpenCodeSettings, ) { - const serverConfig = yield* ServerConfig.ServerConfig; + const host = yield* ProviderHost.ProviderHost; const openCodeRuntime = yield* OpenCodeRuntime.OpenCodeRuntime; const serverOwner = yield* OpenCodeServerOwner.OpenCodeServerOwner; @@ -169,8 +169,7 @@ export const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration" const fileParts = OpenCodeRuntime.toOpenCodeFileParts({ attachments: input.attachments?.filter((attachment) => attachment.type === "image"), - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }), + resolveAttachmentPath: host.resolveAttachmentPath, }); const runAgainstServer = Effect.fn("runOpenCodeJson.runAgainstServer")( diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCodeToolItems.ts b/packages/provider-opencode/src/server/toolItems.ts similarity index 100% rename from apps/server/src/orchestration-v2/Adapters/OpenCodeToolItems.ts rename to packages/provider-opencode/src/server/toolItems.ts diff --git a/apps/server/src/provider/openCodeUsageLimits.ts b/packages/provider-opencode/src/server/usageLimits.ts similarity index 100% rename from apps/server/src/provider/openCodeUsageLimits.ts rename to packages/provider-opencode/src/server/usageLimits.ts diff --git a/apps/server/src/provider/opencode2/OpenCode2Client.live.test.ts b/packages/provider-opencode/src/server/v2/OpenCode2Client.live.test.ts similarity index 100% rename from apps/server/src/provider/opencode2/OpenCode2Client.live.test.ts rename to packages/provider-opencode/src/server/v2/OpenCode2Client.live.test.ts diff --git a/apps/server/src/provider/opencode2/OpenCode2Client.test.ts b/packages/provider-opencode/src/server/v2/OpenCode2Client.test.ts similarity index 100% rename from apps/server/src/provider/opencode2/OpenCode2Client.test.ts rename to packages/provider-opencode/src/server/v2/OpenCode2Client.test.ts diff --git a/apps/server/src/provider/opencode2/OpenCode2Client.ts b/packages/provider-opencode/src/server/v2/OpenCode2Client.ts similarity index 99% rename from apps/server/src/provider/opencode2/OpenCode2Client.ts rename to packages/provider-opencode/src/server/v2/OpenCode2Client.ts index 231a5da3ae2b06de2299d0c317f22afaba2bcfb0..3b9141e6e7183cdd11e8ef9c34ae79fdc8bc2675 100644 --- a/apps/server/src/provider/opencode2/OpenCode2Client.ts +++ b/packages/provider-opencode/src/server/v2/OpenCode2Client.ts @@ -90,7 +90,7 @@ export class OpenCode2Client extends Context.Service< readonly password: string | Redacted.Redacted; }) => Effect.Effect; } ->()("t3/provider/opencode2/OpenCode2Client") {} +>()("@t3tools/provider-opencode/server/v2/OpenCode2Client") {} /** * OpenCode writes a `: heartbeat` comment every 10 to 15 seconds, so this long diff --git a/apps/server/src/provider/opencode2/OpenCode2Server.live.test.ts b/packages/provider-opencode/src/server/v2/OpenCode2Server.live.test.ts similarity index 99% rename from apps/server/src/provider/opencode2/OpenCode2Server.live.test.ts rename to packages/provider-opencode/src/server/v2/OpenCode2Server.live.test.ts index 93f511b4e843ded91321633a0718f044655805af..81a38f9ae22b24a14443eaa80052b4da8b3c3d46 100644 --- a/apps/server/src/provider/opencode2/OpenCode2Server.live.test.ts +++ b/packages/provider-opencode/src/server/v2/OpenCode2Server.live.test.ts @@ -23,7 +23,7 @@ import { FetchHttpClient } from "effect/http"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import { describe } from "vite-plus/test"; -import * as OpenCodeRuntime from "../opencodeRuntime.ts"; +import * as OpenCodeRuntime from "../OpenCodeRuntime.ts"; import * as OpenCodeServerLedger from "../OpenCodeServerLedger.ts"; import * as OpenCode2Client from "./OpenCode2Client.ts"; import * as OpenCode2Server from "./OpenCode2Server.ts"; diff --git a/apps/server/src/provider/opencode2/OpenCode2Server.test.ts b/packages/provider-opencode/src/server/v2/OpenCode2Server.test.ts similarity index 99% rename from apps/server/src/provider/opencode2/OpenCode2Server.test.ts rename to packages/provider-opencode/src/server/v2/OpenCode2Server.test.ts index 2704599b873cc3dd483855be49f41332e0e67098..43e31ece86fac86a2421ff31738a3e69ce73d197 100644 --- a/apps/server/src/provider/opencode2/OpenCode2Server.test.ts +++ b/packages/provider-opencode/src/server/v2/OpenCode2Server.test.ts @@ -15,7 +15,7 @@ import * as TestClock from "effect/testing/TestClock"; import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/http"; import { describe } from "vite-plus/test"; -import * as OpenCodeRuntime from "../opencodeRuntime.ts"; +import * as OpenCodeRuntime from "../OpenCodeRuntime.ts"; import * as OpenCodeServerLedger from "../OpenCodeServerLedger.ts"; import * as OpenCode2Client from "./OpenCode2Client.ts"; import * as OpenCode2Server from "./OpenCode2Server.ts"; diff --git a/apps/server/src/provider/opencode2/OpenCode2Server.ts b/packages/provider-opencode/src/server/v2/OpenCode2Server.ts similarity index 94% rename from apps/server/src/provider/opencode2/OpenCode2Server.ts rename to packages/provider-opencode/src/server/v2/OpenCode2Server.ts index 9939055211469efb2a09679105bccbebd1bb97ad..80b3dda1a10157529d2d3cc612348b13b5d12b52 100644 --- a/apps/server/src/provider/opencode2/OpenCode2Server.ts +++ b/packages/provider-opencode/src/server/v2/OpenCode2Server.ts @@ -15,7 +15,7 @@ import * as P from "effect/Predicate"; import * as Redacted from "effect/Redacted"; import * as HttpClientError from "effect/http/HttpClientError"; -import { OpenCodeRuntimeError } from "../opencodeRuntime.ts"; +import * as OpenCodeRuntime from "../OpenCodeRuntime.ts"; import * as OpenCodeServerOwner from "../OpenCodeServerOwner.ts"; import * as OpenCode2Client from "./OpenCode2Client.ts"; @@ -33,9 +33,9 @@ export class OpenCode2Server extends Context.Service< /** Runs `use` against the instance's server, spawning it first when T3 owns it. */ readonly withConnection: ( use: (connection: OpenCode2Connection) => Effect.Effect, - ) => Effect.Effect; + ) => Effect.Effect; } ->()("t3/provider/opencode2/OpenCode2Server") {} +>()("@t3tools/provider-opencode/server/v2/OpenCode2Server") {} /** * A fresh password for a spawned server. OpenCode 2 always requires one and @@ -106,7 +106,7 @@ export const verifyServer = (client: OpenCodeClient) => duration: INFO_TIMEOUT, orElse: () => Effect.fail( - new OpenCodeRuntimeError({ + new OpenCodeRuntime.OpenCodeRuntimeError({ operation: "server.info", detail: "Timed out waiting for the OpenCode server.", }), @@ -115,7 +115,7 @@ export const verifyServer = (client: OpenCodeClient) => Effect.catchTags({ UnauthorizedError: (cause) => Effect.fail( - new OpenCodeRuntimeError({ + new OpenCodeRuntime.OpenCodeRuntimeError({ operation: "server.info", detail: "The OpenCode server rejected the server password.", cause, @@ -123,9 +123,9 @@ export const verifyServer = (client: OpenCodeClient) => ), }), Effect.mapError((cause) => - OpenCodeRuntimeError.is(cause) + OpenCodeRuntime.OpenCodeRuntimeError.is(cause) ? cause - : new OpenCodeRuntimeError({ + : new OpenCodeRuntime.OpenCodeRuntimeError({ operation: "server.info", detail: describeInfoFailure(cause), cause, diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.ts b/packages/provider-opencode/src/server/v2/adapter.ts similarity index 98% rename from apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.ts rename to packages/provider-opencode/src/server/v2/adapter.ts index 6bc194b811a40fac09725f48863907cb8cc49eb1..97429ddfde8581dc9587ab2b4eae8b73f44dc0c2 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.ts +++ b/packages/provider-opencode/src/server/v2/adapter.ts @@ -70,14 +70,10 @@ import * as Schema from "effect/Schema"; import * as Semaphore from "effect/Semaphore"; import * as Stream from "effect/Stream"; -import { resolveAttachmentPath } from "../../attachmentStore.ts"; -import * as ServerConfig from "../../config.ts"; -import { paginate, type OpenCode2StreamEvent } from "../../provider/opencode2/OpenCode2Client.ts"; -import * as OpenCode2Server from "../../provider/opencode2/OpenCode2Server.ts"; -import { - parseOpenCodeModelSlug, - type OpenCodeRuntimeError, -} from "../../provider/opencodeRuntime.ts"; +import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; +import * as OpenCode2Client from "./OpenCode2Client.ts"; +import * as OpenCode2Server from "./OpenCode2Server.ts"; +import * as OpenCodeRuntime from "../OpenCodeRuntime.ts"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions"; import { t3OrchestrationSystemPrompt } from "@t3tools/provider-core/server/orchestrationInstructions"; @@ -98,11 +94,11 @@ import { makeSubagentChildThread, makeSubagentConversationArtifacts, subagentThreadTitle, -} from "../SubagentProjection.ts"; +} from "@t3tools/provider-core/server/subagentProjection"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import { turnScopedSelectionTransition } from "@t3tools/provider-core/server/selectionTransition"; -import { OPENCODE_PROVIDER, openCodePermissionRequestKind } from "./OpenCodeAdapterV2.ts"; -import { openCodeToolTurnItem } from "./OpenCodeToolItems.ts"; +import { OPENCODE_PROVIDER, openCodePermissionRequestKind } from "../adapter.ts"; +import { openCodeToolTurnItem } from "../toolItems.ts"; const OpenCode2ProviderCapabilities = { sessions: { @@ -307,7 +303,7 @@ interface SubagentCall { * the continuation turn T3 opens for it takes them. */ interface Wake { - readonly events: Array; + readonly events: Array; running: boolean; /** The background subagents whose end it answers, as its turn's notification names them. */ readonly reports: Array; @@ -767,7 +763,7 @@ const isProviderAdapterError = Schema.is(ProviderAdapter.ProviderAdapterV2Error) * default while T3 records the requested model. */ const modelRef = (selection: ProviderAdapter.ProviderAdapterV2TurnInput["modelSelection"]) => { - const parsed = parseOpenCodeModelSlug(selection.model); + const parsed = OpenCodeRuntime.parseOpenCodeModelSlug(selection.model); if (parsed === null) return undefined; const variant = getModelSelectionStringOptionValue(selection, "variant"); return Model.Ref.make({ @@ -834,7 +830,7 @@ const turnTokenUsage = (turn: ActiveTurn, status: OrchestrationV2ProviderTurn["s export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: ProviderInstanceId) { const server = yield* OpenCode2Server.OpenCode2Server; const idAllocator = yield* IdAllocator.IdAllocatorV2; - const serverConfig = yield* ServerConfig.ServerConfig; + const host = yield* ProviderHost.ProviderHost; const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests; const crypto = yield* Crypto.Crypto; const driver = OPENCODE_PROVIDER; @@ -846,7 +842,10 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr * server that died is started again on the next borrow. */ const borrow = Effect.gen(function* () { - const lent = yield* Deferred.make(); + const lent = yield* Deferred.make< + OpenCode2Server.OpenCode2Connection, + OpenCodeRuntime.OpenCodeRuntimeError + >(); yield* server .withConnection((connection) => Deferred.succeed(lent, connection).pipe(Effect.andThen(Effect.never)), @@ -874,7 +873,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr // the instance's threads in every directory. const contextWindows = new Map>(); /** A thread without a worktree runs where T3 does, as its session is created. */ - const directoryOf = (cwd: string | null | undefined) => cwd ?? serverConfig.cwd; + const directoryOf = (cwd: string | null | undefined) => cwd ?? host.paths.cwd; const windowOf = (cwd: string | null | undefined, model: string) => contextWindows.get(directoryOf(cwd))?.get(model); const now = yield* DateTime.now; @@ -883,7 +882,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr driver, providerInstanceId: instanceId, status: "ready", - cwd: input.runtimePolicy.cwd ?? serverConfig.cwd, + cwd: input.runtimePolicy.cwd ?? host.paths.cwd, model: input.modelSelection.model, capabilities: OpenCode2ProviderCapabilities, createdAt: now, @@ -2145,7 +2144,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr } }); - const sessionOfEvent = (event: OpenCode2StreamEvent) => + const sessionOfEvent = (event: OpenCode2Client.OpenCode2StreamEvent) => event.type === "unreadable.execution.ended" || event.type === "unreadable.execution.started" ? event.sessionID : "sessionID" in event.data && typeof event.data.sessionID === "string" @@ -2193,7 +2192,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr const onTurnEvent = Effect.fnUntraced(function* ( state: ThreadState, turn: ActiveTurn, - event: OpenCode2StreamEvent, + event: OpenCode2Client.OpenCode2StreamEvent, ) { switch (event.type) { case "session.text.started": @@ -2444,7 +2443,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr ? finishTurn(state, turn.heldEnd) : Effect.void; - const handleEvent = Effect.fnUntraced(function* (event: OpenCode2StreamEvent) { + const handleEvent = Effect.fnUntraced(function* (event: OpenCode2Client.OpenCode2StreamEvent) { const sessionId = sessionOfEvent(event); // `revert.clear` wakes the session into an empty execution of its own // (2.0.18's `Session.revert.clear` ends with a wake). It is no run of @@ -2499,7 +2498,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr /** Handles one event for its session, as the turn running there sees it. */ const route = Effect.fnUntraced(function* ( - event: OpenCode2StreamEvent, + event: OpenCode2Client.OpenCode2StreamEvent, sessionId: string | undefined, ) { // The end of the run a timed-out Stop left behind; no turn is its own. @@ -2678,7 +2677,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr const { before } = turn; if (promptId === undefined && before === undefined) return undefined; const start = before !== undefined ? before : promptId; - const read = yield* paginate( + const read = yield* OpenCode2Client.paginate( { sessionID: Session.ID.make(sessionId), order: "desc" as const, limit: 50 }, client.message.list, ).pipe( @@ -2856,7 +2855,9 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr // The borrow in use when the session closes is returned with it, so a // spawned server can still reach its idle shutdown. yield* Effect.addFinalizer(() => Scope.close(currentScope, Exit.void)); - const follow = (stream: Stream.Stream): Effect.Effect => + const follow = ( + stream: Stream.Stream, + ): Effect.Effect => stream.pipe( Stream.runForEach((event) => lock.withPermit(handleEvent(event))), Effect.exit, @@ -3143,7 +3144,10 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr * reports continuations answer. The list takes one type, so it reads all. */ const userMessages = (sessionId: string) => - paginate({ sessionID: Session.ID.make(sessionId), limit: 100 }, client.message.list).pipe( + OpenCode2Client.paginate( + { sessionID: Session.ID.make(sessionId), limit: 100 }, + client.message.list, + ).pipe( Stream.runCollect, Effect.map( (messages) => @@ -3160,7 +3164,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr providerThread: OrchestrationV2ProviderThread, sessionId: string, ) { - const history = yield* paginate( + const history = yield* OpenCode2Client.paginate( { sessionID: Session.ID.make(sessionId), order: "asc" as const, limit: 100 }, client.message.list, ).pipe(Stream.runCollect); @@ -3216,8 +3220,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr providerMessageTextWithAttachmentPaths({ text: turnInput.message.text, attachments: turnInput.message.attachments, - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ attachmentsDir: serverConfig.attachmentsDir, attachment }), + resolveAttachmentPath: host.resolveAttachmentPath, }).trim(); const removeMcp = (mcp: { readonly name: string; readonly directory: string }) => @@ -3249,7 +3252,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr turnInput: ProviderAdapter.ProviderAdapterV2TurnInput, ) { const mcpSession = McpProviderSession.readMcpProviderSession(turnInput.threadId); - const directory = turnInput.runtimePolicy.cwd ?? serverConfig.cwd; + const directory = turnInput.runtimePolicy.cwd ?? host.paths.cwd; const name = yield* mcpServerNameFor(turnInput.threadId); // An external server may not reach T3's MCP endpoint, as with 1.x. const wanted = @@ -3523,7 +3526,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr if (!sending()) return; return yield* client.session.compact({ sessionID, id }).pipe(Effect.asVoid); } - const location = { directory: turnInput.runtimePolicy.cwd ?? serverConfig.cwd }; + const location = { directory: turnInput.runtimePolicy.cwd ?? host.paths.cwd }; const command = bare ? commandOf(text) : undefined; if (command !== undefined) { const commands = yield* client.command.list({ location }).pipe( @@ -3603,7 +3606,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr detail: malformedModel(threadInput.modelSelection.model), }); } - const directory = threadInput.runtimePolicy.cwd ?? serverConfig.cwd; + const directory = threadInput.runtimePolicy.cwd ?? host.paths.cwd; const policy = threadInput.runtimePolicy; // A new session runs OpenCode's default agent. const permissions = yield* rulesFor( @@ -3898,11 +3901,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr text: providerMessageTextWithAttachmentPaths({ text: steerInput.message.text, attachments: steerInput.message.attachments, - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ - attachmentsDir: serverConfig.attachmentsDir, - attachment, - }), + resolveAttachmentPath: host.resolveAttachmentPath, }).trim(), delivery: "steer", }) diff --git a/apps/server/src/textGeneration/OpenCode2TextGeneration.ts b/packages/provider-opencode/src/server/v2/textGeneration.ts similarity index 92% rename from apps/server/src/textGeneration/OpenCode2TextGeneration.ts rename to packages/provider-opencode/src/server/v2/textGeneration.ts index f6ceb7252a8e1e70df12cbb9c21cd3861973ad10..4ac6b8e2ec81ac967e0a77a9cdefb59fe0498681 100644 --- a/apps/server/src/textGeneration/OpenCode2TextGeneration.ts +++ b/packages/provider-opencode/src/server/v2/textGeneration.ts @@ -14,11 +14,9 @@ import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; -import * as ServerConfig from "../config.ts"; -import { resolveAttachmentPath } from "../attachmentStore.ts"; -import type { OpenCode2Connection } from "../provider/opencode2/OpenCode2Server.ts"; -import * as OpenCode2Server from "../provider/opencode2/OpenCode2Server.ts"; -import { parseOpenCodeModelSlug } from "../provider/opencodeRuntime.ts"; +import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; +import * as OpenCode2Server from "./OpenCode2Server.ts"; +import * as OpenCodeRuntime from "../OpenCodeRuntime.ts"; import * as TextGenerationOperations from "@t3tools/provider-core/server/textGenerationOperations"; const isTextGenerationError = Schema.is(TextGenerationError); @@ -40,13 +38,13 @@ type Outcome = | { readonly _tag: "failed"; readonly detail: string; readonly cause?: unknown }; const runOnServer = ( - connection: OpenCode2Connection, + connection: OpenCode2Server.OpenCode2Connection, input: TextGenerationOperations.Request, - attachmentsDir: string, + resolveAttachmentPath: ProviderHost.ProviderHostShape["resolveAttachmentPath"], ) => Effect.gen(function* () { const { client } = connection; - const parsed = parseOpenCodeModelSlug(input.modelSelection.model); + const parsed = OpenCodeRuntime.parseOpenCodeModelSlug(input.modelSelection.model); if (parsed === null) { return yield* new TextGenerationError({ operation: input.operation, @@ -146,7 +144,7 @@ const runOnServer = ( ); const images = (input.attachments ?? []).flatMap((attachment) => { if (attachment.type !== "image") return []; - const path = resolveAttachmentPath({ attachmentsDir, attachment }); + const path = resolveAttachmentPath(attachment); return path === null ? [] : [{ uri: `file://${path}`, name: attachment.name }]; }); yield* client.session.prompt({ @@ -186,10 +184,10 @@ const runOnServer = ( /** Text generation for an instance whose server is OpenCode 2. */ export const make = Effect.fn("OpenCode2TextGeneration.make")(function* () { const server = yield* OpenCode2Server.OpenCode2Server; - const { attachmentsDir } = yield* ServerConfig.ServerConfig; + const { resolveAttachmentPath } = yield* ProviderHost.ProviderHost; const run: TextGenerationOperations.Runner = (input) => server - .withConnection((connection) => runOnServer(connection, input, attachmentsDir)) + .withConnection((connection) => runOnServer(connection, input, resolveAttachmentPath)) .pipe( Effect.mapError((cause) => isTextGenerationError(cause) diff --git a/apps/server/src/provider/opencodeVersionProbe.test.ts b/packages/provider-opencode/src/server/versionProbe.test.ts similarity index 98% rename from apps/server/src/provider/opencodeVersionProbe.test.ts rename to packages/provider-opencode/src/server/versionProbe.test.ts index 75d3873e19a4d7a1d50f2fd5f46c54f8505ec965..f0296025d3a87b3a248f89aceae6b09e1993a718 100644 --- a/apps/server/src/provider/opencodeVersionProbe.test.ts +++ b/packages/provider-opencode/src/server/versionProbe.test.ts @@ -2,17 +2,17 @@ import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import { HttpClient, HttpClientError, HttpClientRequest } from "effect/http"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import { classifyOpenCodeCliVersion, makeOpenCodeRuntimeProbe, probeOpenCodeRuntime, -} from "./opencodeVersionProbe.ts"; +} from "./versionProbe.ts"; import { OPENCODE_1_RESPONSES, OPENCODE_2_RESPONSES, replayOpenCodeServer, -} from "./testFixtures/opencodeProbeResponses.ts"; +} from "./probeResponses.fixture.ts"; const noBinary = { runOpenCodeCommand: () => Effect.die("A configured server must not run the local binary"), diff --git a/apps/server/src/provider/opencodeVersionProbe.ts b/packages/provider-opencode/src/server/versionProbe.ts similarity index 99% rename from apps/server/src/provider/opencodeVersionProbe.ts rename to packages/provider-opencode/src/server/versionProbe.ts index aca8ab2e8189e0800a2538426ac2ffb090bed833..c1ac522f34247cde90fa50f9980733c798a2f8ff 100644 --- a/apps/server/src/provider/opencodeVersionProbe.ts +++ b/packages/provider-opencode/src/server/versionProbe.ts @@ -8,7 +8,7 @@ import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import { HttpClient, HttpClientRequest } from "effect/http"; -import * as OpenCodeRuntime from "./opencodeRuntime.ts"; +import * as OpenCodeRuntime from "./OpenCodeRuntime.ts"; import { parseGenericCliVersion } from "@t3tools/provider-core/server/snapshotProbe"; export interface ProbedOpenCode { diff --git a/packages/provider-opencode/src/settings.ts b/packages/provider-opencode/src/settings.ts new file mode 100644 index 0000000000000000000000000000000000000000..5881b75e00f94df14540cfa9c40bf766e9390dd6 --- /dev/null +++ b/packages/provider-opencode/src/settings.ts @@ -0,0 +1,66 @@ +/** + * OpenCode instance settings. Shared by the server driver and the client + * settings form, so it holds only browser-safe schema code. + * + * @module provider-opencode/settings + */ +import { + CustomModelSetting, + makeBinaryPathSetting, + makeProviderSettingsSchema, + TrimmedString, +} from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; + +export const OpenCodeSettings = makeProviderSettingsSchema( + { + // Off by default (like Cursor and Grok): the binding is not yet stable + // enough to probe on every install. Users opt in from Settings. + enabled: Schema.Boolean.pipe( + Schema.withDecodingDefault(Effect.succeed(false)), + Schema.annotateKey({ providerSettingsForm: { hidden: true } }), + ), + binaryPath: makeBinaryPathSetting("opencode").pipe( + Schema.annotateKey({ + title: "Binary path", + description: "Path to the OpenCode binary.", + providerSettingsForm: { + placeholder: "opencode", + clearWhenEmpty: "omit", + }, + }), + ), + serverUrl: TrimmedString.pipe( + Schema.withDecodingDefault(Effect.succeed("")), + Schema.annotateKey({ + title: "Server URL", + description: "Leave blank to let T3 Code spawn the server when needed.", + providerSettingsForm: { + placeholder: "http://127.0.0.1:4096", + clearWhenEmpty: "omit", + }, + }), + ), + serverPassword: TrimmedString.pipe( + Schema.withDecodingDefault(Effect.succeed("")), + Schema.annotateKey({ + title: "Server password", + description: "Stored in plain text on disk.", + providerSettingsForm: { + control: "password", + placeholder: "Optional", + clearWhenEmpty: "omit", + }, + }), + ), + customModels: Schema.Array(CustomModelSetting).pipe( + Schema.withDecodingDefault(Effect.succeed([])), + Schema.annotateKey({ providerSettingsForm: { hidden: true } }), + ), + }, + { + order: ["binaryPath", "serverUrl", "serverPassword"], + }, +); +export type OpenCodeSettings = typeof OpenCodeSettings.Type; diff --git a/packages/provider-opencode/src/testing.ts b/packages/provider-opencode/src/testing.ts new file mode 100644 index 0000000000000000000000000000000000000000..5fa87cde4e5082c2a35ee03233ba58fe866cddff --- /dev/null +++ b/packages/provider-opencode/src/testing.ts @@ -0,0 +1,13 @@ +/** + * OpenCode internals the server's replay testkits and live tests drive + * directly: the 2.x client, server, adapter, and text generation, plus the + * 1.x transcript identifiers. + * + * @module provider-opencode/testing + */ +export { + OPENCODE_DEFAULT_INSTANCE_ID, + OPENCODE_PROVIDER, + OPENCODE_SDK_PROTOCOL, +} from "./server/adapter.ts"; +export { OPENCODE_2_STILL_STOPPING, t3McpServerName } from "./server/v2/adapter.ts"; diff --git a/packages/provider-opencode/tsconfig.json b/packages/provider-opencode/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..374bac55202ddcad695ede1c567ec219a61e9c48 --- /dev/null +++ b/packages/provider-opencode/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["src"] +} diff --git a/packages/shared/package.json b/packages/shared/package.json index ec9277fc0cd46a0e0fac51c406a80d059856ebf0..c5bca30542902a24907e24e49323dd2699275094 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -462,6 +462,10 @@ "./mcpApp": { "types": "./src/mcpApp.ts", "import": "./src/mcpApp.ts" + }, + "./atomicWrite": { + "types": "./src/atomicWrite.ts", + "import": "./src/atomicWrite.ts" } }, "scripts": { diff --git a/apps/server/src/atomicWrite.test.ts b/packages/shared/src/atomicWrite.test.ts similarity index 100% rename from apps/server/src/atomicWrite.test.ts rename to packages/shared/src/atomicWrite.test.ts diff --git a/apps/server/src/atomicWrite.ts b/packages/shared/src/atomicWrite.ts similarity index 100% rename from apps/server/src/atomicWrite.ts rename to packages/shared/src/atomicWrite.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4502702d9c1955d2aa5560290792ced89da23075..39a350f843f440e0d337f6c32403ea318eaad1c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -359,6 +359,9 @@ importers: '@t3tools/provider-muse': specifier: workspace:* version: link:../../packages/provider-muse + '@t3tools/provider-opencode': + specifier: workspace:* + version: link:../../packages/provider-opencode '@t3tools/provider-pi': specifier: workspace:* version: link:../../packages/provider-pi @@ -594,21 +597,15 @@ importers: '@opencode-ai/sdk': specifier: ^1.3.15 version: 1.15.13 - '@opencode/client': - specifier: 2.0.23 - version: 2.0.23(patch_hash=f725d6e6d9af0d56dc6ed71b863518fbfc751af1773cff2311209d2c404cf4cd)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f)) - '@opencode/protocol': - specifier: 2.0.23 - version: 2.0.23(patch_hash=17a82f6c2c98fd0371e786c211a4fbdaaba403577a25733e5e99a0c25a16a685) - '@opencode/schema': - specifier: 2.0.23 - version: 2.0.23(patch_hash=650f1b8602789407c010861284272dcb6bd6de01ec6a75bf3c0904a0e7596d59) '@t3tools/provider-core': specifier: workspace:* version: link:../../packages/provider-core '@t3tools/provider-muse': specifier: workspace:* version: link:../../packages/provider-muse + '@t3tools/provider-opencode': + specifier: workspace:* + version: link:../../packages/provider-opencode '@t3tools/provider-pi': specifier: workspace:* version: link:../../packages/provider-pi @@ -742,6 +739,9 @@ importers: '@t3tools/provider-muse': specifier: workspace:* version: link:../../packages/provider-muse + '@t3tools/provider-opencode': + specifier: workspace:* + version: link:../../packages/provider-opencode '@t3tools/provider-pi': specifier: workspace:* version: link:../../packages/provider-pi @@ -1142,6 +1142,49 @@ importers: specifier: 1.0.0 version: 1.0.0(@types/node@24.12.4)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + packages/provider-opencode: + dependencies: + '@opencode-ai/sdk': + specifier: ^1.3.15 + version: 1.15.13 + '@opencode/client': + specifier: 2.0.23 + version: 2.0.23(patch_hash=f725d6e6d9af0d56dc6ed71b863518fbfc751af1773cff2311209d2c404cf4cd)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f)) + '@opencode/protocol': + specifier: 2.0.23 + version: 2.0.23(patch_hash=17a82f6c2c98fd0371e786c211a4fbdaaba403577a25733e5e99a0c25a16a685) + '@opencode/schema': + specifier: 2.0.23 + version: 2.0.23(patch_hash=650f1b8602789407c010861284272dcb6bd6de01ec6a75bf3c0904a0e7596d59) + '@t3tools/contracts': + specifier: workspace:* + version: link:../contracts + '@t3tools/provider-core': + specifier: workspace:* + version: link:../provider-core + '@t3tools/shared': + specifier: workspace:* + version: link:../shared + effect: + specifier: 4.0.1 + version: 4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f) + devDependencies: + '@effect/platform-node': + specifier: 4.0.1 + version: 4.0.1(bufferutil@4.1.0)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f))(redis@6.2.1)(utf-8-validate@6.0.6) + '@effect/vitest': + specifier: 4.0.1 + version: 4.0.1(patch_hash=359f6fb2f7b3ec145bb72208edb9034f02489791aa2491a55cdbd69bd56ee0d2)(@types/node@24.12.4)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + '@t3tools/provider-testing': + specifier: workspace:* + version: link:../provider-testing + '@types/node': + specifier: 24.12.4 + version: 24.12.4 + vite-plus: + specifier: 1.0.0 + version: 1.0.0(@types/node@24.12.4)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + packages/provider-pi: dependencies: '@t3tools/contracts': diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts index 2f39d7a61e93727a4fdde815fc2a0eb7313f6b13..400fc17efab4e3c477be224049a5222f6eea3345 100644 --- a/scripts/release-smoke.ts +++ b/scripts/release-smoke.ts @@ -32,6 +32,7 @@ const workspaceFiles = [ "packages/provider-testing/package.json", "packages/provider-pi/package.json", "packages/provider-muse/package.json", + "packages/provider-opencode/package.json", "packages/effect-acp/package.json", "packages/effect-codex-app-server/package.json", "scripts/package.json", -- 2.54.0