Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f416862
feat(server): add OpenCode 2 provider driver
tarik02 Sep 16, 2026
e66787e
fix(server): harden OpenCode 2 session and connection handling
tarik02 Sep 16, 2026
56bff28
fix(server): tighten OpenCode 2 recovery, lifecycle, and probe details
tarik02 Sep 16, 2026
4f7e4d4
fix(server): catch the OpenCode 2 schema failure with catchTags
tarik02 Sep 16, 2026
62ae128
fix(server): settle OpenCode 2 turns and requests after interruptions
tarik02 Sep 16, 2026
42fd3eb
fix(server): offer only the variants an OpenCode 2 model declares
tarik02 Sep 16, 2026
fcb1d3c
chore(server): bump @opencode/client and @opencode/schema to 2.0.5
tarik02 Sep 16, 2026
8e67120
fix(server): port OpenCode 2 adapter to @opencode/client 2.0.5 API
tarik02 Sep 16, 2026
9d81032
fix(server): compose the OpenCode 2 platform layer at the server and …
tarik02 Sep 17, 2026
a6267df
fix(server): stop labeling OpenCode 2 reads as changed files (#234)
tarik02 Sep 18, 2026
549bd58
fix(server): route OpenCode 2 subagents to the agents surface (#235)
tarik02 Sep 18, 2026
171217d
Merge remote-tracking branch 'upstream/main' into feat/opencode2
tarik02 Sep 18, 2026
92a174d
chore(server): update OpenCode 2 client to 2.0.8
tarik02 Sep 18, 2026
eb8644c
fix(server): harden OpenCode 2 reconnect recovery
tarik02 Sep 18, 2026
4d644d6
style(server): use opencode2 runtime namespace
tarik02 Sep 18, 2026
fa5fc2c
fix(server): isolate opencode2 instance tools
tarik02 Sep 18, 2026
5d3ea23
fix(server): recover stale opencode2 services
tarik02 Sep 18, 2026
3c80cbc
fix(server): recover stale opencode2 services
tarik02 Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/mobile/src/components/ProviderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function ProviderIcon(props: ProviderIconProps) {
);
}

if (props.provider === "opencode") {
if (props.provider === "opencode" || props.provider === "opencode2") {
return (
<Svg width={size} height={size} viewBox="0 0 32 40" fill="none">
<Path d="M24 32H8V16H24V32Z" fill={isDarkMode ? "#4B4646" : "#CFCECD"} />
Expand Down
2 changes: 2 additions & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"@effect/platform-node-shared": "catalog:",
"@ff-labs/fff-node": "0.9.4",
"@opencode-ai/sdk": "^1.3.15",
"@opencode/client": "2.0.8",
"@opencode/schema": "2.0.8",
"diff": "8.0.3",
"effect": "catalog:",
"node-pty": "^1.1.0",
Expand Down
154 changes: 154 additions & 0 deletions apps/server/src/provider/Drivers/OpenCode2Driver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* OpenCode2Driver — `ProviderDriver` for the OpenCode 2 background service.
*
* Mirrors the OpenCodeDriver's bundle shape (snapshot / adapter /
* textGeneration closures over the per-instance `OpenCode2Settings`), but
* there is no process to own: the adapter and probe connect to the machine's
* background service (or the configured `serverUrl`). Maintenance is
* manual-only — the v2 binary manages its own updates.
*
* @module provider/Drivers/OpenCode2Driver
*/
import { OpenCode2Settings, ProviderDriverKind } from "@t3tools/contracts";
import * as Crypto from "effect/Crypto";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";

import { makeOpenCode2TextGeneration } from "../../textGeneration/OpenCode2TextGeneration.ts";
import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
import { ServerConfig } from "../../config.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { ProviderDriverError } from "../Errors.ts";
import { makeOpenCode2Adapter } from "../Layers/OpenCode2Adapter.ts";
import {
checkOpenCode2ProviderStatus,
makePendingOpenCode2Provider,
} from "../Layers/OpenCode2Provider.ts";
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
import * as OpenCode2Runtime from "../opencode2Runtime.ts";
import {
defaultProviderContinuationIdentity,
type ProviderDriver,
type ProviderInstance,
} from "../ProviderDriver.ts";
import { withInstanceIdentity } from "./instanceIdentity.ts";
import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts";
import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts";
import {
haveProviderSnapshotSettingsChanged,
makeProviderSnapshotSettingsSource,
type ProviderSnapshotSettings,
} from "../providerUpdateSettings.ts";

const decodeOpenCode2Settings = Schema.decodeSync(OpenCode2Settings);

const DRIVER_KIND = ProviderDriverKind.make("opencode2");

// The v2 binary ships its own updater; T3 never manages it.
const MANUAL_ONLY = makeManualOnlyProviderMaintenanceCapabilities({
provider: DRIVER_KIND,
packageName: null,
});

export type OpenCode2DriverEnv =
| BackgroundPolicy.BackgroundPolicy
| Crypto.Crypto
| FileSystem.FileSystem
| OpenCode2Runtime.OpenCode2Runtime
| Path.Path
| ServerConfig
| ServerSettingsService;

export const OpenCode2Driver: ProviderDriver<OpenCode2Settings, OpenCode2DriverEnv> = {
driverKind: DRIVER_KIND,
metadata: {
displayName: "OpenCode 2",
supportsMultipleInstances: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Drivers/OpenCode2Driver.ts:68

Running two OpenCode 2 instances causes the newer session to lose its T3 MCP tools: both adapters register the same name for a given threadId, and the older instance's stopStaleSessionsForThread cleanup removes that registration. Set supportsMultipleInstances to false unless the registration name is made instance-specific.

Suggested change
supportsMultipleInstances: true,
supportsMultipleInstances: false,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Drivers/OpenCode2Driver.ts around line 68:

Running two OpenCode 2 instances causes the newer session to lose its T3 MCP tools: both adapters register the same name for a given `threadId`, and the older instance's `stopStaleSessionsForThread` cleanup removes that registration. Set `supportsMultipleInstances` to `false` unless the registration name is made instance-specific.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3c80cbc: OpenCode 2 MCP registration names now include the bound provider instance ID (openCode2McpServerName(..., boundInstanceId)), so separate instances no longer share a per-thread registration or remove each other’s tools. supportsMultipleInstances: true remains intentional and is now safe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

},
configSchema: OpenCode2Settings,
defaultConfig: (): OpenCode2Settings => decodeOpenCode2Settings({}),
create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>
Effect.gen(function* () {
const serverConfig = yield* ServerConfig;
const openCode2Runtime = yield* OpenCode2Runtime.OpenCode2Runtime;
const serverSettings = yield* ServerSettingsService;
const processEnv = mergeProviderInstanceEnvironment(environment);
const continuationIdentity = defaultProviderContinuationIdentity({
driverKind: DRIVER_KIND,
instanceId,
});
const stampIdentity = withInstanceIdentity({
instanceId,
driverKind: DRIVER_KIND,
displayName,
accentColor,
continuationGroupKey: continuationIdentity.continuationKey,
});
const effectiveConfig = { ...config, enabled } satisfies OpenCode2Settings;

const adapter = yield* makeOpenCode2Adapter(effectiveConfig, {
instanceId,
environment: processEnv,
});

const textGeneration = yield* makeOpenCode2TextGeneration(effectiveConfig);

const checkProviderForCwd = (cwd: string) =>
checkOpenCode2ProviderStatus(effectiveConfig, cwd).pipe(
Effect.map(stampIdentity),
Effect.provideService(OpenCode2Runtime.OpenCode2Runtime, openCode2Runtime),
);
const checkProvider = checkProviderForCwd(serverConfig.cwd);

const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
const snapshot = yield* makeManagedServerProvider<
ProviderSnapshotSettings<OpenCode2Settings>
>({
resolveMaintenance: () => Effect.succeed(MANUAL_ONLY),
getSettings: snapshotSettings.getSettings,
streamSettings: snapshotSettings.streamSettings,
haveSettingsChanged: haveProviderSnapshotSettingsChanged,
checkProviderOnSettingsChange: () => true,
refreshOnInterval: false,
initialSnapshot: (settings) =>
makePendingOpenCode2Provider(settings.provider).pipe(Effect.map(stampIdentity)),
checkProvider,
}).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: "Failed to build OpenCode 2 snapshot.",
cause,
}),
),
);

return {
instanceId,
driverKind: DRIVER_KIND,
continuationIdentity,
displayName,
accentColor,
enabled,
snapshot,
snapshotForCwd: (cwd) =>
checkProviderForCwd(cwd).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: `Failed to probe OpenCode 2 for '${cwd}'`,
cause,
}),
),
),
adapter,
textGeneration,
} satisfies ProviderInstance;
}),
};
Loading
Loading