@windmill-labs/chat-sdk 0.0.0-stage → 1.830.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ import type { ChatTransport, UIMessage } from 'ai';
2
+ import { type WindmillChatApiOptions } from './api';
3
+ import type { ChatMessage, Conversation } from './types';
4
+ export interface WindmillChatTransportOptions extends WindmillChatApiOptions {
5
+ /** Path of a deployed flow with chat mode enabled, e.g. `f/support/assistant`. */
6
+ flowPath: string;
7
+ /** Extra flow inputs sent with every message; `sendMessage(msg, { body })` adds per-message ones. */
8
+ inputs?: Record<string, unknown>;
9
+ }
10
+ export interface WindmillChatTransport<UI_MESSAGE extends UIMessage = UIMessage> extends ChatTransport<UI_MESSAGE> {
11
+ /** The Windmill conversation id behind an AI SDK chat id (a UUID chat id is used as is). */
12
+ conversationId(chatId: string): string;
13
+ /** Server history of a chat as `UIMessage`s, oldest first, for `useChat({ messages })`. Needs `flow_conversations:read`. */
14
+ loadMessages(chatId: string, options?: {
15
+ page?: number;
16
+ perPage?: number;
17
+ }): Promise<UI_MESSAGE[]>;
18
+ /** The user's conversations for this flow, most recent first. */
19
+ listConversations(options?: {
20
+ page?: number;
21
+ perPage?: number;
22
+ }): Promise<Conversation[]>;
23
+ deleteConversation(chatId: string): Promise<void>;
24
+ }
25
+ /**
26
+ * A Vercel AI SDK `ChatTransport` over a chat-mode flow: `useChat({ transport })`
27
+ * (and AI Elements, which builds on it) then work against Windmill unchanged.
28
+ * The chat id is the conversation, so a UUID id lines up with server history.
29
+ */
30
+ export declare function createWindmillChatTransport<UI_MESSAGE extends UIMessage = UIMessage>(options: WindmillChatTransportOptions): WindmillChatTransport<UI_MESSAGE>;
31
+ /**
32
+ * `ChatMessage`s (Windmill's role-per-row model) as `UIMessage`s: an assistant
33
+ * turn becomes one message whose parts carry its text, reasoning and tool calls. A
34
+ * user message's attachments ride in `metadata.attachments`, as references for
35
+ * `WindmillChatApi.attachmentUrl`: a `file` part would need a URL the browser can
36
+ * load unauthenticated.
37
+ */
38
+ export declare function toUIMessages(messages: ChatMessage[]): UIMessage[];
package/dist/ai-sdk.js ADDED
@@ -0,0 +1,342 @@
1
+ import { C as _assertClassBrand, S as _classPrivateFieldSet2, T as _classPrivateMethodInitSpec, _ as WindmillApiError, a as errorResultMessage, c as isErrorResult, d as randomId, o as extractChatAnswer, r as conversationIdFor, s as isAbortError, t as followJob, u as parseJsonOr, v as WindmillChatApi, w as _classPrivateFieldInitSpec, x as _classPrivateFieldGet2 } from "./follow-DXfmfPfr.js";
2
+ //#region src/ai-sdk.ts
3
+ /**
4
+ * A Vercel AI SDK `ChatTransport` over a chat-mode flow: `useChat({ transport })`
5
+ * (and AI Elements, which builds on it) then work against Windmill unchanged.
6
+ * The chat id is the conversation, so a UUID id lines up with server history.
7
+ */
8
+ function createWindmillChatTransport(options) {
9
+ const api = new WindmillChatApi(options);
10
+ const jobs = /* @__PURE__ */ new Map();
11
+ return {
12
+ conversationId: conversationIdFor,
13
+ async sendMessages({ chatId, messages, abortSignal, body }) {
14
+ const last = messages[messages.length - 1];
15
+ if (!last || last.role !== "user") throw new Error("windmill-chat: the last message must be a user message");
16
+ if (last.parts.some((p) => p.type === "file")) throw new Error("windmill-chat: attachments are not supported; upload the file yourself and pass its reference through `body`");
17
+ const text = last.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
18
+ const memoryId = conversationIdFor(chatId);
19
+ const entry = {
20
+ jobId: await api.runFlow(options.flowPath, {
21
+ ...options.inputs,
22
+ ...body,
23
+ user_message: text
24
+ }, {
25
+ memoryId,
26
+ signal: abortSignal
27
+ }),
28
+ done: false
29
+ };
30
+ jobs.set(chatId, entry);
31
+ return chunkStream(api, entry, abortSignal);
32
+ },
33
+ async reconnectToStream({ chatId, abortSignal }) {
34
+ const entry = jobs.get(chatId);
35
+ if (!entry || entry.done) return null;
36
+ return chunkStream(api, entry, abortSignal);
37
+ },
38
+ async loadMessages(chatId, pagination) {
39
+ return toUIMessages((await api.listMessages(conversationIdFor(chatId), pagination).catch((e) => {
40
+ if (e instanceof WindmillApiError && e.status === 404) return [];
41
+ throw e;
42
+ })).map((row) => ({
43
+ id: row.id,
44
+ serverId: row.id,
45
+ role: row.message_type,
46
+ content: row.content,
47
+ success: row.success ?? true,
48
+ createdAt: row.created_at,
49
+ jobId: row.job_id ?? void 0,
50
+ stepName: row.step_name ?? void 0,
51
+ pending: false,
52
+ seq: row.created_seq,
53
+ reasoning: row.reasoning ?? void 0,
54
+ attachments: row.attachments ?? void 0,
55
+ tool: toolFromRow(row)
56
+ })));
57
+ },
58
+ async listConversations(pagination) {
59
+ return (await api.listConversations(options.flowPath, pagination)).map((row) => ({
60
+ id: row.id,
61
+ title: row.title ?? void 0,
62
+ createdAt: row.created_at,
63
+ updatedAt: row.updated_at
64
+ }));
65
+ },
66
+ async deleteConversation(chatId) {
67
+ await api.deleteConversation(conversationIdFor(chatId));
68
+ jobs.delete(chatId);
69
+ }
70
+ };
71
+ }
72
+ /** The call a stored tool row carries: its tool, named by the sentence the worker words
73
+ * every tool row from, and the model's arguments and what it got back — for a failed
74
+ * tool, the result is what it failed with. */
75
+ function toolFromRow(row) {
76
+ if (row.message_type !== "tool") return void 0;
77
+ const name = /^Used (.+) tool$/.exec(row.content)?.[1] ?? /^Error executing (.+)$/.exec(row.content)?.[1];
78
+ if (!name) return void 0;
79
+ return {
80
+ name,
81
+ status: row.success ?? true ? "success" : "error",
82
+ arguments: row.tool_arguments ?? void 0,
83
+ result: row.tool_result ?? void 0
84
+ };
85
+ }
86
+ /** Streams a job's answer as AI SDK chunks; resumes from `entry.offset` when the job is already running. */
87
+ function chunkStream(api, entry, signal) {
88
+ return new ReadableStream({ async start(controller) {
89
+ const parts = new PartWriter((chunk) => controller.enqueue(chunk));
90
+ parts.emit({ type: "start" });
91
+ try {
92
+ let failure;
93
+ for await (const event of followJob(api, entry.jobId, {
94
+ signal,
95
+ streamOffset: entry.offset,
96
+ onOffset: (offset) => {
97
+ entry.offset = offset;
98
+ }
99
+ })) {
100
+ if (event.type === "stream") {
101
+ for (const e of event.events) parts.apply(e);
102
+ continue;
103
+ }
104
+ const cut = parts.openText ?? "";
105
+ parts.closeOpen();
106
+ failure = await failureText(api, entry.jobId, event.result, signal);
107
+ if (failure === void 0 && event.streamLost) {
108
+ const answer = extractChatAnswer(event.result);
109
+ if (answer !== void 0 && answer !== cut) {
110
+ parts.text(answer.startsWith(cut) ? answer.slice(cut.length) : answer);
111
+ parts.closeOpen();
112
+ }
113
+ } else if (failure === void 0 && !parts.streamedText) {
114
+ const answer = extractChatAnswer(event.result);
115
+ if (answer !== void 0) {
116
+ parts.text(answer);
117
+ parts.closeOpen();
118
+ }
119
+ }
120
+ }
121
+ entry.done = true;
122
+ parts.emit(failure === void 0 ? { type: "finish" } : {
123
+ type: "error",
124
+ errorText: failure
125
+ });
126
+ } catch (e) {
127
+ if (!isAbortError(e)) {
128
+ parts.closeOpen();
129
+ parts.emit({
130
+ type: "error",
131
+ errorText: e instanceof Error ? e.message : String(e)
132
+ });
133
+ }
134
+ } finally {
135
+ controller.close();
136
+ }
137
+ } });
138
+ }
139
+ /** A completed flow's error, when the job did fail (the envelope alone is a legitimate result). */
140
+ async function failureText(api, jobId, result, signal) {
141
+ if (!isErrorResult(result)) return void 0;
142
+ return await api.getCompletedResult(jobId, signal).then((r) => r.success === false).catch(() => true) ? errorResultMessage(result) : void 0;
143
+ }
144
+ var _textId = /* @__PURE__ */ new WeakMap();
145
+ var _reasoningId = /* @__PURE__ */ new WeakMap();
146
+ var _started = /* @__PURE__ */ new WeakMap();
147
+ var _inputSent = /* @__PURE__ */ new WeakMap();
148
+ var _PartWriter_brand = /* @__PURE__ */ new WeakSet();
149
+ /**
150
+ * Turns agent events into AI SDK chunks. Text and reasoning are open parts that a
151
+ * tool call closes (a new round starts new parts); tool calls are `dynamic-tool`
152
+ * parts, since the UI declares no tools of its own.
153
+ */
154
+ var PartWriter = class {
155
+ constructor(emit) {
156
+ this.emit = emit;
157
+ _classPrivateMethodInitSpec(this, _PartWriter_brand);
158
+ this.streamedText = false;
159
+ _classPrivateFieldInitSpec(this, _textId, void 0);
160
+ _classPrivateFieldInitSpec(this, _reasoningId, void 0);
161
+ _classPrivateFieldInitSpec(this, _started, /* @__PURE__ */ new Set());
162
+ _classPrivateFieldInitSpec(this, _inputSent, /* @__PURE__ */ new Set());
163
+ }
164
+ text(delta) {
165
+ this.streamedText = true;
166
+ if (!_classPrivateFieldGet2(_textId, this)) {
167
+ _classPrivateFieldSet2(_textId, this, randomId());
168
+ this.openText = "";
169
+ this.emit({
170
+ type: "text-start",
171
+ id: _classPrivateFieldGet2(_textId, this)
172
+ });
173
+ }
174
+ this.openText += delta;
175
+ this.emit({
176
+ type: "text-delta",
177
+ id: _classPrivateFieldGet2(_textId, this),
178
+ delta
179
+ });
180
+ }
181
+ reasoning(delta) {
182
+ if (!_classPrivateFieldGet2(_reasoningId, this)) {
183
+ _classPrivateFieldSet2(_reasoningId, this, randomId());
184
+ this.emit({
185
+ type: "reasoning-start",
186
+ id: _classPrivateFieldGet2(_reasoningId, this)
187
+ });
188
+ }
189
+ this.emit({
190
+ type: "reasoning-delta",
191
+ id: _classPrivateFieldGet2(_reasoningId, this),
192
+ delta
193
+ });
194
+ }
195
+ closeOpen() {
196
+ if (_classPrivateFieldGet2(_reasoningId, this)) {
197
+ this.emit({
198
+ type: "reasoning-end",
199
+ id: _classPrivateFieldGet2(_reasoningId, this)
200
+ });
201
+ _classPrivateFieldSet2(_reasoningId, this, void 0);
202
+ }
203
+ if (_classPrivateFieldGet2(_textId, this)) {
204
+ this.emit({
205
+ type: "text-end",
206
+ id: _classPrivateFieldGet2(_textId, this)
207
+ });
208
+ _classPrivateFieldSet2(_textId, this, void 0);
209
+ this.openText = void 0;
210
+ }
211
+ }
212
+ apply(event) {
213
+ switch (event.type) {
214
+ case "token_delta":
215
+ this.text(event.content);
216
+ break;
217
+ case "reasoning_token_delta":
218
+ this.reasoning(event.content);
219
+ break;
220
+ case "tool_call":
221
+ this.closeOpen();
222
+ _assertClassBrand(_PartWriter_brand, this, _toolStart).call(this, event.call_id, event.function_name);
223
+ break;
224
+ case "tool_call_arguments":
225
+ this.closeOpen();
226
+ _assertClassBrand(_PartWriter_brand, this, _toolStart).call(this, event.call_id, event.function_name);
227
+ _classPrivateFieldGet2(_inputSent, this).add(event.call_id);
228
+ this.emit({
229
+ type: "tool-input-available",
230
+ toolCallId: event.call_id,
231
+ toolName: event.function_name,
232
+ input: parseJsonOr(event.arguments),
233
+ dynamic: true
234
+ });
235
+ break;
236
+ case "tool_execution":
237
+ this.closeOpen();
238
+ _assertClassBrand(_PartWriter_brand, this, _toolStart).call(this, event.call_id, event.function_name);
239
+ break;
240
+ case "tool_result":
241
+ _assertClassBrand(_PartWriter_brand, this, _toolStart).call(this, event.call_id, event.function_name);
242
+ if (!_classPrivateFieldGet2(_inputSent, this).has(event.call_id)) {
243
+ _classPrivateFieldGet2(_inputSent, this).add(event.call_id);
244
+ this.emit({
245
+ type: "tool-input-available",
246
+ toolCallId: event.call_id,
247
+ toolName: event.function_name,
248
+ input: void 0,
249
+ dynamic: true
250
+ });
251
+ }
252
+ this.emit(event.success ? {
253
+ type: "tool-output-available",
254
+ toolCallId: event.call_id,
255
+ output: parseJsonOr(event.result),
256
+ dynamic: true
257
+ } : {
258
+ type: "tool-output-error",
259
+ toolCallId: event.call_id,
260
+ errorText: event.result,
261
+ dynamic: true
262
+ });
263
+ }
264
+ }
265
+ };
266
+ function _toolStart(callId, name) {
267
+ if (_classPrivateFieldGet2(_started, this).has(callId)) return;
268
+ _classPrivateFieldGet2(_started, this).add(callId);
269
+ this.emit({
270
+ type: "tool-input-start",
271
+ toolCallId: callId,
272
+ toolName: name,
273
+ dynamic: true
274
+ });
275
+ }
276
+ /**
277
+ * `ChatMessage`s (Windmill's role-per-row model) as `UIMessage`s: an assistant
278
+ * turn becomes one message whose parts carry its text, reasoning and tool calls. A
279
+ * user message's attachments ride in `metadata.attachments`, as references for
280
+ * `WindmillChatApi.attachmentUrl`: a `file` part would need a URL the browser can
281
+ * load unauthenticated.
282
+ */
283
+ function toUIMessages(messages) {
284
+ const out = [];
285
+ for (const m of messages) {
286
+ if (m.role === "user" || m.role === "system") {
287
+ out.push({
288
+ id: m.id,
289
+ role: m.role,
290
+ ...m.attachments?.length ? { metadata: { attachments: m.attachments } } : {},
291
+ parts: [{
292
+ type: "text",
293
+ text: m.content
294
+ }]
295
+ });
296
+ continue;
297
+ }
298
+ let target = out[out.length - 1];
299
+ if (!target || target.role !== "assistant") {
300
+ target = {
301
+ id: m.id,
302
+ role: "assistant",
303
+ parts: []
304
+ };
305
+ out.push(target);
306
+ }
307
+ if (m.reasoning) target.parts.push({
308
+ type: "reasoning",
309
+ text: m.reasoning,
310
+ state: "done"
311
+ });
312
+ if (m.role === "tool") {
313
+ const toolCallId = m.tool?.callId ?? m.id;
314
+ const toolName = m.tool?.name ?? "tool";
315
+ const input = parseJsonOr(m.tool?.arguments);
316
+ target.parts.push(m.success ? {
317
+ type: "dynamic-tool",
318
+ toolName,
319
+ toolCallId,
320
+ state: "output-available",
321
+ input,
322
+ output: m.tool?.result !== void 0 ? parseJsonOr(m.tool.result) : m.content
323
+ } : {
324
+ type: "dynamic-tool",
325
+ toolName,
326
+ toolCallId,
327
+ state: "output-error",
328
+ input,
329
+ errorText: m.tool?.result ?? m.content
330
+ });
331
+ continue;
332
+ }
333
+ if (m.content) target.parts.push({
334
+ type: "text",
335
+ text: m.content,
336
+ state: "done"
337
+ });
338
+ }
339
+ return out;
340
+ }
341
+ //#endregion
342
+ export { createWindmillChatTransport, toUIMessages };
package/dist/api.d.ts ADDED
@@ -0,0 +1,182 @@
1
+ import type { ChatAttachment, FetchLike, TokenSource } from './types';
2
+ export interface WindmillChatApiOptions {
3
+ baseUrl: string;
4
+ workspace: string;
5
+ /** Omit to rely on the session cookie of the Windmill origin. */
6
+ token?: TokenSource;
7
+ fetch?: FetchLike;
8
+ /** Server poll interval for a turn's stream (Enterprise; see `ChatOptions.pollDelayMs`). */
9
+ pollDelayMs?: number;
10
+ }
11
+ export declare class WindmillApiError extends Error {
12
+ readonly status: number;
13
+ /** The response body, as the server sent it. */
14
+ readonly body?: string | undefined;
15
+ constructor(message: string, status: number,
16
+ /** The response body, as the server sent it. */
17
+ body?: string | undefined);
18
+ }
19
+ /** The turn a conversation is still answering, as the server reports it. */
20
+ export interface RunningTurn {
21
+ jobId: string;
22
+ /** `created_seq` of the user message that started the turn. */
23
+ userSeq: number;
24
+ }
25
+ /**
26
+ * A message was sent to a conversation whose turn is still running. The server refuses
27
+ * it (409) so two runs never write one agent memory; `turn` is the run to follow instead.
28
+ */
29
+ export declare class TurnRunningError extends Error {
30
+ readonly turn: RunningTurn;
31
+ constructor(message: string, turn: RunningTurn);
32
+ }
33
+ /**
34
+ * The running turn named by a run's 409 body, or undefined for any other body. Exported
35
+ * for a custom `run` that calls Windmill through its own client: it rethrows the refusal
36
+ * as a `TurnRunningError` so the chat can follow the running turn.
37
+ */
38
+ export declare function turnRunningError(body: string): TurnRunningError | undefined;
39
+ export interface FlowConversation {
40
+ id: string;
41
+ workspace_id: string;
42
+ flow_path: string;
43
+ title?: string | null;
44
+ created_at: string;
45
+ updated_at: string;
46
+ created_by: string;
47
+ /** Started from the flow editor's test panel rather than a deployed run. */
48
+ is_test: boolean;
49
+ /** Set by the list: the turn this conversation is still answering. */
50
+ running_turn?: {
51
+ job_id: string;
52
+ user_seq: number;
53
+ } | null;
54
+ }
55
+ /**
56
+ * Which conversations a listing holds: the flow editor's test chats, the deployed flow's
57
+ * own (the server's default), or both.
58
+ */
59
+ export type ConversationKind = 'test' | 'deployed' | 'all';
60
+ export interface FlowConversationMessage {
61
+ id: string;
62
+ conversation_id: string;
63
+ message_type: 'user' | 'assistant' | 'system' | 'tool';
64
+ content: string;
65
+ job_id?: string | null;
66
+ created_at: string;
67
+ created_seq: number;
68
+ step_name?: string | null;
69
+ success?: boolean;
70
+ /** On a tool row, the arguments the model wrote, without the inputs a step wires in; null for a web search. */
71
+ tool_arguments?: string | null;
72
+ /** On a tool row, the text the model got back or what the call failed with; a web search's citations. */
73
+ tool_result?: string | null;
74
+ /** On an answer, the thinking that produced it; on a tool row, the thinking that led to the call. */
75
+ reasoning?: string | null;
76
+ /** The files a user message carried, as object-storage references. */
77
+ attachments?: ChatAttachment[] | null;
78
+ }
79
+ export type JobUpdateEvent = {
80
+ type: 'update';
81
+ running?: boolean;
82
+ completed?: boolean;
83
+ new_result_stream?: string;
84
+ stream_offset?: number;
85
+ only_result?: unknown;
86
+ flow_stream_job_id?: string;
87
+ } | {
88
+ type: 'error';
89
+ error: string;
90
+ } | {
91
+ type: 'notfound';
92
+ } | {
93
+ type: 'timeout';
94
+ } | {
95
+ type: 'ping';
96
+ };
97
+ export interface CompletedJobResult {
98
+ completed: boolean;
99
+ success?: boolean;
100
+ result?: unknown;
101
+ }
102
+ /** The part of a flow job's status that names the jobs its steps ran as. */
103
+ export interface FlowJobStatus {
104
+ flow_status?: {
105
+ modules?: FlowStepStatus[] | null;
106
+ failure_module?: FlowStepStatus | null;
107
+ preprocessor_module?: FlowStepStatus | null;
108
+ } | null;
109
+ }
110
+ export interface FlowStepStatus {
111
+ job?: string | null;
112
+ flow_jobs?: string[] | null;
113
+ /** An agent step's rounds; a tool call ran as a job of its own, which its row is persisted under. */
114
+ agent_actions?: {
115
+ type?: string;
116
+ job_id?: string | null;
117
+ }[] | null;
118
+ }
119
+ /** Thin client over the Windmill endpoints a chat-mode flow uses. */
120
+ export declare class WindmillChatApi {
121
+ #private;
122
+ constructor(options: WindmillChatApiOptions);
123
+ /**
124
+ * Starts a turn: runs the flow with `memory_id` set to the conversation id. Returns the
125
+ * job id. Throws `TurnRunningError` when the conversation is still answering.
126
+ */
127
+ runFlow(flowPath: string, args: Record<string, unknown>, options: {
128
+ memoryId: string;
129
+ signal?: AbortSignal;
130
+ }): Promise<string>;
131
+ /**
132
+ * One server-sent-events connection to a job's updates. The server closes it after
133
+ * `TIMEOUT_SSE_STREAM` (a `timeout` event); resume by calling again with the last
134
+ * `stream_offset`, never by re-running the flow.
135
+ */
136
+ streamJob(jobId: string, options?: {
137
+ streamOffset?: number;
138
+ signal?: AbortSignal;
139
+ }): AsyncGenerator<JobUpdateEvent>;
140
+ getCompletedResult(jobId: string, signal?: AbortSignal): Promise<CompletedJobResult>;
141
+ /** A flow job with its status: the step job ids are what persisted messages carry as `job_id`. */
142
+ getFlowJob(jobId: string, signal?: AbortSignal): Promise<FlowJobStatus>;
143
+ /**
144
+ * Where a message's attachment downloads from. The endpoint authenticates like every other
145
+ * request: a consumer holding a token must fetch it with that token, not put the URL in an
146
+ * `img src`, which would send only the Windmill session cookie.
147
+ */
148
+ attachmentUrl(attachment: ChatAttachment): string;
149
+ cancelJob(jobId: string, reason?: string): Promise<void>;
150
+ listConversations(flowPath: string, options?: {
151
+ page?: number;
152
+ perPage?: number;
153
+ kind?: ConversationKind;
154
+ signal?: AbortSignal;
155
+ }): Promise<FlowConversation[]>;
156
+ /** Sets a conversation's title. Its place in the list is kept: only a turn moves one. */
157
+ renameConversation(conversationId: string, title: string): Promise<void>;
158
+ /**
159
+ * Without `afterSeq`: one page counted from the newest message, returned oldest first.
160
+ * With `afterSeq`: the messages created after that cursor, oldest first.
161
+ */
162
+ listMessages(conversationId: string, options?: {
163
+ page?: number;
164
+ perPage?: number;
165
+ afterSeq?: number;
166
+ signal?: AbortSignal;
167
+ }): Promise<FlowConversationMessage[]>;
168
+ /**
169
+ * Puts bytes in the workspace's object storage under `fileKey` and returns the key they were
170
+ * stored under (the server may rewrite it). Needs the workspace to have object storage set.
171
+ */
172
+ uploadFile(fileKey: string, body: Blob, options?: {
173
+ contentType?: string;
174
+ signal?: AbortSignal;
175
+ }): Promise<{
176
+ file_key: string;
177
+ }>;
178
+ deleteConversation(conversationId: string): Promise<void>;
179
+ }
180
+ export declare function normalizeBaseUrl(baseUrl: string): string;
181
+ /** Yields the `data` payload of each event in a `text/event-stream` body. */
182
+ export declare function readServerSentEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<string>;
@@ -0,0 +1,122 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_follow = require("./follow-Dug-Afeu.cjs");
3
+ const require_react = require("./react.cjs");
4
+ let _assistant_ui_react = require("@assistant-ui/react");
5
+ let react = require("react");
6
+ //#region src/assistant-ui.ts
7
+ /**
8
+ * An assistant-ui runtime over a chat-mode flow, for `AssistantRuntimeProvider`.
9
+ * Conversations become threads, so the thread list primitives switch, create and
10
+ * delete Windmill conversations.
11
+ */
12
+ function useWindmillRuntime(options) {
13
+ const chat = require_react.useWindmillChat(options);
14
+ const turns = (0, react.useMemo)(() => groupTurns(chat.messages), [chat.messages]);
15
+ const withThreadList = options.threadList !== false;
16
+ (0, react.useEffect)(() => {
17
+ if (withThreadList) chat.loadConversations().catch(() => {});
18
+ }, [chat.chat, withThreadList]);
19
+ const adapter = {
20
+ messages: turns,
21
+ isRunning: chat.status === "submitted" || chat.status === "streaming",
22
+ convertMessage: toThreadMessage,
23
+ onNew: async (message) => {
24
+ if (message.role !== "user") return;
25
+ await chat.sendMessage(appendedText(message));
26
+ },
27
+ onCancel: async () => {
28
+ await chat.stop();
29
+ },
30
+ adapters: withThreadList ? { threadList: {
31
+ threadId: chat.conversationId,
32
+ threads: chat.conversations.map((c) => ({
33
+ status: "regular",
34
+ id: c.id,
35
+ title: c.title
36
+ })),
37
+ onSwitchToNewThread: () => chat.newConversation(),
38
+ onSwitchToThread: (id) => chat.selectConversation(id),
39
+ onDelete: (id) => chat.deleteConversation(id),
40
+ onRename: (id, title) => chat.renameConversation(id, title)
41
+ } } : void 0
42
+ };
43
+ return (0, _assistant_ui_react.useExternalStoreRuntime)(adapter);
44
+ }
45
+ function appendedText(message) {
46
+ return message.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
47
+ }
48
+ /** Folds the role-per-row message list into turns: one entry per user message, one per answer. */
49
+ function groupTurns(messages) {
50
+ const turns = [];
51
+ for (const m of messages) {
52
+ const last = turns[turns.length - 1];
53
+ if (m.role === "user" || m.role === "system" || !last || last.role !== "assistant") turns.push({
54
+ id: m.id,
55
+ role: m.role === "tool" ? "assistant" : m.role,
56
+ messages: [m]
57
+ });
58
+ else last.messages.push(m);
59
+ }
60
+ return turns;
61
+ }
62
+ /** A turn as assistant-ui content parts; the status reflects streaming and a failed flow. */
63
+ function toThreadMessage(turn) {
64
+ const first = turn.messages[0];
65
+ const createdAt = new Date(first.createdAt);
66
+ if (turn.role !== "assistant") return {
67
+ id: turn.id,
68
+ role: turn.role,
69
+ createdAt,
70
+ content: [{
71
+ type: "text",
72
+ text: first.content
73
+ }]
74
+ };
75
+ const content = [];
76
+ for (const m of turn.messages) {
77
+ if (m.reasoning) content.push({
78
+ type: "reasoning",
79
+ text: m.reasoning
80
+ });
81
+ if (m.role === "tool") {
82
+ const args = require_follow.parseJsonOr(m.tool?.arguments);
83
+ content.push({
84
+ type: "tool-call",
85
+ toolCallId: m.tool?.callId ?? m.id,
86
+ toolName: m.tool?.name ?? "tool",
87
+ args: isJsonObject(args) ? args : args === void 0 ? {} : { input: args },
88
+ argsText: m.tool?.arguments ?? "",
89
+ result: m.tool?.status === "running" ? void 0 : m.tool?.result !== void 0 ? require_follow.parseJsonOr(m.tool.result) : m.content,
90
+ isError: m.tool?.status === "error"
91
+ });
92
+ continue;
93
+ }
94
+ if (m.content) content.push({
95
+ type: "text",
96
+ text: m.content
97
+ });
98
+ }
99
+ const last = turn.messages[turn.messages.length - 1];
100
+ const status = turn.messages.some((m) => m.pending) ? { type: "running" } : last.role === "assistant" && !last.success ? {
101
+ type: "incomplete",
102
+ reason: "error",
103
+ error: last.content
104
+ } : {
105
+ type: "complete",
106
+ reason: "stop"
107
+ };
108
+ return {
109
+ id: turn.id,
110
+ role: "assistant",
111
+ createdAt,
112
+ content,
113
+ status
114
+ };
115
+ }
116
+ function isJsonObject(value) {
117
+ return typeof value === "object" && value !== null && !Array.isArray(value);
118
+ }
119
+ //#endregion
120
+ exports.groupTurns = groupTurns;
121
+ exports.toThreadMessage = toThreadMessage;
122
+ exports.useWindmillRuntime = useWindmillRuntime;