Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions apps/server/src/provider/Drivers/OpenCodeDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv>
const client = openCodeRuntime.createOpenCodeSdkClient({
baseUrl: server.url,
directory: cwd,
external: server.external,
...(effectiveConfig.serverPassword
? { serverPassword: effectiveConfig.serverPassword }
: {}),
Expand All @@ -192,6 +193,7 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv>
openCodeRuntime.createOpenCodeSdkClient({
baseUrl: server.url,
directory: cwd,
external: false,
...(server.serverPassword !== undefined
? { serverPassword: server.serverPassword }
: {}),
Expand Down
58 changes: 58 additions & 0 deletions apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,13 @@ const openCodeAdapterTestSettings = Schema.decodeSync(OpenCodeSettings)({
serverPassword: "secret-password",
});

// A non-loopback server URL, so the adapter treats the session as remote.
const openCodeAdapterRemoteSettings = Schema.decodeSync(OpenCodeSettings)({
binaryPath: "fake-opencode",
serverUrl: "http://10.0.0.5:4096",
serverPassword: "secret-password",
});

const OpenCodeAdapterTestLayer = Layer.effect(
OpenCodeAdapter,
makeOpenCodeAdapter(openCodeAdapterTestSettings),
Expand Down Expand Up @@ -1240,6 +1247,57 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
}),
);

it.effect(
"reuses a remote session without forking even when the server directory differs",
() => {
const remoteLayer = Layer.effect(
OpenCodeAdapter,
makeOpenCodeAdapter(openCodeAdapterRemoteSettings),
).pipe(
Layer.provideMerge(Layer.succeed(OpenCodeRuntime, OpenCodeRuntimeTestDouble)),
Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),
Layer.provideMerge(
ServerSettingsService.layerTest({
providers: {
opencode: {
binaryPath: "fake-opencode",
serverUrl: "http://10.0.0.5:4096",
serverPassword: "secret-password",
},
},
}),
),
Layer.provideMerge(providerSessionDirectoryTestLayer),
Layer.provideMerge(NodeServices.layer),
);

return Effect.gen(function* () {
const adapter = yield* OpenCodeAdapter;
// The server-side directory differs from this test's local cwd.
runtimeMock.state.sessionDirectoryById.set("ses_remote", "/var/log");

const session = yield* adapter.startSession({
provider: ProviderDriverKind.make("opencode"),
threadId: asThreadId("thread-opencode-remote-reuse"),
runtimeMode: "full-access",
resumeCursor: { schemaVersion: 1, sessionId: "ses_remote" },
});

NodeAssert.deepEqual(runtimeMock.state.sessionGetIds, ["ses_remote"]);
NodeAssert.deepEqual(runtimeMock.state.sessionCreateUrls, []);
NodeAssert.deepEqual(runtimeMock.state.forkCalls, []);
NodeAssert.deepEqual(session.resumeCursor, {
schemaVersion: 1,
sessionId: "ses_remote",
});
NodeAssert.equal(runtimeMock.state.sessionUpdateCalls.length, 1);
NodeAssert.equal(runtimeMock.state.sessionUpdateCalls[0]?.sessionID, "ses_remote");

yield* adapter.stopSession(asThreadId("thread-opencode-remote-reuse"));
}).pipe(Effect.provide(remoteLayer));
},
);

it.effect("fails sendTurn for missing sessions through the typed error channel", () =>
Effect.gen(function* () {
const adapter = yield* OpenCodeAdapter;
Expand Down
14 changes: 10 additions & 4 deletions apps/server/src/provider/Layers/OpenCodeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { buildRuntimeInstructions } from "../RuntimeInstructions.ts";
import { type OpenCodeAdapterShape } from "../Services/OpenCodeAdapter.ts";
import {
buildOpenCodePermissionRules,
isLoopbackBaseUrl,
OpenCodeRuntime,
OpenCodeRuntimeError,
openCodeQuestionId,
Expand Down Expand Up @@ -2811,6 +2812,7 @@ export function makeOpenCodeAdapter(
const client = openCodeRuntime.createOpenCodeSdkClient({
baseUrl: server.url,
directory,
external: server.external,
...(server.serverPassword ? { serverPassword: server.serverPassword } : {}),
});
const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId);
Expand Down Expand Up @@ -2845,12 +2847,16 @@ export function makeOpenCodeAdapter(
),
)
: undefined;
// Non-loopback external servers retain their own session directory.
const isRemote = server.external && !isLoopbackBaseUrl(server.url);

// Reuse in place only when the session still matches the
// requested cwd; on a cwd change it is forked below instead.
// Remote sessions keep their server-side cwd. Local sessions need a
// matching cwd to be reused; otherwise they are forked below.
const reusable =
adopted &&
(!adopted.directory || (yield* sameDirectory(adopted.directory, directory)))
(isRemote ||
!adopted.directory ||
(yield* sameDirectory(adopted.directory, directory)))
? adopted
: undefined;

Expand All @@ -2871,7 +2877,7 @@ export function makeOpenCodeAdapter(
// moved into a git worktree). Fork it into the requested
// directory instead of minting an empty one — the fork carries
// the full history, so the follow-up keeps its context (#3604).
if (adopted) {
if (adopted && !isRemote) {
yield* Effect.logInfo(
`OpenCode session '${adopted.id}' was created under a different working directory; forking into '${directory}' to preserve conversation history.`,
);
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/provider/Layers/OpenCodeProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const runtimeMock = {
sdkClientInputs: [] as Array<{
baseUrl: string;
directory: string;
external: boolean;
serverPassword?: string;
}>,
inventory: {
Expand Down Expand Up @@ -372,6 +373,7 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
{
baseUrl: "http://127.0.0.1:4301",
directory: process.cwd(),
external: false,
serverPassword: "secret-password",
},
]);
Expand All @@ -390,6 +392,7 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
{
baseUrl: "http://127.0.0.1:4301",
directory: process.cwd(),
external: false,
serverPassword: "environment-password",
},
]);
Expand Down Expand Up @@ -438,6 +441,7 @@ it.layer(testLayer)("checkOpenCodeProviderStatus with configured server URL", (i
{
baseUrl: "http://127.0.0.1:9999",
directory: process.cwd(),
external: true,
},
]);
}),
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/provider/Layers/OpenCodeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,12 +478,14 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu
readonly url: string;
readonly serverPassword?: string;
readonly version: string;
readonly external?: boolean;
}) =>
openCodeRuntime
.loadOpenCodeInventory(
openCodeRuntime.createOpenCodeSdkClient({
baseUrl: server.url,
directory: cwd,
external: server.external ?? false,
...(server.serverPassword !== undefined ? { serverPassword: server.serverPassword } : {}),
}),
)
Expand Down
Loading
Loading