diff --git a/src/services/ai/opencode-provider.ts b/src/services/ai/opencode-provider.ts index b82749a5..da3b96a6 100644 --- a/src/services/ai/opencode-provider.ts +++ b/src/services/ai/opencode-provider.ts @@ -94,6 +94,11 @@ export function isProviderConnected(providerName: string): boolean { export function setV2Client(client: OpencodeClient): void { _v2Client = client; + // Native v2 adapters pass a session-capable client without a server URL. + // Enable the SDK transport path so structured output does not require createV2Client. + if (hasV2SessionClient(client)) { + _useSdkTransport = true; + } } export function getV2Client(): OpencodeClient | undefined { diff --git a/src/v2/legacy-client.ts b/src/v2/legacy-client.ts index 2f7a11bc..a5cede09 100644 --- a/src/v2/legacy-client.ts +++ b/src/v2/legacy-client.ts @@ -239,7 +239,12 @@ export function toLegacyEvent(raw: any): { type: string; properties: any } { const envelope = raw?.payload ?? raw; const source = envelope?.type === "sync" && envelope.syncEvent ? envelope.syncEvent : envelope; const rawType = typeof source?.type === "string" ? source.type.replace(/\.1$/, "") : source?.type; - const type = rawType === "session.compaction.ended" ? "session.compacted" : rawType; + const type = + rawType === "session.compaction.ended" + ? "session.compacted" + : rawType === "session.execution.succeeded" + ? "session.idle" + : rawType; const data = source?.data ?? {}; if (source && typeof source === "object" && "properties" in source) { return { type, properties: source.properties }; diff --git a/tests/opencode-provider.test.ts b/tests/opencode-provider.test.ts index 612d9d19..91f435bd 100644 --- a/tests/opencode-provider.test.ts +++ b/tests/opencode-provider.test.ts @@ -103,6 +103,44 @@ describe("v2 client cache", () => { expect(typeof client.session.prompt).toBe("function"); expect(typeof client.session.delete).toBe("function"); }); + + it("setV2Client enables SDK structured output without createV2Client", async () => { + const mod = await import(`../src/services/ai/opencode-provider.js?cachebust=${Math.random()}`); + const calls: string[] = []; + const client = { + session: { + create: async () => { + calls.push("create"); + return { data: { id: "ses_sdk_native" } }; + }, + prompt: async () => { + calls.push("prompt"); + return { + data: { + info: { structured_output: { topic: "native-v2", count: 1 } }, + parts: [], + }, + }; + }, + delete: async () => { + calls.push("delete"); + }, + }, + }; + + mod.setV2Client(client); + const result = await mod.generateStructuredOutput({ + client, + providerID: "github-copilot", + modelID: "gpt-4o-mini", + systemPrompt: "s", + userPrompt: "u", + schema, + }); + + expect(result).toEqual({ topic: "native-v2", count: 1 }); + expect(calls).toEqual(["create", "prompt", "delete"]); + }); }); describe("generateStructuredOutput", () => { diff --git a/tests/v2-legacy-client.test.ts b/tests/v2-legacy-client.test.ts index d2870cff..203d4a29 100644 --- a/tests/v2-legacy-client.test.ts +++ b/tests/v2-legacy-client.test.ts @@ -121,6 +121,18 @@ describe("OpenCode v2 legacy client bridge", () => { }); }); + it("maps V2 execution.succeeded to session.idle for auto-capture", () => { + expect( + toLegacyEvent({ + type: "session.execution.succeeded", + data: { sessionID: "ses-1" }, + }) + ).toEqual({ + type: "session.idle", + properties: { sessionID: "ses-1" }, + }); + }); + it("filters the global event stream by direct or session location", async () => { const ctx = createContext(); expect(