Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2722,6 +2722,58 @@ describe("OpenCode2 adapter", () => {
}).pipe(Effect.scoped),
);

it.effect("adds T3's MCP server again on every turn, in case OpenCode dropped it", () =>
Effect.gen(function* () {
McpProviderSession.setMcpProviderSession({
environmentId: EnvironmentId.make("environment:opencode2-adapter"),
threadId,
providerSessionId: "mcp:opencode2-adapter",
providerInstanceId: instanceId,
endpoint: "http://127.0.0.1:3773/mcp",
authorizationHeader: "Bearer thread-credential",
browserToolsAvailable: false,
});
yield* Effect.addFinalizer(() =>
Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)),
);
const server = "t3-code-thread_opencode2-adapter";
const add = [
out("mcp.add", {
server,
"location[directory]": WORK,
config: {
type: "remote",
url: "http://127.0.0.1:3773/mcp",
headers: { Authorization: "Bearer thread-credential" },
oauth: false,
},
}),
reply("mcp.add", null),
];
const turn = [
out("session.prompt", { sessionID: SESSION, text: "<any>" }),
promptAccepted,
event("session.execution.succeeded", { sessionID: SESSION }),
];
// OpenCode keeps runtime-added servers with the Location and forgets them
// when it evicts an idle one, without a reconnect T3 could notice.
const { runtime, thread } = yield* resumed([
...add,
...turn,
...add,
...turn,
// Removed when the session closes.
out("mcp.remove", { server, "location[directory]": WORK }),
reply("mcp.remove", null),
]);
for (let index = 0; index < 2; index++) {
const terminal = yield* terminalOf(runtime).pipe(Effect.forkScoped);
yield* runtime.startTurn(turnInput(thread));
assert.equal((yield* Fiber.join(terminal))?.status, "completed");
}
}).pipe(Effect.scoped),
);

it.effect("registers a long thread id's MCP server under a name OpenCode accepts", () =>
Effect.gen(function* () {
// Spelled out, this delegated thread's server name would be 120 characters.
Expand Down
15 changes: 9 additions & 6 deletions apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ interface ThreadState {
* inbox, where the next prompt would deliver them first, so it cancels them.
*/
readonly strandedSteers: Set<string>;
/** T3's MCP server as registered for this thread, and the instructions entry sent with it. */
/** T3's MCP server as last registered for this thread, and the instructions entry sent with it. */
mcp:
| { readonly name: string; readonly directory: string; readonly credential: string }
| undefined;
Expand Down Expand Up @@ -3260,9 +3260,12 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr
yield* removeMcp(state.mcp);
state.mcp = undefined;
}
// T3's tools are an addition: a server that cannot add them still runs the turn.
if (wanted !== undefined && state.mcp === undefined) {
const added = yield* client.mcp
// Added on every turn: OpenCode drops runtime-added servers when it evicts
// an idle Location, and leaves an unchanged one connected. T3's tools are
// an addition: a server that cannot add them still runs the turn.
let mcpAvailable = false;
if (wanted !== undefined) {
mcpAvailable = yield* client.mcp
.add({
server: name,
location: { directory },
Expand All @@ -3282,11 +3285,11 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr
),
),
);
if (added) state.mcp = wanted;
if (mcpAvailable) state.mcp = wanted;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
const instructions = [
buildRuntimeInstructions({ harness: "OpenCode", model: turnInput.modelSelection.model }),
t3OrchestrationSystemPrompt(state.mcp !== undefined),
t3OrchestrationSystemPrompt(mcpAvailable),
]
.filter((part) => part !== undefined && part.length > 0)
.join("\n\n");
Expand Down
Loading