From f35aee60ce49039022b4b8b73d3cbd363152e23c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 14:36:03 +0000 Subject: [PATCH 1/2] Update OpenCode client to 2.2.0 --- packages/opencode-adapter/package.json | 2 +- packages/opencode-adapter/src/index.ts | 2 +- pnpm-lock.yaml | 28 +++++++++++++------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode-adapter/package.json b/packages/opencode-adapter/package.json index 8baf54d..86b83cc 100644 --- a/packages/opencode-adapter/package.json +++ b/packages/opencode-adapter/package.json @@ -18,7 +18,7 @@ "typecheck": "tsc -p tsconfig.json" }, "dependencies": { - "@opencode/client": "2.1.4" + "@opencode/client": "2.2.0" }, "devDependencies": { "@opencode2-mobile/test-fixtures": "workspace:*", diff --git a/packages/opencode-adapter/src/index.ts b/packages/opencode-adapter/src/index.ts index adf2b2c..0e5c98e 100644 --- a/packages/opencode-adapter/src/index.ts +++ b/packages/opencode-adapter/src/index.ts @@ -23,7 +23,7 @@ import { import { ensurePromiseWithResolvers } from "./promise-with-resolvers"; -export const openCodeClientContractVersion = "2.1.4"; +export const openCodeClientContractVersion = "2.2.0"; export type OpenCodeClientOptions = { authorization?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f91cbf..a3880a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,8 +203,8 @@ importers: packages/opencode-adapter: dependencies: '@opencode/client': - specifier: 2.1.4 - version: 2.1.4(effect@4.0.1) + specifier: 2.2.0 + version: 2.2.0(effect@4.0.1) devDependencies: '@opencode2-mobile/test-fixtures': specifier: workspace:* @@ -1517,8 +1517,8 @@ packages: solid-js: optional: true - '@opencode/client@2.1.4': - resolution: {integrity: sha512-xNHP8Xu4wcq4G849Xnbb8IxYxMTzF0TqvohWVUQZH5aURrVHIIbHMfXbZY6s4i4C9kpVbwjXFFwWAo6KIK3oiw==} + '@opencode/client@2.2.0': + resolution: {integrity: sha512-gus2XLaUcGh3ERH+OnOqEO6K379cS9DFNPOgWr8OTM2VM+zn8b4Qe+dgfyysTayv1BpMMlILsRG4eKlDSuyYWQ==} peerDependencies: effect: 4.0.1 solid-js: '>=1.9.0' @@ -1548,14 +1548,14 @@ packages: '@opencode/protocol@2.0.27': resolution: {integrity: sha512-t1j+m5T3TBusHaL58t6gSSQQEFoi+qjVyQ0Eta/4mLeZLJDqSV8BNN6N93GFpV2MJ1iBpGmrH0C02n2Q+nDHfw==} - '@opencode/protocol@2.1.4': - resolution: {integrity: sha512-dnifgvY6p4xdHkXRH3PEaobfCqrHb1AhihKxPNRNkGn5SueRCFCfK2pZW1GkeC36fGCmcLxlfihuIMaQL1BVzA==} + '@opencode/protocol@2.2.0': + resolution: {integrity: sha512-bvZGwEU0WVgHXzDOsbmUluMWSCQZw6NVKNI+dGjNynxVWRvvpFJ+GA1wDiBkaAwx7dJ6FOH3SO3kNCJlPu2kOA==} '@opencode/schema@2.0.27': resolution: {integrity: sha512-BxGuiIFo503tW6UYxoBrrgjyczEvPiU3fDn45FxUHtmZgBC8kFE+ldI56ab8kV0Lu5C94ZKOniRb2bWqDu4tAg==} - '@opencode/schema@2.1.4': - resolution: {integrity: sha512-PLr8eFW81Yz6U7YsSgfSUClUUH//8omh3HiXu/L08VX5arorWNAu2uSjoXyHIGkm7TaTbdXBGPmmDfpvrDM2wQ==} + '@opencode/schema@2.2.0': + resolution: {integrity: sha512-oyj1xiSnGA/Gwmqyk3MUVOwleozuT1Lt/x7eErhvNO0lyq2xhONnu7mK+riV98uMbD6+GxGWQV369gmpO9fAkQ==} '@opencode/util@2.0.27': resolution: {integrity: sha512-2TIEAJsX4UsyV9CHSk+/6jHWrEllgomgD0WQ8Ne+SN4xlmN9cGUvZo/Jgk0XrZZXSmADDOIER2HLcaI0tAr22A==} @@ -7409,10 +7409,10 @@ snapshots: optionalDependencies: effect: 4.0.1 - '@opencode/client@2.1.4(effect@4.0.1)': + '@opencode/client@2.2.0(effect@4.0.1)': dependencies: - '@opencode/protocol': 2.1.4 - '@opencode/schema': 2.1.4 + '@opencode/protocol': 2.2.0 + '@opencode/schema': 2.2.0 optionalDependencies: effect: 4.0.1 @@ -7438,9 +7438,9 @@ snapshots: '@opencode/schema': 2.0.27 effect: 4.0.1 - '@opencode/protocol@2.1.4': + '@opencode/protocol@2.2.0': dependencies: - '@opencode/schema': 2.1.4 + '@opencode/schema': 2.2.0 effect: 4.0.1 '@opencode/schema@2.0.27': @@ -7448,7 +7448,7 @@ snapshots: '@standard-schema/spec': 1.1.0 effect: 4.0.1 - '@opencode/schema@2.1.4': + '@opencode/schema@2.2.0': dependencies: '@standard-schema/spec': 1.1.0 effect: 4.0.1 From 87eb2c5c043442d267c1df3c6b0165376c20f7a7 Mon Sep 17 00:00:00 2001 From: Eivind Jonassen Date: Wed, 28 Oct 2026 10:55:36 +0100 Subject: [PATCH 2/2] Fix OpenCode 2.2.0 compatibility checks Limit event probes to their unfiltered, cancellation-aware subscription contract so the client's new generic filter signature does not reject test fixtures. Replace unsafe fixture casts with checked types. Move declared ProjectNotFoundError coverage to project.update because project.list no longer declares a 404 response, and cover missing project-list routes as incompatible. --- packages/opencode-adapter/src/index.test.ts | 40 +++++++++++++++------ packages/opencode-adapter/src/index.ts | 20 ++++++----- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/opencode-adapter/src/index.test.ts b/packages/opencode-adapter/src/index.test.ts index f907ac2..09da106 100644 --- a/packages/opencode-adapter/src/index.test.ts +++ b/packages/opencode-adapter/src/index.test.ts @@ -1718,8 +1718,8 @@ it("classifies a malformed fake API event frame", async () => { it.each([404, 405])( "classifies a missing required V2 endpoint HTTP %s as incompatible", async (status) => { - // Project-list 404 is a declared API error in 2.0.23. Server info still - // treats missing routes as unexpected statuses, including its legacy fallback. + // Server info treats missing routes as unexpected statuses, including its + // legacy fallback. const api = createFakeOpenCodeApi({ failures: { "/api/info": { body: {}, status }, @@ -1736,10 +1736,26 @@ it.each([404, 405])( }, ); -it("classifies a declared project-list 404 as not found", async () => { +it.each([404, 405])( + "classifies a missing project-list route HTTP %s as incompatible", + async (status) => { + const api = createFakeOpenCodeApi({ + failures: { "/api/project": { body: {}, status } }, + }); + const client = createOpenCodeClient({ + baseUrl: "https://fake.invalid", + fetch: api.fetch, + }); + + const error = await client.project.list().catch((caught: unknown) => caught); + expect(classifyOpenCodeError(error)).toBe("INCOMPATIBLE"); + }, +); + +it("classifies a declared project-update 404 as not found", async () => { const api = createFakeOpenCodeApi({ failures: { - "/api/project": { + "/api/project/missing-project": { body: { _tag: "ProjectNotFoundError", projectID: "missing-project", @@ -1754,7 +1770,9 @@ it("classifies a declared project-list 404 as not found", async () => { fetch: api.fetch, }); - const error = await client.project.list().catch((caught: unknown) => caught); + const error = await client.project + .update({ projectID: "missing-project", name: "Fixture" }) + .catch((caught: unknown) => caught); expect(classifyOpenCodeError(error)).toBe("NOT_FOUND"); }); @@ -1837,7 +1855,7 @@ it("reports an event stream that ignores cancellation", async () => { }, }), }, - }; + } satisfies Parameters[0]; await expect( probeEventStream(client, { cancellationTimeoutMs: 10, firstEventTimeoutMs: 10 }), @@ -1849,7 +1867,7 @@ it("keeps an event generation open until lifecycle cancellation", async () => { const event: OpenCodeEvent = { data: {}, id: "evt_test", type: "server.connected" }; const client = { event: { - subscribe: ({ signal }: { signal: AbortSignal }) => ({ + subscribe: ({ signal }) => ({ async *[Symbol.asyncIterator]() { yield event; if (signal.aborted) { @@ -1865,7 +1883,7 @@ it("keeps an event generation open until lifecycle cancellation", async () => { }, }), }, - } as unknown as Parameters[0]; + } satisfies Parameters[0]; const generation = await openEventStreamGeneration(client); expect(generation.eventType).toBe("server.connected"); @@ -1886,7 +1904,7 @@ it("reports a pending event read that ignores lifecycle cancellation", async () }, }), }, - } as unknown as Parameters[0]; + } satisfies Parameters[0]; const generation = await openEventStreamGeneration(client, { cancellationTimeoutMs: 10 }); await expect(generation.cancel()).rejects.toMatchObject({ reason: "CANCELLATION_IGNORED" }); @@ -1896,7 +1914,7 @@ it("can stop an event probe before its first event", async () => { let aborted = false; const client = { event: { - subscribe: ({ signal }: { signal: AbortSignal }) => ({ + subscribe: ({ signal }) => ({ [Symbol.asyncIterator]() { return { next: () => @@ -1910,7 +1928,7 @@ it("can stop an event probe before its first event", async () => { }, }), }, - } as unknown as Parameters[0]; + } satisfies Parameters[0]; const probe = startEventStreamProbe(client); await expect(Promise.all([probe.stop(), probe.stop()])).resolves.toEqual([undefined, undefined]); diff --git a/packages/opencode-adapter/src/index.ts b/packages/opencode-adapter/src/index.ts index 0e5c98e..cef7a2f 100644 --- a/packages/opencode-adapter/src/index.ts +++ b/packages/opencode-adapter/src/index.ts @@ -1743,10 +1743,15 @@ export async function probePtyTransport( return result as NonNullable; } -export async function probeEventStream( - client: Pick, - options: EventProbeOptions = {}, -) { +// Probes consume all events and only need subscriber-local cancellation, not +// the generated client's generic event-filter signature. +type EventProbeClient = { + event: { + subscribe(options: { signal: AbortSignal }): AsyncIterable; + }; +}; + +export async function probeEventStream(client: EventProbeClient, options: EventProbeOptions = {}) { const probe = startEventStreamProbe(client, options); const first = await probe.firstEvent; await probe.stop(); @@ -1754,7 +1759,7 @@ export async function probeEventStream( } export async function openEventStreamGeneration( - client: Pick, + client: EventProbeClient, options: EventProbeOptions = {}, ) { const probe = startEventStreamProbe(client, options); @@ -1767,10 +1772,7 @@ export async function openEventStreamGeneration( } } -export function startEventStreamProbe( - client: Pick, - options: EventProbeOptions = {}, -) { +export function startEventStreamProbe(client: EventProbeClient, options: EventProbeOptions = {}) { const controller = new AbortController(); const iterator = client.event.subscribe({ signal: controller.signal })[Symbol.asyncIterator](); const firstRead = withDeadline(