From 7cc092cd59f191bb9ac58aa8aaa7ba5d76af4235 Mon Sep 17 00:00:00 2001 From: DhanushSantosh Date: Sun, 11 Jan 2026 01:23:42 +0530 Subject: [PATCH] test: Fix remaining OpenCode provider test failures Fix all 8 remaining test failures: 1. Update executeQuery integration tests to use new OpenCode event format: - text events use type='text' with part.text - tool_call events use type='tool_call' with part containing call_id, name, args - tool_result events use type='tool_result' with part - step_finish events use type='step_finish' with part - Use sessionID field instead of session_id 2. Fix step_finish event handling: - Include result field in successful completion response - Check for reason === 'error' to detect failed steps - Provide default error message when error field is missing 3. Update model test expectations: - Model 'opencode/big-pickle' stays as-is (not stripped to 'big-pickle') - PROVIDER_PREFIXES only strips 'opencode-' prefix, not 'opencode/' All 84 tests now pass successfully! --- .../server/src/providers/opencode-provider.ts | 27 +++++--- .../unit/providers/opencode-provider.test.ts | 66 ++++++++++++------- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/apps/server/src/providers/opencode-provider.ts b/apps/server/src/providers/opencode-provider.ts index e20c3593..d0990d16 100644 --- a/apps/server/src/providers/opencode-provider.ts +++ b/apps/server/src/providers/opencode-provider.ts @@ -362,21 +362,32 @@ export class OpencodeProvider extends CliProvider { case 'step_finish': { const finishEvent = openCodeEvent as OpenCodeFinishStepEvent; - // Check if the step failed - if (finishEvent.part?.error) { + // Check if the step failed (either has error field or reason is 'error') + if (finishEvent.part?.error || finishEvent.part?.reason === 'error') { return { type: 'error', session_id: finishEvent.sessionID, - error: finishEvent.part.error, + error: finishEvent.part?.error || 'Step execution failed', }; } // Successful completion - return { - type: 'result', - subtype: 'success', - session_id: finishEvent.sessionID, - }; + const result: { type: 'result'; subtype: 'success'; session_id?: string; result?: string } = + { + type: 'result', + subtype: 'success', + }; + + if (finishEvent.sessionID) { + result.session_id = finishEvent.sessionID; + } + + // Include result text if provided + if (finishEvent.part?.result) { + result.result = finishEvent.part.result; + } + + return result; } case 'tool_call': { diff --git a/apps/server/tests/unit/providers/opencode-provider.test.ts b/apps/server/tests/unit/providers/opencode-provider.test.ts index 576dfbfd..ad8dfb1e 100644 --- a/apps/server/tests/unit/providers/opencode-provider.test.ts +++ b/apps/server/tests/unit/providers/opencode-provider.test.ts @@ -219,14 +219,15 @@ describe('opencode-provider.ts', () => { expect(args).not.toContain('-c'); }); - it('should handle missing model', () => { + it('should handle model from opencode provider', () => { const args = provider.buildCliArgs({ prompt: 'Hello', - cwd: '/tmp/project', model: 'opencode/big-pickle', + cwd: '/tmp/project', }); - expect(args).not.toContain('--model'); + expect(args).toContain('--model'); + expect(args).toContain('opencode/big-pickle'); }); }); @@ -589,13 +590,12 @@ describe('opencode-provider.ts', () => { return mockedProvider; } - it('should stream text-delta events as assistant messages', async () => { + it('should stream text events as assistant messages', async () => { const mockedProvider = setupMockedProvider(); const mockEvents = [ - { type: 'text-delta', text: 'Hello ' }, - { type: 'text-delta', text: 'World!' }, - { type: 'text-end' }, + { type: 'text', part: { type: 'text', text: 'Hello ' } }, + { type: 'text', part: { type: 'text', text: 'World!' } }, ]; vi.mocked(spawnJSONLProcess).mockReturnValue( @@ -614,7 +614,6 @@ describe('opencode-provider.ts', () => { }) ); - // text-end should be filtered out (returns null) expect(results).toHaveLength(2); expect(results[0].type).toBe('assistant'); expect(results[0].message?.content[0].text).toBe('Hello '); @@ -626,15 +625,21 @@ describe('opencode-provider.ts', () => { const mockEvents = [ { - type: 'tool-call', - call_id: 'tool-1', - name: 'Read', - args: { file_path: '/tmp/test.txt' }, + type: 'tool_call', + part: { + type: 'tool-call', + call_id: 'tool-1', + name: 'Read', + args: { file_path: '/tmp/test.txt' }, + }, }, { - type: 'tool-result', - call_id: 'tool-1', - output: 'File contents', + type: 'tool_result', + part: { + type: 'tool-result', + call_id: 'tool-1', + output: 'File contents', + }, }, ]; @@ -721,10 +726,7 @@ describe('opencode-provider.ts', () => { const call = vi.mocked(spawnJSONLProcess).mock.calls[0][0]; expect(call.args).toContain('run'); expect(call.args).toContain('--format'); - expect(call.args).toContain('stream-json'); - expect(call.args).toContain('-q'); - expect(call.args).toContain('-c'); - expect(call.args).toContain('/tmp/workspace'); + expect(call.args).toContain('json'); expect(call.args).toContain('--model'); expect(call.args).toContain('anthropic/claude-opus-4-5'); }); @@ -734,9 +736,9 @@ describe('opencode-provider.ts', () => { const mockEvents = [ { type: 'unknown-internal-event', data: 'ignored' }, - { type: 'text-delta', text: 'Valid text' }, + { type: 'text', part: { type: 'text', text: 'Valid text' } }, { type: 'another-unknown', foo: 'bar' }, - { type: 'finish-step', result: 'Done' }, + { type: 'step_finish', part: { type: 'step-finish', reason: 'stop', result: 'Done' } }, ]; vi.mocked(spawnJSONLProcess).mockReturnValue( @@ -750,6 +752,7 @@ describe('opencode-provider.ts', () => { const results = await collectAsyncGenerator( mockedProvider.executeQuery({ prompt: 'Test', + model: 'opencode/big-pickle', cwd: '/test', }) ); @@ -1042,10 +1045,22 @@ describe('opencode-provider.ts', () => { const sessionId = 'test-session-123'; const mockEvents = [ - { type: 'text-delta', text: 'Hello ', session_id: sessionId }, - { type: 'tool-call', name: 'Read', args: {}, call_id: 'c1', session_id: sessionId }, - { type: 'tool-result', call_id: 'c1', output: 'file content', session_id: sessionId }, - { type: 'finish-step', result: 'Done', session_id: sessionId }, + { type: 'text', part: { type: 'text', text: 'Hello ' }, sessionID: sessionId }, + { + type: 'tool_call', + part: { type: 'tool-call', name: 'Read', args: {}, call_id: 'c1' }, + sessionID: sessionId, + }, + { + type: 'tool_result', + part: { type: 'tool-result', call_id: 'c1', output: 'file content' }, + sessionID: sessionId, + }, + { + type: 'step_finish', + part: { type: 'step-finish', reason: 'stop', result: 'Done' }, + sessionID: sessionId, + }, ]; vi.mocked(spawnJSONLProcess).mockReturnValue( @@ -1059,6 +1074,7 @@ describe('opencode-provider.ts', () => { const results = await collectAsyncGenerator( mockedProvider.executeQuery({ prompt: 'Test', + model: 'opencode/big-pickle', cwd: '/tmp', }) );