mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/rfc/loop' into codex/goal-domain
This commit is contained in:
@@ -33,6 +33,10 @@ The full-screen terminal sibling of `repl-agent`: it reuses the same coding back
|
||||
|
||||
Run with: `pnpm run demo:tui` (needs `DEEPSEEK_API_KEY`). See [tui-agent/README.md](tui-agent/README.md) for controls and composition.
|
||||
|
||||
## jsonrpc-agent
|
||||
|
||||
An unattended coding agent driven through the Python SDK: JSON-RPC stdio, foreground-only `bash`, `read` / `write` / `edit`, one foreground `subagent`, `todo_write`, JSONL persistence, and compaction. It excludes terminal UI, stdout logging, approvals, skills, and background task controls. See [jsonrpc-agent/README.md](jsonrpc-agent/README.md).
|
||||
|
||||
## cordis-agent
|
||||
|
||||
The **self-referential** demo: the coding spine plus [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis), whose three tools (`cordis_inspect` / `cordis_mount` / `cordis_unmount`) let the agent inspect the live cordis runtime it runs inside, mount model-written plugins into it (an event listener, a brand-new tool for itself, or a service another mount injects), and dispose them again — all dynamic mounts grouped under one `cordis-dynamic` fiber subtree. The `ctx.fs`/`ctx.web` services ride along provider-only, as the capabilities those plugins build on.
|
||||
|
||||
24
examples/jsonrpc-agent/README.md
Normal file
24
examples/jsonrpc-agent/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# jsonrpc-agent
|
||||
|
||||
The unattended coding-agent composition for the Python SDK's bundled JSON-RPC runtime. It intentionally loads no terminal UI, console logger, approval surface, or user-interaction tool because stdout belongs to the SDK protocol and turns are driven by the SDK.
|
||||
|
||||
The model-facing tools are:
|
||||
|
||||
- `bash`, foreground only
|
||||
- `read`, `write`, and `edit`
|
||||
- `subagent`, using one foreground in-process spawn provider
|
||||
- `todo_write`
|
||||
|
||||
The surrounding runtime also loads JSONL session persistence and automatic context compaction. `maxTokensAsSuccess` keeps a token-limited model turn as an accepted evaluation result while preserving its `max-tokens` reason.
|
||||
|
||||
## Runtime environment
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `DEEPSEEK_API_KEY` | Credential passed to the OpenAI-compatible host endpoint |
|
||||
| `DEEPSEEK_BASE_URL` | Host endpoint used by `dsh-llm-deepseek` |
|
||||
| `DSH_CWD` | Agent workspace for bash and filesystem tools |
|
||||
| `DSH_SESSION_ROOT` | JSONL trajectory directory |
|
||||
| `DSH_SYSTEM_PROMPT` | Deployment-provided coding persona |
|
||||
|
||||
Pass the config path through the Python SDK's `cordis` option or `DSH_CORDIS_CONFIG`. The bundled executable already carries every plugin named by this file; the target machine does not need Node.js.
|
||||
74
examples/jsonrpc-agent/cordis.yml
Normal file
74
examples/jsonrpc-agent/cordis.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
# Unattended coding-agent deployment for the bundled dsh-jsonrpc-agent runtime.
|
||||
# stdout is reserved for JSON-RPC; do not add a console logger or terminal UI.
|
||||
|
||||
- id: jsonrpc
|
||||
name: '@deepseek-ai/dsh-jsonrpc'
|
||||
config:
|
||||
maxTokensAsSuccess: true
|
||||
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
baseURL: !!js process.env.DEEPSEEK_BASE_URL
|
||||
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
timeoutMs: 60000
|
||||
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a coding agent.'
|
||||
workspaceContext: false
|
||||
skills:
|
||||
enabled: false
|
||||
toolBash:
|
||||
enableRunInBackground: false
|
||||
toolTasks: false
|
||||
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
|
||||
|
||||
- id: subagent
|
||||
name: '@deepseek-ai/dsh-subagent'
|
||||
|
||||
- id: subagent-spawn
|
||||
name: '@deepseek-ai/dsh-subagent-spawn'
|
||||
config:
|
||||
providerName: spawn
|
||||
|
||||
- id: tool-subagent
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableRunInBackground: false
|
||||
|
||||
- id: tool-todo
|
||||
name: '@deepseek-ai/dsh-tool-todo'
|
||||
|
||||
- id: fs-local
|
||||
name: '@deepseek-ai/dsh-fs-local'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
- id: fs-policy
|
||||
name: '@deepseek-ai/dsh-fs-policy'
|
||||
|
||||
- id: tool-fs
|
||||
name: '@deepseek-ai/dsh-tool-fs'
|
||||
|
||||
- id: compact-basic
|
||||
name: '@deepseek-ai/dsh-compact-basic'
|
||||
config:
|
||||
contextWindow: 128000
|
||||
thresholdRatio: 0.8
|
||||
retainTokens: 20480
|
||||
summarizationModel: ''
|
||||
maxTokens: 8192
|
||||
compactionRetries: 1
|
||||
7
examples/jsonrpc-agent/package.json
Normal file
7
examples/jsonrpc-agent/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "jsonrpc-agent-example",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"description": "Unattended JSON-RPC coding-agent composition"
|
||||
}
|
||||
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)
|
||||
})
|
||||
@@ -8,6 +8,7 @@
|
||||
"@cordisjs/plugin-hmr": "workspace:*",
|
||||
"@cordisjs/plugin-include": "workspace:*",
|
||||
"@deepseek-ai/dsh-acp-demo": "workspace:*",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "workspace:*",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-bash-sandbox": "workspace:*",
|
||||
"@deepseek-ai/dsh-cli-demo": "workspace:*",
|
||||
@@ -18,12 +19,14 @@
|
||||
"@deepseek-ai/dsh-goal": "workspace:*",
|
||||
"@deepseek-ai/dsh-hooks-claude": "workspace:*",
|
||||
"@deepseek-ai/dsh-hooks-codex": "workspace:*",
|
||||
"@deepseek-ai/dsh-jsonrpc": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm-deepseek": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm-replay": "workspace:*",
|
||||
"@deepseek-ai/dsh-permission": "workspace:*",
|
||||
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:*",
|
||||
"@deepseek-ai/dsh-sandbox-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:*",
|
||||
"@deepseek-ai/dsh-spill-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-spill-policy": "workspace:*",
|
||||
"@deepseek-ai/dsh-stdio-demo": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user