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>
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
/** The events an AI agent step streams, one JSON object per line of the job's result stream. */
|
|
export type AgentStreamEvent =
|
|
| { type: 'token_delta'; content: string }
|
|
| { type: 'reasoning_token_delta'; content: string }
|
|
| { type: 'tool_call'; call_id: string; function_name: string }
|
|
| { type: 'tool_call_arguments'; call_id: string; function_name: string; arguments: string }
|
|
| { type: 'tool_execution'; call_id: string; function_name: string }
|
|
| {
|
|
type: 'tool_result'
|
|
call_id: string
|
|
function_name: string
|
|
result: string
|
|
success: boolean
|
|
}
|
|
|
|
const KNOWN_TYPES = new Set([
|
|
'token_delta',
|
|
'reasoning_token_delta',
|
|
'tool_call',
|
|
'tool_call_arguments',
|
|
'tool_execution',
|
|
'tool_result'
|
|
])
|
|
|
|
/**
|
|
* Incremental parser for the `new_result_stream` chunks of a job update. A chunk is
|
|
* not guaranteed to end on a line boundary, so an incomplete last line waits for the
|
|
* next `push` (or `flush` once the job completes).
|
|
*/
|
|
export function createStreamEventParser() {
|
|
let pending = ''
|
|
return {
|
|
push(chunk: string): AgentStreamEvent[] {
|
|
pending += chunk
|
|
const lastNewline = pending.lastIndexOf('\n')
|
|
if (lastNewline === -1) return []
|
|
const complete = pending.slice(0, lastNewline)
|
|
pending = pending.slice(lastNewline + 1)
|
|
return parseStreamEvents(complete)
|
|
},
|
|
flush(): AgentStreamEvent[] {
|
|
const rest = pending
|
|
pending = ''
|
|
return parseStreamEvents(rest)
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Parses complete NDJSON lines; lines that aren't agent events are skipped. */
|
|
export function parseStreamEvents(ndjson: string): AgentStreamEvent[] {
|
|
const events: AgentStreamEvent[] = []
|
|
for (const line of ndjson.split('\n')) {
|
|
const trimmed = line.trim()
|
|
if (!trimmed) continue
|
|
try {
|
|
const parsed = JSON.parse(trimmed)
|
|
if (parsed && typeof parsed === 'object' && KNOWN_TYPES.has(parsed.type)) {
|
|
events.push(parsed as AgentStreamEvent)
|
|
}
|
|
} catch {
|
|
// not an agent event
|
|
}
|
|
}
|
|
return events
|
|
}
|