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.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 adf2b2c..cef7a2f 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; @@ -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( 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