fix(tools): preserve canonical output boundaries

This commit is contained in:
Tianyi Cui
2026-07-21 18:03:01 +08:00
parent 8f3aca4128
commit e1633fbc3f
33 changed files with 269 additions and 103 deletions

View File

@@ -165,7 +165,7 @@ function validateEvent(trace: SessionTrace, event: SessionEvent): SessionTraceTr
// A result needs a prior matching call in the same step. (The converse
// does NOT hold: a call may have no result — a throwing tool-execution
// pipeline step ends the turn with no tool/result, which is legal.)
const syntheticInterrupted = event.data.isError && event.data.error?.info?.code === 'interrupted'
const syntheticInterrupted = event.data.isError && event.data.error?.code === 'interrupted'
if (!trace.pendingCalls.has(event.data.callId) && !syntheticInterrupted) {
throw new InvariantError(`tool/result for ${event.data.callId} with no prior tool/call in this step`)
}

View File

@@ -217,7 +217,7 @@ describe('session-log invariants', () => {
callId: CallId('crashed'),
content: [{ type: 'text', text: 'interrupted' }],
isError: true,
error: { message: 'interrupted', info: { name: 'InterruptedError', code: 'interrupted' } },
error: { name: 'InterruptedError', code: 'interrupted' },
}, { surfaceOp: 'append' })
session.append('step/end', { turn: 1, step: 1 })
session.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } })
@@ -500,7 +500,7 @@ describe('surface contract under the invariants composition', () => {
callId: CallId('rewrite'),
content: [{ type: 'text' as const, text: 'original' }],
isError: true,
error: { message: 'exit 1', info: { name: 'ExitError', code: 'EXIT_1' } },
error: { name: 'ExitError', code: 'EXIT_1' },
meta: { presentation: { kind: 'terminal', output: 'full output' } },
futureField: { nested: ['preserve', 1] },
}
@@ -585,7 +585,7 @@ describe('surface contract under the invariants composition', () => {
['callId', { callId: CallId('forged') }],
['turn', { turn: 2 }],
['step', { step: 2 }],
['error', { error: { message: 'exit 1', info: { name: 'ExitError', code: 'DIFFERENT' } } }],
['error', { error: { name: 'ExitError', code: 'DIFFERENT' } }],
['meta', { meta: { presentation: { kind: 'generic' } } }],
['future data', { futureField: { nested: ['changed'] } }],
])('rejects a content rewrite with altered %s', async (_label, altered) => {