refactor(examples): rename DSBench composition to jsonrpc-agent
This commit is contained in:
146
examples/jsonrpc-agent/tests/keyless-smoke.e2e.ts
Normal file
146
examples/jsonrpc-agent/tests/keyless-smoke.e2e.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
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))
|
||||
|
||||
function waitForLine(
|
||||
lines: string[],
|
||||
predicate: (value: Record<string, unknown>) => boolean,
|
||||
stderr: () => string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
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<string, unknown>
|
||||
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('boots the real Cordis tree and serves initialize/shutdown over clean stdout', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-agent-smoke-'))
|
||||
const modelRequests: Record<string, unknown>[] = []
|
||||
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<string, unknown>)
|
||||
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":"stop"}],"usage":{"prompt_tokens":3,"completion_tokens":1}}\n\n')
|
||||
response.end('data: [DONE]\n\n')
|
||||
})
|
||||
})
|
||||
await new Promise<void>(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')
|
||||
const child = spawn(process.execPath, [
|
||||
'--expose-internals',
|
||||
'--import',
|
||||
'tsx',
|
||||
binScript,
|
||||
configPath,
|
||||
], {
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...process.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'),
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
})
|
||||
const lines: string[] = []
|
||||
let stdoutBuffer = ''
|
||||
let stderr = ''
|
||||
child.stdout.setEncoding('utf8')
|
||||
child.stdout.on('data', (chunk: string) => {
|
||||
stdoutBuffer += chunk
|
||||
const parts = stdoutBuffer.split('\n')
|
||||
stdoutBuffer = parts.pop() ?? ''
|
||||
lines.push(...parts)
|
||||
})
|
||||
child.stderr.setEncoding('utf8')
|
||||
child.stderr.on('data', (chunk: string) => { stderr += chunk })
|
||||
|
||||
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 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: {} })
|
||||
if (child.exitCode === null) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
child.once('exit', (code) => {
|
||||
if (code === 0) resolve()
|
||||
else reject(new Error(`runtime exited ${code}; stderr=${stderr}`))
|
||||
})
|
||||
})
|
||||
} else {
|
||||
expect(child.exitCode, stderr).toBe(0)
|
||||
}
|
||||
} finally {
|
||||
if (child.exitCode === null) child.kill('SIGKILL')
|
||||
await new Promise<void>(resolve => modelServer.close(() => { resolve() }))
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
}, 40_000)
|
||||
})
|
||||
Reference in New Issue
Block a user