@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,22 @@
1
+ import { type AssistantRuntime, type ThreadMessageLike } from '@assistant-ui/react';
2
+ import type { ChatMessage, ChatOptions } from './types';
3
+ /** One assistant turn: the assistant and tool rows between two user messages. */
4
+ export interface WindmillTurn {
5
+ id: string;
6
+ role: 'user' | 'assistant' | 'system';
7
+ messages: ChatMessage[];
8
+ }
9
+ export interface WindmillRuntimeOptions extends ChatOptions {
10
+ /** Load the conversation list on mount so `ThreadListPrimitive` has something to show. Default true. */
11
+ threadList?: boolean;
12
+ }
13
+ /**
14
+ * An assistant-ui runtime over a chat-mode flow, for `AssistantRuntimeProvider`.
15
+ * Conversations become threads, so the thread list primitives switch, create and
16
+ * delete Windmill conversations.
17
+ */
18
+ export declare function useWindmillRuntime(options: WindmillRuntimeOptions): AssistantRuntime;
19
+ /** Folds the role-per-row message list into turns: one entry per user message, one per answer. */
20
+ export declare function groupTurns(messages: ChatMessage[]): WindmillTurn[];
21
+ /** A turn as assistant-ui content parts; the status reflects streaming and a failed flow. */
22
+ export declare function toThreadMessage(turn: WindmillTurn): ThreadMessageLike;
@@ -0,0 +1,119 @@
1
+ import { u as parseJsonOr } from "./follow-DXfmfPfr.js";
2
+ import { useWindmillChat } from "./react.js";
3
+ import { useExternalStoreRuntime } from "@assistant-ui/react";
4
+ import { useEffect, useMemo } from "react";
5
+ //#region src/assistant-ui.ts
6
+ /**
7
+ * An assistant-ui runtime over a chat-mode flow, for `AssistantRuntimeProvider`.
8
+ * Conversations become threads, so the thread list primitives switch, create and
9
+ * delete Windmill conversations.
10
+ */
11
+ function useWindmillRuntime(options) {
12
+ const chat = useWindmillChat(options);
13
+ const turns = useMemo(() => groupTurns(chat.messages), [chat.messages]);
14
+ const withThreadList = options.threadList !== false;
15
+ useEffect(() => {
16
+ if (withThreadList) chat.loadConversations().catch(() => {});
17
+ }, [chat.chat, withThreadList]);
18
+ const adapter = {
19
+ messages: turns,
20
+ isRunning: chat.status === "submitted" || chat.status === "streaming",
21
+ convertMessage: toThreadMessage,
22
+ onNew: async (message) => {
23
+ if (message.role !== "user") return;
24
+ await chat.sendMessage(appendedText(message));
25
+ },
26
+ onCancel: async () => {
27
+ await chat.stop();
28
+ },
29
+ adapters: withThreadList ? { threadList: {
30
+ threadId: chat.conversationId,
31
+ threads: chat.conversations.map((c) => ({
32
+ status: "regular",
33
+ id: c.id,
34
+ title: c.title
35
+ })),
36
+ onSwitchToNewThread: () => chat.newConversation(),
37
+ onSwitchToThread: (id) => chat.selectConversation(id),
38
+ onDelete: (id) => chat.deleteConversation(id),
39
+ onRename: (id, title) => chat.renameConversation(id, title)
40
+ } } : void 0
41
+ };
42
+ return useExternalStoreRuntime(adapter);
43
+ }
44
+ function appendedText(message) {
45
+ return message.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
46
+ }
47
+ /** Folds the role-per-row message list into turns: one entry per user message, one per answer. */
48
+ function groupTurns(messages) {
49
+ const turns = [];
50
+ for (const m of messages) {
51
+ const last = turns[turns.length - 1];
52
+ if (m.role === "user" || m.role === "system" || !last || last.role !== "assistant") turns.push({
53
+ id: m.id,
54
+ role: m.role === "tool" ? "assistant" : m.role,
55
+ messages: [m]
56
+ });
57
+ else last.messages.push(m);
58
+ }
59
+ return turns;
60
+ }
61
+ /** A turn as assistant-ui content parts; the status reflects streaming and a failed flow. */
62
+ function toThreadMessage(turn) {
63
+ const first = turn.messages[0];
64
+ const createdAt = new Date(first.createdAt);
65
+ if (turn.role !== "assistant") return {
66
+ id: turn.id,
67
+ role: turn.role,
68
+ createdAt,
69
+ content: [{
70
+ type: "text",
71
+ text: first.content
72
+ }]
73
+ };
74
+ const content = [];
75
+ for (const m of turn.messages) {
76
+ if (m.reasoning) content.push({
77
+ type: "reasoning",
78
+ text: m.reasoning
79
+ });
80
+ if (m.role === "tool") {
81
+ const args = parseJsonOr(m.tool?.arguments);
82
+ content.push({
83
+ type: "tool-call",
84
+ toolCallId: m.tool?.callId ?? m.id,
85
+ toolName: m.tool?.name ?? "tool",
86
+ args: isJsonObject(args) ? args : args === void 0 ? {} : { input: args },
87
+ argsText: m.tool?.arguments ?? "",
88
+ result: m.tool?.status === "running" ? void 0 : m.tool?.result !== void 0 ? parseJsonOr(m.tool.result) : m.content,
89
+ isError: m.tool?.status === "error"
90
+ });
91
+ continue;
92
+ }
93
+ if (m.content) content.push({
94
+ type: "text",
95
+ text: m.content
96
+ });
97
+ }
98
+ const last = turn.messages[turn.messages.length - 1];
99
+ const status = turn.messages.some((m) => m.pending) ? { type: "running" } : last.role === "assistant" && !last.success ? {
100
+ type: "incomplete",
101
+ reason: "error",
102
+ error: last.content
103
+ } : {
104
+ type: "complete",
105
+ reason: "stop"
106
+ };
107
+ return {
108
+ id: turn.id,
109
+ role: "assistant",
110
+ createdAt,
111
+ content,
112
+ status
113
+ };
114
+ }
115
+ function isJsonObject(value) {
116
+ return typeof value === "object" && value !== null && !Array.isArray(value);
117
+ }
118
+ //#endregion
119
+ export { groupTurns, toThreadMessage, useWindmillRuntime };
@@ -0,0 +1,27 @@
1
+ import type { WindmillChatApi } from './api';
2
+ import type { AttachmentUpload } from './types';
3
+ /**
4
+ * Where a chat's uploads live in the workspace's object storage. Under `windmill_uploads/`
5
+ * because the default Enterprise storage permissions grant every user write and read there
6
+ * and deny any other top-level prefix: a key outside it is refused for non-admins, both on
7
+ * upload and when the agent's job reads the file back.
8
+ */
9
+ export declare const CHAT_UPLOADS_PREFIX = "windmill_uploads/chat";
10
+ /** What an AI agent step reads out of `user_attachments`. */
11
+ export interface UploadedAttachment {
12
+ s3: string;
13
+ filename: string;
14
+ }
15
+ /**
16
+ * The name an attachment is stored under: the picked name with the extension its media type
17
+ * needs, e.g. a `photo.webp` re-encoded to PNG becomes `photo.png`. Other types keep their name.
18
+ */
19
+ export declare function storedAttachmentName(filename: string, mediaType: string): string;
20
+ /** The bytes of an attachment as a Blob carrying its media type. */
21
+ export declare function attachmentBlob(attachment: AttachmentUpload): Blob;
22
+ /**
23
+ * Put each attachment in the workspace's object storage and hand back what the agent reads.
24
+ * The key's turn prefix and per-file index keep two files with the same name, in this turn or
25
+ * an earlier one, from overwriting each other; the name stays the last segment.
26
+ */
27
+ export declare function uploadAttachments(api: WindmillChatApi, attachments: AttachmentUpload[], turnId: string, signal?: AbortSignal): Promise<UploadedAttachment[]>;