@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,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/threads/:threadId/state`.
|
|
3
|
+
*
|
|
4
|
+
* Reads checkpointed thread state. Returns 404 when the thread has no
|
|
5
|
+
* checkpoint yet so the LangGraph SDK can bootstrap it before the first run.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getAgent } from "../../../utils/runtime";
|
|
9
|
+
import { ThreadNotFoundError, getThreadState } from "../../../utils/threads";
|
|
10
|
+
|
|
11
|
+
export default defineEventHandler(async (event) => {
|
|
12
|
+
const threadId = getRouterParam(event, "threadId") ?? "local";
|
|
13
|
+
try {
|
|
14
|
+
return await getThreadState(getAgent().graph, threadId);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof ThreadNotFoundError) {
|
|
17
|
+
setResponseStatus(event, 404);
|
|
18
|
+
return { error: "not_found", message: error.message };
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /api/threads/:threadId/state`.
|
|
3
|
+
*
|
|
4
|
+
* Creates or updates checkpointed thread state. Used by the browser bootstrap
|
|
5
|
+
* and by the SDK when hydrating or editing conversation history.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getAgent } from "../../../utils/runtime";
|
|
9
|
+
import { updateThreadState } from "../../../utils/threads";
|
|
10
|
+
|
|
11
|
+
type StateUpdateBody = {
|
|
12
|
+
values?: Record<string, unknown> | null;
|
|
13
|
+
checkpoint?: Record<string, unknown> | null;
|
|
14
|
+
as_node?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default defineEventHandler(async (event) => {
|
|
18
|
+
const threadId = getRouterParam(event, "threadId") ?? "local";
|
|
19
|
+
const body = await readBody<StateUpdateBody>(event).catch(
|
|
20
|
+
() => ({}) as StateUpdateBody,
|
|
21
|
+
);
|
|
22
|
+
try {
|
|
23
|
+
return await updateThreadState(getAgent().graph, threadId, {
|
|
24
|
+
values: body.values ?? null,
|
|
25
|
+
checkpoint: body.checkpoint ?? null,
|
|
26
|
+
asNode: body.as_node,
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
setResponseStatus(event, 422);
|
|
30
|
+
return { error: "invalid_state_update", message: String(error) };
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /api/threads/:threadId/stream`.
|
|
3
|
+
*
|
|
4
|
+
* The request body is a connection-scoped `SubscribeParams` filter. The
|
|
5
|
+
* response is an SSE stream that first replays matching buffered events and
|
|
6
|
+
* then stays attached for live events from the same thread.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SubscribeParams } from "@langchain/protocol";
|
|
10
|
+
|
|
11
|
+
import { getSession } from "../../../utils/runtime";
|
|
12
|
+
|
|
13
|
+
export default defineEventHandler(async (event) => {
|
|
14
|
+
const threadId = getRouterParam(event, "threadId") ?? "local";
|
|
15
|
+
const params = await readBody<SubscribeParams>(event);
|
|
16
|
+
|
|
17
|
+
setResponseHeader(event, "content-type", "text/event-stream");
|
|
18
|
+
setResponseHeader(event, "cache-control", "no-cache");
|
|
19
|
+
setResponseHeader(event, "connection", "keep-alive");
|
|
20
|
+
// Disable proxy buffering so SSE frames flush immediately.
|
|
21
|
+
setResponseHeader(event, "x-accel-buffering", "no");
|
|
22
|
+
|
|
23
|
+
return getSession(threadId).stream(params);
|
|
24
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/threads` — list every thread known to the checkpointer.
|
|
3
|
+
*
|
|
4
|
+
* The agent's in-memory `MemorySaver` is the single source of truth, so the
|
|
5
|
+
* sidebar is always derived from it (no client-side cache).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getAgent, getCheckpointer } from "../../utils/runtime";
|
|
9
|
+
import { listThreads } from "../../utils/threads";
|
|
10
|
+
|
|
11
|
+
export default defineEventHandler(async () => {
|
|
12
|
+
return listThreads(getAgent().graph, getCheckpointer());
|
|
13
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { agent, checkpointer } from "../agent";
|
|
2
|
+
import { LocalThreadSession } from "./session";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Process-local registry for the agent and its per-thread sessions.
|
|
6
|
+
*
|
|
7
|
+
* Under Nuxt the Agent Streaming Protocol is served by Nitro route handlers, so
|
|
8
|
+
* the shared agent + session registry is a module singleton instead of a
|
|
9
|
+
* standalone Hono server.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: This is in-memory and process-local. A serverless/multi-instance
|
|
12
|
+
* deployment needs a durable checkpointer (Postgres, SQLite, …) and a shared
|
|
13
|
+
* session/replay store. The wiring here stays the same; only the checkpointer
|
|
14
|
+
* in `server/agent/index.ts` and this store change.
|
|
15
|
+
*/
|
|
16
|
+
const sessions = new Map<string, LocalThreadSession>();
|
|
17
|
+
|
|
18
|
+
/** The shared, compiled agent (and its checkpointer). */
|
|
19
|
+
export function getAgent() {
|
|
20
|
+
return agent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The shared checkpointer — the single source of truth for threads. */
|
|
24
|
+
export function getCheckpointer() {
|
|
25
|
+
return checkpointer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Get or create the process-local session for a thread. */
|
|
29
|
+
export function getSession(threadId: string): LocalThreadSession {
|
|
30
|
+
let session = sessions.get(threadId);
|
|
31
|
+
if (session == null) {
|
|
32
|
+
session = new LocalThreadSession(agent, threadId);
|
|
33
|
+
sessions.set(threadId, session);
|
|
34
|
+
}
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Delete a thread: remove its session and its checkpointed state. */
|
|
39
|
+
export async function deleteThread(threadId: string): Promise<void> {
|
|
40
|
+
sessions.delete(threadId);
|
|
41
|
+
await checkpointer.deleteThread(threadId);
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseMessage } from "@langchain/core/messages";
|
|
2
|
+
|
|
3
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return typeof value === "object" && value !== null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Recursively replace LangChain message instances with plain protocol dicts.
|
|
9
|
+
*
|
|
10
|
+
* Uses {@link BaseMessage.isInstance} and {@link BaseMessage.toDict} from
|
|
11
|
+
* `@langchain/core/messages` — the canonical LangChain serialization primitive.
|
|
12
|
+
* Message instances surface in `values`/`updates` stream data and in the
|
|
13
|
+
* checkpointer state snapshot returned by the thread-state routes, often nested
|
|
14
|
+
* under `messages`.
|
|
15
|
+
*/
|
|
16
|
+
export function sanitizeForJson(value: unknown): unknown {
|
|
17
|
+
if (BaseMessage.isInstance(value)) {
|
|
18
|
+
const { type, data } = value.toDict();
|
|
19
|
+
return sanitizeForJson({ ...data, type });
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) return value.map(sanitizeForJson);
|
|
22
|
+
if (isRecord(value)) {
|
|
23
|
+
const result: Record<string, unknown> = {};
|
|
24
|
+
for (const [key, item] of Object.entries(value)) {
|
|
25
|
+
result[key] = sanitizeForJson(item);
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { ReactAgent } from "langchain";
|
|
2
|
+
// `StreamChannel` buffers events; `matchesSubscription` is the shared protocol
|
|
3
|
+
// predicate from `@langchain/langgraph/stream` — the same one langgraph-api
|
|
4
|
+
// uses, so this custom transport stays aligned with the production server.
|
|
5
|
+
import {
|
|
6
|
+
StreamChannel,
|
|
7
|
+
matchesSubscription,
|
|
8
|
+
type ProtocolEvent,
|
|
9
|
+
} from "@langchain/langgraph/stream";
|
|
10
|
+
import type {
|
|
11
|
+
Command,
|
|
12
|
+
CommandResponse,
|
|
13
|
+
ErrorResponse,
|
|
14
|
+
SubscribeParams,
|
|
15
|
+
} from "@langchain/protocol";
|
|
16
|
+
|
|
17
|
+
import { isRecord, sanitizeForJson } from "./serialize";
|
|
18
|
+
|
|
19
|
+
// `ReactAgent<any>` accepts both `createAgent` results and `DeepAgent`
|
|
20
|
+
// instances (which carry a specific, non-default type config).
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
type AnyReactAgent = ReactAgent<any>;
|
|
23
|
+
|
|
24
|
+
type AgentRunInput = Parameters<AnyReactAgent["streamEvents"]>[0];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Make an event safe to `JSON.stringify` onto the SSE wire.
|
|
28
|
+
*
|
|
29
|
+
* Only the protocol payload (`params.data`) and any `params.interrupts` can
|
|
30
|
+
* carry LangChain message instances, so those are the fields we sanitize into
|
|
31
|
+
* the plain, role-keyed protocol message shape the SDK expects.
|
|
32
|
+
*/
|
|
33
|
+
function sanitizeEvent(event: ProtocolEvent): ProtocolEvent {
|
|
34
|
+
const params = event.params as Record<string, unknown>;
|
|
35
|
+
const sanitizedParams: Record<string, unknown> = {
|
|
36
|
+
...params,
|
|
37
|
+
data: sanitizeForJson(params.data),
|
|
38
|
+
};
|
|
39
|
+
if ("interrupts" in params) {
|
|
40
|
+
sanitizedParams.interrupts = sanitizeForJson(params.interrupts);
|
|
41
|
+
}
|
|
42
|
+
return { ...event, params: sanitizedParams } as ProtocolEvent;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Encode an Agent Protocol event as a Server-Sent Event frame.
|
|
47
|
+
*
|
|
48
|
+
* When available, `event_id` is mirrored into the SSE `id:` field for
|
|
49
|
+
* transport-level reconnection. The SDK primarily deduplicates by `event_id`
|
|
50
|
+
* and replays by `seq`; if an event has no `event_id`, this example falls back
|
|
51
|
+
* to `seq` as a stable frame id.
|
|
52
|
+
*/
|
|
53
|
+
function encodeSse(event: ProtocolEvent) {
|
|
54
|
+
const eventId = (event as { event_id?: string }).event_id;
|
|
55
|
+
const id = eventId ?? (typeof event.seq === "number" ? `${event.seq}` : "");
|
|
56
|
+
const idLine = id ? `id: ${id}\n` : "";
|
|
57
|
+
return new TextEncoder().encode(
|
|
58
|
+
`${idLine}event: message\ndata: ${JSON.stringify(event)}\n\n`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Minimal in-memory Agent Streaming Protocol session for the example.
|
|
64
|
+
*
|
|
65
|
+
* This class is the server-side counterpart to `HttpAgentServerAdapter`:
|
|
66
|
+
*
|
|
67
|
+
* - `POST /threads/:thread_id/commands` sends a JSON `Command` and receives a
|
|
68
|
+
* `CommandResponse` or `ErrorResponse`.
|
|
69
|
+
* - `POST /threads/:thread_id/stream` opens a connection-scoped SSE
|
|
70
|
+
* subscription described by `SubscribeParams`.
|
|
71
|
+
* - Events are buffered by `seq` and replayed to later subscriptions, enabling
|
|
72
|
+
* the SDK to rotate streams as subscriptions widen or narrow.
|
|
73
|
+
*
|
|
74
|
+
* The implementation is intentionally small and process-local. It is suitable
|
|
75
|
+
* for this example and for understanding the protocol shape, but production
|
|
76
|
+
* servers should persist threads, enforce concurrency policies, and coordinate
|
|
77
|
+
* replay buffers across workers.
|
|
78
|
+
*/
|
|
79
|
+
export class LocalThreadSession {
|
|
80
|
+
readonly #agent: AnyReactAgent;
|
|
81
|
+
readonly #threadId: string;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Per-thread protocol event log.
|
|
85
|
+
*
|
|
86
|
+
* A {@link StreamChannel} is LangGraph's buffered, append-only stream with
|
|
87
|
+
* independent per-consumer cursors. Every event ever published stays
|
|
88
|
+
* buffered, and each SSE subscription gets its own cursor via
|
|
89
|
+
* {@link StreamChannel.iterate}, so buffered replay and live delivery are the
|
|
90
|
+
* same iteration.
|
|
91
|
+
*/
|
|
92
|
+
readonly #log = StreamChannel.local<ProtocolEvent>();
|
|
93
|
+
|
|
94
|
+
/** Monotonic seq across all runs on this thread (graph runs reset at 0). */
|
|
95
|
+
#nextSeq = 0;
|
|
96
|
+
|
|
97
|
+
#activeRun:
|
|
98
|
+
| {
|
|
99
|
+
abort(reason?: unknown): void;
|
|
100
|
+
}
|
|
101
|
+
| undefined;
|
|
102
|
+
|
|
103
|
+
constructor(agent: AnyReactAgent, threadId: string) {
|
|
104
|
+
this.#agent = agent;
|
|
105
|
+
this.#threadId = threadId;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Handle a thread command sent to the Agent Protocol `/commands` endpoint.
|
|
110
|
+
*
|
|
111
|
+
* The SDK sends `run.start` to start or resume a graph run on the current
|
|
112
|
+
* thread. This starts the in-process v3 stream and immediately returns a
|
|
113
|
+
* success response containing a generated `run_id`, while streamed events
|
|
114
|
+
* flow asynchronously through active `/stream` subscriptions.
|
|
115
|
+
*/
|
|
116
|
+
async handleCommand(
|
|
117
|
+
command: Command,
|
|
118
|
+
): Promise<CommandResponse | ErrorResponse> {
|
|
119
|
+
if (command.method !== "run.start") {
|
|
120
|
+
return {
|
|
121
|
+
type: "error",
|
|
122
|
+
id: command.id,
|
|
123
|
+
error: "unknown_command",
|
|
124
|
+
message: `Unsupported command: ${command.method}`,
|
|
125
|
+
} as ErrorResponse;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const params = isRecord(command.params)
|
|
129
|
+
? (command.params as { input?: unknown })
|
|
130
|
+
: {};
|
|
131
|
+
const runId = crypto.randomUUID();
|
|
132
|
+
void this.#startRun(params.input as AgentRunInput, runId);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
type: "success",
|
|
136
|
+
id: command.id,
|
|
137
|
+
result: { run_id: runId },
|
|
138
|
+
} as CommandResponse;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Open a connection-scoped SSE subscription for this thread.
|
|
143
|
+
*
|
|
144
|
+
* The returned `ReadableStream` first replays buffered events matching the
|
|
145
|
+
* requested `channels`, `namespaces`, `depth`, and optional `since` cursor,
|
|
146
|
+
* then stays attached for live events. Closing the HTTP connection releases
|
|
147
|
+
* this subscription's event-log cursor.
|
|
148
|
+
*/
|
|
149
|
+
stream(params: SubscribeParams) {
|
|
150
|
+
const cursor = this.#log.iterate();
|
|
151
|
+
|
|
152
|
+
return new ReadableStream<Uint8Array>({
|
|
153
|
+
pull: async (controller) => {
|
|
154
|
+
// Scan forward until we find an event matching this subscription's
|
|
155
|
+
// filter, enqueue exactly one frame, and return so the channel honors
|
|
156
|
+
// the consumer's backpressure. `cursor.next()` resolves immediately for
|
|
157
|
+
// buffered events and suspends once the live edge is reached.
|
|
158
|
+
for (;;) {
|
|
159
|
+
const { value: event, done } = await cursor.next();
|
|
160
|
+
if (done) {
|
|
161
|
+
controller.close();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (matchesSubscription(event, params)) {
|
|
165
|
+
controller.enqueue(encodeSse(event));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
cancel: () => {
|
|
171
|
+
void cursor.return?.(undefined);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#publish(rawEvent: ProtocolEvent) {
|
|
177
|
+
const seq = this.#nextSeq;
|
|
178
|
+
this.#nextSeq += 1;
|
|
179
|
+
const event = sanitizeEvent({
|
|
180
|
+
...rawEvent,
|
|
181
|
+
type: "event",
|
|
182
|
+
seq,
|
|
183
|
+
} as ProtocolEvent);
|
|
184
|
+
this.#log.push(event);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async #startRun(input: AgentRunInput, runId: string) {
|
|
188
|
+
this.#activeRun?.abort("Starting a new run.");
|
|
189
|
+
// Thread the `thread_id` / `run_id` into the run config so the checkpointer
|
|
190
|
+
// persists conversation state per thread and downstream events carry the
|
|
191
|
+
// run identity.
|
|
192
|
+
const run = await this.#agent.streamEvents(input, {
|
|
193
|
+
version: "v3",
|
|
194
|
+
configurable: { thread_id: this.#threadId, run_id: runId },
|
|
195
|
+
});
|
|
196
|
+
this.#activeRun = run;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
for await (const rawEvent of run) {
|
|
200
|
+
this.#publish(rawEvent as ProtocolEvent);
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error(error);
|
|
204
|
+
} finally {
|
|
205
|
+
if (this.#activeRun === run) {
|
|
206
|
+
this.#activeRun = undefined;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|