Files
magpie/tests/providers/opencode-cli.test.ts
tgrosinger 2163ea45d2 OpenCode: Add OpenCode as a new provider
The OpenCode provider allows using a variety of models with an agent
harness that can gather more information from the codebase as required
(like with claude-code, codex, or gemini-cli).

This is an alternative to using OpenRouter directly, where the api
provider is more like a chatbot and cannot gather any additional context
beyond what was handed to it.
2026-05-29 16:19:13 -07:00

110 lines
4.6 KiB
TypeScript

// Verifies the JSON event parser against captured opencode 1.15.11 output.
// The schema is internal to opencode; if it changes, these tests fail loudly
// rather than the provider silently returning empty reviewer responses.
import { describe, it, expect } from 'vitest'
import { OpencodeCliProvider } from '../../src/providers/opencode-cli.js'
// Real captures from `opencode run --format json -m openrouter/openai/gpt-4o-mini`.
const STEP_START_EVENT = '{"type":"step_start","timestamp":1780089625130,"sessionID":"ses_abc","part":{"id":"prt_1","messageID":"msg_1","sessionID":"ses_abc","type":"step-start"}}'
const TEXT_EVENT = '{"type":"text","timestamp":1780089625396,"sessionID":"ses_abc","part":{"id":"prt_2","messageID":"msg_1","sessionID":"ses_abc","type":"text","text":"ok","time":{"start":1780089625131,"end":1780089625393}}}'
// Access private parser methods. They're pure logic and worth testing directly;
// extracting them into a separate module just for visibility would be churn.
type ParserHandle = {
extractEventText(event: unknown): string
parseJsonOutput(output: string): string
}
function asParser(p: OpencodeCliProvider): ParserHandle {
return p as unknown as ParserHandle
}
describe('OpencodeCliProvider parser', () => {
describe('extractEventText', () => {
it('returns the text from a text-part event', () => {
const parser = asParser(new OpencodeCliProvider())
expect(parser.extractEventText(JSON.parse(TEXT_EVENT))).toBe('ok')
})
it('returns empty for a step_start event', () => {
const parser = asParser(new OpencodeCliProvider())
expect(parser.extractEventText(JSON.parse(STEP_START_EVENT))).toBe('')
})
it('returns empty for an unknown event type', () => {
const parser = asParser(new OpencodeCliProvider())
expect(parser.extractEventText({ type: 'tool.use', sessionID: 'ses_abc', tool: 'read' })).toBe('')
})
it('returns empty for non-object inputs', () => {
const parser = asParser(new OpencodeCliProvider())
expect(parser.extractEventText(null)).toBe('')
expect(parser.extractEventText('text')).toBe('')
expect(parser.extractEventText(42)).toBe('')
})
})
describe('parseJsonOutput', () => {
it('concatenates text across multiple events, ignoring others', () => {
const provider = new OpencodeCliProvider()
const output = [
STEP_START_EVENT,
'{"type":"text","sessionID":"ses_abc","part":{"type":"text","text":"hello "}}',
'{"type":"tool.use","sessionID":"ses_abc"}',
'{"type":"text","sessionID":"ses_abc","part":{"type":"text","text":"world"}}',
].join('\n')
expect(asParser(provider).parseJsonOutput(output)).toBe('hello world')
})
it('skips blank lines and malformed JSON', () => {
const provider = new OpencodeCliProvider()
const output = [
'',
'not valid json',
TEXT_EVENT,
'{ partial',
'',
].join('\n')
expect(asParser(provider).parseJsonOutput(output)).toBe('ok')
})
it('returns empty when no text events are present', () => {
const provider = new OpencodeCliProvider()
expect(asParser(provider).parseJsonOutput(STEP_START_EVENT)).toBe('')
})
})
describe('session id capture', () => {
it('does not capture sessionID when sessions are disabled', () => {
const provider = new OpencodeCliProvider()
asParser(provider).parseJsonOutput(TEXT_EVENT)
expect(provider.sessionId).toBeUndefined()
})
it('captures sessionID from the first event after startSession', () => {
const provider = new OpencodeCliProvider()
provider.startSession('reviewer-1')
expect(provider.sessionId).toBeUndefined() // not pre-generated
asParser(provider).parseJsonOutput(STEP_START_EVENT)
expect(provider.sessionId).toBe('ses_abc')
})
it('does not overwrite a captured sessionID with a later event', () => {
const provider = new OpencodeCliProvider()
provider.startSession('reviewer-1')
asParser(provider).parseJsonOutput(STEP_START_EVENT)
const laterEvent = '{"type":"text","sessionID":"ses_different","part":{"type":"text","text":"x"}}'
asParser(provider).parseJsonOutput(laterEvent)
expect(provider.sessionId).toBe('ses_abc')
})
it('clears sessionID on endSession', () => {
const provider = new OpencodeCliProvider()
provider.startSession('reviewer-1')
asParser(provider).parseJsonOutput(TEXT_EVENT)
expect(provider.sessionId).toBe('ses_abc')
provider.endSession()
expect(provider.sessionId).toBeUndefined()
})
})
})