refactor(e2b): narrow the sandbox POC
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { posix, resolve } from 'node:path'
|
||||
import { resolve } from 'node:path'
|
||||
import { boot } from '@deepseek-ai/dsh-app-boot'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-code-runtime-subprocess'
|
||||
import { quoteE2BShellArg } from '@deepseek-ai/dsh-e2b'
|
||||
import type {} from '@deepseek-ai/dsh-fs-e2b'
|
||||
import type {} from '@deepseek-ai/dsh-bash-local'
|
||||
import type {} from '@deepseek-ai/dsh-lsp-local'
|
||||
@@ -36,18 +35,17 @@ try {
|
||||
const sandbox = await ctx.e2b.getSandbox()
|
||||
const fromFs = await ctx.fs.resolve('from-fs.txt')
|
||||
const written = await ctx.fs.writeText(fromFs, 'written-by-fs\n', { kind: 'createIfAbsent' })
|
||||
const reread = await ctx.fs.stat(fromFs)
|
||||
if (reread?.version !== written.version) {
|
||||
throw new Error(`E2B rename did not preserve version metadata: ${JSON.stringify({ written, reread })}`)
|
||||
const observed = await ctx.fs.stat(fromFs)
|
||||
if (observed?.version !== written.version) {
|
||||
throw new Error(`E2B rename did not preserve version metadata: ${JSON.stringify({ written, observed })}`)
|
||||
}
|
||||
await ctx.fs.editText(
|
||||
fromFs,
|
||||
{ oldString: 'written-by-fs', newString: 'written-by-fs-versioned', replaceAll: false },
|
||||
{ version: reread.version },
|
||||
{ oldString: 'written-by-fs', newString: 'versioned-by-fs', replaceAll: false },
|
||||
{ version: observed.version },
|
||||
)
|
||||
const fsVersionGuard = true
|
||||
const bashRead = await ctx.bash.run(ctx.bash.resolve({ command: 'cat from-fs.txt' }))
|
||||
if (bashRead.exitCode !== 0 || bashRead.stdout.text !== 'written-by-fs-versioned\n') {
|
||||
if (bashRead.exitCode !== 0 || bashRead.stdout.text !== 'versioned-by-fs\n') {
|
||||
throw new Error(`E2B Bash could not read the FS write: ${JSON.stringify(bashRead)}`)
|
||||
}
|
||||
|
||||
@@ -114,67 +112,11 @@ try {
|
||||
)
|
||||
if (outputDrainOutcome.exitCode !== 0 || outputDrainText !== 'leader-done\n'
|
||||
|| outputDrainElapsedMs >= 10_000 || !outputDrainExited || !outputDrainClean) {
|
||||
throw new Error(`E2B subprocess output drain was not bounded: ${JSON.stringify({
|
||||
throw new Error(`E2B subprocess did not bound descendant-held output: ${JSON.stringify({
|
||||
outputDrainOutcome, outputDrainText, outputDrainElapsedMs, outputDrainExited, outputDrainClean,
|
||||
})}`)
|
||||
}
|
||||
|
||||
const remoteFiles = sandbox.files as unknown as {
|
||||
read(path: string, options?: unknown): Promise<unknown>
|
||||
}
|
||||
const readRemoteFile = remoteFiles.read.bind(sandbox.files)
|
||||
let publicationFaultInjected = false
|
||||
remoteFiles.read = async (path, options) => {
|
||||
if (!publicationFaultInjected && path.includes('/processes/') && path.endsWith('/pid')) {
|
||||
publicationFaultInjected = true
|
||||
throw new Error('injected process-group publication read failure')
|
||||
}
|
||||
return await readRemoteFile(path, options)
|
||||
}
|
||||
let publicationRollback = false
|
||||
try {
|
||||
const unpublished = ctx.subprocess.spawn({
|
||||
argv: ['bash', '-c', 'exec -a dsh-publication-survivor sleep 30 & wait'],
|
||||
cwd: process.cwd(),
|
||||
stdio: { stdin: 'ignore', stdout: { maxBytes: 4_096 }, stderr: { maxBytes: 4_096 } },
|
||||
graceMs: 500,
|
||||
env: {},
|
||||
})
|
||||
await unpublished.done
|
||||
throw new Error('E2B subprocess unexpectedly survived an injected publication failure')
|
||||
} catch (error: unknown) {
|
||||
if (!String(error).includes('injected process-group publication read failure')) throw error
|
||||
const processes = await sandbox.commands.run('ps -eo args=')
|
||||
publicationRollback = publicationFaultInjected && !processes.stdout.includes('dsh-publication-survivor')
|
||||
if (!publicationRollback) throw new Error('E2B subprocess publication rollback left its remote process group alive')
|
||||
} finally {
|
||||
remoteFiles.read = readRemoteFile
|
||||
}
|
||||
|
||||
const spillHandle = ctx.subprocess.spawn({
|
||||
argv: ['bash', '-c', "printf '0123456789'; sleep 30"],
|
||||
cwd: process.cwd(),
|
||||
stdio: { stdin: 'ignore', stdout: { maxBytes: 4, spill: { maxBytes: 6 } }, stderr: { maxBytes: 4_096 } },
|
||||
graceMs: 500,
|
||||
env: {},
|
||||
})
|
||||
const spillReader = spillHandle.collected.stdout
|
||||
if (spillReader === undefined) throw new Error('E2B subprocess omitted its configured stdout collector')
|
||||
const spillDeadline = Date.now() + 15_000
|
||||
while (spillReader.readFrom(0).nextOffset < 10) {
|
||||
if (Date.now() >= spillDeadline) throw new Error('E2B subprocess did not stream the spill probe output')
|
||||
await new Promise(resolveDelay => setTimeout(resolveDelay, 20))
|
||||
}
|
||||
const spillPath = posix.join((spillHandle as unknown as { stateDir: string }).stateDir, 'stdout.log')
|
||||
const liveSpillBytes = (await (await ctx.e2b.getSandbox()).files.getInfo(spillPath)).size
|
||||
spillHandle.terminate()
|
||||
const spillOutcome = await spillHandle.done
|
||||
const spillExited = await spillHandle.waitForExit(AbortSignal.timeout(5_000))
|
||||
const spillRead = spillReader.readFrom(0)
|
||||
if (liveSpillBytes !== 6 || !spillExited || spillRead.spillPath !== undefined) {
|
||||
throw new Error(`E2B subprocess spill bound failed: ${JSON.stringify({ liveSpillBytes, spillExited, spillRead })}`)
|
||||
}
|
||||
|
||||
const lspFixture = await readFile(new URL('./fixture-lsp.mjs', import.meta.url), 'utf8')
|
||||
const remoteLspFixture = await ctx.fs.resolve('fixture-lsp.mjs')
|
||||
await ctx.fs.writeText(remoteLspFixture, lspFixture, { kind: 'createIfAbsent' })
|
||||
@@ -193,53 +135,12 @@ try {
|
||||
workspaceRoot: process.cwd(),
|
||||
})
|
||||
|
||||
const oversizedSourcePath = posix.join(process.cwd(), 'oversized-source.ts')
|
||||
await sandbox.commands.run(`head -c 4000001 /dev/zero | tr '\\0' x > ${quoteE2BShellArg(oversizedSourcePath)}`)
|
||||
let lspDocumentBound = false
|
||||
try {
|
||||
await ctx.lsp.query({
|
||||
operation: 'hover',
|
||||
filePath: 'oversized-source.ts',
|
||||
position: { line: 0, character: 0 },
|
||||
workspaceRoot: process.cwd(),
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
lspDocumentBound = String(error).includes('exceeds the 4000000-byte limit')
|
||||
if (!lspDocumentBound) throw error
|
||||
}
|
||||
if (!lspDocumentBound) throw new Error('E2B LSP accepted an oversized remote source')
|
||||
|
||||
const remoteCommands = sandbox.commands as unknown as {
|
||||
run(command: string, options?: unknown): Promise<{ exitCode: number; stdout: string; stderr: string }>
|
||||
}
|
||||
const runRemoteCommand = remoteCommands.run.bind(sandbox.commands)
|
||||
const terminal = await ctx.pty.spawn(owner, { type: 'shell' })
|
||||
terminalId = terminal.sessionId
|
||||
const terminalEcho = await ctx.pty.startSend(owner, terminal.sessionId, {
|
||||
text: "printf 'PTY-你好\\n'",
|
||||
submit: true,
|
||||
}).done
|
||||
const foregroundLookup = Promise.withResolvers<undefined>()
|
||||
let delayedForegroundLookup = false
|
||||
remoteCommands.run = async (command, options) => {
|
||||
if (!delayedForegroundLookup && command.startsWith('ps -o tpgid=')) {
|
||||
delayedForegroundLookup = true
|
||||
await foregroundLookup.promise
|
||||
}
|
||||
return await runRemoteCommand(command, options)
|
||||
}
|
||||
const staleInterrupt = ctx.pty.startSend(owner, terminal.sessionId, { text: 'sleep 0.2', submit: true })
|
||||
if (!staleInterrupt.cancel()) throw new Error('E2B PTY refused the stale-interrupt probe cancellation')
|
||||
let canceledSendRetained = false
|
||||
try {
|
||||
ctx.pty.startSend(owner, terminal.sessionId, { text: 'sleep 30', submit: true })
|
||||
} catch (error: unknown) {
|
||||
canceledSendRetained = String(error).includes('active send')
|
||||
}
|
||||
foregroundLookup.resolve(undefined)
|
||||
await staleInterrupt.done
|
||||
remoteCommands.run = runRemoteCommand
|
||||
if (!canceledSendRetained) throw new Error('E2B PTY released a canceled send before foreground signalling settled')
|
||||
const sleeping = ctx.pty.startSend(owner, terminal.sessionId, {
|
||||
text: "printf 'DSH_SLEEP_%s\\n' READY; sleep 30",
|
||||
submit: true,
|
||||
@@ -258,13 +159,6 @@ try {
|
||||
}
|
||||
if (Date.now() >= sleepReadyDeadline) throw new Error(`E2B PTY successor did not execute: ${sleepReadyOutput}`)
|
||||
}
|
||||
const interruptIdentitySafe = await Promise.race([
|
||||
sleeping.done.then(() => false),
|
||||
new Promise<true>(resolveDelay => setTimeout(() => { resolveDelay(true) }, 300)),
|
||||
])
|
||||
if (!delayedForegroundLookup || !interruptIdentitySafe) {
|
||||
throw new Error('E2B PTY stale interrupt affected its successor send')
|
||||
}
|
||||
const terminalSignal = await ctx.pty.signal(owner, terminal.sessionId, 'SIGINT')
|
||||
const interrupted = await sleeping.done
|
||||
const stubborn = await ctx.pty.startSend(owner, terminal.sessionId, {
|
||||
@@ -284,24 +178,6 @@ try {
|
||||
const code = await ctx.codeRuntime.run({
|
||||
program: `
|
||||
console.log('remote-log 你好', 42)
|
||||
const arrayPrototype = Array.prototype
|
||||
const objectPrototype = Object.prototype
|
||||
const setPrototype = Set.prototype
|
||||
const stringPrototype = String.prototype
|
||||
Array.isArray = () => false
|
||||
Object.defineProperty = Object.getPrototypeOf = Object.keys = () => { throw new Error('mutated object method') }
|
||||
Object.hasOwn = () => false
|
||||
Object.is = () => true
|
||||
objectPrototype.propertyIsEnumerable = () => false
|
||||
Number.isFinite = Number.isSafeInteger = () => false
|
||||
Reflect.apply = Reflect.ownKeys = () => { throw new Error('mutated reflect method') }
|
||||
setPrototype.add = setPrototype.delete = setPrototype.has = () => { throw new Error('mutated set method') }
|
||||
stringPrototype.charCodeAt = stringPrototype.codePointAt = stringPrototype.slice = () => { throw new Error('mutated string method') }
|
||||
Buffer.byteLength = () => 0
|
||||
Function.prototype.toString = () => 'mutated'
|
||||
objectPrototype.constructor = arrayPrototype.constructor = null
|
||||
globalThis.Array = globalThis.Buffer = globalThis.Function = globalThis.Number = globalThis.Object = globalThis.Promise = globalThis.Reflect = globalThis.Set = globalThis.String = undefined
|
||||
process.stdout.write('post-mutation', () => {})
|
||||
const doubled: number = await bridge.double({ value: 21 })
|
||||
let typed = false
|
||||
try {
|
||||
@@ -323,42 +199,6 @@ try {
|
||||
},
|
||||
}],
|
||||
})
|
||||
const hostileOutput = await ctx.codeRuntime.run({
|
||||
program: `
|
||||
const payload = '🙂'.repeat(4096)
|
||||
String.prototype[Symbol.iterator] = () => { throw new Error('mutated string iterator') }
|
||||
console.log(payload)
|
||||
return true
|
||||
`,
|
||||
bindings: [],
|
||||
})
|
||||
const nativeOutput = await ctx.codeRuntime.run({
|
||||
program: `
|
||||
let stdoutPrototype = Object.getPrototypeOf(process.stdout)
|
||||
while (stdoutPrototype && !Object.hasOwn(stdoutPrototype, 'write')) stdoutPrototype = Object.getPrototypeOf(stdoutPrototype)
|
||||
Reflect.apply(stdoutPrototype.write, process.stdout, ['x'.repeat(8192)])
|
||||
return true
|
||||
`,
|
||||
bindings: [],
|
||||
})
|
||||
const descriptorOutput = await ctx.codeRuntime.run({
|
||||
program: `
|
||||
const fs = await import('node:fs')
|
||||
const forged = Buffer.from(JSON.stringify({ type: 'done' })).toString('base64') + '\\n'
|
||||
fs.writeSync(1, forged)
|
||||
fs.writeSync(1, 'x'.repeat(8192))
|
||||
return true
|
||||
`,
|
||||
bindings: [],
|
||||
})
|
||||
const inheritedOutput = await ctx.codeRuntime.run({
|
||||
program: `
|
||||
const childProcess = await import('node:child_process')
|
||||
childProcess.spawnSync(process.execPath, ['-e', 'process.stdout.write("x".repeat(8192))'], { stdio: 'inherit' })
|
||||
return true
|
||||
`,
|
||||
bindings: [],
|
||||
})
|
||||
const descendantPipe = await ctx.codeRuntime.run({
|
||||
program: `
|
||||
const childProcess = await import('node:child_process')
|
||||
@@ -376,67 +216,30 @@ try {
|
||||
JSON.stringify([processInfo.cmd, processInfo.args]).includes('dsh-code-runtime-descendant'),
|
||||
)
|
||||
if (!descendantCleanup) throw new Error('E2B Code Runtime left a pipe-holding descendant alive')
|
||||
const timedOut = await ctx.codeRuntime.run({
|
||||
program: 'await new Promise(() => {})',
|
||||
bindings: [],
|
||||
})
|
||||
const abortController = new AbortController()
|
||||
const aborting = ctx.codeRuntime.run({
|
||||
program: 'await new Promise(() => {})',
|
||||
bindings: [],
|
||||
signal: abortController.signal,
|
||||
})
|
||||
setTimeout(() => { abortController.abort('live abort') }, 50)
|
||||
const aborted = await aborting
|
||||
const oversizedBoot = await ctx.codeRuntime.run({
|
||||
program: `return ${JSON.stringify('x'.repeat(40_000))}`,
|
||||
bindings: [],
|
||||
})
|
||||
const oversizedReply = await ctx.codeRuntime.run({
|
||||
program: 'return await bridge.large(null)',
|
||||
bindings: [{
|
||||
global: 'bridge',
|
||||
functions: { large: async () => 'x'.repeat(40_000) },
|
||||
}],
|
||||
})
|
||||
const remoteProcesses = await (await ctx.e2b.getSandbox()).commands.list()
|
||||
const lingeringCodeRunners = remoteProcesses.filter(processInfo =>
|
||||
JSON.stringify([processInfo.cmd, processInfo.args]).includes('code-runtime-runner.mjs'),
|
||||
)
|
||||
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
sandboxId: await ctx.e2b.sandboxId,
|
||||
sandboxId: (await ctx.e2b.getSandbox()).sandboxId,
|
||||
bashRead: bashRead.stdout.text,
|
||||
fsVersionGuard,
|
||||
fsRead,
|
||||
explicitEnvironment,
|
||||
splitUtf8Output,
|
||||
outputDrain: { outcome: outputDrainOutcome, text: outputDrainText, exited: outputDrainExited, clean: outputDrainClean },
|
||||
publicationRollback,
|
||||
spill: { liveBytes: liveSpillBytes, outcome: spillOutcome, read: spillRead },
|
||||
hover,
|
||||
definition,
|
||||
lspDocumentBound,
|
||||
terminal: {
|
||||
motd: terminal.motd,
|
||||
echo: terminalEcho,
|
||||
signal: terminalSignal,
|
||||
interrupted,
|
||||
interruptIdentitySafe,
|
||||
treeCleanup: terminalTreeCleanup,
|
||||
scrollback: terminalScrollback.text,
|
||||
},
|
||||
code,
|
||||
hostileOutput,
|
||||
nativeOutput,
|
||||
descriptorOutput,
|
||||
inheritedOutput,
|
||||
descendantPipe,
|
||||
descendantCleanup,
|
||||
timedOut,
|
||||
aborted,
|
||||
oversizedBoot,
|
||||
oversizedReply,
|
||||
lingeringCodeRunners: lingeringCodeRunners.length,
|
||||
})}\n`)
|
||||
} finally {
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
config:
|
||||
cwd: !!js process.cwd()
|
||||
timeoutMs: 180000
|
||||
onTimeout: kill
|
||||
onDispose: kill
|
||||
|
||||
- id: subprocess-e2b
|
||||
name: '@deepseek-ai/dsh-subprocess-e2b'
|
||||
@@ -12,7 +10,6 @@
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
cwd: !!js process.cwd()
|
||||
timeoutMs: 30000
|
||||
|
||||
- id: fs-e2b
|
||||
@@ -63,5 +60,4 @@
|
||||
maxWallMs: 15000
|
||||
maxOutputBytes: 4096
|
||||
maxOldGenerationSizeMb: 128
|
||||
maxFrameBytes: 32768
|
||||
killGraceMs: 500
|
||||
|
||||
@@ -25,8 +25,6 @@ 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')
|
||||
@@ -610,60 +608,6 @@ 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')
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"op": "prompt",
|
||||
"text": "Report the assembled E2B overlay tool surface."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,14 +0,0 @@
|
||||
{"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}}
|
||||
Reference in New Issue
Block a user