@alexanderolsen/create-deepagent 0.1.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 (193) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +661 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +59 -0
  5. package/registry/frameworks/deno/.env.example +6 -0
  6. package/registry/frameworks/deno/README.md +137 -0
  7. package/registry/frameworks/deno/client/index.html +23 -0
  8. package/registry/frameworks/deno/client/package.json +30 -0
  9. package/registry/frameworks/deno/client/public/favicon.ico +0 -0
  10. package/registry/frameworks/deno/client/src/components/Chat.tsx +124 -0
  11. package/registry/frameworks/deno/client/src/components/ChatApp.tsx +129 -0
  12. package/registry/frameworks/deno/client/src/components/Conversation.tsx +91 -0
  13. package/registry/frameworks/deno/client/src/components/MessageBubbles.tsx +88 -0
  14. package/registry/frameworks/deno/client/src/components/MessageReasoning.tsx +71 -0
  15. package/registry/frameworks/deno/client/src/components/MessageThread.tsx +135 -0
  16. package/registry/frameworks/deno/client/src/components/StreamingIndicator.tsx +36 -0
  17. package/registry/frameworks/deno/client/src/components/Subagents.tsx +120 -0
  18. package/registry/frameworks/deno/client/src/components/ThemeIcons.tsx +31 -0
  19. package/registry/frameworks/deno/client/src/components/ThreadHistory.tsx +73 -0
  20. package/registry/frameworks/deno/client/src/components/ToolCall.tsx +89 -0
  21. package/registry/frameworks/deno/client/src/lib/agent-type.ts +4 -0
  22. package/registry/frameworks/deno/client/src/lib/chat/threads-client.ts +51 -0
  23. package/registry/frameworks/deno/client/src/main.tsx +11 -0
  24. package/registry/frameworks/deno/client/src/styles/globals.css +714 -0
  25. package/registry/frameworks/deno/client/src/vite-env.d.ts +1 -0
  26. package/registry/frameworks/deno/client/tsconfig.app.json +7 -0
  27. package/registry/frameworks/deno/client/tsconfig.json +24 -0
  28. package/registry/frameworks/deno/client/tsconfig.node.json +19 -0
  29. package/registry/frameworks/deno/client/vite.config.ts +24 -0
  30. package/registry/frameworks/deno/deno.json +16 -0
  31. package/registry/frameworks/deno/main.ts +23 -0
  32. package/registry/frameworks/deno/package.json +14 -0
  33. package/registry/frameworks/deno/server/agent/index.ts +60 -0
  34. package/registry/frameworks/deno/server/agent/middleware.ts +24 -0
  35. package/registry/frameworks/deno/server/agent/tools.ts +64 -0
  36. package/registry/frameworks/deno/server/registry.ts +40 -0
  37. package/registry/frameworks/deno/server/routes.ts +114 -0
  38. package/registry/frameworks/deno/server/serialize.ts +30 -0
  39. package/registry/frameworks/deno/server/session.ts +210 -0
  40. package/registry/frameworks/deno/server/threads.ts +404 -0
  41. package/registry/frameworks/deno.ts +17 -0
  42. package/registry/frameworks/hono/.env.example +6 -0
  43. package/registry/frameworks/hono/README.md +186 -0
  44. package/registry/frameworks/hono/index.html +22 -0
  45. package/registry/frameworks/hono/package.json +42 -0
  46. package/registry/frameworks/hono/src/components/Chat.tsx +124 -0
  47. package/registry/frameworks/hono/src/components/ChatApp.tsx +129 -0
  48. package/registry/frameworks/hono/src/components/Conversation.tsx +90 -0
  49. package/registry/frameworks/hono/src/components/MessageBubbles.tsx +88 -0
  50. package/registry/frameworks/hono/src/components/MessageReasoning.tsx +71 -0
  51. package/registry/frameworks/hono/src/components/MessageThread.tsx +135 -0
  52. package/registry/frameworks/hono/src/components/StreamingIndicator.tsx +36 -0
  53. package/registry/frameworks/hono/src/components/Subagents.tsx +120 -0
  54. package/registry/frameworks/hono/src/components/ThemeIcons.tsx +31 -0
  55. package/registry/frameworks/hono/src/components/ThreadHistory.tsx +73 -0
  56. package/registry/frameworks/hono/src/components/ToolCall.tsx +89 -0
  57. package/registry/frameworks/hono/src/lib/agent/types.ts +4 -0
  58. package/registry/frameworks/hono/src/lib/chat/threads-client.ts +57 -0
  59. package/registry/frameworks/hono/src/main.tsx +11 -0
  60. package/registry/frameworks/hono/src/styles/globals.css +714 -0
  61. package/registry/frameworks/hono/src/vite-env.d.ts +1 -0
  62. package/registry/frameworks/hono/tsconfig.app.json +22 -0
  63. package/registry/frameworks/hono/tsconfig.json +7 -0
  64. package/registry/frameworks/hono/tsconfig.worker.json +18 -0
  65. package/registry/frameworks/hono/vite.config.ts +16 -0
  66. package/registry/frameworks/hono/worker/agent/index.ts +53 -0
  67. package/registry/frameworks/hono/worker/agent/middleware.ts +20 -0
  68. package/registry/frameworks/hono/worker/agent/tools.ts +55 -0
  69. package/registry/frameworks/hono/worker/durable-objects/thread-session.ts +159 -0
  70. package/registry/frameworks/hono/worker/env.d.ts +17 -0
  71. package/registry/frameworks/hono/worker/index.ts +140 -0
  72. package/registry/frameworks/hono/worker/server/registry.ts +39 -0
  73. package/registry/frameworks/hono/worker/server/runs.ts +82 -0
  74. package/registry/frameworks/hono/worker/server/serialize.ts +30 -0
  75. package/registry/frameworks/hono/worker/server/threads.ts +404 -0
  76. package/registry/frameworks/hono/worker/tsconfig.json +4 -0
  77. package/registry/frameworks/hono/wrangler.jsonc +28 -0
  78. package/registry/frameworks/hono.ts +35 -0
  79. package/registry/frameworks/next/.env.example +6 -0
  80. package/registry/frameworks/next/README.md +173 -0
  81. package/registry/frameworks/next/app/api/threads/[threadId]/commands/route.ts +21 -0
  82. package/registry/frameworks/next/app/api/threads/[threadId]/history/route.ts +35 -0
  83. package/registry/frameworks/next/app/api/threads/[threadId]/route.ts +13 -0
  84. package/registry/frameworks/next/app/api/threads/[threadId]/state/route.ts +51 -0
  85. package/registry/frameworks/next/app/api/threads/[threadId]/stream/route.ts +30 -0
  86. package/registry/frameworks/next/app/api/threads/route.ts +11 -0
  87. package/registry/frameworks/next/app/favicon.ico +0 -0
  88. package/registry/frameworks/next/app/globals.css +712 -0
  89. package/registry/frameworks/next/app/layout.tsx +34 -0
  90. package/registry/frameworks/next/app/page.tsx +5 -0
  91. package/registry/frameworks/next/components/Chat.tsx +124 -0
  92. package/registry/frameworks/next/components/ChatApp.tsx +129 -0
  93. package/registry/frameworks/next/components/Conversation.tsx +90 -0
  94. package/registry/frameworks/next/components/MessageBubbles.tsx +88 -0
  95. package/registry/frameworks/next/components/MessageReasoning.tsx +71 -0
  96. package/registry/frameworks/next/components/MessageThread.tsx +135 -0
  97. package/registry/frameworks/next/components/StreamingIndicator.tsx +36 -0
  98. package/registry/frameworks/next/components/Subagents.tsx +120 -0
  99. package/registry/frameworks/next/components/ThemeIcons.tsx +31 -0
  100. package/registry/frameworks/next/components/ThreadHistory.tsx +73 -0
  101. package/registry/frameworks/next/components/ToolCall.tsx +89 -0
  102. package/registry/frameworks/next/eslint.config.mjs +18 -0
  103. package/registry/frameworks/next/lib/agent/index.ts +95 -0
  104. package/registry/frameworks/next/lib/agent/middleware.ts +40 -0
  105. package/registry/frameworks/next/lib/agent/tools.ts +66 -0
  106. package/registry/frameworks/next/lib/chat/threads-client.ts +57 -0
  107. package/registry/frameworks/next/lib/server/registry.ts +57 -0
  108. package/registry/frameworks/next/lib/server/serialize.ts +32 -0
  109. package/registry/frameworks/next/lib/server/session.ts +212 -0
  110. package/registry/frameworks/next/lib/server/threads.ts +406 -0
  111. package/registry/frameworks/next/next.config.ts +7 -0
  112. package/registry/frameworks/next/package.json +37 -0
  113. package/registry/frameworks/next/postcss.config.mjs +7 -0
  114. package/registry/frameworks/next/public/file.svg +1 -0
  115. package/registry/frameworks/next/public/globe.svg +1 -0
  116. package/registry/frameworks/next/public/next.svg +1 -0
  117. package/registry/frameworks/next/public/vercel.svg +1 -0
  118. package/registry/frameworks/next/public/window.svg +1 -0
  119. package/registry/frameworks/next/tsconfig.json +34 -0
  120. package/registry/frameworks/next.ts +17 -0
  121. package/registry/frameworks/nuxt/.env.example +3 -0
  122. package/registry/frameworks/nuxt/README.md +133 -0
  123. package/registry/frameworks/nuxt/app/app.vue +26 -0
  124. package/registry/frameworks/nuxt/app/assets/css/main.css +707 -0
  125. package/registry/frameworks/nuxt/app/components/Chat.vue +105 -0
  126. package/registry/frameworks/nuxt/app/components/ChatApp.vue +89 -0
  127. package/registry/frameworks/nuxt/app/components/ChatThread.vue +27 -0
  128. package/registry/frameworks/nuxt/app/components/MessageBubble.vue +60 -0
  129. package/registry/frameworks/nuxt/app/components/MessageBubbles.vue +213 -0
  130. package/registry/frameworks/nuxt/app/components/MessageList.vue +51 -0
  131. package/registry/frameworks/nuxt/app/components/MessageReasoning.vue +53 -0
  132. package/registry/frameworks/nuxt/app/components/StreamingIndicator.vue +9 -0
  133. package/registry/frameworks/nuxt/app/components/SubagentDetail.vue +51 -0
  134. package/registry/frameworks/nuxt/app/components/SubagentList.vue +49 -0
  135. package/registry/frameworks/nuxt/app/components/ThemeToggle.vue +43 -0
  136. package/registry/frameworks/nuxt/app/components/ThreadHistory.vue +65 -0
  137. package/registry/frameworks/nuxt/app/components/ToolCall.vue +81 -0
  138. package/registry/frameworks/nuxt/app/components/TypingDots.vue +14 -0
  139. package/registry/frameworks/nuxt/app/composables/useTheme.ts +14 -0
  140. package/registry/frameworks/nuxt/app/utils/streaming.ts +44 -0
  141. package/registry/frameworks/nuxt/app/utils/threads.ts +57 -0
  142. package/registry/frameworks/nuxt/nuxt.config.ts +6 -0
  143. package/registry/frameworks/nuxt/package.json +28 -0
  144. package/registry/frameworks/nuxt/public/favicon.ico +0 -0
  145. package/registry/frameworks/nuxt/public/robots.txt +2 -0
  146. package/registry/frameworks/nuxt/server/agent/index.ts +89 -0
  147. package/registry/frameworks/nuxt/server/agent/middleware.ts +38 -0
  148. package/registry/frameworks/nuxt/server/agent/tools.ts +66 -0
  149. package/registry/frameworks/nuxt/server/api/threads/[threadId]/commands.post.ts +16 -0
  150. package/registry/frameworks/nuxt/server/api/threads/[threadId]/history.post.ts +37 -0
  151. package/registry/frameworks/nuxt/server/api/threads/[threadId]/index.delete.ts +12 -0
  152. package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.get.ts +22 -0
  153. package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.post.ts +32 -0
  154. package/registry/frameworks/nuxt/server/api/threads/[threadId]/stream.post.ts +24 -0
  155. package/registry/frameworks/nuxt/server/api/threads/index.get.ts +13 -0
  156. package/registry/frameworks/nuxt/server/utils/runtime.ts +42 -0
  157. package/registry/frameworks/nuxt/server/utils/serialize.ts +30 -0
  158. package/registry/frameworks/nuxt/server/utils/session.ts +210 -0
  159. package/registry/frameworks/nuxt/server/utils/threads.ts +404 -0
  160. package/registry/frameworks/nuxt/tsconfig.json +18 -0
  161. package/registry/frameworks/nuxt.ts +17 -0
  162. package/registry/frameworks/vite/.env.example +20 -0
  163. package/registry/frameworks/vite/README.md +149 -0
  164. package/registry/frameworks/vite/agent/index.ts +59 -0
  165. package/registry/frameworks/vite/agent/middleware.ts +24 -0
  166. package/registry/frameworks/vite/agent/tools.ts +64 -0
  167. package/registry/frameworks/vite/index.html +23 -0
  168. package/registry/frameworks/vite/langgraph.json +16 -0
  169. package/registry/frameworks/vite/package.json +39 -0
  170. package/registry/frameworks/vite/public/favicon.ico +0 -0
  171. package/registry/frameworks/vite/scripts/vite-langgraph-proxy.ts +34 -0
  172. package/registry/frameworks/vite/src/components/Chat.tsx +124 -0
  173. package/registry/frameworks/vite/src/components/ChatApp.tsx +122 -0
  174. package/registry/frameworks/vite/src/components/Conversation.tsx +91 -0
  175. package/registry/frameworks/vite/src/components/MessageBubbles.tsx +88 -0
  176. package/registry/frameworks/vite/src/components/MessageReasoning.tsx +71 -0
  177. package/registry/frameworks/vite/src/components/MessageThread.tsx +135 -0
  178. package/registry/frameworks/vite/src/components/StreamingIndicator.tsx +36 -0
  179. package/registry/frameworks/vite/src/components/Subagents.tsx +120 -0
  180. package/registry/frameworks/vite/src/components/ThemeIcons.tsx +31 -0
  181. package/registry/frameworks/vite/src/components/ThreadHistory.tsx +73 -0
  182. package/registry/frameworks/vite/src/components/ToolCall.tsx +89 -0
  183. package/registry/frameworks/vite/src/lib/agent-type.ts +4 -0
  184. package/registry/frameworks/vite/src/lib/chat/threads-client.ts +114 -0
  185. package/registry/frameworks/vite/src/main.tsx +11 -0
  186. package/registry/frameworks/vite/src/styles/globals.css +714 -0
  187. package/registry/frameworks/vite/src/vite-env.d.ts +11 -0
  188. package/registry/frameworks/vite/tsconfig.app.json +24 -0
  189. package/registry/frameworks/vite/tsconfig.json +7 -0
  190. package/registry/frameworks/vite/tsconfig.node.json +21 -0
  191. package/registry/frameworks/vite/vercel.json +3 -0
  192. package/registry/frameworks/vite/vite.config.ts +24 -0
  193. package/registry/frameworks/vite.ts +17 -0
