Files
windmill/chat-sdk/test/assistant-ui.test.ts
T
Ruben FiszelandClaude Fable 5.1 e8c02c04cd feat: windmill-chat sdk for chat-mode flows in external frontends and raw apps (#11117)
* feat: windmill-chat sdk for chat-mode flows in external frontends and raw apps

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018aQiZNAU8g17kWkyTryS5J

* fix: keep streamed answers until persisted, finish turns after history fallback

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018aQiZNAU8g17kWkyTryS5J

* feat: ai sdk transport and assistant-ui runtime for windmill-chat

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: finish a turn from the flow result until its answer row lands, hash chat ids without crypto.subtle

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: judge a turn answered by a persisted assistant row, wherever it was fetched

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: attribute a turn's answer to its own jobs, keep a local turn when switching conversations

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: mirror local history on every change, attribute failure-handler answers to the turn

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: new chat per token string in the React hook, idle after destroy, no reorder on view

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: recreate the hook's chat on any credential change, namespace local history per user

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: send the latest inputs from the React hook

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 22:32:45 +02:00

39 lines
2.0 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('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' })
})
})