@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,105 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from "vue";
3
+ import { HumanMessage } from "@langchain/core/messages";
4
+ import { useStreamContext } from "@langchain/vue";
5
+
6
+ import MessageList from "./MessageList.vue";
7
+ import SubagentDetail from "./SubagentDetail.vue";
8
+
9
+ const EXAMPLE_PROMPT =
10
+ "Research LangGraph streaming, and separately calculate 42 * 17.";
11
+
12
+ const emit = defineEmits<{ runSettled: [] }>();
13
+
14
+ const stream = useStreamContext();
15
+
16
+ // Refresh the sidebar whenever a run finishes (titles derive from the first
17
+ // message; order from the latest checkpoint, both owned by the server).
18
+ watch(
19
+ () => stream.isLoading.value,
20
+ (loading) => {
21
+ if (!loading) emit("runSettled");
22
+ },
23
+ );
24
+
25
+ const content = ref(EXAMPLE_PROMPT);
26
+ const openSubagentId = ref<string | null>(null);
27
+ const textareaRef = ref<HTMLTextAreaElement | null>(null);
28
+
29
+ const subagents = computed(() => [...stream.subagents.value.values()]);
30
+ const openSubagent = computed(() =>
31
+ openSubagentId.value
32
+ ? subagents.value.find((snapshot) => snapshot.id === openSubagentId.value)
33
+ : undefined,
34
+ );
35
+
36
+ function autoGrow() {
37
+ const node = textareaRef.value;
38
+ if (!node) return;
39
+ node.style.height = "auto";
40
+ node.style.height = `${Math.min(node.scrollHeight, 200)}px`;
41
+ }
42
+
43
+ function handleSubmit() {
44
+ const nextContent = content.value.trim();
45
+ if (nextContent.length === 0 || stream.isLoading.value) return;
46
+
47
+ content.value = "";
48
+ if (textareaRef.value) textareaRef.value.style.height = "auto";
49
+ void stream.submit({ messages: [new HumanMessage(nextContent)] });
50
+ }
51
+
52
+ function handleKeydown(event: KeyboardEvent) {
53
+ if (event.key === "Enter" && !event.shiftKey) {
54
+ event.preventDefault();
55
+ handleSubmit();
56
+ }
57
+ }
58
+ </script>
59
+
60
+ <template>
61
+ <!-- Subagent detail view: breadcrumb + that subagent's chat (no composer). -->
62
+ <main v-if="openSubagent" class="chat-main">
63
+ <nav aria-label="Breadcrumb" class="breadcrumb">
64
+ <button class="crumb-link" type="button" @click="openSubagentId = null">
65
+ Main chat
66
+ </button>
67
+ <span class="crumb-sep">/</span>
68
+ <span class="crumb-current">{{ openSubagent.name }}</span>
69
+ </nav>
70
+ <div class="conversation">
71
+ <div class="conversation-inner">
72
+ <SubagentDetail :snapshot="openSubagent" />
73
+ </div>
74
+ </div>
75
+ </main>
76
+
77
+ <!-- Main view: messages + subagent chips, composer pinned at the bottom. -->
78
+ <main v-else class="chat-main">
79
+ <div class="conversation">
80
+ <div class="conversation-inner">
81
+ <MessageList @open-subagent="openSubagentId = $event" />
82
+ </div>
83
+ </div>
84
+
85
+ <div class="composer-bar">
86
+ <form class="composer" @submit.prevent="handleSubmit">
87
+ <textarea
88
+ ref="textareaRef"
89
+ v-model="content"
90
+ aria-label="Message"
91
+ placeholder="Ask for research, a calculation, or both..."
92
+ rows="1"
93
+ @input="autoGrow"
94
+ @keydown="handleKeydown"
95
+ />
96
+ <button
97
+ :disabled="content.trim() === '' || stream.isLoading.value"
98
+ type="submit"
99
+ >
100
+ Send
101
+ </button>
102
+ </form>
103
+ </div>
104
+ </main>
105
+ </template>
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from "vue";
3
+
4
+ import { useTheme } from "~/composables/useTheme";
5
+ import {
6
+ type ThreadSummary,
7
+ createThread,
8
+ deleteThread,
9
+ fetchThreads,
10
+ } from "~/utils/threads";
11
+ import ChatThread from "./ChatThread.vue";
12
+ import ThemeToggle from "./ThemeToggle.vue";
13
+ import ThreadHistory from "./ThreadHistory.vue";
14
+
15
+ const { theme } = useTheme();
16
+
17
+ const mounted = ref(false);
18
+ const threads = ref<ThreadSummary[]>([]);
19
+ const threadId = ref("");
20
+
21
+ async function refreshThreads() {
22
+ threads.value = await fetchThreads();
23
+ }
24
+
25
+ // On mount, load threads from the server (single source of truth). If none
26
+ // exist yet, create one.
27
+ onMounted(async () => {
28
+ const list = await fetchThreads();
29
+ if (list.length > 0) {
30
+ threads.value = list;
31
+ threadId.value = list[0]!.id;
32
+ } else {
33
+ const id = await createThread();
34
+ threads.value = await fetchThreads();
35
+ threadId.value = id;
36
+ }
37
+ mounted.value = true;
38
+ });
39
+
40
+ function handleSelect(id: string) {
41
+ if (id !== threadId.value) threadId.value = id;
42
+ }
43
+
44
+ async function handleCreate() {
45
+ const id = await createThread();
46
+ await refreshThreads();
47
+ threadId.value = id;
48
+ }
49
+
50
+ async function handleDelete(id: string) {
51
+ await deleteThread(id);
52
+ const list = await fetchThreads();
53
+ threads.value = list;
54
+ if (id !== threadId.value) return;
55
+ if (list.length > 0) {
56
+ threadId.value = list[0]!.id;
57
+ } else {
58
+ const freshId = await createThread();
59
+ threads.value = await fetchThreads();
60
+ threadId.value = freshId;
61
+ }
62
+ }
63
+ </script>
64
+
65
+ <template>
66
+ <div :class="['app-shell', { light: theme === 'light' }]">
67
+ <template v-if="!mounted || !threadId">
68
+ <div class="empty-state center">Preparing chat…</div>
69
+ </template>
70
+
71
+ <template v-else>
72
+ <ThemeToggle />
73
+
74
+ <ThreadHistory
75
+ :active-thread-id="threadId"
76
+ :threads="threads"
77
+ @create="handleCreate"
78
+ @delete="handleDelete"
79
+ @select="handleSelect"
80
+ />
81
+
82
+ <ChatThread
83
+ :key="threadId"
84
+ :thread-id="threadId"
85
+ @run-settled="refreshThreads"
86
+ />
87
+ </template>
88
+ </div>
89
+ </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { HttpAgentServerAdapter, provideStream } from "@langchain/vue";
3
+
4
+ import { getApiUrl } from "~/utils/threads";
5
+ import Chat from "./Chat.vue";
6
+
7
+ const props = defineProps<{ threadId: string }>();
8
+ const emit = defineEmits<{ runSettled: [] }>();
9
+
10
+ // The component is keyed by `threadId` in the parent, so the transport is built
11
+ // once per thread. Provide the stream so every descendant reads it via
12
+ // `useStreamContext()`.
13
+ const transport = new HttpAgentServerAdapter({
14
+ apiUrl: getApiUrl(),
15
+ threadId: props.threadId,
16
+ paths: {
17
+ commands: `/threads/${props.threadId}/commands`,
18
+ stream: `/threads/${props.threadId}/stream`,
19
+ },
20
+ });
21
+
22
+ provideStream({ transport, threadId: props.threadId });
23
+ </script>
24
+
25
+ <template>
26
+ <Chat @run-settled="emit('runSettled')" />
27
+ </template>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
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
+ const props = defineProps<{
12
+ message: BaseMessage;
13
+ /** Override which tool calls are shown (e.g. to hide `task` calls). */
14
+ toolCalls?: ToolCallLike[];
15
+ }>();
16
+
17
+ const calls = computed<ToolCallLike[]>(() => {
18
+ if (props.toolCalls) return props.toolCalls;
19
+ return AIMessage.isInstance(props.message)
20
+ ? (props.message.tool_calls ?? [])
21
+ : [];
22
+ });
23
+
24
+ function messageLabel(message: BaseMessage) {
25
+ if (message.type === "human") return "You";
26
+ if (message.type === "tool") return `Tool · ${message.name ?? "result"}`;
27
+ if (message.type === "ai") return "Assistant";
28
+ return message.type;
29
+ }
30
+
31
+ function formatToolArgs(args: Record<string, unknown>) {
32
+ const entries = Object.entries(args);
33
+ if (entries.length === 0) return "";
34
+ if (entries.length === 1) return String(entries[0]?.[1] ?? "");
35
+ return JSON.stringify(args);
36
+ }
37
+ </script>
38
+
39
+ <template>
40
+ <div
41
+ :class="[
42
+ 'message',
43
+ { user: message.type === 'human', tool: message.type === 'tool' },
44
+ ]"
45
+ >
46
+ <span>{{ messageLabel(message) }}</span>
47
+ <ul v-if="calls.length > 0" class="tool-call-list">
48
+ <li
49
+ v-for="(toolCall, toolIndex) in calls"
50
+ :key="toolCall.id ?? toolIndex"
51
+ >
52
+ <strong>{{ toolCall.name }}</strong>
53
+ <template v-if="formatToolArgs(toolCall.args ?? {})">
54
+ ({{ formatToolArgs(toolCall.args ?? {}) }})
55
+ </template>
56
+ </li>
57
+ </ul>
58
+ <p v-if="message.text">{{ message.text }}</p>
59
+ </div>
60
+ </template>
@@ -0,0 +1,213 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import { AIMessage, type BaseMessage } from "@langchain/core/messages";
4
+ import type { SubagentDiscoverySnapshot } from "@langchain/vue";
5
+
6
+ import { getReasoningText } from "~/utils/streaming";
7
+ import MessageBubble from "./MessageBubble.vue";
8
+ import MessageReasoning from "./MessageReasoning.vue";
9
+ import SubagentList, { type SubagentCard } from "./SubagentList.vue";
10
+ import ToolCall, { type ToolCallView } from "./ToolCall.vue";
11
+
12
+ type ToolCallLike = {
13
+ name: string;
14
+ args?: Record<string, unknown>;
15
+ id?: string;
16
+ };
17
+
18
+ /** Deep agents delegate to subagents through the built-in `task` tool. */
19
+ const TASK_TOOL = "task";
20
+
21
+ const EMPTY_TOOL_CALL: ToolCallView = {
22
+ id: "",
23
+ name: "",
24
+ args: {},
25
+ status: "complete",
26
+ };
27
+
28
+ const props = defineProps<{
29
+ messages: BaseMessage[];
30
+ /** Whether the surrounding run is streaming — drives reasoning/tool-call state. */
31
+ isLoading?: boolean;
32
+ /**
33
+ * Root-stream subagent snapshots. When provided ("root mode"), `task` tool
34
+ * calls render as inline subagent cards at the point of delegation instead of
35
+ * tool-call chips. In both modes, regular tool calls render as collapsible
36
+ * {@link ToolCall} chips with their result folded in, and standalone tool
37
+ * result messages are hidden.
38
+ */
39
+ subagents?: SubagentDiscoverySnapshot[];
40
+ }>();
41
+
42
+ const emit = defineEmits<{ openSubagent: [id: string] }>();
43
+
44
+ const rootMode = computed(() => props.subagents != null);
45
+
46
+ const subagentsById = computed(() => {
47
+ const map = new Map<string, SubagentDiscoverySnapshot>();
48
+ for (const snapshot of props.subagents ?? []) map.set(snapshot.id, snapshot);
49
+ return map;
50
+ });
51
+
52
+ type Item = {
53
+ key: string;
54
+ kind: "reasoning" | "bubble" | "subagents" | "toolcall";
55
+ message: BaseMessage;
56
+ reasoning: string;
57
+ active: boolean;
58
+ cards: SubagentCard[];
59
+ toolCall: ToolCallView;
60
+ };
61
+
62
+ function hasToolCalls(message: BaseMessage): boolean {
63
+ return AIMessage.isInstance(message) && (message.tool_calls?.length ?? 0) > 0;
64
+ }
65
+
66
+ function buildCards(tasks: ToolCallLike[]): SubagentCard[] {
67
+ return tasks.map((call, index) => {
68
+ const snapshot = call.id ? subagentsById.value.get(call.id) : undefined;
69
+ const args = (call.args ?? {}) as Record<string, unknown>;
70
+ return {
71
+ id: call.id ?? `task-${index}`,
72
+ name: snapshot?.name ?? String(args.subagent_type ?? "subagent"),
73
+ task:
74
+ snapshot?.taskInput ??
75
+ (typeof args.description === "string" ? args.description : undefined),
76
+ status: snapshot?.status ?? "running",
77
+ openable: snapshot != null,
78
+ };
79
+ });
80
+ }
81
+
82
+ // Each tool call is folded together with its result message (matched by
83
+ // `tool_call_id`) into a single collapsible chip; reasoning renders standalone
84
+ // before the answer; and `task` delegations render as subagent cards (root
85
+ // mode). Standalone tool result messages are hidden.
86
+ const items = computed<Item[]>(() => {
87
+ const result: Item[] = [];
88
+
89
+ const resultsByCallId = new Map<string, BaseMessage>();
90
+ for (const message of props.messages) {
91
+ if (message.type !== "tool") continue;
92
+ const id = (message as { tool_call_id?: unknown }).tool_call_id;
93
+ if (typeof id === "string") resultsByCallId.set(id, message);
94
+ }
95
+
96
+ props.messages.forEach((message, index) => {
97
+ if (message.type === "tool") return; // folded into its tool-call chip
98
+
99
+ if (AIMessage.isInstance(message)) {
100
+ const calls = (message.tool_calls ?? []) as ToolCallLike[];
101
+ const tasks = rootMode.value
102
+ ? calls.filter((call) => call.name === TASK_TOOL)
103
+ : [];
104
+ const chipCalls = rootMode.value
105
+ ? calls.filter((call) => call.name !== TASK_TOOL)
106
+ : calls;
107
+
108
+ const reasoning = getReasoningText(message);
109
+ if (reasoning) {
110
+ const active =
111
+ Boolean(props.isLoading) &&
112
+ index === props.messages.length - 1 &&
113
+ !message.text?.trim() &&
114
+ !hasToolCalls(message);
115
+ result.push({
116
+ key: `reason-${message.id ?? index}`,
117
+ kind: "reasoning",
118
+ message,
119
+ reasoning,
120
+ active,
121
+ cards: [],
122
+ toolCall: EMPTY_TOOL_CALL,
123
+ });
124
+ }
125
+
126
+ if (message.text?.trim()) {
127
+ result.push({
128
+ key: message.id ?? `m-${index}`,
129
+ kind: "bubble",
130
+ message,
131
+ reasoning: "",
132
+ active: false,
133
+ cards: [],
134
+ toolCall: EMPTY_TOOL_CALL,
135
+ });
136
+ }
137
+
138
+ if (tasks.length > 0) {
139
+ result.push({
140
+ key: `task-${message.id ?? index}`,
141
+ kind: "subagents",
142
+ message,
143
+ reasoning: "",
144
+ active: false,
145
+ cards: buildCards(tasks),
146
+ toolCall: EMPTY_TOOL_CALL,
147
+ });
148
+ }
149
+
150
+ chipCalls.forEach((call, callIndex) => {
151
+ const resultMessage = call.id
152
+ ? resultsByCallId.get(call.id)
153
+ : undefined;
154
+ const errored =
155
+ (resultMessage as { status?: string } | undefined)?.status ===
156
+ "error";
157
+ const view: ToolCallView = {
158
+ id: call.id ?? `${index}-${callIndex}`,
159
+ name: call.name,
160
+ args: call.args ?? {},
161
+ output: resultMessage?.text,
162
+ status: resultMessage
163
+ ? errored
164
+ ? "error"
165
+ : "complete"
166
+ : props.isLoading
167
+ ? "running"
168
+ : "complete",
169
+ };
170
+ result.push({
171
+ key: `tc-${view.id}`,
172
+ kind: "toolcall",
173
+ message,
174
+ reasoning: "",
175
+ active: false,
176
+ cards: [],
177
+ toolCall: view,
178
+ });
179
+ });
180
+ return;
181
+ }
182
+
183
+ result.push({
184
+ key: message.id ?? `m-${index}`,
185
+ kind: "bubble",
186
+ message,
187
+ reasoning: "",
188
+ active: false,
189
+ cards: [],
190
+ toolCall: EMPTY_TOOL_CALL,
191
+ });
192
+ });
193
+
194
+ return result;
195
+ });
196
+ </script>
197
+
198
+ <template>
199
+ <template v-for="item in items" :key="item.key">
200
+ <MessageReasoning
201
+ v-if="item.kind === 'reasoning'"
202
+ :active="item.active"
203
+ :reasoning="item.reasoning"
204
+ />
205
+ <SubagentList
206
+ v-else-if="item.kind === 'subagents'"
207
+ :cards="item.cards"
208
+ @open="emit('openSubagent', $event)"
209
+ />
210
+ <ToolCall v-else-if="item.kind === 'toolcall'" :call="item.toolCall" />
211
+ <MessageBubble v-else :message="item.message" :tool-calls="[]" />
212
+ </template>
213
+ </template>
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type { BaseMessage } from "@langchain/core/messages";
4
+ import { useStreamContext } from "@langchain/vue";
5
+
6
+ import { shouldShowTypingIndicator } from "~/utils/streaming";
7
+ import MessageBubbles from "./MessageBubbles.vue";
8
+ import StreamingIndicator from "./StreamingIndicator.vue";
9
+
10
+ const emit = defineEmits<{ openSubagent: [id: string] }>();
11
+
12
+ const stream = useStreamContext();
13
+
14
+ const messages = computed(() =>
15
+ stream.messages.value.filter(
16
+ (message): message is BaseMessage => message != null,
17
+ ),
18
+ );
19
+
20
+ const subagents = computed(() => [...stream.subagents.value.values()]);
21
+
22
+ const showTypingIndicator = computed(() =>
23
+ shouldShowTypingIndicator(messages.value, stream.isLoading.value),
24
+ );
25
+ </script>
26
+
27
+ <template>
28
+ <div v-if="messages.length === 0 && !stream.error.value" class="empty-state">
29
+ Ask a question below. The coordinator will delegate to its subagents and
30
+ stream tokens, tool calls, and results.
31
+ </div>
32
+
33
+ <MessageBubbles
34
+ :is-loading="stream.isLoading.value"
35
+ :messages="messages"
36
+ :subagents="subagents"
37
+ @open-subagent="emit('openSubagent', $event)"
38
+ />
39
+
40
+ <StreamingIndicator v-if="showTypingIndicator" />
41
+
42
+ <div
43
+ v-if="
44
+ messages.length === 0 && !stream.isLoading.value && stream.error.value
45
+ "
46
+ class="error"
47
+ >
48
+ Could not reach the agent API. Make sure the dev server is running and
49
+ <code>OPENAI_API_KEY</code> is set, then try again.
50
+ </div>
51
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from "vue";
3
+
4
+ import TypingDots from "./TypingDots.vue";
5
+
6
+ const props = defineProps<{ reasoning: string; active: boolean }>();
7
+
8
+ // Follow the streaming state: expand on start, collapse on finish. A manual
9
+ // toggle in between is preserved until `active` flips again.
10
+ const open = ref(props.active);
11
+ watch(
12
+ () => props.active,
13
+ (value) => {
14
+ open.value = value;
15
+ },
16
+ );
17
+ </script>
18
+
19
+ <template>
20
+ <div :class="['reasoning', { open }]">
21
+ <button
22
+ :aria-expanded="open"
23
+ class="reasoning-toggle"
24
+ type="button"
25
+ @click="open = !open"
26
+ >
27
+ <span aria-hidden class="reasoning-caret">▸</span>
28
+ <span aria-hidden class="reasoning-icon">
29
+ <svg
30
+ fill="none"
31
+ stroke="currentColor"
32
+ stroke-linecap="round"
33
+ stroke-linejoin="round"
34
+ stroke-width="1.6"
35
+ viewBox="0 0 24 24"
36
+ >
37
+ <path
38
+ 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"
39
+ />
40
+ <path
41
+ 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"
42
+ />
43
+ <path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4" />
44
+ <path d="M6 18a4 4 0 0 1-1.967-.516" />
45
+ <path d="M19.967 17.484A4 4 0 0 1 18 18" />
46
+ </svg>
47
+ </span>
48
+ <span class="reasoning-label">Thinking</span>
49
+ <TypingDots v-if="active" variant="inline" class="reasoning-dots" />
50
+ </button>
51
+ <p v-if="open" class="reasoning-text">{{ reasoning }}</p>
52
+ </div>
53
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ import TypingDots from "./TypingDots.vue";
3
+ </script>
4
+
5
+ <template>
6
+ <div aria-label="Loading response" class="streaming-indicator" role="status">
7
+ <TypingDots />
8
+ </div>
9
+ </template>
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { computed, toRef } from "vue";
3
+ import type { BaseMessage } from "@langchain/core/messages";
4
+ import {
5
+ useMessages,
6
+ useStreamContext,
7
+ type SubagentDiscoverySnapshot,
8
+ } from "@langchain/vue";
9
+
10
+ import MessageBubbles from "./MessageBubbles.vue";
11
+ import StreamingIndicator from "./StreamingIndicator.vue";
12
+
13
+ const props = defineProps<{ snapshot: SubagentDiscoverySnapshot }>();
14
+
15
+ const stream = useStreamContext();
16
+
17
+ // `useMessages` is scoped to the subagent's namespace, so its tokens, tool
18
+ // calls, and results stream independently from the root conversation.
19
+ const messages = useMessages(stream, toRef(props, "snapshot"));
20
+
21
+ function omitTaskHumanMessage(
22
+ thread: BaseMessage[],
23
+ taskInput?: string,
24
+ ): BaseMessage[] {
25
+ const task = taskInput?.trim();
26
+ if (!task) return thread;
27
+ return thread.filter(
28
+ (message) => message.type !== "human" || message.text?.trim() !== task,
29
+ );
30
+ }
31
+
32
+ const visibleMessages = computed(() =>
33
+ omitTaskHumanMessage(messages.value, props.snapshot.taskInput),
34
+ );
35
+ </script>
36
+
37
+ <template>
38
+ <div v-if="snapshot.taskInput" class="subagent-prompt">
39
+ <span>Task</span>
40
+ <p>{{ snapshot.taskInput }}</p>
41
+ </div>
42
+
43
+ <MessageBubbles
44
+ :is-loading="snapshot.status === 'running'"
45
+ :messages="visibleMessages"
46
+ />
47
+
48
+ <StreamingIndicator
49
+ v-if="snapshot.status === 'running' && visibleMessages.length === 0"
50
+ />
51
+ </template>