fix: keep Code Mode result cards complete

This commit is contained in:
Tianyi Cui
2026-07-21 04:55:56 +08:00
parent c1d7b0df81
commit 7c2fb0a6fe
10 changed files with 133 additions and 67 deletions

View File

@@ -107,22 +107,9 @@ function renderValue(value: JsonValue): string {
return typeof value === 'string' ? value : JSON.stringify(value, null, 2)
}
/** The run_code result's `meta` payload (JSON-serializable; `presentResult` narrows it back). */
interface RunCodeMeta {
logs: CodeRunResult['logs']
}
/** Canonical value returned by the outer Code Mode transport. */
type RunCodeOutput = { logs: string[]; result?: JsonValue }
/** Soft-narrow a result `meta` back to {@link RunCodeMeta} (replay may carry older shapes; presentation must not throw). */
function asRunCodeMeta(meta: unknown): RunCodeMeta | undefined {
if (typeof meta !== 'object' || meta === null) return undefined
const m = meta as Record<string, unknown>
if (!Array.isArray(m.logs) || !m.logs.every(log => typeof log === 'string')) return undefined
return m as unknown as RunCodeMeta
}
/**
* Build the `run_code` {@link ToolDefinition}: one required `code` parameter,
* executed through the dispatch bridge described above. The
@@ -293,15 +280,8 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
}),
// Title omitted on the result: an update replaces only the fields it
// carries, so the pending card's program title persists through
// completion; the captured output rides as body content.
presentResult: (_args, result) => {
const meta = asRunCodeMeta(result.meta)
if (!meta) return undefined
const output = meta.logs.join('\n')
return {
card: 'generic',
...output.length > 0 ? { content: [{ type: 'text' as const, text: output }] } : {},
}
},
// completion. The durable final content already includes logs plus the
// return value, failure, or post-policy spill preview.
presentResult: (_args, result) => ({ card: 'generic', content: result.content }),
})
}

View File

@@ -663,7 +663,7 @@ 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 on both call and result (the one slot execute cards always show)', async () => {
it('presents the program as the execute-card title', async () => {
const { ctx } = await setup({ mode: 'code' })
const tool = ctx.tools.get(RUN_CODE_NAME)!
// The program IS the title, mirroring how command tools title their cards
@@ -676,24 +676,28 @@ describe('the run_code dispatch bridge', () => {
kind: 'execute',
rawInput: 'return 1',
})
const view = tool.presentResult?.({ code: 'return 1' }, {
content: [{ type: 'text', text: 'model-facing' }],
isError: false,
meta: { logs: ['printed'] },
})
})
it.each([
['logs only', 'printed', false],
['result only', 'returned', false],
['logs plus result', 'printed\nreturned', false],
['no output', '(run_code completed with no output)', false],
['failure', 'Error: code run failed (output-limit): outer output exceeded 8 bytes', true],
['spilled result', 'HEAD\n\n(Omitted 100 bytes. Full formatted result stored at: /tmp/run-code.txt.)\n\nTAIL', false],
] as const)('presents %s from the final post-policy content', async (_name, text, isError) => {
const { ctx } = await setup({ mode: 'code' })
const tool = ctx.tools.get(RUN_CODE_NAME)!
// The result omits the title — an update replaces only provided fields,
// so the pending card's program title persists through completion.
expect(view).toEqual({
card: 'generic',
content: [{ type: 'text', text: 'printed' }],
})
// No captured output → no content either; everything pending persists.
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [] } }))
.toEqual({ card: 'generic' })
// Replay with an unrecognizable meta falls back to the generic rendering.
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [{ text: 'legacy' }], dispatches: 1 } })).toBeUndefined()
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { other: true } })).toBeUndefined()
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false })).toBeUndefined()
const content = [{ type: 'text' as const, text }]
expect(tool.presentResult?.({ code: 'return 1' }, {
content,
isError,
// Stale or unrelated metadata must not replace the authoritative
// post-policy content used by the card.
meta: { logs: ['stale logs-only projection'] },
})).toEqual({ card: 'generic', content })
})
it('renders non-text sub-result blocks as placeholders and truncates long event summaries', async () => {