@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.
Files changed (129) hide show
  1. package/LICENSE +662 -0
  2. package/dist/agents/lib/definitions.server.d.mts +11 -0
  3. package/dist/agents/lib/definitions.server.mjs +14 -0
  4. package/dist/agents/lib/delegation.server.d.mts +15 -0
  5. package/dist/agents/lib/delegation.server.mjs +28 -0
  6. package/dist/agents/lib/instructions.server.d.mts +14 -0
  7. package/dist/agents/lib/instructions.server.mjs +26 -0
  8. package/dist/agents/lib/types.d.mts +26 -0
  9. package/dist/agents/server.d.mts +5 -0
  10. package/dist/agents/server.mjs +5 -0
  11. package/dist/chat/client.d.mts +10 -0
  12. package/dist/chat/client.mjs +12 -0
  13. package/dist/chat/components/agent-activity.client.d.mts +14 -0
  14. package/dist/chat/components/agent-activity.client.mjs +26 -0
  15. package/dist/chat/components/assistant-panel.client.d.mts +40 -0
  16. package/dist/chat/components/assistant-panel.client.mjs +53 -0
  17. package/dist/chat/components/chat-composer.client.d.mts +160 -0
  18. package/dist/chat/components/chat-composer.client.mjs +266 -0
  19. package/dist/chat/components/chat-message.client.d.mts +92 -0
  20. package/dist/chat/components/chat-message.client.mjs +121 -0
  21. package/dist/chat/components/chat-thread.client.d.mts +130 -0
  22. package/dist/chat/components/chat-thread.client.mjs +164 -0
  23. package/dist/chat/components/message-part.client.d.mts +23 -0
  24. package/dist/chat/components/message-part.client.mjs +38 -0
  25. package/dist/chat/components/starter-prompts.client.d.mts +25 -0
  26. package/dist/chat/components/starter-prompts.client.mjs +31 -0
  27. package/dist/chat/components/tool-call-card.client.d.mts +29 -0
  28. package/dist/chat/components/tool-call-card.client.mjs +60 -0
  29. package/dist/chat/lib/classes.mjs +7 -0
  30. package/dist/chat/lib/parts.d.mts +10 -0
  31. package/dist/chat/lib/parts.mjs +25 -0
  32. package/dist/chat/lib/stream.server.d.mts +24 -0
  33. package/dist/chat/lib/stream.server.mjs +34 -0
  34. package/dist/chat/lib/types.d.mts +21 -0
  35. package/dist/chat/server.d.mts +3 -0
  36. package/dist/chat/server.mjs +3 -0
  37. package/dist/chat.d.mts +3 -0
  38. package/dist/chat.mjs +3 -0
  39. package/dist/index.d.mts +1 -0
  40. package/dist/index.mjs +1 -0
  41. package/dist/mcp/lib/content.d.mts +66 -0
  42. package/dist/mcp/lib/content.d.mts.map +1 -0
  43. package/dist/mcp/lib/content.mjs +115 -0
  44. package/dist/mcp/lib/content.mjs.map +1 -0
  45. package/dist/mcp/lib/tool-result.server.d.mts +12 -0
  46. package/dist/mcp/lib/tool-result.server.d.mts.map +1 -0
  47. package/dist/mcp/lib/tool-result.server.mjs +10 -0
  48. package/dist/mcp/lib/tool-result.server.mjs.map +1 -0
  49. package/dist/mcp/lib/types.d.mts +27 -0
  50. package/dist/mcp/server.d.mts +3 -0
  51. package/dist/mcp/server.mjs +3 -0
  52. package/dist/mcp/types.d.mts +27 -0
  53. package/dist/mcp/types.d.mts.map +1 -0
  54. package/dist/server/mcp-content.d.mts +44 -0
  55. package/dist/server/mcp-content.d.mts.map +1 -0
  56. package/dist/server/mcp-content.mjs +71 -0
  57. package/dist/server/mcp-content.mjs.map +1 -0
  58. package/dist/server/tools.d.mts +61 -0
  59. package/dist/server/tools.d.mts.map +1 -0
  60. package/dist/server/tools.mjs +47 -0
  61. package/dist/server/tools.mjs.map +1 -0
  62. package/dist/server.d.mts +3 -0
  63. package/dist/server.mjs +4 -0
  64. package/dist/tools/client.d.mts +3 -0
  65. package/dist/tools/client.mjs +5 -0
  66. package/dist/tools/lib/ai-sdk.server.d.mts +12 -0
  67. package/dist/tools/lib/ai-sdk.server.d.mts.map +1 -0
  68. package/dist/tools/lib/ai-sdk.server.mjs +38 -0
  69. package/dist/tools/lib/ai-sdk.server.mjs.map +1 -0
  70. package/dist/tools/lib/define-tool.server.d.mts +9 -0
  71. package/dist/tools/lib/define-tool.server.d.mts.map +1 -0
  72. package/dist/tools/lib/define-tool.server.mjs +13 -0
  73. package/dist/tools/lib/define-tool.server.mjs.map +1 -0
  74. package/dist/tools/lib/definitions.client.d.mts +20 -0
  75. package/dist/tools/lib/definitions.client.mjs +21 -0
  76. package/dist/tools/lib/definitions.d.mts +16 -0
  77. package/dist/tools/lib/definitions.d.mts.map +1 -0
  78. package/dist/tools/lib/definitions.mjs +20 -0
  79. package/dist/tools/lib/definitions.mjs.map +1 -0
  80. package/dist/tools/lib/resolvers.d.mts +41 -0
  81. package/dist/tools/lib/resolvers.d.mts.map +1 -0
  82. package/dist/tools/lib/resolvers.mjs +43 -0
  83. package/dist/tools/lib/resolvers.mjs.map +1 -0
  84. package/dist/tools/lib/types.client.d.mts +13 -0
  85. package/dist/tools/lib/types.d.mts +33 -0
  86. package/dist/tools/resolvers/agent.server.d.mts +27 -0
  87. package/dist/tools/resolvers/agent.server.d.mts.map +1 -0
  88. package/dist/tools/resolvers/agent.server.mjs +44 -0
  89. package/dist/tools/resolvers/agent.server.mjs.map +1 -0
  90. package/dist/tools/resolvers/mcp.server.d.mts +31 -0
  91. package/dist/tools/resolvers/mcp.server.d.mts.map +1 -0
  92. package/dist/tools/resolvers/mcp.server.mjs +20 -0
  93. package/dist/tools/resolvers/mcp.server.mjs.map +1 -0
  94. package/dist/tools/server.d.mts +5 -0
  95. package/dist/tools/server.mjs +5 -0
  96. package/dist/tools/types.d.mts +33 -0
  97. package/dist/tools/types.d.mts.map +1 -0
  98. package/package.json +61 -0
  99. package/src/agents/entry.server.ts +7 -0
  100. package/src/agents/lib/definitions.server.ts +14 -0
  101. package/src/agents/lib/delegation.server.ts +33 -0
  102. package/src/agents/lib/instructions.server.ts +37 -0
  103. package/src/agents/lib/types.ts +32 -0
  104. package/src/chat/components/agent-activity.client.tsx +21 -0
  105. package/src/chat/components/chat-composer.client.tsx +379 -0
  106. package/src/chat/components/chat-message.client.tsx +166 -0
  107. package/src/chat/components/chat-thread.client.tsx +242 -0
  108. package/src/chat/components/message-part.client.tsx +59 -0
  109. package/src/chat/components/starter-prompts.client.tsx +49 -0
  110. package/src/chat/components/tool-call-card.client.tsx +78 -0
  111. package/src/chat/entry.client.ts +18 -0
  112. package/src/chat/entry.server.ts +5 -0
  113. package/src/chat/entry.ts +5 -0
  114. package/src/chat/lib/parts.ts +33 -0
  115. package/src/chat/lib/stream.server.ts +40 -0
  116. package/src/chat/lib/types.ts +20 -0
  117. package/src/entry.ts +24 -0
  118. package/src/mcp/entry.server.ts +2 -0
  119. package/src/mcp/lib/content.ts +131 -0
  120. package/src/mcp/lib/types.ts +28 -0
  121. package/src/styles.css +1 -0
  122. package/src/tools/entry.client.ts +7 -0
  123. package/src/tools/entry.server.ts +11 -0
  124. package/src/tools/lib/definitions.client.ts +21 -0
  125. package/src/tools/lib/definitions.ts +24 -0
  126. package/src/tools/lib/types.client.ts +11 -0
  127. package/src/tools/lib/types.ts +44 -0
  128. package/src/tools/resolvers/agent.server.ts +78 -0
  129. 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,5 @@
1
+ /**
2
+ * Lib
3
+ */
4
+ export * from "./lib/stream.server";
5
+ export * from "./lib/types";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Lib
3
+ */
4
+ export * from "./lib/parts";
5
+ 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
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./lib/content";
2
+ export * from "./lib/types";