diff --git a/examples/headless-agent/e2b.cordis.snapshot.yml b/examples/headless-agent/e2b.cordis.snapshot.yml new file mode 100644 index 0000000000..3f39f0ffe4 --- /dev/null +++ b/examples/headless-agent/e2b.cordis.snapshot.yml @@ -0,0 +1,58 @@ +# Keyless counterpart to e2b.cordis.yml. It keeps the same generic PTY, LSP, +# and subprocess Code Runtime consumers, but supplies their fundamental +# filesystem/process world locally and replaces the live model with replay. +- id: base + name: '@cordisjs/plugin-include' + config: + path: ./cordis.yml + patches: + - id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek' + disabled: true + - id: cli-agent + name: '@deepseek-ai/dsh-cli-demo' + config: + provider: deepseek + model: deepseek-v4-pro + persistenceRoot: './.sessions' + persistenceCompression: none + workspaceContext: + maxBytes: 65536 + tools: + mode: both + persona: | + You are headless-agent, a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + + Verify your work by running the code or tests. Keep answers brief and factual. + - insert: + - id: code-runtime-subprocess + name: '@deepseek-ai/dsh-code-runtime-subprocess' + - id: tool-cordis + name: '@deepseek-ai/dsh-tool-cordis' + - id: sandbox + name: '@deepseek-ai/dsh-sandbox-local' + - id: sandbox-policy + name: '@deepseek-ai/dsh-sandbox-policy' + config: + mode: danger-full-access + workspaceRoot: !!js process.cwd() + - id: pty + name: '@deepseek-ai/dsh-pty' + - id: pty-local + name: '@deepseek-ai/dsh-pty-local' + - id: tool-pty + name: '@deepseek-ai/dsh-tool-pty' + - id: lsp + name: '@deepseek-ai/dsh-lsp' + - id: lsp-local + name: '@deepseek-ai/dsh-lsp-local' + config: + servers: + typescript: + command: node + extensionToLanguage: + .ts: typescript + - id: tool-lsp + name: '@deepseek-ai/dsh-tool-lsp' + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' diff --git a/examples/headless-agent/tests/headless.snapshot.ts b/examples/headless-agent/tests/headless.snapshot.ts index 87261439da..ca8e93724c 100644 --- a/examples/headless-agent/tests/headless.snapshot.ts +++ b/examples/headless-agent/tests/headless.snapshot.ts @@ -25,6 +25,8 @@ const advancedScenarioDir = join(snapshotsDir, 'advanced-toolchain') const advancedSessionFixture = join(advancedScenarioDir, 'session.jsonl') const advancedStreamExpected = join(advancedScenarioDir, 'stream-json.expected.jsonl') const advancedConfigPath = fileURLToPath(new URL('../advanced.cordis.snapshot.yml', import.meta.url)) +const e2bScenarioDir = join(snapshotsDir, 'e2b-overlay') +const e2bConfigPath = fileURLToPath(new URL('../e2b.cordis.snapshot.yml', import.meta.url)) const ptyScenarioDir = join(snapshotsDir, 'pty-tools') const ptySessionFixture = join(ptyScenarioDir, 'session.jsonl') const ptyStreamExpected = join(ptyScenarioDir, 'stream-json.expected.jsonl') @@ -608,6 +610,60 @@ describe('headless stream-json snapshots', () => { expect(normalized).toBe(await readFile(advancedStreamExpected, 'utf8')) }, LOADER_SMOKE_TEST_TIMEOUT_MS) + it('pins the E2B overlay provider-neutral tool surface', async () => { + const prompt = await scenarioPrompt(e2bScenarioDir, 'e2b-overlay') + const streamExpected = join(e2bScenarioDir, 'stream-json.expected.jsonl') + let runCwd = '' + const result = await runLoaderSmoke({ + label: 'E2B overlay headless stream-json snapshot', + tempDirPrefix: 'headless-snapshot-e2b-overlay-', + binScript, + configPath: e2bConfigPath, + binArgs: ['--config', e2bConfigPath, '--output-format', 'stream-json', prompt], + tsconfigPath, + env: { + DSH_SNAPSHOT: 'replay', + DSH_SNAPSHOT_FILE: join(e2bScenarioDir, 'session.jsonl'), + DSH_SNAPSHOT_OVERRIDE: join(e2bScenarioDir, 'replay.override.json'), + NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '), + }, + prepare: (cwd) => { runCwd = cwd }, + inspect: async (cwd) => { + const logs = await persistedLogs(cwd) + expect(logs).toHaveLength(1) + const headers = parseJsonl(logs[0]?.content ?? '').filter(record => record.type === 'request/header') + expect(headers).toHaveLength(1) + const data = headers[0]?.data as JsonObject | undefined + const header = data?.header as JsonObject | undefined + if (!Array.isArray(header?.tools)) throw new Error('E2B overlay snapshot request has no tool schemas') + const toolNames = header.tools.map((tool, index) => { + if (tool === null || typeof tool !== 'object' || Array.isArray(tool)) { + throw new Error(`E2B overlay snapshot tool schema ${index} is not an object`) + } + const name = (tool as JsonObject).name + if (typeof name !== 'string') throw new Error(`E2B overlay snapshot tool schema ${index} has no name`) + return name + }) + expect(toolNames.filter(name => name === 'lsp' || name === 'run_code' || name.startsWith('terminal_')).sort()) + .toEqual([ + 'lsp', + 'run_code', + 'terminal_close', + 'terminal_list', + 'terminal_open', + 'terminal_read', + 'terminal_send', + 'terminal_signal', + ]) + }, + }) + + expect(result.stderr).toBe('') + const normalized = normalizeHeadlessStream(result.stdout, runCwd) + if (refreshing) await writeFile(streamExpected, normalized) + expect(normalized).toBe(await readFile(streamExpected, 'utf8')) + }, LOADER_SMOKE_TEST_TIMEOUT_MS) + it('replays persisted goal tools through the one-shot app', async () => { const prompt = await scenarioPrompt(goalScenarioDir, 'goal-tools') const streamExpected = join(goalScenarioDir, 'stream-json.expected.jsonl') diff --git a/examples/headless-agent/tests/snapshots/e2b-overlay/input.json b/examples/headless-agent/tests/snapshots/e2b-overlay/input.json new file mode 100644 index 0000000000..00fd27ce2e --- /dev/null +++ b/examples/headless-agent/tests/snapshots/e2b-overlay/input.json @@ -0,0 +1,8 @@ +{ + "steps": [ + { + "op": "prompt", + "text": "Report the assembled E2B overlay tool surface." + } + ] +} diff --git a/examples/headless-agent/tests/snapshots/e2b-overlay/replay.override.json b/examples/headless-agent/tests/snapshots/e2b-overlay/replay.override.json new file mode 100644 index 0000000000..d1a71ce58c --- /dev/null +++ b/examples/headless-agent/tests/snapshots/e2b-overlay/replay.override.json @@ -0,0 +1,12 @@ +[ + { + "kind": "chunks", + "chunks": [ + { "type": "block-start", "index": 0, "blockType": "text" }, + { "type": "text-delta", "index": 0, "text": "E2B_SURFACE_OK" }, + { "type": "block-end", "index": 0, "block": { "type": "text", "text": "E2B_SURFACE_OK" } }, + { "type": "usage", "usage": { "inputTokens": 8, "outputTokens": 3 } }, + { "type": "finish", "reason": { "kind": "stop" } } + ] + } +] diff --git a/examples/headless-agent/tests/snapshots/e2b-overlay/stream-json.expected.jsonl b/examples/headless-agent/tests/snapshots/e2b-overlay/stream-json.expected.jsonl new file mode 100644 index 0000000000..2fb74415cb --- /dev/null +++ b/examples/headless-agent/tests/snapshots/e2b-overlay/stream-json.expected.jsonl @@ -0,0 +1,14 @@ +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Report the assembled E2B overlay tool surface."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":2,"time":0,"data":{"title":"Report the assembled E2B overlay","messageSeqs":[1],"source":{"kind":"fallback"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-pro"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"E2B_SURFACE_OK"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"E2B_SURFACE_OK"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":8,"outputTokens":3}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"E2B_SURFACE_OK"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-pro"},"id":"{{sessionId}}"},"usage":{"inputTokens":8,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":11,"time":0,"data":{"turn":1,"step":1}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":12,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} +{"type":"result","success":true,"sessionId":"{{sessionId}}","turn":1,"result":"E2B_SURFACE_OK","reason":{"kind":"completed"},"usage":{"inputTokens":8,"outputTokens":3}}