mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
* 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>
39 lines
2.0 KiB
TypeScript
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' })
|
|
})
|
|
})
|