mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-04 16:02:17 +00:00
* feat: store mcp tool call, result and reasoning on flow conversation rows Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep the failure reason and web search citations on tool rows Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: persist a structured answer as its own row and keep reasoning-only rows from closing a turn Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a failed Windmill tool's error on its conversation row Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: let a structured answer row claim its streamed thinking Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: scope a structured answer's claim to its own turn and document the row fields as stored Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: claim a textless assistant row only within the newest turn Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor: store the thinking that led to a tool call on the tool row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep tool calls in stream order and scope a structured answer to its turn Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: store the files a user message carried as object-storage references on its row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(chat-sdk): read a message's attachments and build their download URL Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(chat-sdk): carry a loaded user message's attachments in AI SDK metadata Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: store the model's call and what it got back on every tool row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: pass no extras in the orphaned-conversation test's message insert Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(chat-sdk): keep a stored JSON null tool result instead of the row text Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: label a tool call the turn finished without as not finished Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a structured answer's streamed call out of the did-not-finish label Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: write a turn's conversation rows in order and describe stored tool calls Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: await the image answer row like the agent loop's other rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(chat-sdk): place a row nothing streamed by its sequence, not at the end Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(chat-sdk): place only tool rows by sequence, keep a closing row last Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(flow chat): show a failed tool's error, and no Retry on a running or stopped turn A failed tool card showed the row label in place of the error the row now stores. The live tool result now reports failures, so a failed call briefly flagged a running turn as failed; a stopped turn, whose last row is the failed tool or the cancelled flow's failure, offered Retry too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
46 lines
2.3 KiB
TypeScript
46 lines
2.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import { groupTurns, toThreadMessage } from '../src/assistant-ui'
|
|
import type { ChatMessage } from '../src/types'
|
|
|
|
const base = { success: true, createdAt: '2026-01-01T00:00:00Z', pending: false }
|
|
|
|
describe('assistant-ui conversion', () => {
|
|
test('groups rows into turns and renders tool calls as content parts', () => {
|
|
const messages: ChatMessage[] = [
|
|
{ ...base, id: 'u1', role: 'user', content: 'hi' },
|
|
{ ...base, id: 't1', role: 'tool', content: 'Used lookup tool', tool: { callId: 'c1', name: 'lookup', status: 'success', arguments: '{"q":1}', result: '42' } },
|
|
{ ...base, id: 'a1', role: 'assistant', content: 'The answer is 42', reasoning: 'hmm' },
|
|
{ ...base, id: 'u2', role: 'user', content: 'again' },
|
|
{ ...base, id: 'a2', role: 'assistant', content: 'partial', pending: true }
|
|
]
|
|
const turns = groupTurns(messages)
|
|
expect(turns.map((t) => [t.id, t.role, t.messages.length])).toEqual([
|
|
['u1', 'user', 1],
|
|
['t1', 'assistant', 2],
|
|
['u2', 'user', 1],
|
|
['a2', 'assistant', 1]
|
|
])
|
|
const answer = toThreadMessage(turns[1])
|
|
expect(answer).toMatchObject({ id: 't1', role: 'assistant', status: { type: 'complete', reason: 'stop' } })
|
|
expect(answer.content).toEqual([
|
|
{ type: 'tool-call', toolCallId: 'c1', toolName: 'lookup', args: { q: 1 }, argsText: '{"q":1}', result: 42, isError: false },
|
|
{ type: 'reasoning', text: 'hmm' },
|
|
{ type: 'text', text: 'The answer is 42' }
|
|
])
|
|
expect(toThreadMessage(turns[3]).status).toEqual({ type: 'running' })
|
|
expect(toThreadMessage(turns[0])).toMatchObject({ role: 'user', content: [{ type: 'text', text: 'hi' }] })
|
|
})
|
|
|
|
test('keeps a stored JSON null result rather than the row text', () => {
|
|
const turn = groupTurns([
|
|
{ ...base, id: 't1', role: 'tool', content: 'Used notify tool', tool: { callId: 'c1', name: 'notify', status: 'success', arguments: '{}', result: 'null' } }
|
|
])[0]
|
|
expect(toThreadMessage(turn).content).toMatchObject([{ type: 'tool-call', toolName: 'notify', result: null }])
|
|
})
|
|
|
|
test('marks a failed answer as incomplete', () => {
|
|
const [turn] = groupTurns([{ ...base, id: 'a', role: 'assistant', content: 'boom', success: false }])
|
|
expect(toThreadMessage(turn).status).toEqual({ type: 'incomplete', reason: 'error', error: 'boom' })
|
|
})
|
|
})
|