Created
April 23, 2026 20:35
-
-
Save rm-rf-etc/fcdb35b3d0fba3a2e126d75b30c07c4c to your computer and use it in GitHub Desktop.
Archon OpenCode provider: CodeRabbit review fixes for PR coleam00/Archon#1372
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/packages/providers/src/community/opencode/config.ts b/packages/providers/src/community/opencode/config.ts | |
| index bd25ede..680dc68 100644 | |
| --- a/packages/providers/src/community/opencode/config.ts | |
| +++ b/packages/providers/src/community/opencode/config.ts | |
| @@ -28,7 +28,12 @@ export function parseOpencodeConfig(raw: Record<string, unknown>): OpencodeProvi | |
| result.hostname = raw.hostname; | |
| } | |
| - if (typeof raw.port === 'number') { | |
| + if ( | |
| + typeof raw.port === 'number' && | |
| + Number.isInteger(raw.port) && | |
| + raw.port >= 1 && | |
| + raw.port <= 65535 | |
| + ) { | |
| result.port = raw.port; | |
| } | |
| diff --git a/packages/providers/src/community/opencode/event-bridge.ts b/packages/providers/src/community/opencode/event-bridge.ts | |
| index 5015805..9c90524 100644 | |
| --- a/packages/providers/src/community/opencode/event-bridge.ts | |
| +++ b/packages/providers/src/community/opencode/event-bridge.ts | |
| @@ -23,15 +23,6 @@ export async function* bridgeEvents( | |
| ): AsyncGenerator<MessageChunk> { | |
| const events = await client.event.subscribe(); | |
| - const accumulatedTokens = { | |
| - input: 0, | |
| - output: 0, | |
| - reasoning: 0, | |
| - cacheRead: 0, | |
| - cacheWrite: 0, | |
| - }; | |
| - let totalCost = 0; | |
| - | |
| try { | |
| for await (const event of events.stream) { | |
| if (abortSignal?.aborted) { | |
| @@ -46,41 +37,19 @@ export async function* bridgeEvents( | |
| const chunk = mapEventToChunk(event); | |
| if (!chunk) continue; | |
| - // Accumulate token usage from result chunks for final tally | |
| - if (chunk.type === 'result' && chunk.tokens) { | |
| - accumulatedTokens.input += chunk.tokens.input ?? 0; | |
| - accumulatedTokens.output += chunk.tokens.output ?? 0; | |
| - } | |
| - if (chunk.type === 'result' && typeof chunk.cost === 'number') { | |
| - totalCost += chunk.cost; | |
| - } | |
| - | |
| yield chunk; | |
| - // Stop consuming on final result or error | |
| if (chunk.type === 'result') { | |
| return; | |
| } | |
| } | |
| } finally { | |
| - // Ensure the SSE stream is cancelled | |
| try { | |
| await events.stream.return?.(undefined); | |
| } catch { | |
| // Ignore cleanup errors | |
| } | |
| } | |
| - | |
| - // If the stream ends without a result chunk, yield one with accumulated stats | |
| - yield { | |
| - type: 'result', | |
| - sessionId, | |
| - tokens: { | |
| - input: accumulatedTokens.input, | |
| - output: accumulatedTokens.output, | |
| - }, | |
| - cost: totalCost > 0 ? totalCost : undefined, | |
| - }; | |
| } | |
| function mapEventToChunk(event: Event): MessageChunk | undefined { | |
| @@ -133,13 +102,16 @@ function mapEventToChunk(event: Event): MessageChunk | undefined { | |
| const info = event.properties.info; | |
| if (info.role === 'assistant') { | |
| const tokens = info.tokens; | |
| + const input = tokens?.input ?? 0; | |
| + const output = tokens?.output ?? 0; | |
| + const reasoning = tokens?.reasoning ?? 0; | |
| return { | |
| type: 'result', | |
| sessionId: info.sessionID, | |
| tokens: { | |
| - input: tokens?.input ?? 0, | |
| - output: tokens?.output ?? 0, | |
| - total: tokens ? tokens.input + tokens.output + tokens.reasoning : undefined, | |
| + input, | |
| + output, | |
| + total: tokens ? input + output + reasoning : undefined, | |
| }, | |
| cost: info.cost > 0 ? info.cost : undefined, | |
| }; | |
| diff --git a/packages/providers/src/community/opencode/provider.test.ts b/packages/providers/src/community/opencode/provider.test.ts | |
| index f81f1f0..ec2d587 100644 | |
| --- a/packages/providers/src/community/opencode/provider.test.ts | |
| +++ b/packages/providers/src/community/opencode/provider.test.ts | |
| @@ -20,7 +20,7 @@ const mockCreateSession = mock(async () => ({ | |
| data: { id: mockSessionId }, | |
| })); | |
| -const mockSessionStatus = mock(async () => ({ data: { id: mockSessionId } })); | |
| +const mockSessionGet = mock(async () => ({ data: { id: mockSessionId } })); | |
| const mockPromptAsync = mock(async () => ({ data: { id: 'msg-123' } })); | |
| @@ -37,7 +37,7 @@ const mockEventSubscribe = mock(async () => ({ | |
| const mockClient = { | |
| session: { | |
| create: mockCreateSession, | |
| - status: mockSessionStatus, | |
| + get: mockSessionGet, | |
| promptAsync: mockPromptAsync, | |
| abort: mockSessionAbort, | |
| list: mock(async () => ({ data: mockSessionList })), | |
| @@ -84,7 +84,7 @@ describe('OpenCodeProvider', () => { | |
| mockEventSequence = []; | |
| mockSessionList = []; | |
| mockCreateSession.mockClear(); | |
| - mockSessionStatus.mockClear(); | |
| + mockSessionGet.mockClear(); | |
| mockPromptAsync.mockClear(); | |
| mockEventSubscribe.mockClear(); | |
| }); | |
| @@ -174,12 +174,12 @@ describe('OpenCodeProvider', () => { | |
| chunks.push(chunk); | |
| } | |
| - expect(mockSessionStatus).toHaveBeenCalledWith({ path: { id: 'existing-session-id' } }); | |
| + expect(mockSessionGet).toHaveBeenCalledWith({ path: { id: 'existing-session-id' } }); | |
| expect(mockCreateSession).not.toHaveBeenCalled(); | |
| }); | |
| test('sendQuery falls back to new session when resumeSessionId is invalid', async () => { | |
| - mockSessionStatus.mockImplementationOnce(async () => { | |
| + mockSessionGet.mockImplementationOnce(async () => { | |
| throw new Error('Session not found'); | |
| }); | |
| @@ -211,7 +211,7 @@ describe('OpenCodeProvider', () => { | |
| chunks.push(chunk); | |
| } | |
| - expect(mockSessionStatus).toHaveBeenCalled(); | |
| + expect(mockSessionGet).toHaveBeenCalled(); | |
| expect(mockCreateSession).toHaveBeenCalled(); | |
| // Should yield a system warning about resume failure | |
| expect(chunks.some(c => c.type === 'system')).toBe(true); | |
| diff --git a/packages/providers/src/community/opencode/provider.ts b/packages/providers/src/community/opencode/provider.ts | |
| index 57e2d27..fd414d7 100644 | |
| --- a/packages/providers/src/community/opencode/provider.ts | |
| +++ b/packages/providers/src/community/opencode/provider.ts | |
| @@ -54,9 +54,13 @@ export class OpenCodeProvider implements IAgentProvider { | |
| // 1. Ensure server is running | |
| const serverInfo = await ensureServer({ hostname, port, cwd, password }, autoStart); | |
| - // 2. Create SDK client | |
| + // 2. Create SDK client with HTTP Basic Auth | |
| + const credentials = Buffer.from(`opencode:${serverInfo.password}`).toString('base64'); | |
| const client = createOpencodeClient({ | |
| baseUrl: `http://${serverInfo.hostname}:${serverInfo.port}`, | |
| + headers: { | |
| + Authorization: `Basic ${credentials}`, | |
| + }, | |
| }); | |
| // 3. Resolve model | |
| diff --git a/packages/providers/src/community/opencode/server-manager.ts b/packages/providers/src/community/opencode/server-manager.ts | |
| index 390f991..10a4a0f 100644 | |
| --- a/packages/providers/src/community/opencode/server-manager.ts | |
| +++ b/packages/providers/src/community/opencode/server-manager.ts | |
| @@ -1,4 +1,5 @@ | |
| import { spawn } from 'node:child_process'; | |
| +import { randomBytes } from 'node:crypto'; | |
| import { createLogger } from '@archon/paths'; | |
| let cachedLog: ReturnType<typeof createLogger> | undefined; | |
| @@ -61,7 +62,7 @@ export async function ensureServer(config: ServerConfig, autoStart = true): Prom | |
| getLog().info({ port: config.port, cwd: config.cwd }, 'opencode.server.starting'); | |
| const proc = spawn( | |
| - 'opencode', | |
| + process.env.OPENCODE_BIN_PATH ?? 'opencode', | |
| ['serve', '--port', String(config.port), '--hostname', config.hostname], | |
| { | |
| cwd: config.cwd, | |
| @@ -78,10 +79,19 @@ export async function ensureServer(config: ServerConfig, autoStart = true): Prom | |
| getLog().error({ err }, 'opencode.server.process_error'); | |
| }); | |
| + proc.stdout?.resume(); | |
| + | |
| proc.stderr?.on('data', (data: Buffer) => { | |
| getLog().debug({ msg: data.toString().trim() }, 'opencode.server.stderr'); | |
| }); | |
| + proc.on('exit', (code, signal) => { | |
| + getLog().info({ code, signal }, 'opencode.server.exited'); | |
| + if (managedServer?.proc === proc) { | |
| + managedServer = undefined; | |
| + } | |
| + }); | |
| + | |
| // 4. Wait for readiness | |
| await waitForReady(config.hostname, config.port, 30000); | |
| @@ -123,5 +133,5 @@ async function waitForReady(hostname: string, port: number, timeoutMs: number): | |
| * Generate a random password for the OpenCode Server. | |
| */ | |
| export function generatePassword(): string { | |
| - return `archon-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}`; | |
| + return `archon-${randomBytes(16).toString('hex')}`; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment