@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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +661 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/registry/frameworks/deno/.env.example +6 -0
- package/registry/frameworks/deno/README.md +137 -0
- package/registry/frameworks/deno/client/index.html +23 -0
- package/registry/frameworks/deno/client/package.json +30 -0
- package/registry/frameworks/deno/client/public/favicon.ico +0 -0
- package/registry/frameworks/deno/client/src/components/Chat.tsx +124 -0
- package/registry/frameworks/deno/client/src/components/ChatApp.tsx +129 -0
- package/registry/frameworks/deno/client/src/components/Conversation.tsx +91 -0
- package/registry/frameworks/deno/client/src/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/deno/client/src/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/deno/client/src/components/MessageThread.tsx +135 -0
- package/registry/frameworks/deno/client/src/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/deno/client/src/components/Subagents.tsx +120 -0
- package/registry/frameworks/deno/client/src/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/deno/client/src/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/deno/client/src/components/ToolCall.tsx +89 -0
- package/registry/frameworks/deno/client/src/lib/agent-type.ts +4 -0
- package/registry/frameworks/deno/client/src/lib/chat/threads-client.ts +51 -0
- package/registry/frameworks/deno/client/src/main.tsx +11 -0
- package/registry/frameworks/deno/client/src/styles/globals.css +714 -0
- package/registry/frameworks/deno/client/src/vite-env.d.ts +1 -0
- package/registry/frameworks/deno/client/tsconfig.app.json +7 -0
- package/registry/frameworks/deno/client/tsconfig.json +24 -0
- package/registry/frameworks/deno/client/tsconfig.node.json +19 -0
- package/registry/frameworks/deno/client/vite.config.ts +24 -0
- package/registry/frameworks/deno/deno.json +16 -0
- package/registry/frameworks/deno/main.ts +23 -0
- package/registry/frameworks/deno/package.json +14 -0
- package/registry/frameworks/deno/server/agent/index.ts +60 -0
- package/registry/frameworks/deno/server/agent/middleware.ts +24 -0
- package/registry/frameworks/deno/server/agent/tools.ts +64 -0
- package/registry/frameworks/deno/server/registry.ts +40 -0
- package/registry/frameworks/deno/server/routes.ts +114 -0
- package/registry/frameworks/deno/server/serialize.ts +30 -0
- package/registry/frameworks/deno/server/session.ts +210 -0
- package/registry/frameworks/deno/server/threads.ts +404 -0
- package/registry/frameworks/deno.ts +17 -0
- package/registry/frameworks/hono/.env.example +6 -0
- package/registry/frameworks/hono/README.md +186 -0
- package/registry/frameworks/hono/index.html +22 -0
- package/registry/frameworks/hono/package.json +42 -0
- package/registry/frameworks/hono/src/components/Chat.tsx +124 -0
- package/registry/frameworks/hono/src/components/ChatApp.tsx +129 -0
- package/registry/frameworks/hono/src/components/Conversation.tsx +90 -0
- package/registry/frameworks/hono/src/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/hono/src/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/hono/src/components/MessageThread.tsx +135 -0
- package/registry/frameworks/hono/src/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/hono/src/components/Subagents.tsx +120 -0
- package/registry/frameworks/hono/src/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/hono/src/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/hono/src/components/ToolCall.tsx +89 -0
- package/registry/frameworks/hono/src/lib/agent/types.ts +4 -0
- package/registry/frameworks/hono/src/lib/chat/threads-client.ts +57 -0
- package/registry/frameworks/hono/src/main.tsx +11 -0
- package/registry/frameworks/hono/src/styles/globals.css +714 -0
- package/registry/frameworks/hono/src/vite-env.d.ts +1 -0
- package/registry/frameworks/hono/tsconfig.app.json +22 -0
- package/registry/frameworks/hono/tsconfig.json +7 -0
- package/registry/frameworks/hono/tsconfig.worker.json +18 -0
- package/registry/frameworks/hono/vite.config.ts +16 -0
- package/registry/frameworks/hono/worker/agent/index.ts +53 -0
- package/registry/frameworks/hono/worker/agent/middleware.ts +20 -0
- package/registry/frameworks/hono/worker/agent/tools.ts +55 -0
- package/registry/frameworks/hono/worker/durable-objects/thread-session.ts +159 -0
- package/registry/frameworks/hono/worker/env.d.ts +17 -0
- package/registry/frameworks/hono/worker/index.ts +140 -0
- package/registry/frameworks/hono/worker/server/registry.ts +39 -0
- package/registry/frameworks/hono/worker/server/runs.ts +82 -0
- package/registry/frameworks/hono/worker/server/serialize.ts +30 -0
- package/registry/frameworks/hono/worker/server/threads.ts +404 -0
- package/registry/frameworks/hono/worker/tsconfig.json +4 -0
- package/registry/frameworks/hono/wrangler.jsonc +28 -0
- package/registry/frameworks/hono.ts +35 -0
- package/registry/frameworks/next/.env.example +6 -0
- package/registry/frameworks/next/README.md +173 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/commands/route.ts +21 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/history/route.ts +35 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/route.ts +13 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/state/route.ts +51 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/stream/route.ts +30 -0
- package/registry/frameworks/next/app/api/threads/route.ts +11 -0
- package/registry/frameworks/next/app/favicon.ico +0 -0
- package/registry/frameworks/next/app/globals.css +712 -0
- package/registry/frameworks/next/app/layout.tsx +34 -0
- package/registry/frameworks/next/app/page.tsx +5 -0
- package/registry/frameworks/next/components/Chat.tsx +124 -0
- package/registry/frameworks/next/components/ChatApp.tsx +129 -0
- package/registry/frameworks/next/components/Conversation.tsx +90 -0
- package/registry/frameworks/next/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/next/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/next/components/MessageThread.tsx +135 -0
- package/registry/frameworks/next/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/next/components/Subagents.tsx +120 -0
- package/registry/frameworks/next/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/next/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/next/components/ToolCall.tsx +89 -0
- package/registry/frameworks/next/eslint.config.mjs +18 -0
- package/registry/frameworks/next/lib/agent/index.ts +95 -0
- package/registry/frameworks/next/lib/agent/middleware.ts +40 -0
- package/registry/frameworks/next/lib/agent/tools.ts +66 -0
- package/registry/frameworks/next/lib/chat/threads-client.ts +57 -0
- package/registry/frameworks/next/lib/server/registry.ts +57 -0
- package/registry/frameworks/next/lib/server/serialize.ts +32 -0
- package/registry/frameworks/next/lib/server/session.ts +212 -0
- package/registry/frameworks/next/lib/server/threads.ts +406 -0
- package/registry/frameworks/next/next.config.ts +7 -0
- package/registry/frameworks/next/package.json +37 -0
- package/registry/frameworks/next/postcss.config.mjs +7 -0
- package/registry/frameworks/next/public/file.svg +1 -0
- package/registry/frameworks/next/public/globe.svg +1 -0
- package/registry/frameworks/next/public/next.svg +1 -0
- package/registry/frameworks/next/public/vercel.svg +1 -0
- package/registry/frameworks/next/public/window.svg +1 -0
- package/registry/frameworks/next/tsconfig.json +34 -0
- package/registry/frameworks/next.ts +17 -0
- package/registry/frameworks/nuxt/.env.example +3 -0
- package/registry/frameworks/nuxt/README.md +133 -0
- package/registry/frameworks/nuxt/app/app.vue +26 -0
- package/registry/frameworks/nuxt/app/assets/css/main.css +707 -0
- package/registry/frameworks/nuxt/app/components/Chat.vue +105 -0
- package/registry/frameworks/nuxt/app/components/ChatApp.vue +89 -0
- package/registry/frameworks/nuxt/app/components/ChatThread.vue +27 -0
- package/registry/frameworks/nuxt/app/components/MessageBubble.vue +60 -0
- package/registry/frameworks/nuxt/app/components/MessageBubbles.vue +213 -0
- package/registry/frameworks/nuxt/app/components/MessageList.vue +51 -0
- package/registry/frameworks/nuxt/app/components/MessageReasoning.vue +53 -0
- package/registry/frameworks/nuxt/app/components/StreamingIndicator.vue +9 -0
- package/registry/frameworks/nuxt/app/components/SubagentDetail.vue +51 -0
- package/registry/frameworks/nuxt/app/components/SubagentList.vue +49 -0
- package/registry/frameworks/nuxt/app/components/ThemeToggle.vue +43 -0
- package/registry/frameworks/nuxt/app/components/ThreadHistory.vue +65 -0
- package/registry/frameworks/nuxt/app/components/ToolCall.vue +81 -0
- package/registry/frameworks/nuxt/app/components/TypingDots.vue +14 -0
- package/registry/frameworks/nuxt/app/composables/useTheme.ts +14 -0
- package/registry/frameworks/nuxt/app/utils/streaming.ts +44 -0
- package/registry/frameworks/nuxt/app/utils/threads.ts +57 -0
- package/registry/frameworks/nuxt/nuxt.config.ts +6 -0
- package/registry/frameworks/nuxt/package.json +28 -0
- package/registry/frameworks/nuxt/public/favicon.ico +0 -0
- package/registry/frameworks/nuxt/public/robots.txt +2 -0
- package/registry/frameworks/nuxt/server/agent/index.ts +89 -0
- package/registry/frameworks/nuxt/server/agent/middleware.ts +38 -0
- package/registry/frameworks/nuxt/server/agent/tools.ts +66 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/commands.post.ts +16 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/history.post.ts +37 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/index.delete.ts +12 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.get.ts +22 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.post.ts +32 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/stream.post.ts +24 -0
- package/registry/frameworks/nuxt/server/api/threads/index.get.ts +13 -0
- package/registry/frameworks/nuxt/server/utils/runtime.ts +42 -0
- package/registry/frameworks/nuxt/server/utils/serialize.ts +30 -0
- package/registry/frameworks/nuxt/server/utils/session.ts +210 -0
- package/registry/frameworks/nuxt/server/utils/threads.ts +404 -0
- package/registry/frameworks/nuxt/tsconfig.json +18 -0
- package/registry/frameworks/nuxt.ts +17 -0
- package/registry/frameworks/vite/.env.example +20 -0
- package/registry/frameworks/vite/README.md +149 -0
- package/registry/frameworks/vite/agent/index.ts +59 -0
- package/registry/frameworks/vite/agent/middleware.ts +24 -0
- package/registry/frameworks/vite/agent/tools.ts +64 -0
- package/registry/frameworks/vite/index.html +23 -0
- package/registry/frameworks/vite/langgraph.json +16 -0
- package/registry/frameworks/vite/package.json +39 -0
- package/registry/frameworks/vite/public/favicon.ico +0 -0
- package/registry/frameworks/vite/scripts/vite-langgraph-proxy.ts +34 -0
- package/registry/frameworks/vite/src/components/Chat.tsx +124 -0
- package/registry/frameworks/vite/src/components/ChatApp.tsx +122 -0
- package/registry/frameworks/vite/src/components/Conversation.tsx +91 -0
- package/registry/frameworks/vite/src/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/vite/src/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/vite/src/components/MessageThread.tsx +135 -0
- package/registry/frameworks/vite/src/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/vite/src/components/Subagents.tsx +120 -0
- package/registry/frameworks/vite/src/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/vite/src/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/vite/src/components/ToolCall.tsx +89 -0
- package/registry/frameworks/vite/src/lib/agent-type.ts +4 -0
- package/registry/frameworks/vite/src/lib/chat/threads-client.ts +114 -0
- package/registry/frameworks/vite/src/main.tsx +11 -0
- package/registry/frameworks/vite/src/styles/globals.css +714 -0
- package/registry/frameworks/vite/src/vite-env.d.ts +11 -0
- package/registry/frameworks/vite/tsconfig.app.json +24 -0
- package/registry/frameworks/vite/tsconfig.json +7 -0
- package/registry/frameworks/vite/tsconfig.node.json +21 -0
- package/registry/frameworks/vite/vercel.json +3 -0
- package/registry/frameworks/vite/vite.config.ts +24 -0
- package/registry/frameworks/vite.ts +17 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
|
|
5
|
+
import type { BaseMessage } from "@langchain/core/messages";
|
|
6
|
+
import type { SubagentDiscoverySnapshot } from "@langchain/langgraph-sdk/stream";
|
|
7
|
+
import { useMessages, useStreamContext } from "@langchain/react";
|
|
8
|
+
|
|
9
|
+
import type { Agent } from "@/lib/agent-type";
|
|
10
|
+
import { MessageThread } from "./MessageThread";
|
|
11
|
+
import { StreamingIndicator } from "./StreamingIndicator";
|
|
12
|
+
|
|
13
|
+
export type SubagentStatus = SubagentDiscoverySnapshot["status"];
|
|
14
|
+
|
|
15
|
+
/** Lightweight model for a subagent card, derived from a `task` tool call. */
|
|
16
|
+
export type SubagentCard = {
|
|
17
|
+
/** The `task` tool-call id — also the subagent discovery key. */
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
task?: string;
|
|
21
|
+
status: SubagentStatus;
|
|
22
|
+
/** Whether a discovery snapshot exists yet (i.e. the card can be opened). */
|
|
23
|
+
openable: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function statusLabel(status: SubagentStatus) {
|
|
27
|
+
if (status === "running") return "Running";
|
|
28
|
+
if (status === "complete") return "Complete";
|
|
29
|
+
return "Error";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The task prompt is shown separately; skip the matching human message. */
|
|
33
|
+
function omitTaskHumanMessage(
|
|
34
|
+
messages: BaseMessage[],
|
|
35
|
+
taskInput?: string,
|
|
36
|
+
): BaseMessage[] {
|
|
37
|
+
const task = taskInput?.trim();
|
|
38
|
+
if (!task) return messages;
|
|
39
|
+
return messages.filter(
|
|
40
|
+
(message) => message.type !== "human" || message.text?.trim() !== task,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compact, clickable subagent cards showing only the name and task prompt.
|
|
46
|
+
* Rendered inline where the coordinator spawned the subagents. Selecting one
|
|
47
|
+
* drills into its dedicated chat view.
|
|
48
|
+
*/
|
|
49
|
+
export function SubagentList({
|
|
50
|
+
cards,
|
|
51
|
+
onOpen,
|
|
52
|
+
}: {
|
|
53
|
+
cards: SubagentCard[];
|
|
54
|
+
onOpen: (id: string) => void;
|
|
55
|
+
}) {
|
|
56
|
+
if (cards.length === 0) return null;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div aria-label="Subagents" className="subagent-list">
|
|
60
|
+
{cards.map((card) => (
|
|
61
|
+
<button
|
|
62
|
+
className="subagent-chip"
|
|
63
|
+
disabled={!card.openable}
|
|
64
|
+
key={card.id}
|
|
65
|
+
onClick={() => card.openable && onOpen(card.id)}
|
|
66
|
+
type="button"
|
|
67
|
+
>
|
|
68
|
+
<span className="subagent-chip-head">
|
|
69
|
+
<span className="subagent-chip-name">{card.name}</span>
|
|
70
|
+
<span className={`subagent-status status-${card.status}`}>
|
|
71
|
+
{statusLabel(card.status)}
|
|
72
|
+
</span>
|
|
73
|
+
</span>
|
|
74
|
+
{card.task ? (
|
|
75
|
+
<span className="subagent-chip-task">{card.task}</span>
|
|
76
|
+
) : null}
|
|
77
|
+
</button>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The chat interface for a single subagent.
|
|
85
|
+
*
|
|
86
|
+
* `useMessages` is scoped to the subagent's namespace, so its tokens, tool
|
|
87
|
+
* calls, and results stream independently from the root conversation.
|
|
88
|
+
*/
|
|
89
|
+
export function SubagentDetail({
|
|
90
|
+
snapshot,
|
|
91
|
+
}: {
|
|
92
|
+
snapshot: SubagentDiscoverySnapshot;
|
|
93
|
+
}) {
|
|
94
|
+
const stream = useStreamContext<Agent>();
|
|
95
|
+
const messages = useMessages(stream, snapshot);
|
|
96
|
+
const visibleMessages = useMemo(
|
|
97
|
+
() => omitTaskHumanMessage(messages, snapshot.taskInput),
|
|
98
|
+
[messages, snapshot.taskInput],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
{snapshot.taskInput ? (
|
|
104
|
+
<div className="subagent-prompt">
|
|
105
|
+
<span>Task</span>
|
|
106
|
+
<p>{snapshot.taskInput}</p>
|
|
107
|
+
</div>
|
|
108
|
+
) : null}
|
|
109
|
+
|
|
110
|
+
<MessageThread
|
|
111
|
+
isLoading={snapshot.status === "running"}
|
|
112
|
+
messages={visibleMessages}
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
{snapshot.status === "running" && visibleMessages.length === 0 ? (
|
|
116
|
+
<StreamingIndicator />
|
|
117
|
+
) : null}
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function SunIcon() {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
aria-hidden
|
|
5
|
+
fill="none"
|
|
6
|
+
stroke="currentColor"
|
|
7
|
+
strokeLinecap="round"
|
|
8
|
+
strokeWidth="1.5"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
>
|
|
11
|
+
<circle cx="12" cy="12" r="4" />
|
|
12
|
+
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function MoonIcon() {
|
|
18
|
+
return (
|
|
19
|
+
<svg
|
|
20
|
+
aria-hidden
|
|
21
|
+
fill="none"
|
|
22
|
+
stroke="currentColor"
|
|
23
|
+
strokeLinecap="round"
|
|
24
|
+
strokeLinejoin="round"
|
|
25
|
+
strokeWidth="1.5"
|
|
26
|
+
viewBox="0 0 24 24"
|
|
27
|
+
>
|
|
28
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
29
|
+
</svg>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ThreadSummary } from "@/lib/chat/threads-client";
|
|
4
|
+
|
|
5
|
+
function formatTime(updatedAt: string | null) {
|
|
6
|
+
if (!updatedAt) return "";
|
|
7
|
+
const date = new Date(updatedAt);
|
|
8
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
9
|
+
return date.toLocaleString(undefined, {
|
|
10
|
+
month: "short",
|
|
11
|
+
day: "numeric",
|
|
12
|
+
hour: "numeric",
|
|
13
|
+
minute: "2-digit",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ThreadHistory({
|
|
18
|
+
threads,
|
|
19
|
+
activeThreadId,
|
|
20
|
+
onSelect,
|
|
21
|
+
onCreate,
|
|
22
|
+
onDelete,
|
|
23
|
+
}: {
|
|
24
|
+
threads: ThreadSummary[];
|
|
25
|
+
activeThreadId: string;
|
|
26
|
+
onSelect: (threadId: string) => void;
|
|
27
|
+
onCreate: () => void;
|
|
28
|
+
onDelete: (threadId: string) => void;
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<aside aria-label="Thread history" className="sidebar">
|
|
32
|
+
<div className="sidebar-head">
|
|
33
|
+
<span className="eyebrow">History</span>
|
|
34
|
+
<button className="new-thread" onClick={onCreate} type="button">
|
|
35
|
+
+ New
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<ul className="thread-list">
|
|
40
|
+
{threads.length === 0 ? (
|
|
41
|
+
<li className="thread-empty">No conversations yet.</li>
|
|
42
|
+
) : null}
|
|
43
|
+
{threads.map((thread) => (
|
|
44
|
+
<li
|
|
45
|
+
className={`thread-item ${
|
|
46
|
+
thread.id === activeThreadId ? "active" : ""
|
|
47
|
+
}`}
|
|
48
|
+
key={thread.id}
|
|
49
|
+
>
|
|
50
|
+
<button
|
|
51
|
+
className="thread-open"
|
|
52
|
+
onClick={() => onSelect(thread.id)}
|
|
53
|
+
type="button"
|
|
54
|
+
>
|
|
55
|
+
<span className="thread-title">{thread.title}</span>
|
|
56
|
+
<span className="thread-time">
|
|
57
|
+
{formatTime(thread.updatedAt)}
|
|
58
|
+
</span>
|
|
59
|
+
</button>
|
|
60
|
+
<button
|
|
61
|
+
aria-label="Delete conversation"
|
|
62
|
+
className="thread-delete"
|
|
63
|
+
onClick={() => onDelete(thread.id)}
|
|
64
|
+
type="button"
|
|
65
|
+
>
|
|
66
|
+
×
|
|
67
|
+
</button>
|
|
68
|
+
</li>
|
|
69
|
+
))}
|
|
70
|
+
</ul>
|
|
71
|
+
</aside>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
export type ToolCallStatus = "running" | "complete" | "error";
|
|
6
|
+
|
|
7
|
+
export type ToolCallView = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
args: Record<string, unknown>;
|
|
11
|
+
output?: string;
|
|
12
|
+
status: ToolCallStatus;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function stringifyArgs(args: Record<string, unknown>) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(args, null, 2);
|
|
18
|
+
} catch {
|
|
19
|
+
return String(args);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function statusLabel(status: ToolCallStatus) {
|
|
24
|
+
if (status === "running") return "Running";
|
|
25
|
+
if (status === "error") return "Error";
|
|
26
|
+
return "Done";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ToolIcon() {
|
|
30
|
+
return (
|
|
31
|
+
<svg
|
|
32
|
+
aria-hidden
|
|
33
|
+
fill="none"
|
|
34
|
+
stroke="currentColor"
|
|
35
|
+
strokeLinecap="round"
|
|
36
|
+
strokeLinejoin="round"
|
|
37
|
+
strokeWidth="1.6"
|
|
38
|
+
viewBox="0 0 24 24"
|
|
39
|
+
>
|
|
40
|
+
<path d="M14.7 6.3a4 4 0 0 1-5.4 5.4L4 17v3h3l5.3-5.3a4 4 0 0 1 5.4-5.4l-2.7 2.7-1.4-1.4 2.7-2.7a4 4 0 0 0-1.6.4z" />
|
|
41
|
+
</svg>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A subtle, collapsible representation of a single tool call: an icon, the
|
|
47
|
+
* tool name, and its status. Expanding reveals the stringified input (args)
|
|
48
|
+
* and output (the tool result).
|
|
49
|
+
*/
|
|
50
|
+
export function ToolCall({ call }: { call: ToolCallView }) {
|
|
51
|
+
const [open, setOpen] = useState(false);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={`toolcall status-${call.status}`}>
|
|
55
|
+
<button
|
|
56
|
+
aria-expanded={open}
|
|
57
|
+
className="toolcall-head"
|
|
58
|
+
onClick={() => setOpen((value) => !value)}
|
|
59
|
+
type="button"
|
|
60
|
+
>
|
|
61
|
+
<span className="toolcall-icon">
|
|
62
|
+
<ToolIcon />
|
|
63
|
+
</span>
|
|
64
|
+
<span className="toolcall-name">{call.name}</span>
|
|
65
|
+
<span className={`subagent-status status-${call.status}`}>
|
|
66
|
+
{statusLabel(call.status)}
|
|
67
|
+
</span>
|
|
68
|
+
<span aria-hidden className="toolcall-chevron">
|
|
69
|
+
{open ? "▾" : "▸"}
|
|
70
|
+
</span>
|
|
71
|
+
</button>
|
|
72
|
+
|
|
73
|
+
{open ? (
|
|
74
|
+
<div className="toolcall-body">
|
|
75
|
+
<div className="toolcall-section">
|
|
76
|
+
<span>Input</span>
|
|
77
|
+
<pre>{stringifyArgs(call.args)}</pre>
|
|
78
|
+
</div>
|
|
79
|
+
{call.output != null && call.output !== "" ? (
|
|
80
|
+
<div className="toolcall-section">
|
|
81
|
+
<span>Output</span>
|
|
82
|
+
<pre>{call.output}</pre>
|
|
83
|
+
</div>
|
|
84
|
+
) : null}
|
|
85
|
+
</div>
|
|
86
|
+
) : null}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side thread helpers for a LangSmith Deployment.
|
|
3
|
+
*
|
|
4
|
+
* Uses the LangGraph SDK against the Agent Server's built-in `/threads`
|
|
5
|
+
* API — no custom protocol routes required.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Client } from "@langchain/langgraph-sdk/client";
|
|
9
|
+
|
|
10
|
+
/** Graph id from `agent/langgraph.json`. */
|
|
11
|
+
const ASSISTANT_ID = "agent";
|
|
12
|
+
|
|
13
|
+
/** LangSmith deployment root URL (no path suffix). */
|
|
14
|
+
export function getAgentApiUrl(): string {
|
|
15
|
+
const configured = import.meta.env.VITE_AGENT_API_URL?.trim();
|
|
16
|
+
if (configured) return configured.replace(/\/$/, "");
|
|
17
|
+
// Dev: same-origin Vite proxy avoids CORS (see scripts/vite-langgraph-proxy.ts).
|
|
18
|
+
if (import.meta.env.DEV) {
|
|
19
|
+
return `${window.location.origin}/api/langgraph`;
|
|
20
|
+
}
|
|
21
|
+
return "http://localhost:2025";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getApiKey(): string | undefined {
|
|
25
|
+
return import.meta.env.LANGSMITH_API_KEY?.trim() || undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getClient(): Client {
|
|
29
|
+
return new Client({
|
|
30
|
+
apiUrl: getAgentApiUrl(),
|
|
31
|
+
apiKey: getApiKey(),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Summary of a thread for the history sidebar. */
|
|
36
|
+
export type ThreadSummary = {
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
updatedAt: string | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const UNTITLED = "New conversation";
|
|
43
|
+
|
|
44
|
+
function deriveTitle(values: unknown): string {
|
|
45
|
+
if (typeof values !== "object" || values == null) return UNTITLED;
|
|
46
|
+
const messages = (values as { messages?: unknown }).messages;
|
|
47
|
+
if (!Array.isArray(messages)) return UNTITLED;
|
|
48
|
+
for (const message of messages) {
|
|
49
|
+
if (typeof message !== "object" || message == null) continue;
|
|
50
|
+
const record = message as { type?: string; content?: unknown };
|
|
51
|
+
if (record.type !== "human") continue;
|
|
52
|
+
const { content } = record;
|
|
53
|
+
const text =
|
|
54
|
+
typeof content === "string"
|
|
55
|
+
? content
|
|
56
|
+
: Array.isArray(content)
|
|
57
|
+
? content
|
|
58
|
+
.map((block) =>
|
|
59
|
+
typeof block === "object" &&
|
|
60
|
+
block != null &&
|
|
61
|
+
"text" in block &&
|
|
62
|
+
typeof (block as { text?: unknown }).text === "string"
|
|
63
|
+
? (block as { text: string }).text
|
|
64
|
+
: "",
|
|
65
|
+
)
|
|
66
|
+
.join("")
|
|
67
|
+
: "";
|
|
68
|
+
const trimmed = text.trim();
|
|
69
|
+
if (trimmed) return trimmed.slice(0, 80);
|
|
70
|
+
}
|
|
71
|
+
return UNTITLED;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Fetch threads from the deployment, newest first. */
|
|
75
|
+
export async function fetchThreads(): Promise<ThreadSummary[]> {
|
|
76
|
+
const client = getClient();
|
|
77
|
+
const threads = await client.threads.search({
|
|
78
|
+
metadata: { graph_id: ASSISTANT_ID },
|
|
79
|
+
limit: 100,
|
|
80
|
+
sortBy: "updated_at",
|
|
81
|
+
sortOrder: "desc",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const summaries: ThreadSummary[] = [];
|
|
85
|
+
for (const thread of threads) {
|
|
86
|
+
let title = UNTITLED;
|
|
87
|
+
try {
|
|
88
|
+
const state = await client.threads.getState(thread.thread_id);
|
|
89
|
+
title = deriveTitle(state.values);
|
|
90
|
+
} catch {
|
|
91
|
+
// Thread may have no checkpoint yet.
|
|
92
|
+
}
|
|
93
|
+
summaries.push({
|
|
94
|
+
id: thread.thread_id,
|
|
95
|
+
title,
|
|
96
|
+
updatedAt: thread.updated_at ?? null,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return summaries;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Create a thread on the deployment. */
|
|
103
|
+
export async function createThread(): Promise<string> {
|
|
104
|
+
const client = getClient();
|
|
105
|
+
const thread = await client.threads.create({
|
|
106
|
+
metadata: { graph_id: ASSISTANT_ID },
|
|
107
|
+
});
|
|
108
|
+
return thread.thread_id;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Delete a thread on the deployment. */
|
|
112
|
+
export async function deleteThread(threadId: string): Promise<void> {
|
|
113
|
+
await getClient().threads.delete(threadId);
|
|
114
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
import { ChatApp } from "./components/ChatApp";
|
|
5
|
+
import "./styles/globals.css";
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById("root")!).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<ChatApp />
|
|
10
|
+
</StrictMode>,
|
|
11
|
+
);
|