@tulip-systems/ai 0.11.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.
- package/LICENSE +662 -0
- package/dist/agents/lib/definitions.server.d.mts +11 -0
- package/dist/agents/lib/definitions.server.mjs +14 -0
- package/dist/agents/lib/delegation.server.d.mts +15 -0
- package/dist/agents/lib/delegation.server.mjs +28 -0
- package/dist/agents/lib/instructions.server.d.mts +14 -0
- package/dist/agents/lib/instructions.server.mjs +26 -0
- package/dist/agents/lib/types.d.mts +26 -0
- package/dist/agents/server.d.mts +5 -0
- package/dist/agents/server.mjs +5 -0
- package/dist/chat/client.d.mts +10 -0
- package/dist/chat/client.mjs +12 -0
- package/dist/chat/components/agent-activity.client.d.mts +14 -0
- package/dist/chat/components/agent-activity.client.mjs +26 -0
- package/dist/chat/components/assistant-panel.client.d.mts +40 -0
- package/dist/chat/components/assistant-panel.client.mjs +53 -0
- package/dist/chat/components/chat-composer.client.d.mts +160 -0
- package/dist/chat/components/chat-composer.client.mjs +266 -0
- package/dist/chat/components/chat-message.client.d.mts +92 -0
- package/dist/chat/components/chat-message.client.mjs +121 -0
- package/dist/chat/components/chat-thread.client.d.mts +130 -0
- package/dist/chat/components/chat-thread.client.mjs +164 -0
- package/dist/chat/components/message-part.client.d.mts +23 -0
- package/dist/chat/components/message-part.client.mjs +38 -0
- package/dist/chat/components/starter-prompts.client.d.mts +25 -0
- package/dist/chat/components/starter-prompts.client.mjs +31 -0
- package/dist/chat/components/tool-call-card.client.d.mts +29 -0
- package/dist/chat/components/tool-call-card.client.mjs +60 -0
- package/dist/chat/lib/classes.mjs +7 -0
- package/dist/chat/lib/parts.d.mts +10 -0
- package/dist/chat/lib/parts.mjs +25 -0
- package/dist/chat/lib/stream.server.d.mts +24 -0
- package/dist/chat/lib/stream.server.mjs +34 -0
- package/dist/chat/lib/types.d.mts +21 -0
- package/dist/chat/server.d.mts +3 -0
- package/dist/chat/server.mjs +3 -0
- package/dist/chat.d.mts +3 -0
- package/dist/chat.mjs +3 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/mcp/lib/content.d.mts +66 -0
- package/dist/mcp/lib/content.d.mts.map +1 -0
- package/dist/mcp/lib/content.mjs +115 -0
- package/dist/mcp/lib/content.mjs.map +1 -0
- package/dist/mcp/lib/tool-result.server.d.mts +12 -0
- package/dist/mcp/lib/tool-result.server.d.mts.map +1 -0
- package/dist/mcp/lib/tool-result.server.mjs +10 -0
- package/dist/mcp/lib/tool-result.server.mjs.map +1 -0
- package/dist/mcp/lib/types.d.mts +27 -0
- package/dist/mcp/server.d.mts +3 -0
- package/dist/mcp/server.mjs +3 -0
- package/dist/mcp/types.d.mts +27 -0
- package/dist/mcp/types.d.mts.map +1 -0
- package/dist/server/mcp-content.d.mts +44 -0
- package/dist/server/mcp-content.d.mts.map +1 -0
- package/dist/server/mcp-content.mjs +71 -0
- package/dist/server/mcp-content.mjs.map +1 -0
- package/dist/server/tools.d.mts +61 -0
- package/dist/server/tools.d.mts.map +1 -0
- package/dist/server/tools.mjs +47 -0
- package/dist/server/tools.mjs.map +1 -0
- package/dist/server.d.mts +3 -0
- package/dist/server.mjs +4 -0
- package/dist/tools/client.d.mts +3 -0
- package/dist/tools/client.mjs +5 -0
- package/dist/tools/lib/ai-sdk.server.d.mts +12 -0
- package/dist/tools/lib/ai-sdk.server.d.mts.map +1 -0
- package/dist/tools/lib/ai-sdk.server.mjs +38 -0
- package/dist/tools/lib/ai-sdk.server.mjs.map +1 -0
- package/dist/tools/lib/define-tool.server.d.mts +9 -0
- package/dist/tools/lib/define-tool.server.d.mts.map +1 -0
- package/dist/tools/lib/define-tool.server.mjs +13 -0
- package/dist/tools/lib/define-tool.server.mjs.map +1 -0
- package/dist/tools/lib/definitions.client.d.mts +20 -0
- package/dist/tools/lib/definitions.client.mjs +21 -0
- package/dist/tools/lib/definitions.d.mts +16 -0
- package/dist/tools/lib/definitions.d.mts.map +1 -0
- package/dist/tools/lib/definitions.mjs +20 -0
- package/dist/tools/lib/definitions.mjs.map +1 -0
- package/dist/tools/lib/resolvers.d.mts +41 -0
- package/dist/tools/lib/resolvers.d.mts.map +1 -0
- package/dist/tools/lib/resolvers.mjs +43 -0
- package/dist/tools/lib/resolvers.mjs.map +1 -0
- package/dist/tools/lib/types.client.d.mts +13 -0
- package/dist/tools/lib/types.d.mts +33 -0
- package/dist/tools/resolvers/agent.server.d.mts +27 -0
- package/dist/tools/resolvers/agent.server.d.mts.map +1 -0
- package/dist/tools/resolvers/agent.server.mjs +44 -0
- package/dist/tools/resolvers/agent.server.mjs.map +1 -0
- package/dist/tools/resolvers/mcp.server.d.mts +31 -0
- package/dist/tools/resolvers/mcp.server.d.mts.map +1 -0
- package/dist/tools/resolvers/mcp.server.mjs +20 -0
- package/dist/tools/resolvers/mcp.server.mjs.map +1 -0
- package/dist/tools/server.d.mts +5 -0
- package/dist/tools/server.mjs +5 -0
- package/dist/tools/types.d.mts +33 -0
- package/dist/tools/types.d.mts.map +1 -0
- package/package.json +61 -0
- package/src/agents/entry.server.ts +7 -0
- package/src/agents/lib/definitions.server.ts +14 -0
- package/src/agents/lib/delegation.server.ts +33 -0
- package/src/agents/lib/instructions.server.ts +37 -0
- package/src/agents/lib/types.ts +32 -0
- package/src/chat/components/agent-activity.client.tsx +21 -0
- package/src/chat/components/chat-composer.client.tsx +379 -0
- package/src/chat/components/chat-message.client.tsx +166 -0
- package/src/chat/components/chat-thread.client.tsx +242 -0
- package/src/chat/components/message-part.client.tsx +59 -0
- package/src/chat/components/starter-prompts.client.tsx +49 -0
- package/src/chat/components/tool-call-card.client.tsx +78 -0
- package/src/chat/entry.client.ts +18 -0
- package/src/chat/entry.server.ts +5 -0
- package/src/chat/entry.ts +5 -0
- package/src/chat/lib/parts.ts +33 -0
- package/src/chat/lib/stream.server.ts +40 -0
- package/src/chat/lib/types.ts +20 -0
- package/src/entry.ts +24 -0
- package/src/mcp/entry.server.ts +2 -0
- package/src/mcp/lib/content.ts +131 -0
- package/src/mcp/lib/types.ts +28 -0
- package/src/styles.css +1 -0
- package/src/tools/entry.client.ts +7 -0
- package/src/tools/entry.server.ts +11 -0
- package/src/tools/lib/definitions.client.ts +21 -0
- package/src/tools/lib/definitions.ts +24 -0
- package/src/tools/lib/types.client.ts +11 -0
- package/src/tools/lib/types.ts +44 -0
- package/src/tools/resolvers/agent.server.ts +78 -0
- package/src/tools/resolvers/mcp.server.ts +40 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Message, MessageContent } from "@tulip-systems/core/components";
|
|
4
|
+
import {
|
|
5
|
+
MessageScroller,
|
|
6
|
+
MessageScrollerButton,
|
|
7
|
+
MessageScrollerContent,
|
|
8
|
+
MessageScrollerItem,
|
|
9
|
+
MessageScrollerProvider,
|
|
10
|
+
MessageScrollerViewport,
|
|
11
|
+
} from "@tulip-systems/core/components/client";
|
|
12
|
+
import { cn } from "@tulip-systems/core/lib";
|
|
13
|
+
import type { ChatStatus, UIMessage } from "ai";
|
|
14
|
+
import { type ComponentProps, createContext, useContext, useMemo } from "react";
|
|
15
|
+
import type { ToolClientDefinition } from "../../tools/lib/types.client";
|
|
16
|
+
import { AgentActivity } from "./agent-activity.client";
|
|
17
|
+
import { ChatMessage, ChatMessageContent, ChatMessageParts } from "./chat-message.client";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Chat Thread Context
|
|
21
|
+
*/
|
|
22
|
+
type ChatThreadContextValue = {
|
|
23
|
+
status: ChatStatus;
|
|
24
|
+
isAssistantResponding: boolean;
|
|
25
|
+
autoScroll: boolean;
|
|
26
|
+
defaultScrollPosition: "start" | "end" | "last-anchor";
|
|
27
|
+
scrollPreviousItemPeek: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ChatThreadContext = createContext<ChatThreadContextValue>({} as ChatThreadContextValue);
|
|
31
|
+
|
|
32
|
+
export type ChatThreadProps = ComponentProps<"div"> & {
|
|
33
|
+
status?: ChatStatus;
|
|
34
|
+
autoScroll?: boolean;
|
|
35
|
+
defaultScrollPosition?: "start" | "end" | "last-anchor";
|
|
36
|
+
scrollPreviousItemPeek?: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Provides shared thread state for all chat thread slots.
|
|
41
|
+
* Use it to configure chat status and scroll behavior once.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <ChatThread status={status}>
|
|
46
|
+
* {messages.length === 0 && <EmptyState />}
|
|
47
|
+
*
|
|
48
|
+
* {messages.length > 0 && (
|
|
49
|
+
* <ChatThreadContent>
|
|
50
|
+
* {messages.map((message) => (
|
|
51
|
+
* <ChatThreadMessage key={message.id} message={message} />
|
|
52
|
+
* ))}
|
|
53
|
+
* <ChatThreadActivity />
|
|
54
|
+
* </ChatThreadContent>
|
|
55
|
+
* )}
|
|
56
|
+
* </ChatThread>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function ChatThread({
|
|
60
|
+
status = "ready",
|
|
61
|
+
autoScroll = true,
|
|
62
|
+
defaultScrollPosition = "end",
|
|
63
|
+
scrollPreviousItemPeek = 64,
|
|
64
|
+
className,
|
|
65
|
+
children,
|
|
66
|
+
...props
|
|
67
|
+
}: ChatThreadProps) {
|
|
68
|
+
const isAssistantResponding = status === "submitted" || status === "streaming";
|
|
69
|
+
|
|
70
|
+
const contextValue = useMemo(
|
|
71
|
+
() => ({
|
|
72
|
+
status,
|
|
73
|
+
isAssistantResponding,
|
|
74
|
+
autoScroll,
|
|
75
|
+
defaultScrollPosition,
|
|
76
|
+
scrollPreviousItemPeek,
|
|
77
|
+
}),
|
|
78
|
+
[status, isAssistantResponding, autoScroll, defaultScrollPosition, scrollPreviousItemPeek],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<ChatThreadContext.Provider value={contextValue}>
|
|
83
|
+
<div
|
|
84
|
+
data-slot="chat-thread"
|
|
85
|
+
data-status={status}
|
|
86
|
+
data-responding={isAssistantResponding ? true : undefined}
|
|
87
|
+
className={cn("min-h-0 flex-1", className)}
|
|
88
|
+
{...props}
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
</div>
|
|
92
|
+
</ChatThreadContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Renders the scrollable message area for a chat thread.
|
|
98
|
+
* Place `ChatThreadMessage`, `ChatThreadActivity`, and optional sentinels inside it.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```tsx
|
|
102
|
+
* <ChatThreadContent className="px-3">
|
|
103
|
+
* {messages.map((message) => (
|
|
104
|
+
* <ChatThreadMessage key={message.id} message={message} />
|
|
105
|
+
* ))}
|
|
106
|
+
* <ChatThreadActivity />
|
|
107
|
+
* </ChatThreadContent>
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export function ChatThreadContent({
|
|
111
|
+
className,
|
|
112
|
+
children,
|
|
113
|
+
...props
|
|
114
|
+
}: ComponentProps<typeof MessageScrollerContent>) {
|
|
115
|
+
const { isAssistantResponding, autoScroll, defaultScrollPosition, scrollPreviousItemPeek } =
|
|
116
|
+
useContext(ChatThreadContext);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<MessageScrollerProvider
|
|
120
|
+
autoScroll={autoScroll}
|
|
121
|
+
defaultScrollPosition={defaultScrollPosition}
|
|
122
|
+
scrollPreviousItemPeek={scrollPreviousItemPeek}
|
|
123
|
+
>
|
|
124
|
+
<MessageScroller className="min-h-0 flex-1">
|
|
125
|
+
<MessageScrollerViewport>
|
|
126
|
+
<MessageScrollerContent
|
|
127
|
+
aria-busy={isAssistantResponding}
|
|
128
|
+
className={cn("mx-auto w-full max-w-3xl gap-7 px-1 py-4 md:py-6", className)}
|
|
129
|
+
{...props}
|
|
130
|
+
>
|
|
131
|
+
{children}
|
|
132
|
+
</MessageScrollerContent>
|
|
133
|
+
</MessageScrollerViewport>
|
|
134
|
+
|
|
135
|
+
<ChatThreadScrollButton />
|
|
136
|
+
</MessageScroller>
|
|
137
|
+
</MessageScrollerProvider>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Renders one AI SDK message using the current `ChatThread` configuration.
|
|
143
|
+
* Use this inside `ChatThreadContent` when mapping over messages.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* {messages.map((message) => (
|
|
148
|
+
* <ChatThreadMessage key={message.id} message={message} toolRenderers={toolRenderers} />
|
|
149
|
+
* ))}
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function ChatThreadMessage({
|
|
153
|
+
message,
|
|
154
|
+
messageId = message.id,
|
|
155
|
+
scrollAnchor = message.role === "user",
|
|
156
|
+
toolRenderers,
|
|
157
|
+
...props
|
|
158
|
+
}: Omit<ComponentProps<typeof MessageScrollerItem>, "children" | "messageId" | "scrollAnchor"> & {
|
|
159
|
+
message: UIMessage;
|
|
160
|
+
messageId?: string;
|
|
161
|
+
scrollAnchor?: boolean;
|
|
162
|
+
toolRenderers?: ToolClientDefinition[];
|
|
163
|
+
}) {
|
|
164
|
+
return (
|
|
165
|
+
<MessageScrollerItem messageId={messageId} scrollAnchor={scrollAnchor} {...props}>
|
|
166
|
+
<ChatMessage message={message}>
|
|
167
|
+
<ChatMessageContent>
|
|
168
|
+
<ChatMessageParts toolRenderers={toolRenderers} />
|
|
169
|
+
</ChatMessageContent>
|
|
170
|
+
</ChatMessage>
|
|
171
|
+
</MessageScrollerItem>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Renders the assistant activity indicator while the thread status is `submitted` or `streaming`.
|
|
177
|
+
* It returns nothing when the assistant is not responding.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```tsx
|
|
181
|
+
* <ChatThreadActivity />
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export function ChatThreadActivity({
|
|
185
|
+
messageId = "agent-activity",
|
|
186
|
+
activityClassName,
|
|
187
|
+
...props
|
|
188
|
+
}: Omit<ComponentProps<typeof MessageScrollerItem>, "children" | "messageId"> & {
|
|
189
|
+
messageId?: string;
|
|
190
|
+
activityClassName?: string;
|
|
191
|
+
}) {
|
|
192
|
+
const { isAssistantResponding } = useContext(ChatThreadContext);
|
|
193
|
+
|
|
194
|
+
if (!isAssistantResponding) return null;
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<MessageScrollerItem messageId={messageId} {...props}>
|
|
198
|
+
<Message align="start">
|
|
199
|
+
<MessageContent>
|
|
200
|
+
<AgentActivity className={activityClassName} />
|
|
201
|
+
</MessageContent>
|
|
202
|
+
</Message>
|
|
203
|
+
</MessageScrollerItem>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Renders a bottom sentinel item inside the thread scroller.
|
|
209
|
+
* Use it when callers need a stable ref at the end of the message list.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```tsx
|
|
213
|
+
* <ChatThreadBottom ref={bottomRef} />
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export function ChatThreadBottom({
|
|
217
|
+
messageId = "bottom-sentinel",
|
|
218
|
+
scrollAnchor = false,
|
|
219
|
+
...props
|
|
220
|
+
}: ComponentProps<"div"> & {
|
|
221
|
+
messageId?: string;
|
|
222
|
+
scrollAnchor?: boolean;
|
|
223
|
+
}) {
|
|
224
|
+
return (
|
|
225
|
+
<MessageScrollerItem messageId={messageId} scrollAnchor={scrollAnchor}>
|
|
226
|
+
<div {...props} />
|
|
227
|
+
</MessageScrollerItem>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Renders the scroll-to-bottom button for `ChatThreadContent`.
|
|
233
|
+
* `ChatThreadContent` includes this button by default.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```tsx
|
|
237
|
+
* <ChatThreadScrollButton />
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export function ChatThreadScrollButton(props: ComponentProps<typeof MessageScrollerButton>) {
|
|
241
|
+
return <MessageScrollerButton {...props} />;
|
|
242
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@tulip-systems/core/lib";
|
|
4
|
+
import type { ToolClientDefinition } from "../../tools/lib/types.client";
|
|
5
|
+
import { isToolPart } from "../lib/parts";
|
|
6
|
+
import type { ChatPart, ToolChatPart } from "../lib/types";
|
|
7
|
+
import { ToolCallCard } from "./tool-call-card.client";
|
|
8
|
+
|
|
9
|
+
export type ChatMessagePartProps = {
|
|
10
|
+
part: ChatPart;
|
|
11
|
+
isUser?: boolean;
|
|
12
|
+
toolRenderers?: ToolClientDefinition[];
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders a visible AI SDK message part with a user bubble and plain assistant text.
|
|
18
|
+
*/
|
|
19
|
+
export function ChatMessagePart({
|
|
20
|
+
part,
|
|
21
|
+
isUser = false,
|
|
22
|
+
toolRenderers,
|
|
23
|
+
className,
|
|
24
|
+
}: ChatMessagePartProps) {
|
|
25
|
+
if (part.type === "text") {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={cn(
|
|
29
|
+
"max-w-[min(44rem,100%)]",
|
|
30
|
+
isUser ? "rounded-3xl bg-muted px-4 py-2.5 text-foreground" : "px-1 text-foreground",
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
<p className="whitespace-pre-wrap text-[15px] leading-7">{part.text}</p>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isToolPart(part)) {
|
|
40
|
+
const toolRenderer = getToolRenderer(part, toolRenderers);
|
|
41
|
+
|
|
42
|
+
return toolRenderer ? toolRenderer.render(part) : <ToolCallCard part={part} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Helpers
|
|
50
|
+
*/
|
|
51
|
+
function getToolName(part: ToolChatPart) {
|
|
52
|
+
return part.type.startsWith("tool-") ? part.type.slice(5) : part.type;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getToolRenderer(part: ToolChatPart, toolRenderers?: ToolClientDefinition[]) {
|
|
56
|
+
const toolName = getToolName(part);
|
|
57
|
+
|
|
58
|
+
return toolRenderers?.find((renderer) => renderer.toolName === toolName);
|
|
59
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@tulip-systems/core/lib";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
export type StarterPromptsProps = {
|
|
7
|
+
prompts: string[];
|
|
8
|
+
onSelect: (prompt: string) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
label?: ReactNode;
|
|
11
|
+
icon?: ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Renders starter prompt buttons for a new chat state.
|
|
17
|
+
*/
|
|
18
|
+
export function StarterPrompts({
|
|
19
|
+
prompts,
|
|
20
|
+
onSelect,
|
|
21
|
+
disabled = false,
|
|
22
|
+
label,
|
|
23
|
+
icon,
|
|
24
|
+
className,
|
|
25
|
+
}: StarterPromptsProps) {
|
|
26
|
+
const hasMeta = Boolean(label || icon);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={cn("grid w-full gap-2 sm:grid-cols-2", className)}>
|
|
30
|
+
{prompts.map((prompt) => (
|
|
31
|
+
<button
|
|
32
|
+
key={prompt}
|
|
33
|
+
type="button"
|
|
34
|
+
className="group rounded-2xl border border-border/60 bg-background/80 p-3 text-left shadow-sm backdrop-blur transition-colors hover:bg-muted/40 disabled:cursor-not-allowed disabled:opacity-60"
|
|
35
|
+
disabled={disabled}
|
|
36
|
+
onClick={() => onSelect(prompt)}
|
|
37
|
+
>
|
|
38
|
+
{hasMeta && (
|
|
39
|
+
<div className="mb-2 flex items-center gap-2 font-medium text-muted-foreground text-xs uppercase tracking-[0.14em]">
|
|
40
|
+
{icon}
|
|
41
|
+
{label}
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
<p className="text-foreground/90 text-sm leading-6">{prompt}</p>
|
|
45
|
+
</button>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@tulip-systems/core/lib";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { getDefaultToolTitle, getToolName, stringifyChatValue } from "../lib/parts";
|
|
6
|
+
import type { ToolChatPart } from "../lib/types";
|
|
7
|
+
|
|
8
|
+
export type ToolCallCardLabels = {
|
|
9
|
+
input?: string;
|
|
10
|
+
output?: string;
|
|
11
|
+
preliminaryOutput?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ToolCallCardProps = {
|
|
15
|
+
part: ToolChatPart;
|
|
16
|
+
title?: ReactNode | ((toolName: string, part: ToolChatPart) => ReactNode);
|
|
17
|
+
icon?: ReactNode | ((toolName: string, part: ToolChatPart) => ReactNode);
|
|
18
|
+
labels?: ToolCallCardLabels;
|
|
19
|
+
className?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Renders a streamed AI SDK tool part with input, output, state, and error details.
|
|
24
|
+
*/
|
|
25
|
+
export function ToolCallCard({ part, title, icon, labels, className }: ToolCallCardProps) {
|
|
26
|
+
const toolName = getToolName(part);
|
|
27
|
+
const resolvedTitle = typeof title === "function" ? title(toolName, part) : title;
|
|
28
|
+
const resolvedIcon = typeof icon === "function" ? icon(toolName, part) : icon;
|
|
29
|
+
const inputLabel = labels?.input ?? "Input";
|
|
30
|
+
const outputLabel = part.preliminary
|
|
31
|
+
? (labels?.preliminaryOutput ?? "Intermediate result")
|
|
32
|
+
: (labels?.output ?? "Result");
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn(
|
|
37
|
+
"max-w-full rounded-3xl border border-border/60 bg-muted/35 p-3.5 text-sm shadow-sm backdrop-blur-sm",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
<div className="flex items-center gap-2 font-medium text-[11px] text-muted-foreground uppercase tracking-[0.18em]">
|
|
42
|
+
{resolvedIcon}
|
|
43
|
+
<span>{resolvedTitle ?? getDefaultToolTitle(toolName)}</span>
|
|
44
|
+
{part.state && (
|
|
45
|
+
<span className="text-[10px] normal-case tracking-normal">{part.state}</span>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{part.input !== undefined && (
|
|
50
|
+
<div className="mt-3 space-y-1.5">
|
|
51
|
+
<div className="font-medium text-[11px] text-muted-foreground uppercase tracking-[0.14em]">
|
|
52
|
+
{inputLabel}
|
|
53
|
+
</div>
|
|
54
|
+
<pre className="overflow-x-auto whitespace-pre-wrap rounded-2xl bg-background/80 p-3 text-foreground/90 text-xs leading-5">
|
|
55
|
+
{stringifyChatValue(part.input)}
|
|
56
|
+
</pre>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
{part.output !== undefined && (
|
|
61
|
+
<div className="mt-3 space-y-1.5">
|
|
62
|
+
<div className="font-medium text-[11px] text-muted-foreground uppercase tracking-[0.14em]">
|
|
63
|
+
{outputLabel}
|
|
64
|
+
</div>
|
|
65
|
+
<pre className="overflow-x-auto whitespace-pre-wrap rounded-2xl bg-background/80 p-3 text-foreground/90 text-xs leading-5">
|
|
66
|
+
{stringifyChatValue(part.output)}
|
|
67
|
+
</pre>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{part.errorText && (
|
|
72
|
+
<div className="mt-3 rounded-xl border border-red-200 bg-red-50 p-3 text-red-700 text-xs">
|
|
73
|
+
{part.errorText}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Components
|
|
5
|
+
*/
|
|
6
|
+
export * from "./components/agent-activity.client";
|
|
7
|
+
export * from "./components/chat-composer.client";
|
|
8
|
+
export * from "./components/chat-message.client";
|
|
9
|
+
export * from "./components/chat-thread.client";
|
|
10
|
+
export * from "./components/message-part.client";
|
|
11
|
+
export * from "./components/starter-prompts.client";
|
|
12
|
+
export * from "./components/tool-call-card.client";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lib
|
|
16
|
+
*/
|
|
17
|
+
export * from "./lib/parts";
|
|
18
|
+
export * from "./lib/types";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChatPart, ToolChatPart } from "./types";
|
|
2
|
+
|
|
3
|
+
export function isToolPart(part: ChatPart): part is ToolChatPart {
|
|
4
|
+
return part.type.startsWith("tool-");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getToolName(part: ToolChatPart) {
|
|
8
|
+
return part.type.slice(5);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getDefaultToolTitle(toolName: string) {
|
|
12
|
+
return toolName.replaceAll("_", " ");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function stringifyChatValue(value: unknown) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(value, null, 2);
|
|
18
|
+
} catch {
|
|
19
|
+
return String(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getChatPartKey(messageId: string, part: ChatPart) {
|
|
24
|
+
if (part.type === "text") {
|
|
25
|
+
return `${messageId}-text-${part.text.slice(0, 40)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isToolPart(part)) {
|
|
29
|
+
return `${messageId}-${part.type}-${stringifyChatValue(part.input ?? part.output ?? part.type).slice(0, 60)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${messageId}-${part.type}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import "server-cli-only";
|
|
2
|
+
|
|
3
|
+
import { streamToEventIterator } from "@orpc/server";
|
|
4
|
+
import { convertToModelMessages, toUIMessageStream } from "ai";
|
|
5
|
+
import type { StreamAgentChatOptions } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Streams a Tulip agent response as an AI SDK UI message stream.
|
|
9
|
+
* Use this when the caller wants a framework-neutral `ReadableStream`.
|
|
10
|
+
*/
|
|
11
|
+
export async function streamAgentChat({
|
|
12
|
+
agent,
|
|
13
|
+
messages,
|
|
14
|
+
abortSignal,
|
|
15
|
+
convertToModelMessagesOptions,
|
|
16
|
+
uiMessageStreamOptions,
|
|
17
|
+
}: StreamAgentChatOptions) {
|
|
18
|
+
const modelMessages = await convertToModelMessages(messages, {
|
|
19
|
+
ignoreIncompleteToolCalls: true,
|
|
20
|
+
...convertToModelMessagesOptions,
|
|
21
|
+
});
|
|
22
|
+
const result = await agent.stream({
|
|
23
|
+
messages: modelMessages,
|
|
24
|
+
abortSignal,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return toUIMessageStream({
|
|
28
|
+
...uiMessageStreamOptions,
|
|
29
|
+
stream: result.stream,
|
|
30
|
+
tools: agent.tools,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Streams a Tulip agent response as an oRPC event iterator.
|
|
36
|
+
* Use this from oRPC procedures that return an AI SDK UI message stream to clients.
|
|
37
|
+
*/
|
|
38
|
+
export async function streamAgentChatEventIterator(options: StreamAgentChatOptions) {
|
|
39
|
+
return streamToEventIterator(await streamAgentChat(options));
|
|
40
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { convertToModelMessages, toUIMessageStream, UIMessage } from "ai";
|
|
2
|
+
import type { CreatedAgent } from "../../agents/lib/types";
|
|
3
|
+
|
|
4
|
+
export type ChatPart = UIMessage["parts"][number];
|
|
5
|
+
|
|
6
|
+
export type ToolChatPart = ChatPart & {
|
|
7
|
+
input?: unknown;
|
|
8
|
+
output?: unknown;
|
|
9
|
+
state?: string;
|
|
10
|
+
preliminary?: boolean;
|
|
11
|
+
errorText?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type StreamAgentChatOptions = {
|
|
15
|
+
agent: CreatedAgent;
|
|
16
|
+
messages: UIMessage[];
|
|
17
|
+
abortSignal?: AbortSignal;
|
|
18
|
+
convertToModelMessagesOptions?: Parameters<typeof convertToModelMessages>[1];
|
|
19
|
+
uiMessageStreamOptions?: Omit<Parameters<typeof toUIMessageStream>[0], "stream" | "tools">;
|
|
20
|
+
};
|
package/src/entry.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entrypoints for Vite
|
|
3
|
+
*/
|
|
4
|
+
export const entry = {
|
|
5
|
+
/**
|
|
6
|
+
* Agents
|
|
7
|
+
*/
|
|
8
|
+
"agents/server": "./src/agents/entry.server.ts",
|
|
9
|
+
/**
|
|
10
|
+
* Chat
|
|
11
|
+
*/
|
|
12
|
+
chat: "./src/chat/entry.ts",
|
|
13
|
+
"chat/client": "./src/chat/entry.client.ts",
|
|
14
|
+
"chat/server": "./src/chat/entry.server.ts",
|
|
15
|
+
/**
|
|
16
|
+
* MCP
|
|
17
|
+
*/
|
|
18
|
+
"mcp/server": "./src/mcp/entry.server.ts",
|
|
19
|
+
/**
|
|
20
|
+
* Tools
|
|
21
|
+
*/
|
|
22
|
+
"tools/client": "./src/tools/entry.client.ts",
|
|
23
|
+
"tools/server": "./src/tools/entry.server.ts",
|
|
24
|
+
};
|