feat(agent-loop): surface max-tokens as a distinct turn-end reason
Add a `max-tokens` variant to `TurnEndReasonMap` and carry the model finish reason up from `runStep` to `runTurn`, applying the rule "any max-tokens step in the turn surfaces as max-tokens" (disposed/aborted/ error still take precedence). This lets consumers distinguish a clean stop from a truncated one — the contract RFC 010's ACP bridge maps to the `max_tokens` stop reason. Also add an AGENTS.md rule: write an ADR when (and only when) a PR makes a durable, contested, surprising decision.
This commit is contained in:
@@ -26,6 +26,19 @@ describe('Session', () => {
|
||||
expect(messages[2]!.content[0]).toMatchObject({ type: 'tool-result', toolCallId: CallId('c1') })
|
||||
})
|
||||
|
||||
it('accepts and round-trips a max-tokens turn/end reason', () => {
|
||||
// The max-tokens TurnEndReason variant carries no extra data, so it must
|
||||
// append and persist like any other reason (JSON-serializable, no fields).
|
||||
const session = new Session(SessionId('s1'))
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'max-tokens' } })
|
||||
|
||||
const turnEnd = session.events.findLast(e => e.type === 'turn/end')!
|
||||
expect(turnEnd.data.reason).toEqual({ kind: 'max-tokens' })
|
||||
// survives a structuredClone (the persistence-serialization boundary)
|
||||
expect(structuredClone(turnEnd.data.reason)).toEqual({ kind: 'max-tokens' })
|
||||
})
|
||||
|
||||
it('renders context and steering messages as tagged synthetic user content', () => {
|
||||
const session = new Session(SessionId('s2'))
|
||||
session.append('context/message', {
|
||||
|
||||
Reference in New Issue
Block a user