@@ -0,0 +1,124 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
+ import { HumanMessage } from "@langchain/core/messages";
6
+ import { useStreamContext } from "@langchain/react";
7
+
8
+ import type { Agent } from "@/lib/agent/types";
9
+ import { Conversation } from "./Conversation";
10
+ import { SubagentDetail } from "./Subagents";
11
+
12
+ const EXAMPLE_PROMPT =
13
+ "Research LangGraph streaming, and separately calculate 42 * 17.";
14
+
15
+ export function Chat({
16
+ onRunSettled,
17
+ }: {
18
+ threadId: string;
19
+ /** Called when a run settles, so the sidebar can refresh titles/order. */
20
+ onRunSettled: () => void;
21
+ }) {
22
+ const stream = useStreamContext<Agent>();
23
+ const [content, setContent] = useState(EXAMPLE_PROMPT);
24
+ const [openSubagentId, setOpenSubagentId] = useState<string | null>(null);
25
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
26
+
27
+ // Refresh the sidebar whenever a run finishes (titles derive from the first
28
+ // message; order from the latest checkpoint, both owned by the server).
29
+ useEffect(() => {
30
+ if (!stream.isLoading) onRunSettled();
31
+ }, [stream.isLoading, onRunSettled]);
32
+
33
+ function autoGrow() {
34
+ const node = textareaRef.current;
35
+ if (!node) return;
36
+ node.style.height = "auto";
37
+ node.style.height = `${Math.min(node.scrollHeight, 200)}px`;
38
+ }
39
+
40
+ const subagents = [...stream.subagents.values()];
41
+ const openSubagent = openSubagentId
42
+ ? subagents.find((snapshot) => snapshot.id === openSubagentId)
43
+ : undefined;
44
+
45
+ function handleSubmit() {
46
+ const nextContent = content.trim();
47
+ if (nextContent.length === 0 || stream.isLoading) return;
48
+
49
+ setContent("");
50
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
51
+ void stream.submit({
52
+ messages: [new HumanMessage(nextContent)],
53
+ });
54
+ }
55
+
56
+ // Subagent detail view: breadcrumb + that subagent's chat (no composer).
57
+ if (openSubagent) {
58
+ return (
59
+ <main className="chat-main">
60
+ <nav aria-label="Breadcrumb" className="breadcrumb">
61
+ <button
62
+ className="crumb-link"
63
+ onClick={() => setOpenSubagentId(null)}
64
+ type="button"
65
+ >
66
+ Main chat
67
+ </button>
68
+ <span className="crumb-sep">/</span>
69
+ <span className="crumb-current">{openSubagent.name}</span>
70
+ </nav>
71
+ <div className="conversation">
72
+ <div className="conversation-inner">
73
+ <SubagentDetail snapshot={openSubagent} />
74
+ </div>
75
+ </div>
76
+ </main>
77
+ );
78
+ }
79
+
80
+ // Main view: messages + subagent chips, with the composer pinned at the bottom.
81
+ return (
82
+ <main className="chat-main">
83
+ <div className="conversation">
84
+ <div className="conversation-inner">
85
+ <Conversation onOpenSubagent={setOpenSubagentId} />
86
+ </div>
87
+ </div>
88
+
89
+ <div className="composer-bar">
90
+ <form
91
+ className="composer"
92
+ onSubmit={(event) => {
93
+ event.preventDefault();
94
+ handleSubmit();
95
+ }}
96
+ >
97
+ <textarea
98
+ aria-label="Message"
99
+ onChange={(event) => {
100
+ setContent(event.target.value);
101
+ autoGrow();
102
+ }}
103
+ onKeyDown={(event) => {
104
+ if (event.key === "Enter" && !event.shiftKey) {
105
+ event.preventDefault();
106
+ handleSubmit();
107
+ }
108
+ }}
109
+ placeholder="Ask for research, a calculation, or both..."
110
+ ref={textareaRef}
111
+ rows={1}
112
+ value={content}
113
+ />
114
+ <button
115
+ disabled={content.trim() === "" || stream.isLoading}
116
+ type="submit"
117
+ >
118
+ Send
119
+ </button>
120
+ </form>
121
+ </div>
122
+ </main>
123
+ );
124
+ }
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+
5
+ import { HttpAgentServerAdapter, StreamProvider } from "@langchain/react";
6
+
7
+ import {
8
+ type ThreadSummary,
9
+ createThread,
10
+ deleteThread,
11
+ fetchThreads,
12
+ getApiUrl,
13
+ } from "@/lib/chat/threads-client";
14
+ import { Chat } from "./Chat";
15
+ import { ThreadHistory } from "./ThreadHistory";
16
+ import { MoonIcon, SunIcon } from "./ThemeIcons";
17
+
18
+ export function ChatApp() {
19
+ const [mounted, setMounted] = useState(false);
20
+ const [theme, setTheme] = useState<"dark" | "light">("dark");
21
+ const [threads, setThreads] = useState<ThreadSummary[]>([]);
22
+ const [threadId, setThreadId] = useState<string>("");
23
+ // Guards the one-time init against React Strict Mode's double-invoke in dev,
24
+ // which would otherwise create two threads when none exist yet.
25
+ const initStarted = useRef(false);
26
+
27
+ const refreshThreads = useCallback(async () => {
28
+ setThreads(await fetchThreads());
29
+ }, []);
30
+
31
+ // On mount, load threads from the server (single source of truth). If none
32
+ // exist yet, create one. All setState happens in an async callback, so the
33
+ // effect body never calls setState synchronously.
34
+ useEffect(() => {
35
+ if (initStarted.current) return;
36
+ initStarted.current = true;
37
+ void (async () => {
38
+ const list = await fetchThreads();
39
+ if (list.length > 0) {
40
+ setThreads(list);
41
+ setThreadId(list[0].id);
42
+ } else {
43
+ const id = await createThread();
44
+ setThreads(await fetchThreads());
45
+ setThreadId(id);
46
+ }
47
+ setMounted(true);
48
+ })();
49
+ }, []);
50
+
51
+ const transport = useMemo(() => {
52
+ if (!threadId) return null;
53
+ return new HttpAgentServerAdapter({
54
+ apiUrl: getApiUrl(),
55
+ threadId,
56
+ paths: {
57
+ commands: `/threads/${threadId}/commands`,
58
+ stream: `/threads/${threadId}/stream`,
59
+ },
60
+ });
61
+ }, [threadId]);
62
+
63
+ const handleSelect = useCallback(
64
+ (id: string) => {
65
+ if (id !== threadId) setThreadId(id);
66
+ },
67
+ [threadId],
68
+ );
69
+
70
+ const handleCreate = useCallback(async () => {
71
+ const id = await createThread();
72
+ await refreshThreads();
73
+ setThreadId(id);
74
+ }, [refreshThreads]);
75
+
76
+ const handleDelete = useCallback(
77
+ async (id: string) => {
78
+ await deleteThread(id);
79
+ const list = await fetchThreads();
80
+ setThreads(list);
81
+ if (id !== threadId) return;
82
+ if (list.length > 0) {
83
+ setThreadId(list[0].id);
84
+ } else {
85
+ const freshId = await createThread();
86
+ setThreads(await fetchThreads());
87
+ setThreadId(freshId);
88
+ }
89
+ },
90
+ [threadId],
91
+ );
92
+
93
+ const shellClassName = `app-shell ${theme === "light" ? "light" : ""}`;
94
+
95
+ if (!mounted || !threadId || !transport) {
96
+ return (
97
+ <div className={shellClassName}>
98
+ <div className="empty-state center">Preparing chat…</div>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ return (
104
+ <div className={shellClassName}>
105
+ <button
106
+ aria-label={
107
+ theme === "dark" ? "Switch to light mode" : "Switch to dark mode"
108
+ }
109
+ className="theme-toggle"
110
+ onClick={() => setTheme((cur) => (cur === "dark" ? "light" : "dark"))}
111
+ type="button"
112
+ >
113
+ {theme === "dark" ? <SunIcon /> : <MoonIcon />}
114
+ </button>
115
+
116
+ <ThreadHistory
117
+ activeThreadId={threadId}
118
+ onCreate={handleCreate}
119
+ onDelete={handleDelete}
120
+ onSelect={handleSelect}
121
+ threads={threads}
122
+ />
123
+
124
+ <StreamProvider key={threadId} threadId={threadId} transport={transport}>
125
+ <Chat onRunSettled={refreshThreads} threadId={threadId} />
126
+ </StreamProvider>
127
+ </div>
128
+ );
129
+ }
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+
5
+ import type { SubagentDiscoverySnapshot } from "@langchain/langgraph-sdk/stream";
6
+ import { useStreamContext } from "@langchain/react";
7
+
8
+ import type { Agent } from "@/lib/agent/types";
9
+ import { MessageThread } from "./MessageThread";
10
+ import {
11
+ shouldShowTypingIndicator,
12
+ StreamingIndicator,
13
+ } from "./StreamingIndicator";
14
+ import { SubagentList, type SubagentCard } from "./Subagents";
15
+
16
+ /**
17
+ * The root conversation, rendered in message order.
18
+ *
19
+ * Subagent `task` delegations are rendered as cards (not raw tool rows) at the
20
+ * position they occur, and all other tool calls are folded into collapsible
21
+ * tool-call chips by {@link MessageThread}.
22
+ */
23
+ export function Conversation({
24
+ onOpenSubagent,
25
+ }: {
26
+ onOpenSubagent: (id: string) => void;
27
+ }) {
28
+ const stream = useStreamContext<Agent>();
29
+
30
+ const messages = useMemo(
31
+ () => stream.messages.filter((message) => message != null),
32
+ [stream.messages],
33
+ );
34
+
35
+ const subagentsById = useMemo(() => {
36
+ const map = new Map<string, SubagentDiscoverySnapshot>();
37
+ for (const snapshot of stream.subagents.values()) {
38
+ map.set(snapshot.id, snapshot);
39
+ }
40
+ return map;
41
+ }, [stream.subagents]);
42
+
43
+ const showTypingIndicator = shouldShowTypingIndicator(
44
+ messages,
45
+ stream.isLoading,
46
+ );
47
+
48
+ return (
49
+ <>
50
+ {messages.length === 0 && !stream.error ? (
51
+ <div className="empty-state">
52
+ Ask a question below. The coordinator will delegate to its subagents
53
+ and stream tokens, tool calls, and results.
54
+ </div>
55
+ ) : null}
56
+
57
+ <MessageThread
58
+ isLoading={stream.isLoading}
59
+ messages={messages}
60
+ taskRenderer={(tasks) => {
61
+ const cards: SubagentCard[] = tasks.map((call, index) => {
62
+ const snapshot = call.id ? subagentsById.get(call.id) : undefined;
63
+ const args = (call.args ?? {}) as Record<string, unknown>;
64
+ return {
65
+ id: call.id ?? `task-${index}`,
66
+ name: snapshot?.name ?? String(args.subagent_type ?? "subagent"),
67
+ task:
68
+ snapshot?.taskInput ??
69
+ (typeof args.description === "string"
70
+ ? args.description
71
+ : undefined),
72
+ status: snapshot?.status ?? "running",
73
+ openable: snapshot != null,
74
+ };
75
+ });
76
+ return <SubagentList cards={cards} onOpen={onOpenSubagent} />;
77
+ }}
78
+ />
79
+
80
+ {showTypingIndicator ? <StreamingIndicator /> : null}
81
+
82
+ {messages.length === 0 && !stream.isLoading && stream.error ? (
83
+ <div className="error">
84
+ Could not reach the agent API. Make sure the dev server is running and
85
+ <code>OPENAI_API_KEY</code> is set, then try again.
86
+ </div>
87
+ ) : null}
88
+ </>
89
+ );
90
+ }
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import { AIMessage, type BaseMessage } from "@langchain/core/messages";
4
+
5
+ type ToolCallLike = {
6
+ name: string;
7
+ args?: Record<string, unknown>;
8
+ id?: string;
9
+ };
10
+
11
+ function messageLabel(message: { type: string; name?: string }) {
12
+ if (message.type === "human") return "You";
13
+ if (message.type === "tool") return `Tool · ${message.name ?? "result"}`;
14
+ if (message.type === "ai") return "Assistant";
15
+ return message.type;
16
+ }
17
+
18
+ function formatToolArgs(args: Record<string, unknown>) {
19
+ const entries = Object.entries(args);
20
+ if (entries.length === 0) return "";
21
+ if (entries.length === 1) return String(entries[0]?.[1] ?? "");
22
+ return JSON.stringify(args);
23
+ }
24
+
25
+ /**
26
+ * Extract reasoning-summary text from a message.
27
+ *
28
+ * Reasoning models surface their summaries as `{ type: "reasoning" }` standard
29
+ * content blocks (see `@langchain/openai`'s Responses API converter). Only AI
30
+ * messages carry reasoning; everything else returns an empty string.
31
+ */
32
+ export function getReasoningText(message: BaseMessage): string {
33
+ if (!AIMessage.isInstance(message)) return "";
34
+ try {
35
+ return message.contentBlocks
36
+ .filter(
37
+ (block): block is { type: "reasoning"; reasoning: string } =>
38
+ (block as { type?: string })?.type === "reasoning",
39
+ )
40
+ .map((block) => block.reasoning)
41
+ .join("")
42
+ .trim();
43
+ } catch {
44
+ return "";
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Renders a single message as a chat bubble with its tool-call rows.
50
+ *
51
+ * `toolCalls` can be passed to override which tool calls are shown (e.g. to
52
+ * hide the `task` calls that are rendered as subagent cards instead).
53
+ */
54
+ export function MessageBubble({
55
+ message,
56
+ toolCalls,
57
+ }: {
58
+ message: BaseMessage;
59
+ toolCalls?: ToolCallLike[];
60
+ }) {
61
+ const calls =
62
+ toolCalls ??
63
+ (AIMessage.isInstance(message) ? (message.tool_calls ?? []) : []);
64
+
65
+ return (
66
+ <div
67
+ className={`message ${message.type === "human" ? "user" : ""} ${
68
+ message.type === "tool" ? "tool" : ""
69
+ }`}
70
+ >
71
+ <span>{messageLabel(message)}</span>
72
+ {calls.length > 0 ? (
73
+ <ul className="tool-call-list">
74
+ {calls.map((toolCall, toolIndex) => {
75
+ const args = formatToolArgs(toolCall.args ?? {});
76
+ return (
77
+ <li key={toolCall.id ?? toolIndex}>
78
+ <strong>{toolCall.name}</strong>
79
+ {args ? `(${args})` : ""}
80
+ </li>
81
+ );
82
+ })}
83
+ </ul>
84
+ ) : null}
85
+ {message.text ? <p>{message.text}</p> : null}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ import { TypingDots } from "./StreamingIndicator";
6
+
7
+ function BrainIcon() {
8
+ return (
9
+ <svg
10
+ aria-hidden
11
+ fill="none"
12
+ stroke="currentColor"
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ strokeWidth="1.6"
16
+ viewBox="0 0 24 24"
17
+ >
18
+ <path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" />
19
+ <path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z" />
20
+ <path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4" />
21
+ <path d="M6 18a4 4 0 0 1-1.967-.516" />
22
+ <path d="M19.967 17.484A4 4 0 0 1 18 18" />
23
+ </svg>
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Minimalistic reasoning block: a `Thinking` toggle with a brain icon and a
29
+ * caret, rendered inline in the conversation (not inside a message bubble).
30
+ *
31
+ * While reasoning tokens stream (`active`), the block auto-expands so you can
32
+ * watch the model think; once the turn finishes it auto-collapses. The caret
33
+ * stays clickable so a finished block can be re-opened.
34
+ */
35
+ export function MessageReasoning({
36
+ reasoning,
37
+ active,
38
+ }: {
39
+ reasoning: string;
40
+ active: boolean;
41
+ }) {
42
+ const [open, setOpen] = useState(active);
43
+
44
+ // Follow the streaming state: expand on start, collapse on finish. The effect
45
+ // only runs when `active` flips, so a manual toggle in between is preserved.
46
+ useEffect(() => {
47
+ // eslint-disable-next-line react-hooks/set-state-in-effect
48
+ setOpen(active);
49
+ }, [active]);
50
+
51
+ return (
52
+ <div className={`reasoning ${open ? "open" : ""}`}>
53
+ <button
54
+ aria-expanded={open}
55
+ className="reasoning-toggle"
56
+ onClick={() => setOpen((value) => !value)}
57
+ type="button"
58
+ >
59
+ <span aria-hidden className="reasoning-caret">
60
+
61
+ </span>
62
+ <span aria-hidden className="reasoning-icon">
63
+ <BrainIcon />
64
+ </span>
65
+ <span className="reasoning-label">Thinking</span>
66
+ {active ? <TypingDots className="inline-dots reasoning-dots" /> : null}
67
+ </button>
68
+ {open ? <p className="reasoning-text">{reasoning}</p> : null}
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,135 @@
1
+ "use client";
2
+
3
+ import { Fragment, type ReactNode, useMemo } from "react";
4
+
5
+ import { AIMessage, type BaseMessage } from "@langchain/core/messages";
6
+
7
+ import { getReasoningText, MessageBubble } from "./MessageBubbles";
8
+ import { MessageReasoning } from "./MessageReasoning";
9
+ import { ToolCall, type ToolCallView } from "./ToolCall";
10
+
11
+ type ToolCallLike = {
12
+ name: string;
13
+ args?: Record<string, unknown>;
14
+ id?: string;
15
+ };
16
+
17
+ /** Deep agents delegate to subagents through the built-in `task` tool. */
18
+ const TASK_TOOL = "task";
19
+
20
+ /**
21
+ * Renders a list of messages as a chat thread.
22
+ *
23
+ * Tool calls are not shown as raw rows. Each tool call is folded together with
24
+ * its result message (matched by `tool_call_id`) into a single collapsible
25
+ * {@link ToolCall} chip, and the standalone tool result messages are hidden.
26
+ *
27
+ * `taskRenderer`, when provided, receives the `task` tool calls of an assistant
28
+ * message and returns a node to render in their place (e.g. subagent cards);
29
+ * those calls are then excluded from the chip rendering.
30
+ */
31
+ export function MessageThread({
32
+ messages,
33
+ isLoading,
34
+ taskRenderer,
35
+ }: {
36
+ messages: BaseMessage[];
37
+ isLoading: boolean;
38
+ taskRenderer?: (
39
+ tasks: ToolCallLike[],
40
+ message: AIMessage,
41
+ index: number,
42
+ ) => ReactNode | null;
43
+ }) {
44
+ const resultsByCallId = useMemo(() => {
45
+ const map = new Map<string, BaseMessage>();
46
+ for (const message of messages) {
47
+ if (message.type !== "tool") continue;
48
+ const id = (message as { tool_call_id?: unknown }).tool_call_id;
49
+ if (typeof id === "string") map.set(id, message);
50
+ }
51
+ return map;
52
+ }, [messages]);
53
+
54
+ const items: { key: string; node: ReactNode }[] = [];
55
+
56
+ messages.forEach((message, index) => {
57
+ // Tool results are folded into their tool-call chip.
58
+ if (message.type === "tool") return;
59
+
60
+ if (AIMessage.isInstance(message)) {
61
+ // Reasoning renders standalone (not inside the assistant bubble), before
62
+ // the answer. Reasoning summaries stream first, then the model produces
63
+ // either text or tool calls — so reasoning is only "active" (streaming)
64
+ // while the run is loading, this is the last message, and it has not yet
65
+ // produced any text or tool calls.
66
+ const reasoning = getReasoningText(message);
67
+ if (reasoning) {
68
+ const hasToolCalls = (message.tool_calls?.length ?? 0) > 0;
69
+ const reasoningActive =
70
+ isLoading &&
71
+ index === messages.length - 1 &&
72
+ !message.text?.trim() &&
73
+ !hasToolCalls;
74
+ items.push({
75
+ key: `reason-${message.id ?? index}`,
76
+ node: (
77
+ <MessageReasoning active={reasoningActive} reasoning={reasoning} />
78
+ ),
79
+ });
80
+ }
81
+
82
+ if (message.text?.trim()) {
83
+ items.push({
84
+ key: message.id ?? `m-${index}`,
85
+ node: <MessageBubble message={message} toolCalls={[]} />,
86
+ });
87
+ }
88
+
89
+ const calls = (message.tool_calls ?? []) as ToolCallLike[];
90
+ const tasks = calls.filter((call) => call.name === TASK_TOOL);
91
+ const chipCalls = taskRenderer
92
+ ? calls.filter((call) => call.name !== TASK_TOOL)
93
+ : calls;
94
+
95
+ if (taskRenderer && tasks.length > 0) {
96
+ const node = taskRenderer(tasks, message, index);
97
+ if (node) items.push({ key: `task-${message.id ?? index}`, node });
98
+ }
99
+
100
+ chipCalls.forEach((call, callIndex) => {
101
+ const result = call.id ? resultsByCallId.get(call.id) : undefined;
102
+ const errored =
103
+ (result as { status?: string } | undefined)?.status === "error";
104
+ const view: ToolCallView = {
105
+ id: call.id ?? `${index}-${callIndex}`,
106
+ name: call.name,
107
+ args: call.args ?? {},
108
+ output: result?.text,
109
+ status: result
110
+ ? errored
111
+ ? "error"
112
+ : "complete"
113
+ : isLoading
114
+ ? "running"
115
+ : "complete",
116
+ };
117
+ items.push({ key: `tc-${view.id}`, node: <ToolCall call={view} /> });
118
+ });
119
+ return;
120
+ }
121
+
122
+ items.push({
123
+ key: message.id ?? `m-${index}`,
124
+ node: <MessageBubble message={message} />,
125
+ });
126
+ });
127
+
128
+ return (
129
+ <>
130
+ {items.map((item) => (
131
+ <Fragment key={item.key}>{item.node}</Fragment>
132
+ ))}
133
+ </>
134
+ );
135
+ }
@@ -0,0 +1,36 @@
1
+ import type { BaseMessage } from "@langchain/core/messages";
2
+
3
+ export function shouldShowTypingIndicator(
4
+ messages: BaseMessage[],
5
+ isLoading: boolean,
6
+ ) {
7
+ if (!isLoading) return false;
8
+
9
+ const last = messages.at(-1);
10
+ if (!last) return true;
11
+ if (last.type === "human" || last.type === "tool") return true;
12
+ if (last.type === "ai" && !last.text?.trim()) return true;
13
+ return false;
14
+ }
15
+
16
+ export function TypingDots({ className }: { className?: string }) {
17
+ return (
18
+ <span aria-hidden className={className ?? "typing-dots"}>
19
+ <span />
20
+ <span />
21
+ <span />
22
+ </span>
23
+ );
24
+ }
25
+
26
+ export function StreamingIndicator() {
27
+ return (
28
+ <div
29
+ aria-label="Loading response"
30
+ className="streaming-indicator"
31
+ role="status"
32
+ >
33
+ <TypingDots />
34
+ </div>
35
+ );
36
+ }