fix(tools): preserve canonical output boundaries
This commit is contained in:
@@ -58,7 +58,7 @@ Durable values need one accepted representation, not a check followed by a secon
|
||||
|
||||
`context/message` renders its `content` verbatim as a user-role message, and may attach JSON `meta` for replayable plugin state; metadata remains durable but is excluded from `deriveMessages()`.
|
||||
|
||||
`tool/result` persists the model-facing content, canonical failure detail, and optional presentation metadata. A tool's successful canonical `value` is deliberately execution-local and never enters the session event, so replay reconstructs the Native/model presentation but cannot recover intermediate programmatic values. This does not change `SESSION_FORMAT_VERSION`: the persisted projection remains authoritative.
|
||||
`tool/result` persists the model-facing content, optional internal failure identity, and optional presentation metadata. A tool's successful canonical `value` and human-readable canonical failure message remain execution-local; rendered error content is the replay-authoritative message. This preserves the existing event shape and does not change `SESSION_FORMAT_VERSION`.
|
||||
|
||||
### Session event vocabulary (`types.ts`)
|
||||
|
||||
|
||||
@@ -92,10 +92,7 @@ export function interruptedTurnClosers(events: readonly SessionEvent[]): Session
|
||||
callId,
|
||||
content: [{ type: 'text', text: 'Tool call interrupted by a crash; no result was recorded.' }],
|
||||
isError: true,
|
||||
error: {
|
||||
message: 'Tool call interrupted by a crash; no result was recorded.',
|
||||
info: { name: 'InterruptedError', code: 'interrupted' },
|
||||
},
|
||||
error: { name: 'InterruptedError', code: 'interrupted' },
|
||||
},
|
||||
surfaceOp: 'append',
|
||||
...callSeq !== undefined ? { sourceEventSeqs: [callSeq] } : {},
|
||||
|
||||
@@ -243,12 +243,13 @@ export interface SessionEventMap {
|
||||
*/
|
||||
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
|
||||
/**
|
||||
* A completed tool call's model-facing result, canonical failure detail, and
|
||||
* optional tool-private `meta` presentation payload. `meta` is opaque to the
|
||||
* core (the producing tool owns its shape and reads it back in `presentResult`)
|
||||
* but MUST be JSON-serializable: `Session.append` runtime-validates all event
|
||||
* data with `isJsonValue`, so a non-serializable `meta` is rejected at the
|
||||
* source, and the durable log reproduces the identical card on replay. Absent
|
||||
* A completed tool call's model-facing result, optional internal failure
|
||||
* identity, and optional tool-private `meta` presentation payload. `meta` is
|
||||
* opaque to the core (the producing tool owns its shape and reads it back in
|
||||
* `presentResult`) but MUST be JSON-serializable: `Session.append`
|
||||
* runtime-validates all event data with `isJsonValue`, so a non-serializable
|
||||
* `meta` is rejected at the source, and the durable log reproduces the
|
||||
* identical card on replay. Absent
|
||||
* unless the tool attaches one (e.g. `dsh-tool-fs` carries its result-time
|
||||
* contextual diff here).
|
||||
*/
|
||||
@@ -258,7 +259,7 @@ export interface SessionEventMap {
|
||||
callId: CallId
|
||||
content: ContentBlock[]
|
||||
isError: boolean
|
||||
error?: { message: string; info?: { name: string; code: string } }
|
||||
error?: { name: string; code: string }
|
||||
meta?: JsonValue
|
||||
}
|
||||
/** Steering content injected between steps of a running turn. */
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('interruptedTurnClosers', () => {
|
||||
expect(closers.map(e => e.seq)).toEqual([3, 4, 5])
|
||||
const result = closers[0]!
|
||||
expect(result.type === 'tool/result' && result.data).toMatchObject({
|
||||
turn: 2, step: 1, callId: CallId('call-1'), isError: true, error: { info: { code: 'interrupted' } },
|
||||
turn: 2, step: 1, callId: CallId('call-1'), isError: true, error: { code: 'interrupted' },
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user