import { createServer } from 'node:http' import { mkdtemp, readFile, readdir, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { promisify } from 'node:util' import { zstdDecompress } from 'node:zlib' import { execa } from 'execa' import { describe, expect, it } from 'vitest' const binScript = fileURLToPath(new URL('../../../packages/examples/jsonrpc-demo/src/bin.ts', import.meta.url)) const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url)) const repoRoot = fileURLToPath(new URL('../../..', import.meta.url)) const decompress = promisify(zstdDecompress) function waitForLine( lines: string[], predicate: (value: Record) => boolean, stderr: () => string, ): Promise> { return new Promise((resolve, reject) => { const deadline = Date.now() + 30_000 const poll = (): void => { while (lines.length > 0) { const line = lines.shift()! if (!line.trim()) continue try { const value = JSON.parse(line) as Record if (predicate(value)) { resolve(value) return } } catch { reject(new Error(`non-JSON stdout from JSON-RPC agent runtime: ${line}`)) return } } if (Date.now() >= deadline) { reject(new Error(`timed out waiting for JSON-RPC response; stderr=${stderr()}`)) return } setTimeout(poll, 10) } poll() }) } describe('jsonrpc-agent keyless smoke', () => { it.each([ { label: 'accepts max-token results by default', envValue: undefined, expectedStatus: 'ok' }, { label: 'accepts max-token results when enabled through env', envValue: 'true', expectedStatus: 'ok' }, { label: 'reports max-token results as errors when disabled through env', envValue: 'false', expectedStatus: 'error' }, ])('$label', async ({ envValue, expectedStatus }) => { const root = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-agent-smoke-')) const modelRequests: Record[] = [] const modelServer = createServer((request, response) => { let body = '' request.setEncoding('utf8') request.on('data', (chunk: string) => { body += chunk }) request.on('end', () => { modelRequests.push(JSON.parse(body) as Record) response.writeHead(200, { 'content-type': 'text/event-stream' }) response.write('data: {"choices":[{"delta":{"role":"assistant","content":null}}]}\n\n') response.write('data: {"choices":[{"delta":{"content":"done"}}]}\n\n') response.write('data: {"choices":[{"delta":{},"finish_reason":"length"}],"usage":{"prompt_tokens":3,"completion_tokens":1}}\n\n') response.end('data: [DONE]\n\n') }) }) await new Promise(resolve => modelServer.listen(0, '127.0.0.1', resolve)) const address = modelServer.address() if (address === null || typeof address === 'string') throw new Error('model server did not bind a TCP port') // The line-predicate protocol driving below is the genuinely custom part; // execa owns spawn, the deadline, and exit settlement around it. const child = execa(process.execPath, [ '--import', 'tsx', binScript, configPath, ], { cwd: repoRoot, env: { DEEPSEEK_API_KEY: 'keyless-smoke-no-call', DEEPSEEK_BASE_URL: `http://127.0.0.1:${address.port}`, DSH_CWD: root, DSH_SESSION_ROOT: join(root, '.sessions'), ...(envValue === undefined ? {} : { DSH_MAX_TOKENS_AS_SUCCESS: envValue }), }, timeout: 35_000, killSignal: 'SIGKILL', reject: false, }) const lines: string[] = [] let stdoutBuffer = '' let stderr = '' child.stdout.on('data', (chunk: Buffer) => { stdoutBuffer += chunk.toString('utf8') const parts = stdoutBuffer.split('\n') stdoutBuffer = parts.pop() ?? '' lines.push(...parts) }) child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8') }) try { child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { cwd: root, provider: 'deepseek', model: 'deepseek-v4-pro' }, })}\n`) const initialized = await waitForLine(lines, value => value.id === 1, () => stderr) expect(initialized).toMatchObject({ jsonrpc: '2.0', id: 1, result: { serverInfo: { name: 'deepseek-harness-sdk-runtime' } }, }) child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'session/prompt', params: { sessionId: 'main', contentBlocks: [{ type: 'text', text: 'inspect tools' }] }, })}\n`) const finished = await waitForLine(lines, value => value.method === 'session.finished', () => stderr) expect(finished).toMatchObject({ jsonrpc: '2.0', method: 'session.finished', params: { sessionId: 'main', status: expectedStatus, reason: { kind: 'max-tokens' }, }, }) const prompt = await waitForLine(lines, value => value.id === 2, () => stderr) expect(prompt).toMatchObject({ jsonrpc: '2.0', id: 2, result: { accepted: true } }) const tools = modelRequests[0]?.tools as { function?: { name?: string } }[] expect(tools.map(tool => tool.function?.name).sort()).toEqual([ 'bash', 'edit', 'read', 'subagent', 'todo_write', 'write', ]) child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'shutdown' })}\n`) const shutdown = await waitForLine(lines, value => value.id === 3, () => stderr) expect(shutdown).toMatchObject({ jsonrpc: '2.0', id: 3, result: {} }) const exit = await child expect(exit.exitCode, `signal=${String(exit.signal)}; stderr=${stderr}`).toBe(0) const sessionsRoot = join(root, '.sessions') const files = await readdir(sessionsRoot, { recursive: true }) const log = files.find(file => file.endsWith('.jsonl.zstd')) expect(log).toBeDefined() const compressed = await readFile(join(sessionsRoot, log!)) expect(compressed.subarray(0, 4).toString('hex')).toBe('28b52ffd') expect(JSON.parse((await decompress(compressed)).toString())).toMatchObject({ type: 'session', id: 'main' }) } finally { // No-op after exit; reject: false settles on every outcome, so cleanup never races teardown. child.kill('SIGKILL') await child await new Promise(resolve => modelServer.close(() => { resolve() })) await rm(root, { recursive: true, force: true }) } }, 40_000) it('rejects an invalid max-token success env value', async () => { const { exitCode, stdout, stderr } = await execa(process.execPath, [ '--import', 'tsx', binScript, configPath, ], { cwd: repoRoot, env: { DEEPSEEK_API_KEY: 'keyless-smoke-no-call', DSH_MAX_TOKENS_AS_SUCCESS: 'sometimes', }, stdin: 'ignore', timeout: 25_000, killSignal: 'SIGKILL', reject: false, }) expect(exitCode, stderr).toBe(1) expect(stdout).toBe('') expect(stderr).toContain('plugin(s) failed to load: @deepseek-ai/dsh-jsonrpc') }, 30_000) })