expect(result).toEqual({ subject: "Tighten OpenCode parsing", body: "Handle JSON text output locally.", }); }), ), ); it.effect("surfaces the upstream OpenCode structured-output error message", () => withOpenCodeTextGeneration(DEFAULT_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { runtimeMock.state.promptResult = { data: { info: { error: { name: "StructuredOutputError", data: { message: "Model did not produce structured output", retries: 2, }, }, }, }, }; const error = yield* textGeneration .generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT) .pipe(Effect.flip); expect(error.message).toContain("Model did not produce structured output"); expect(error.cause).toMatchObject({ _tag: "OpenCodeTextGenerationPromptResponseError", operation: "generateCommitMessage", cwd: process.cwd(), sessionId: "http://127.0.0.1:4301/session", providerId: "openai", modelId: "gpt-5", providerErrorName: "StructuredOutputError", providerMessage: "Model did not produce structured output", }); expect(error.cause).not.toHaveProperty("cause"); }), ), );}); it.layer(OpenCodeTextGenerationExistingServerTestLayer)( "OpenCodeTextGeneration with configured server URL", (it) => { it.effect("does not send a local environment password to a configured server", () => withOpenCodeTextGeneration( EXTERNAL_SERVER_WITHOUT_AUTH_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { yield* textGeneration.generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT); expect(runtimeMock.state.authHeaders).toEqual([null]); }), { OPENCODE_SERVER_PASSWORD: "local-secret" }, ), ); it.effect("does not create a session when the server version is unsupported", () => withOpenCodeTextGeneration(EXISTING_SERVER_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { runtimeMock.state.connectionError = new Error( "OpenCode v1.14.18 is too old. Upgrade to v1.14.19 or newer.", ); const error = yield* textGeneration .generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT) .pipe(Effect.flip); expect(error).toBeInstanceOf(TextGenerationError); expect(error.message).toContain("v1.14.18 is too old"); expect(runtimeMock.state.sessionCreateCalls).toBe(0); }), ), ); it.effect("reuses a configured OpenCode server URL without spawning or applying idle TTL", () => withOpenCodeTextGeneration(EXISTING_SERVER_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { yield* textGeneration.generateCommitMessage({ cwd: process.cwd(), branch: "feature/opencode-reuse", stagedSummary: "M README.md", stagedPatch: "diff --git a/README.md b/README.md", modelSelection: DEFAULT_TEST_MODEL_SELECTION, }); yield* textGeneration.generateCommitMessage({ cwd: process.cwd(), branch: "feature/opencode-reuse", stagedSummary: "M README.md", stagedPatch: "diff --git a/README.md b/README.md", modelSelection: DEFAULT_TEST_MODEL_SELECTION, }); expect(runtimeMock.state.startCalls).toEqual([]); expect(runtimeMock.state.promptUrls).toEqual([ "http://127.0.0.1:9999", "http://127.0.0.1:9999", ]); expect(runtimeMock.state.authHeaders).toEqual([ `Basic ${btoa("opencode:secret-password")}`, `Basic ${btoa("opencode:secret-password")}`, ]); yield* advanceIdleClock; expect(runtimeMock.state.closeCalls).toEqual([]); }), ).pipe(Effect.provide(TestClock.layer())), ); },);Lines 501–616 of 616. Previous lines
expect(result).toEqual({ subject: "Tighten OpenCode parsing", body: "Handle JSON text output locally.", }); }), ), ); it.effect("surfaces the upstream OpenCode structured-output error message", () => withOpenCodeTextGeneration(DEFAULT_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { runtimeMock.state.promptResult = { data: { info: { error: { name: "StructuredOutputError", data: { message: "Model did not produce structured output", retries: 2, }, }, }, }, }; const error = yield* textGeneration .generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT) .pipe(Effect.flip); expect(error.message).toContain("Model did not produce structured output"); expect(error.cause).toMatchObject({ _tag: "OpenCodeTextGenerationPromptResponseError", operation: "generateCommitMessage", cwd: process.cwd(), sessionId: "http://127.0.0.1:4301/session", providerId: "openai", modelId: "gpt-5", providerErrorName: "StructuredOutputError", providerMessage: "Model did not produce structured output", }); expect(error.cause).not.toHaveProperty("cause"); }), ), ); }); it.layer(OpenCodeTextGenerationExistingServerTestLayer)( "OpenCodeTextGeneration with configured server URL", (it) => { it.effect("does not send a local environment password to a configured server", () => withOpenCodeTextGeneration( EXTERNAL_SERVER_WITHOUT_AUTH_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { yield* textGeneration.generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT); expect(runtimeMock.state.authHeaders).toEqual([null]); }), { OPENCODE_SERVER_PASSWORD: "local-secret" }, ), ); it.effect("does not create a session when the server version is unsupported", () => withOpenCodeTextGeneration(EXISTING_SERVER_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { runtimeMock.state.connectionError = new Error( "OpenCode v1.14.18 is too old. Upgrade to v1.14.19 or newer.", ); const error = yield* textGeneration .generateCommitMessage(DEFAULT_COMMIT_MESSAGE_INPUT) .pipe(Effect.flip); expect(error).toBeInstanceOf(TextGenerationError); expect(error.message).toContain("v1.14.18 is too old"); expect(runtimeMock.state.sessionCreateCalls).toBe(0); }), ), ); it.effect("reuses a configured OpenCode server URL without spawning or applying idle TTL", () => withOpenCodeTextGeneration(EXISTING_SERVER_OPENCODE_SETTINGS, (textGeneration) => Effect.gen(function* () { yield* textGeneration.generateCommitMessage({ cwd: process.cwd(), branch: "feature/opencode-reuse", stagedSummary: "M README.md", stagedPatch: "diff --git a/README.md b/README.md", modelSelection: DEFAULT_TEST_MODEL_SELECTION, }); yield* textGeneration.generateCommitMessage({ cwd: process.cwd(), branch: "feature/opencode-reuse", stagedSummary: "M README.md", stagedPatch: "diff --git a/README.md b/README.md", modelSelection: DEFAULT_TEST_MODEL_SELECTION, }); expect(runtimeMock.state.startCalls).toEqual([]); expect(runtimeMock.state.promptUrls).toEqual([ "http://127.0.0.1:9999", "http://127.0.0.1:9999", ]); expect(runtimeMock.state.authHeaders).toEqual([ `Basic ${btoa("opencode:secret-password")}`, `Basic ${btoa("opencode:secret-password")}`, ]); yield* advanceIdleClock; expect(runtimeMock.state.closeCalls).toEqual([]); }), ).pipe(Effect.provide(TestClock.layer())), ); }, );