fix: complete immutable message migration

This commit is contained in:
_Kerman
2026-07-28 14:15:23 +08:00
parent f5ec71f5b1
commit 350c296cff
27 changed files with 231 additions and 60 deletions

View File

@@ -744,9 +744,24 @@ describe('agent loop', () => {
expect(steps).toBe(2)
expect(adapter.requests).toHaveLength(2)
expect(adapter.requests[1]!.messages).toEqual([
{ role: 'user', content: [{ type: 'text', text: 'go' }] },
{ role: 'assistant', content: [{ type: 'text', text: 'first half' }], provenance: { provider: 'mock', model: 'mock' } },
{ role: 'user', content: [{ type: 'text', text: 'continue after truncation' }] },
{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'go' }],
source: { kind: 'user' },
},
{
id: expect.any(String) as unknown,
role: 'assistant',
content: [{ type: 'text', text: 'first half' }],
source: { kind: 'model', provider: 'mock', model: 'mock' },
},
{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'continue after truncation' }],
source: { kind: 'plugin', plugin: 'max-tokens-test' },
},
])
expect(reasons).toEqual([{ kind: 'max-tokens' }])
})
@@ -799,13 +814,26 @@ describe('agent loop', () => {
expect(executions).toBe(0)
expect(agent.session.events.some(e => e.type === 'tool/call')).toBe(false)
expect(agent.session.deriveMessages()).toEqual([{ role: 'user', content: [{ type: 'text', text: 'go' }] }])
expect(agent.session.deriveMessages()).toEqual([{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'go' }],
source: { kind: 'user' },
}])
expect(reasons).toEqual([{ kind: 'max-tokens' }])
// Empty content still needs an assistant/message to carry usage; derivation
// skips that host so it does not create a spurious assistant turn.
const assistantMessage = agent.session.events.find(e => e.type === 'assistant/message')
expect(assistantMessage?.type === 'assistant/message' && assistantMessage.data).toEqual({
turn: 1, step: 1, content: [], provenance: { provider: 'mock', model: 'mock' }, usage: { inputTokens: 10, outputTokens: 5 },
turn: 1,
step: 1,
message: {
id: expect.any(String) as unknown,
role: 'assistant',
content: [],
source: { kind: 'model', provider: 'mock', model: 'mock' },
},
usage: { inputTokens: 10, outputTokens: 5 },
})
})
@@ -839,11 +867,20 @@ describe('agent loop', () => {
expect(assistant.type === 'assistant/message' && assistant.data).toEqual({
turn: 1,
step: 1,
content: [],
provenance: { provider: 'mock', model: 'mock' },
message: {
id: expect.any(String) as unknown,
role: 'assistant',
content: [],
source: { kind: 'model', provider: 'mock', model: 'mock' },
},
})
expect(assistant.sourceEventSeqs?.length).toBeGreaterThan(0)
expect(agent.session.deriveMessages()).toEqual([{ role: 'user', content: [{ type: 'text', text: 'go' }] }])
expect(agent.session.deriveMessages()).toEqual([{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'go' }],
source: { kind: 'user' },
}])
})
it('appends an empty completion anchor for a normal stop with no usage', async () => {
@@ -864,11 +901,20 @@ describe('agent loop', () => {
expect(assistant.type === 'assistant/message' && assistant.data).toEqual({
turn: 1,
step: 1,
content: [],
provenance: { provider: 'mock', model: 'mock' },
message: {
id: expect.any(String) as unknown,
role: 'assistant',
content: [],
source: { kind: 'model', provider: 'mock', model: 'mock' },
},
})
expect(assistant.sourceEventSeqs?.length).toBe(1)
expect(agent.session.deriveMessages()).toEqual([{ role: 'user', content: [{ type: 'text', text: 'go' }] }])
expect(agent.session.deriveMessages()).toEqual([{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'go' }],
source: { kind: 'user' },
}])
})
it('keeps safe max-tokens assistant content while dropping truncated tool calls', async () => {
@@ -889,8 +935,18 @@ describe('agent loop', () => {
expect(agent.session.events.some(e => e.type === 'tool/call')).toBe(false)
expect(agent.session.deriveMessages()).toEqual([
{ role: 'user', content: [{ type: 'text', text: 'go' }] },
{ role: 'assistant', content: [{ type: 'text', text: 'partial text' }], provenance: { provider: 'mock', model: 'mock' } },
{
id: expect.any(String) as unknown,
role: 'user',
content: [{ type: 'text', text: 'go' }],
source: { kind: 'user' },
},
{
id: expect.any(String) as unknown,
role: 'assistant',
content: [{ type: 'text', text: 'partial text' }],
source: { kind: 'model', provider: 'mock', model: 'mock' },
},
])
})