@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,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "verbatimModuleSyntax": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true,
19
+ "paths": {
20
+ "@/*": ["./src/*"]
21
+ }
22
+ },
23
+ "include": ["src"]
24
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2023"],
5
+ "module": "ESNext",
6
+ "skipLibCheck": true,
7
+ "moduleResolution": "bundler",
8
+ "allowImportingTsExtensions": true,
9
+ "verbatimModuleSyntax": true,
10
+ "moduleDetection": "force",
11
+ "noEmit": true,
12
+ "strict": true,
13
+ "noUnusedLocals": true,
14
+ "noUnusedParameters": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noUncheckedSideEffectImports": true
17
+ },
18
+ "include": ["vite.config.ts"]
19
+ }
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ import { defineConfig } from "vite";
3
+ import react from "@vitejs/plugin-react";
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ "@": path.resolve(__dirname, "./src"),
10
+ },
11
+ },
12
+ server: {
13
+ proxy: {
14
+ "/api": {
15
+ target: "http://localhost:8000",
16
+ changeOrigin: true,
17
+ },
18
+ },
19
+ },
20
+ build: {
21
+ outDir: "../dist",
22
+ emptyOutDir: true,
23
+ },
24
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["deno.window", "deno.unstable"],
4
+ "strict": true
5
+ },
6
+ "tasks": {
7
+ "dev": "deno run --watch --allow-env --allow-net --allow-read --allow-sys main.ts",
8
+ "start": "deno run --allow-env --allow-net --allow-read --allow-sys main.ts",
9
+ "build:client": "cd client && pnpm install && pnpm build"
10
+ },
11
+ "nodeModulesDir": "auto",
12
+ "deploy": {
13
+ "entrypoint": "main.ts",
14
+ "include": ["main.ts", "server/", "dist/"]
15
+ }
16
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Deno Deploy entrypoint.
3
+ *
4
+ * Serves the Agent Streaming Protocol under `/api/threads/...` and the Vite-
5
+ * built React SPA from `dist/` for all other routes.
6
+ */
7
+
8
+ import { Hono } from "hono";
9
+ import { serveStatic } from "hono/deno";
10
+
11
+ import { api } from "./server/routes.ts";
12
+
13
+ const app = new Hono();
14
+
15
+ app.route("/api", api);
16
+
17
+ app.use(serveStatic({ root: "./dist" }));
18
+ app.get("*", serveStatic({ path: "./dist/index.html" }));
19
+
20
+ const port = Number(Deno.env.get("PORT") ?? 8000);
21
+ console.log(`Listening on http://localhost:${port}`);
22
+
23
+ Deno.serve({ port }, app.fetch);
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "js-deno",
3
+ "private": true,
4
+ "dependencies": {
5
+ "@langchain/core": "^1.2.1",
6
+ "@langchain/langgraph": "^1.3.7",
7
+ "@langchain/openai": "^1.4.7",
8
+ "@langchain/protocol": "^0.0.16",
9
+ "deepagents": "^1.10.2",
10
+ "hono": "^4.7.10",
11
+ "langchain": "^1.4.4",
12
+ "zod": "^4.4.3"
13
+ }
14
+ }
@@ -0,0 +1,60 @@
1
+ import { MemorySaver } from "@langchain/langgraph";
2
+ import { ChatOpenAI } from "@langchain/openai";
3
+ import { createDeepAgent } from "deepagents";
4
+
5
+ import { stripReasoningReplay } from "./middleware.ts";
6
+ import { calculator, searchWeb } from "./tools.ts";
7
+
8
+ const coordinatorModel = new ChatOpenAI({
9
+ model: "gpt-5.4-mini",
10
+ reasoning: { effort: "low", summary: "auto" },
11
+ });
12
+
13
+ const subagentModel = new ChatOpenAI({ model: "gpt-5.4-mini" });
14
+
15
+ /**
16
+ * In-memory checkpointer — the single source of truth for threads.
17
+ *
18
+ * Exported so the server can enumerate threads (via `checkpointer.storage`) and
19
+ * delete them (`checkpointer.deleteThread`). It is process-local and volatile:
20
+ * restarting the server clears every thread.
21
+ */
22
+ export const checkpointer = new MemorySaver();
23
+
24
+ export const agent = createDeepAgent({
25
+ model: coordinatorModel,
26
+ middleware: [stripReasoningReplay],
27
+ checkpointer,
28
+ subagents: [
29
+ {
30
+ name: "researcher",
31
+ description:
32
+ "Researches a topic using the search_web tool and reports concise findings.",
33
+ tools: [searchWeb],
34
+ model: subagentModel,
35
+ systemPrompt:
36
+ "You are the researcher subagent. Use the search_web tool to look up " +
37
+ "the requested topic, then summarize the findings in two or three " +
38
+ "sentences. Always call search_web at least once before answering.",
39
+ },
40
+ {
41
+ name: "math-whiz",
42
+ description:
43
+ "Performs calculations using the calculator tool and explains the result.",
44
+ tools: [calculator],
45
+ model: subagentModel,
46
+ systemPrompt:
47
+ "You are the math-whiz subagent. Use the calculator tool to evaluate " +
48
+ "the requested expression, then state the result clearly. Always call " +
49
+ "the calculator tool before answering.",
50
+ },
51
+ ],
52
+ systemPrompt:
53
+ "You are a helpful coordinator. When a request involves looking something " +
54
+ "up, delegate it to the `researcher` subagent. When it involves math, " +
55
+ "delegate it to the `math-whiz` subagent. You may run both subagents for a " +
56
+ "single request. After the subagents respond, combine their results into a " +
57
+ "short, clearly labeled final answer.",
58
+ });
59
+
60
+ export type Agent = typeof agent;
@@ -0,0 +1,24 @@
1
+ import { AIMessage, type BaseMessage } from "@langchain/core/messages";
2
+ import { createMiddleware } from "langchain";
3
+
4
+ /**
5
+ * Rebuild prior assistant messages so the Responses API doesn't replay stale
6
+ * item ids.
7
+ */
8
+ function sanitizeForReplay(message: BaseMessage): BaseMessage {
9
+ if (!AIMessage.isInstance(message)) return message;
10
+
11
+ return new AIMessage({
12
+ id: message.id,
13
+ content: message.content,
14
+ tool_calls: message.tool_calls,
15
+ invalid_tool_calls: message.invalid_tool_calls,
16
+ usage_metadata: message.usage_metadata,
17
+ });
18
+ }
19
+
20
+ export const stripReasoningReplay = createMiddleware({
21
+ name: "StripReasoningReplay",
22
+ wrapModelCall: async (request, handler) =>
23
+ handler({ ...request, messages: request.messages.map(sanitizeForReplay) }),
24
+ });
@@ -0,0 +1,64 @@
1
+ import { tool } from "langchain";
2
+ import { z } from "zod";
3
+
4
+ /**
5
+ * Mock tools used to demonstrate message and tool-call streaming.
6
+ *
7
+ * Both tools are intentionally fake so the example runs offline. What matters
8
+ * is that the agent (and its subagents) emit real tool-call deltas on the
9
+ * `messages` channel and tool results as `ToolMessage`s, which the UI renders.
10
+ */
11
+
12
+ export const searchWeb = tool(
13
+ async ({ query }) => {
14
+ await new Promise((resolve) => setTimeout(resolve, 300));
15
+ return JSON.stringify({
16
+ results: [
17
+ {
18
+ title: `Result for: ${query}`,
19
+ snippet:
20
+ "LangGraph streaming sends token deltas on the messages channel " +
21
+ "and tool lifecycle events on the tools channel.",
22
+ },
23
+ ],
24
+ });
25
+ },
26
+ {
27
+ name: "search_web",
28
+ description: "Search the web for information about a topic.",
29
+ schema: z.object({ query: z.string().describe("Search query.") }),
30
+ },
31
+ );
32
+
33
+ /** Demo-only arithmetic evaluator restricted to numbers and basic operators. */
34
+ function evaluateExpression(expression: string): number {
35
+ if (!/^[\d+\-*/().\s]+$/.test(expression)) {
36
+ throw new Error("Only basic arithmetic is supported.");
37
+ }
38
+ const compute = new Function(
39
+ `"use strict"; return (${expression});`,
40
+ ) as () => unknown;
41
+ const result = compute();
42
+ if (typeof result !== "number" || !Number.isFinite(result)) {
43
+ throw new Error("Expression did not evaluate to a finite number.");
44
+ }
45
+ return result;
46
+ }
47
+
48
+ export const calculator = tool(
49
+ async ({ expression }) => {
50
+ await new Promise((resolve) => setTimeout(resolve, 100));
51
+ try {
52
+ return String(evaluateExpression(expression));
53
+ } catch (error) {
54
+ return `Error evaluating: ${expression} (${String(error)})`;
55
+ }
56
+ },
57
+ {
58
+ name: "calculator",
59
+ description: "Evaluate a math expression.",
60
+ schema: z.object({
61
+ expression: z.string().describe("Math expression to evaluate."),
62
+ }),
63
+ },
64
+ );
@@ -0,0 +1,40 @@
1
+ import { agent, checkpointer } from "./agent/index.ts";
2
+ import { LocalThreadSession } from "./session.ts";
3
+
4
+ /**
5
+ * Process-local registry for the agent and its per-thread sessions.
6
+ *
7
+ * Under Deno Deploy each isolate keeps its own in-memory registry.
8
+ *
9
+ * NOTE: This is in-memory and process-local. A serverless/multi-instance
10
+ * deployment needs a durable checkpointer (Postgres, SQLite, …) and a shared
11
+ * session/replay store. The wiring here stays the same; only the checkpointer
12
+ * in `server/agent/index.ts` and this store change.
13
+ */
14
+ const sessions = new Map<string, LocalThreadSession>();
15
+
16
+ /** The shared, compiled agent (and its checkpointer). */
17
+ export function getAgent() {
18
+ return agent;
19
+ }
20
+
21
+ /** The shared checkpointer — the single source of truth for threads. */
22
+ export function getCheckpointer() {
23
+ return checkpointer;
24
+ }
25
+
26
+ /** Get or create the process-local session for a thread. */
27
+ export function getSession(threadId: string): LocalThreadSession {
28
+ let session = sessions.get(threadId);
29
+ if (session == null) {
30
+ session = new LocalThreadSession(agent, threadId);
31
+ sessions.set(threadId, session);
32
+ }
33
+ return session;
34
+ }
35
+
36
+ /** Delete a thread: remove its session and its checkpointed state. */
37
+ export async function deleteThread(threadId: string): Promise<void> {
38
+ sessions.delete(threadId);
39
+ await checkpointer.deleteThread(threadId);
40
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Agent Streaming Protocol route handlers for Hono.
3
+ *
4
+ * Mirrors the Next.js route handlers in `js-next/app/api/threads/`.
5
+ */
6
+
7
+ import { Hono } from "hono";
8
+ import type { Command, SubscribeParams } from "@langchain/protocol";
9
+
10
+ import {
11
+ deleteThread,
12
+ getAgent,
13
+ getCheckpointer,
14
+ getSession,
15
+ } from "./registry.ts";
16
+ import {
17
+ ThreadNotFoundError,
18
+ getThreadHistory,
19
+ getThreadState,
20
+ listThreads,
21
+ updateThreadState,
22
+ } from "./threads.ts";
23
+
24
+ const SSE_HEADERS = {
25
+ "cache-control": "no-cache, no-transform",
26
+ "content-type": "text/event-stream",
27
+ connection: "keep-alive",
28
+ "x-accel-buffering": "no",
29
+ };
30
+
31
+ export const api = new Hono();
32
+
33
+ api.get("/threads", async (c) => {
34
+ const threads = await listThreads(getAgent().graph, getCheckpointer());
35
+ return c.json(threads);
36
+ });
37
+
38
+ api.delete("/threads/:threadId", async (c) => {
39
+ await deleteThread(c.req.param("threadId"));
40
+ return c.body(null, 204);
41
+ });
42
+
43
+ api.post("/threads/:threadId/commands", async (c) => {
44
+ const threadId = c.req.param("threadId");
45
+ const command = (await c.req.json()) as Command;
46
+ const result = await getSession(threadId).handleCommand(command);
47
+ return c.json(result);
48
+ });
49
+
50
+ api.post("/threads/:threadId/stream", async (c) => {
51
+ const threadId = c.req.param("threadId");
52
+ const subscribeParams = (await c.req.json()) as SubscribeParams;
53
+ const stream = getSession(threadId).stream(subscribeParams);
54
+ return c.newResponse(stream, { headers: SSE_HEADERS });
55
+ });
56
+
57
+ api.get("/threads/:threadId/state", async (c) => {
58
+ const threadId = c.req.param("threadId");
59
+ try {
60
+ const state = await getThreadState(getAgent().graph, threadId);
61
+ return c.json(state);
62
+ } catch (error) {
63
+ if (error instanceof ThreadNotFoundError) {
64
+ return c.json({ error: "not_found", message: error.message }, 404);
65
+ }
66
+ throw error;
67
+ }
68
+ });
69
+
70
+ api.post("/threads/:threadId/state", async (c) => {
71
+ const threadId = c.req.param("threadId");
72
+ const body = (await c.req.json().catch(() => ({}))) as {
73
+ values?: Record<string, unknown> | null;
74
+ checkpoint?: Record<string, unknown> | null;
75
+ as_node?: string;
76
+ };
77
+ try {
78
+ const state = await updateThreadState(getAgent().graph, threadId, {
79
+ values: body.values ?? null,
80
+ checkpoint: body.checkpoint ?? null,
81
+ asNode: body.as_node,
82
+ });
83
+ return c.json(state);
84
+ } catch (error) {
85
+ return c.json(
86
+ { error: "invalid_state_update", message: String(error) },
87
+ 422,
88
+ );
89
+ }
90
+ });
91
+
92
+ api.post("/threads/:threadId/history", async (c) => {
93
+ const threadId = c.req.param("threadId");
94
+ const body = (await c.req.json().catch(() => ({}))) as {
95
+ limit?: number;
96
+ before?: unknown;
97
+ metadata?: Record<string, unknown>;
98
+ checkpoint?: Record<string, unknown>;
99
+ };
100
+ try {
101
+ const history = await getThreadHistory(getAgent().graph, threadId, {
102
+ limit: typeof body.limit === "number" ? body.limit : 10,
103
+ before: body.before,
104
+ metadata: body.metadata,
105
+ checkpoint: body.checkpoint,
106
+ });
107
+ return c.json(history);
108
+ } catch (error) {
109
+ if (error instanceof ThreadNotFoundError) {
110
+ return c.json({ error: "not_found", message: error.message }, 404);
111
+ }
112
+ throw error;
113
+ }
114
+ });
@@ -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.ts";
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
+ }