feat(tools): run_code description param + native-parity dispatch logging + web code-mode seam
run_code gains a required bash-style description parameter: presentCall titles the card with it and moves the program to rawInput, so every surface gets a readable label. tool/code-dispatch now logs each sub-call's complete content/isError (the tool/result vocabulary), replacing the bounded resultSummary and deleting the summarize/cwd machinery — a UI renders sub-calls through the identical path as native results. The dsh config tree mounts the worker code runtime and reads DSH_TOOLS_MODE (temporary seam until per-session mode selection lands). Session format stays v0 (pre-release churn). Code-mode ACP/TUI fixtures re-recorded; TUI presenter pin refreshed; catalogs regenerated. Keyless web smoke pins the code-mode wire contract (tools=[run_code] + SDK prompt section).
This commit is contained in:
@@ -115,7 +115,7 @@ Returning `undefined` selects generic fallback. Presenters depend only on their
|
||||
Under `code` or `both`, the registry exposes the reserved `run_code` transport and a deterministic TypeScript SDK for the current scope; only the program's outer logs and return value re-enter model context. The SDK declares exact `ToolArgsMap` and `ToolOutputMap` entries for every visible tool, and each binding resolves to the tool's canonical JSON value. Each lossless-JSON binding call re-enters the complete tool pipeline sequentially with logged correlation to the outer call. Denials and other failed results reject with the real program-visible `ToolCallError` carrying only `toolName` and `message`; Native content and internal error codes stay outside the Code contract. Ordinary side effects are not rolled back, and sub-call `additionalContexts` are deferred through the parent result to preserve call/result adjacency. Run settlement aborts and drains outstanding bindings; runtime failures surface as `CodeRunFailedError`. See the [Code Mode foundation](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md), [typed-return contract](../../../.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md), and [code-runtime seam](../../code-runtime/README.md). Try `pnpm run demo:code-mode`.
|
||||
|
||||
- **The SDK section** (`tools:sdk`, order 150): a lazy prompt section regenerating, at each assembly, `JsonValue`, exact `ToolArgsMap` / `ToolOutputMap`, `ToolName`, the `ToolCallError` declaration, and a mapped `tools` namespace for the calling scope's visible end capabilities (exotic names via quoted keys), plus fixed usage instructions. Deterministic — lexicographic tool order, byte-identical text for an unchanged tool set (prefix-cache-friendly). The codegen (`jsonSchemaToTs`, exported) handles every unified schema construct and degrades unsupported raw constructs to `unknown`, never throwing during prompt assembly.
|
||||
- **The dispatch bridge** (`run_code`'s execute): every binding call is snapshotted as lossless JSON before dispatch (`undefined`, `BigInt`, cycles, sparse arrays, `-0`, and exotic objects reject that one call), serialized through a per-run queue (even `Promise.all` executes underlying calls one at a time in submission order), given the outer execution's opaque token as `parent`, and run through the complete pre-execute → guards → execute → post-execute → result pipeline. A success returns the final canonical value after policy; a failure reaches the worker as one message and becomes `ToolCallError(toolName, message)`. Each sub-call is logged as a `tool/code-dispatch` session event with deterministic id `<parent>:code:<n>` and a bounded Native-content summary; `deriveMessages()` does not surface that event or persist the value. Token correlation lets commit-style observers defer an inner success until the final `run_code` result without exposing the live outer execution; ordinary tool side effects are not rolled back. Every sub-call `additionalContexts` entry is deferred through the outer `ToolRunContext` in dispatch order; the loop appends those contexts only after the parent `run_code` result, preserving adjacency and retaining each source/meta even when the program later fails.
|
||||
- **The dispatch bridge** (`run_code`'s execute): every binding call is snapshotted as lossless JSON before dispatch (`undefined`, `BigInt`, cycles, sparse arrays, `-0`, and exotic objects reject that one call), serialized through a per-run queue (even `Promise.all` executes underlying calls one at a time in submission order), given the outer execution's opaque token as `parent`, and run through the complete pre-execute → guards → execute → post-execute → result pipeline. A success returns the final canonical value after policy; a failure reaches the worker as one message and becomes `ToolCallError(toolName, message)`. Each sub-call is logged as a `tool/code-dispatch` session event with deterministic id `<parent>:code:<n>` and the complete model-facing `content`/`isError` outcome (the `tool/result` vocabulary, so UIs render sub-calls through the native path); `deriveMessages()` does not surface that event or persist the canonical value. Token correlation lets commit-style observers defer an inner success until the final `run_code` result without exposing the live outer execution; ordinary tool side effects are not rolled back. Every sub-call `additionalContexts` entry is deferred through the outer `ToolRunContext` in dispatch order; the loop appends those contexts only after the parent `run_code` result, preserving adjacency and retaining each source/meta even when the program later fails.
|
||||
- **Settlement discipline**: the bridge owns a run-scoped abort that follows the outer signal in and fires when the run settles for any reason, so a budget expiry aborts an in-flight sub-tool instead of orphaning it; the bridge then drains its queue BEFORE returning, so every `tool/code-dispatch` lands inside the open turn. A failed run throws `CodeRunFailedError` (`code: 'CODE_RUN_FAILED'`, message = the failure kind + captured logs), which the pipeline converts to a structured `isError` the model self-corrects from.
|
||||
- **Result boundary**: intermediate binding values cross the worker boundary whole and have no per-binding byte cap. `run_code` returns canonical `{ logs: string[], result?: JsonValue }`; strings render raw, every other present JSON root renders through a stack-safe pretty JSON traversal whose total indentation is capped at ten characters (deeper subtrees stay compact), `null` remains explicit, and absent `result` means the program returned `undefined`. The worker's configurable `maxOutputBytes` (default 64 MiB) applies only to the combined serialized outer log-array, completion-value, or failure-message payloads; fixed result-envelope syntax and presentation whitespace are outside that ledger. Invalid and over-limit completions fail explicitly, and only this outer result is eligible for ordinary spill.
|
||||
|
||||
@@ -189,5 +189,5 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
- **Caller-defined subagent and workflow structured outputs remain object-rooted** — this is a consumer-level guard; the shared schema vocabulary and tool outputs support every JSON root.
|
||||
- **`timeoutMs` on a definition is declarative only** — the registry never enforces deadlines; enforcement requires the `@deepseek-ai/dsh-timeout-policy` wrapper.
|
||||
- **Code Mode is TypeScript-only and the presentation mode is service-wide** — `mode: code`/`both` rejects prompt assembly unless `ctx.codeRuntime.language === 'typescript'`; scoped restrictions/shadows still choose each agent's visible bindings, but one tool cannot be native-only while another is code-only.
|
||||
- **Code Mode intermediate values are execution-local and unbounded by bytes** — they cannot be reconstructed from session replay and may exhaust process or worker memory; only the outer `run_code` output has the worker's configurable hard cap.
|
||||
- **Code Mode intermediate values are execution-local and unbounded by bytes** — the canonical typed values cannot be reconstructed from session replay and may exhaust process or worker memory; only the outer `run_code` output has the worker's configurable hard cap. The rendered `content` of every sub-call IS logged verbatim on `tool/code-dispatch`, uncapped and outside spill policy, so programs that read huge files grow the session log by the same bytes (spill integration for the logged copy is deferred work).
|
||||
- **`run_code` state is fresh per run** — a persistent REPL-style kernel is rejected for the MVP (cross-call state would be invisible to the log); see [the Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md).
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @module @deepseek-ai/dsh-tools/src/code-mode
|
||||
*/
|
||||
|
||||
import { parse } from 'node:path'
|
||||
import { CallId, HarnessError } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { CodeBindingFunction, CodeRunResult, CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
|
||||
@@ -21,17 +20,16 @@ declare module '@deepseek-ai/dsh-session' {
|
||||
* `run_code` call id, the deterministic sub-call id
|
||||
* (`<parent>:code:<n>`), the tool `name` with its JSON-normalized
|
||||
* `arguments` — the exact value dispatched, normalized BEFORE dispatch,
|
||||
* so this append can never fail on payload shape — whether the sub-call
|
||||
* errored, and a bounded `resultSummary` of its model-facing text. Before
|
||||
* bounding, occurrences of a non-root session workspace path are
|
||||
* normalized to `.` so host-specific absolute path lengths cannot change
|
||||
* the summary.
|
||||
* so this append can never fail on payload shape — and the sub-call's
|
||||
* complete model-facing outcome in `tool/result`'s own vocabulary
|
||||
* (`content` + `isError`), so UIs render a sub-call through the exact
|
||||
* code path that renders a native call.
|
||||
* Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter
|
||||
* model context; persistence and UIs get every call. Appended inside the
|
||||
* parent `run_code`'s execution (the bridge drains its queue before
|
||||
* returning), so the turn-enclosure invariant holds by construction.
|
||||
*/
|
||||
'tool/code-dispatch': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; resultSummary: string }
|
||||
'tool/code-dispatch': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; content: ContentBlock[] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,35 +53,6 @@ export class CodeRunFailedError extends HarnessError {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cap for a `tool/code-dispatch` event's `resultSummary`. A log-ergonomics
|
||||
* constant, not config: the full result already flows to the program; the
|
||||
* summary exists so log readers see what a sub-call returned at a glance.
|
||||
*/
|
||||
const SUMMARY_MAX_CHARS = 200
|
||||
|
||||
/** Join Native content for the bounded durable sub-dispatch summary; non-text blocks become diagnostic placeholders. */
|
||||
function textOf(content: ContentBlock[]): string {
|
||||
return content
|
||||
.map((block) => {
|
||||
switch (block.type) {
|
||||
case 'text': return block.text
|
||||
// ContentBlockMap is merge-extensible — future block kinds land here
|
||||
// deliberately (no assertNever on merge-extensible unions).
|
||||
default: return `[${block.type} content]`
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
/** Normalize workspace paths, then bound a sub-call's model-facing text for its durable log summary. */
|
||||
function summarize(text: string, cwd: string | undefined): string {
|
||||
const stableText = cwd === undefined || cwd === parse(cwd).root
|
||||
? text
|
||||
: text.replaceAll(cwd, '.')
|
||||
return stableText.length > SUMMARY_MAX_CHARS ? `${stableText.slice(0, SUMMARY_MAX_CHARS)}…` : stableText
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot one binding call's argument as lossless JSON, then snapshot that
|
||||
* detached value again so dispatch and logging stay independent without
|
||||
@@ -221,6 +190,13 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
|
||||
+ 'Only what you print or return comes back — curate it.',
|
||||
parameters: {
|
||||
code: { type: 'string', required: true, description: 'The program: the body of an async TypeScript function.' },
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Clear, concise description of what this program does in active voice, '
|
||||
+ '5-10 words (shown in the UI). Examples: "Count TODO markers across packages"; '
|
||||
+ '"Read failing test and its fixture"; "Rename config key in every cordis.yml".',
|
||||
},
|
||||
},
|
||||
output: {
|
||||
schema: {
|
||||
@@ -238,6 +214,9 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
|
||||
},
|
||||
},
|
||||
async execute(args, exec): Promise<RunCodeOutput> {
|
||||
if (args.description.trim().length === 0) {
|
||||
throw new Error('invalid description: expected a non-empty string')
|
||||
}
|
||||
const runtime = requireRuntime()
|
||||
|
||||
// The run-scoped abort: follows the outer signal in, and fires when the
|
||||
@@ -288,7 +267,6 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
|
||||
for (const context of result.additionalContexts ?? []) {
|
||||
exec.deferContext(context)
|
||||
}
|
||||
const text = textOf(result.content)
|
||||
exec.agent?.session.append('tool/code-dispatch', {
|
||||
parentCallId: exec.callId,
|
||||
subCallId,
|
||||
@@ -298,7 +276,9 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
|
||||
// this record from what it actually received.
|
||||
arguments: normalized.logged,
|
||||
isError: result.isError,
|
||||
resultSummary: summarize(text, exec.agent.session.header.cwd),
|
||||
// The registry deep-froze this projection at result finalization;
|
||||
// append snapshots it again, so the log copy stays detached.
|
||||
content: result.content,
|
||||
})
|
||||
return result.isError
|
||||
? { isError: true as const, message: result.error.message }
|
||||
@@ -363,10 +343,11 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
|
||||
exec.signal.removeEventListener('abort', onOuterAbort)
|
||||
}
|
||||
},
|
||||
// The program is the call's always-visible UI label.
|
||||
// The model-authored description is the call's always-visible UI label
|
||||
// (the bash `description` precedent); the program itself rides rawInput.
|
||||
presentCall: args => ({
|
||||
card: 'generic',
|
||||
title: args.code,
|
||||
title: args.description,
|
||||
kind: 'execute',
|
||||
rawInput: args.code,
|
||||
}),
|
||||
|
||||
@@ -87,11 +87,11 @@ function registerEcho(ctx: Context, name = 'echo'): unknown[] {
|
||||
}
|
||||
|
||||
/** A structural fake of the owning agent: captures session appends. */
|
||||
function fakeAgent(options: { cwd?: string } = { cwd: '/workspace' }): { agent: Agent; events: { type: string; data: unknown }[] } {
|
||||
function fakeAgent(): { agent: Agent; events: { type: string; data: unknown }[] } {
|
||||
const events: { type: string; data: unknown }[] = []
|
||||
const agent = {
|
||||
session: {
|
||||
header: options.cwd === undefined ? {} : { cwd: options.cwd },
|
||||
header: { cwd: '/workspace' },
|
||||
append: (type: string, data: unknown) => { events.push({ type, data }) },
|
||||
},
|
||||
} as unknown as Agent
|
||||
@@ -99,12 +99,16 @@ function fakeAgent(options: { cwd?: string } = { cwd: '/workspace' }): { agent:
|
||||
}
|
||||
|
||||
/** Dispatch run_code through the registry pipeline, as the loop would. */
|
||||
async function runCode(ctx: Context, code: string, extras: { agent?: Agent; signal?: AbortSignal } = {}): Promise<ToolExecutionResult> {
|
||||
async function runCode(
|
||||
ctx: Context,
|
||||
code: string,
|
||||
extras: { agent?: Agent; signal?: AbortSignal; description?: string } = {},
|
||||
): Promise<ToolExecutionResult> {
|
||||
return ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('call-1'),
|
||||
name: RUN_CODE_NAME,
|
||||
arguments: { code },
|
||||
arguments: { code, description: extras.description ?? 'Run the test program' },
|
||||
...extras.agent ? { agent: extras.agent } : {},
|
||||
...extras.signal ? { signal: extras.signal } : {},
|
||||
})
|
||||
@@ -374,8 +378,14 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect(calls).toEqual([{ value: 'one' }, { value: 'two' }])
|
||||
const dispatches = events.filter(event => event.type === 'tool/code-dispatch')
|
||||
expect(dispatches.map(event => event.data)).toEqual([
|
||||
{ parentCallId: 'call-1', subCallId: 'call-1:code:1', name: 'echo', arguments: { value: 'one' }, isError: false, resultSummary: 'echo:one' },
|
||||
{ parentCallId: 'call-1', subCallId: 'call-1:code:2', name: 'echo', arguments: { value: 'two' }, isError: false, resultSummary: 'echo:two' },
|
||||
{
|
||||
parentCallId: 'call-1', subCallId: 'call-1:code:1', name: 'echo',
|
||||
arguments: { value: 'one' }, isError: false, content: [{ type: 'text', text: 'echo:one' }],
|
||||
},
|
||||
{
|
||||
parentCallId: 'call-1', subCallId: 'call-1:code:2', name: 'echo',
|
||||
arguments: { value: 'two' }, isError: false, content: [{ type: 'text', text: 'echo:two' }],
|
||||
},
|
||||
])
|
||||
expect(result.meta).toBeUndefined()
|
||||
})
|
||||
@@ -693,19 +703,26 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect((result.content[0] as { text: string }).text).toContain('requires a code runtime')
|
||||
})
|
||||
|
||||
it('presents the program as the execute-card title', async () => {
|
||||
it('presents the model-authored description as the execute-card title over the program input', async () => {
|
||||
const { ctx } = await setup({ mode: 'code' })
|
||||
const tool = ctx.tools.get(RUN_CODE_NAME)!
|
||||
// The program is the title, mirroring how command tools label their cards
|
||||
// with the command while retaining the same value in the expanded input.
|
||||
expect(tool.presentCall?.({ code: 'return 1' })).toEqual({
|
||||
// The description labels the card (the bash description precedent); the
|
||||
// program itself remains the expanded raw input.
|
||||
expect(tool.presentCall?.({ code: 'return 1', description: 'Return the constant one' })).toEqual({
|
||||
card: 'generic',
|
||||
title: 'return 1',
|
||||
title: 'Return the constant one',
|
||||
kind: 'execute',
|
||||
rawInput: 'return 1',
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects a whitespace-only description with a structured isError', async () => {
|
||||
const { ctx } = await setup({ mode: 'code' })
|
||||
const result = await runCode(ctx, 'return 1', { description: ' ' })
|
||||
expect(result.isError).toBe(true)
|
||||
expect((result.content[0] as { text: string }).text).toContain('invalid description')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['logs only', { logs: ['printed'] }, 'printed'],
|
||||
['result only', { logs: [], value: 'returned' }, 'returned'],
|
||||
@@ -759,7 +776,7 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect('presentResult' in tool).toBe(false)
|
||||
})
|
||||
|
||||
it('renders non-text sub-result blocks as placeholders and truncates long event summaries', async () => {
|
||||
it('logs the complete sub-result content verbatim, non-text blocks and long text included', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
const { agent, events } = fakeAgent()
|
||||
const long = 'x'.repeat(300)
|
||||
@@ -786,58 +803,10 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect(result.isError).toBe(false)
|
||||
expect((result.content[0] as { text: string }).text).toBe('mixed-value')
|
||||
const dispatch = events.find(event => event.type === 'tool/code-dispatch')?.data as SessionEventMap['tool/code-dispatch']
|
||||
expect(dispatch.resultSummary.length).toBe(201)
|
||||
expect(dispatch.resultSummary.endsWith('…')).toBe(true)
|
||||
})
|
||||
|
||||
it('normalizes the session workspace root before bounding durable result summaries', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'workspace_path',
|
||||
description: 'Return a path beneath the session workspace.',
|
||||
parameters: {},
|
||||
output: {
|
||||
schema: { type: 'string' },
|
||||
render: (_args, value) => [{ type: 'text', text: value }],
|
||||
},
|
||||
execute(_args, exec) {
|
||||
const cwd = exec.agent?.session.header.cwd ?? ''
|
||||
return Promise.resolve(`<path>${cwd}/nested/task.txt</path>\n${'x'.repeat(240)}`)
|
||||
},
|
||||
}))
|
||||
runtime.behavior = async request => ({
|
||||
logs: [],
|
||||
value: await request.bindings[0]!.functions.workspace_path!({}),
|
||||
})
|
||||
|
||||
const short = fakeAgent({ cwd: '/tmp/workspace' })
|
||||
const long = fakeAgent({ cwd: `/tmp/${'long-segment/'.repeat(30)}workspace` })
|
||||
const shortResult = await runCode(ctx, 'program', { agent: short.agent })
|
||||
const longResult = await runCode(ctx, 'program', { agent: long.agent })
|
||||
const shortDispatch = short.events[0]!.data as SessionEventMap['tool/code-dispatch']
|
||||
const longDispatch = long.events[0]!.data as SessionEventMap['tool/code-dispatch']
|
||||
|
||||
expect(shortResult.content).not.toEqual(longResult.content)
|
||||
expect(shortDispatch.resultSummary).toBe(longDispatch.resultSummary)
|
||||
expect(shortDispatch.resultSummary).toHaveLength(201)
|
||||
expect(shortDispatch.resultSummary).toMatch(/^<path>\.\/nested\/task\.txt<\/path>\n.+…$/)
|
||||
})
|
||||
|
||||
it('leaves result summaries unchanged when a session cwd is absent or is the filesystem root', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
registerEcho(ctx)
|
||||
runtime.behavior = async request => ({
|
||||
logs: [],
|
||||
value: await request.bindings[0]!.functions.echo!({ value: '/workspace/value' }),
|
||||
})
|
||||
|
||||
const absent = fakeAgent({})
|
||||
const root = fakeAgent({ cwd: '/' })
|
||||
await runCode(ctx, 'program', { agent: absent.agent })
|
||||
await runCode(ctx, 'program', { agent: root.agent })
|
||||
|
||||
expect((absent.events[0]!.data as SessionEventMap['tool/code-dispatch']).resultSummary).toBe('echo:/workspace/value')
|
||||
expect((root.events[0]!.data as SessionEventMap['tool/code-dispatch']).resultSummary).toBe('echo:/workspace/value')
|
||||
expect(dispatch.content).toEqual([
|
||||
{ type: 'text', text: long },
|
||||
{ type: 'reasoning', text: 'hidden' },
|
||||
])
|
||||
})
|
||||
|
||||
it('rejects undefined, getter-throwing, exotic, and unrepresentable binding arguments before dispatch', async () => {
|
||||
@@ -1067,7 +1036,7 @@ describe('the run_code dispatch bridge', () => {
|
||||
name: 'echo',
|
||||
arguments: { value: 'x' },
|
||||
isError: false,
|
||||
resultSummary: 'echo:x',
|
||||
content: [{ type: 'text', text: 'echo:x' }],
|
||||
})
|
||||
const derived = session.deriveMessages()
|
||||
expect(derived).toHaveLength(1)
|
||||
|
||||
@@ -752,7 +752,7 @@ describe('exit_plan_mode', () => {
|
||||
const result = await ctx.tools.execute({
|
||||
callId: CallId(`call-exit-${++callCounter}`),
|
||||
name: RUN_CODE_NAME,
|
||||
arguments: { code: `return await tools.${EXIT_PLAN_MODE}({ plan: ${JSON.stringify(plan)} })` },
|
||||
arguments: { code: `return await tools.${EXIT_PLAN_MODE}({ plan: ${JSON.stringify(plan)} })`, description: 'Submit the plan for review' },
|
||||
signal: new AbortController().signal,
|
||||
agent,
|
||||
})
|
||||
|
||||
@@ -204,6 +204,7 @@ describe('outer Code Mode failure capture', () => {
|
||||
name: 'run_code',
|
||||
arguments: {
|
||||
code: 'console.log("HEAD-" + "x".repeat(300)); console.log("TAIL-" + "y".repeat(300)); return "unreachable";',
|
||||
description: 'Print oversized head and tail lines',
|
||||
},
|
||||
agent: agent as never,
|
||||
})
|
||||
|
||||
@@ -439,7 +439,7 @@ describe('in-process structured output', () => {
|
||||
|
||||
it('keeps pure Code Mode at one wire tool and exposes structured capture through the SDK only', async () => {
|
||||
const { ctx, parent, adapter } = await setup([
|
||||
toolCallResponse('c1', RUN_CODE_NAME, { code: 'return await tools.structured_output({ answer: 12 })' }),
|
||||
toolCallResponse('c1', RUN_CODE_NAME, { code: 'return await tools.structured_output({ answer: 12 })', description: 'Capture the structured answer' }),
|
||||
], {
|
||||
toolMode: 'code',
|
||||
codeRun: async (request) => {
|
||||
@@ -465,7 +465,7 @@ describe('in-process structured output', () => {
|
||||
|
||||
it('discards a nested capture when the enclosing run_code execution fails', async () => {
|
||||
const { ctx, parent, adapter } = await setup([
|
||||
toolCallResponse('c1', RUN_CODE_NAME, { code: 'await tools.structured_output({ answer: 12 }); throw new Error("boom")' }),
|
||||
toolCallResponse('c1', RUN_CODE_NAME, { code: 'await tools.structured_output({ answer: 12 }); throw new Error("boom")', description: 'Capture then fail the program' }),
|
||||
textResponse('outer code failed'),
|
||||
], {
|
||||
toolMode: 'code',
|
||||
@@ -494,7 +494,7 @@ describe('in-process structured output', () => {
|
||||
|
||||
it('discards a nested capture when post-policy blocks the enclosing run_code result', async () => {
|
||||
const { ctx, parent, adapter } = await setup([
|
||||
toolCallResponse('c1', RUN_CODE_NAME, { code: 'return await tools.structured_output({ answer: 12 })' }),
|
||||
toolCallResponse('c1', RUN_CODE_NAME, { code: 'return await tools.structured_output({ answer: 12 })', description: 'Capture the structured answer' }),
|
||||
textResponse('outer code was blocked'),
|
||||
], {
|
||||
toolMode: 'code',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
terminal 96x36 buffer=normal length=36 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "DSH snapshot"
|
||||
cursor hidden column=1 viewportRow=13 bufferRow=13
|
||||
cursor hidden column=1 viewportRow=12 bufferRow=12
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
@@ -13,30 +13,27 @@ buffer
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=yellow
|
||||
5| "▌ ◌ const first = await tools.bash({ command: 'echo CODE_ONE' }) "
|
||||
5| "▌ ◌ Echo two markers and combine them "
|
||||
style 0-0 fg=yellow
|
||||
style 2-2 fg=yellow bold
|
||||
style 3-95 bold
|
||||
6| "▌ const second = await tools.bas "
|
||||
style 3-36 bold
|
||||
6| "▌ const first = await tools.bash({ command: 'echo CODE_ONE' }) "
|
||||
style 0-0 fg=yellow
|
||||
style 2-31 bold
|
||||
7| "▌ const first = await tools.bash({ command: 'echo CODE_ONE' }) "
|
||||
7| "▌ const second = await tools.bash({ command: 'echo CODE_TWO' }) "
|
||||
style 0-0 fg=yellow
|
||||
8| "▌ const second = await tools.bash({ command: 'echo CODE_TWO' }) "
|
||||
8| "▌ console.log(first, second) "
|
||||
style 0-0 fg=yellow
|
||||
9| "▌ console.log(first, second) "
|
||||
9| "▌ return `${first}+${second}` "
|
||||
style 0-0 fg=yellow
|
||||
10| "▌ return `${first}+${second}` "
|
||||
10| "▌ "
|
||||
style 0-0 fg=yellow
|
||||
11| "▌ "
|
||||
style 0-0 fg=yellow
|
||||
12| "────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
11| "────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-95 dim
|
||||
13| " "
|
||||
12| " "
|
||||
style 1-1 inverse
|
||||
14| "────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
13| "────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-95 dim
|
||||
15| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
14| "deepseek-v4-flash /workspace/project ↑0 ↓0 0% context tools:collapsed"
|
||||
style 0-43 dim
|
||||
style 69-95 dim
|
||||
16-35| <blank>
|
||||
15-35| <blank>
|
||||
|
||||
@@ -364,6 +364,7 @@ describe('TUI terminal-state snapshots', () => {
|
||||
name: 'run_code',
|
||||
arguments: {
|
||||
code: "const first = await tools.bash({ command: 'echo CODE_ONE' })\nconst second = await tools.bash({ command: 'echo CODE_TWO' })\nconsole.log(first, second)\nreturn `${first}+${second}`",
|
||||
description: 'Echo two markers and combine them',
|
||||
},
|
||||
}
|
||||
await renderAfter(harness, () => { appendToolCalls(harness.session, [call]) })
|
||||
|
||||
Reference in New Issue
Block a user