create-relkit 0.4.0 → 0.5.1

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 (277) hide show
  1. package/dist/add-name.d.ts +9 -0
  2. package/dist/add-name.d.ts.map +1 -0
  3. package/dist/add-name.js +18 -0
  4. package/dist/add-name.js.map +1 -0
  5. package/dist/add-options-parser.d.ts +21 -0
  6. package/dist/add-options-parser.d.ts.map +1 -0
  7. package/dist/add-options-parser.js +103 -0
  8. package/dist/add-options-parser.js.map +1 -0
  9. package/dist/add-options-resource.d.ts +5 -0
  10. package/dist/add-options-resource.d.ts.map +1 -0
  11. package/dist/add-options-resource.js +18 -0
  12. package/dist/add-options-resource.js.map +1 -0
  13. package/dist/add-options.d.ts +6 -0
  14. package/dist/add-options.d.ts.map +1 -0
  15. package/dist/add-options.js +165 -0
  16. package/dist/add-options.js.map +1 -0
  17. package/dist/add-output.d.ts +4 -0
  18. package/dist/add-output.d.ts.map +1 -0
  19. package/dist/add-output.js +33 -0
  20. package/dist/add-output.js.map +1 -0
  21. package/dist/add-plan.d.ts +4 -0
  22. package/dist/add-plan.d.ts.map +1 -0
  23. package/dist/add-plan.js +62 -0
  24. package/dist/add-plan.js.map +1 -0
  25. package/dist/add-resolution-discovery.d.ts +13 -0
  26. package/dist/add-resolution-discovery.d.ts.map +1 -0
  27. package/dist/add-resolution-discovery.js +70 -0
  28. package/dist/add-resolution-discovery.js.map +1 -0
  29. package/dist/add-resolution-state.d.ts +23 -0
  30. package/dist/add-resolution-state.d.ts.map +1 -0
  31. package/dist/add-resolution-state.js +89 -0
  32. package/dist/add-resolution-state.js.map +1 -0
  33. package/dist/add-resolver-domain.d.ts +4 -0
  34. package/dist/add-resolver-domain.d.ts.map +1 -0
  35. package/dist/add-resolver-domain.js +150 -0
  36. package/dist/add-resolver-domain.js.map +1 -0
  37. package/dist/add-resolver-platform.d.ts +4 -0
  38. package/dist/add-resolver-platform.d.ts.map +1 -0
  39. package/dist/add-resolver-platform.js +112 -0
  40. package/dist/add-resolver-platform.js.map +1 -0
  41. package/dist/add-resolver-resources.d.ts +4 -0
  42. package/dist/add-resolver-resources.d.ts.map +1 -0
  43. package/dist/add-resolver-resources.js +150 -0
  44. package/dist/add-resolver-resources.js.map +1 -0
  45. package/dist/add-resolver.d.ts +14 -0
  46. package/dist/add-resolver.d.ts.map +1 -0
  47. package/dist/add-resolver.js +41 -0
  48. package/dist/add-resolver.js.map +1 -0
  49. package/dist/add-transaction-files.d.ts +12 -0
  50. package/dist/add-transaction-files.d.ts.map +1 -0
  51. package/dist/add-transaction-files.js +103 -0
  52. package/dist/add-transaction-files.js.map +1 -0
  53. package/dist/add-transaction.d.ts +17 -0
  54. package/dist/add-transaction.d.ts.map +1 -0
  55. package/dist/add-transaction.js +115 -0
  56. package/dist/add-transaction.js.map +1 -0
  57. package/dist/add-types.d.ts +136 -0
  58. package/dist/add-types.d.ts.map +1 -0
  59. package/dist/add-types.js +41 -0
  60. package/dist/add-types.js.map +1 -0
  61. package/dist/bucket-profiles.d.ts +4 -0
  62. package/dist/bucket-profiles.d.ts.map +1 -0
  63. package/dist/bucket-profiles.js +18 -0
  64. package/dist/bucket-profiles.js.map +1 -0
  65. package/dist/create-additions.d.ts +7 -0
  66. package/dist/create-additions.d.ts.map +1 -0
  67. package/dist/create-additions.js +27 -0
  68. package/dist/create-additions.js.map +1 -0
  69. package/dist/create-preview.d.ts +12 -0
  70. package/dist/create-preview.d.ts.map +1 -0
  71. package/dist/create-preview.js +50 -0
  72. package/dist/create-preview.js.map +1 -0
  73. package/dist/create-resolver.d.ts +14 -0
  74. package/dist/create-resolver.d.ts.map +1 -0
  75. package/dist/create-resolver.js +96 -0
  76. package/dist/create-resolver.js.map +1 -0
  77. package/dist/domain-planning.d.ts +24 -0
  78. package/dist/domain-planning.d.ts.map +1 -0
  79. package/dist/domain-planning.js +94 -0
  80. package/dist/domain-planning.js.map +1 -0
  81. package/dist/generate-files-utilities.d.ts +18 -0
  82. package/dist/generate-files-utilities.d.ts.map +1 -0
  83. package/dist/generate-files-utilities.js +87 -0
  84. package/dist/generate-files-utilities.js.map +1 -0
  85. package/dist/generate-files.d.ts +1 -12
  86. package/dist/generate-files.d.ts.map +1 -1
  87. package/dist/generate-files.js +39 -89
  88. package/dist/generate-files.js.map +1 -1
  89. package/dist/generate-output.d.ts +2 -1
  90. package/dist/generate-output.d.ts.map +1 -1
  91. package/dist/generate-output.js +9 -0
  92. package/dist/generate-output.js.map +1 -1
  93. package/dist/generate-process.d.ts +1 -1
  94. package/dist/generate-process.d.ts.map +1 -1
  95. package/dist/generate-process.js +7 -3
  96. package/dist/generate-process.js.map +1 -1
  97. package/dist/generate-types.d.ts +46 -0
  98. package/dist/generate-types.d.ts.map +1 -0
  99. package/dist/generate-types.js +20 -0
  100. package/dist/generate-types.js.map +1 -0
  101. package/dist/generate.d.ts +1 -38
  102. package/dist/generate.d.ts.map +1 -1
  103. package/dist/generate.js +34 -39
  104. package/dist/generate.js.map +1 -1
  105. package/dist/index.d.ts +18 -0
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +79 -9
  108. package/dist/index.js.map +1 -1
  109. package/dist/options.d.ts +5 -1
  110. package/dist/options.d.ts.map +1 -1
  111. package/dist/options.js +10 -1
  112. package/dist/options.js.map +1 -1
  113. package/dist/plan-builder.d.ts +33 -0
  114. package/dist/plan-builder.d.ts.map +1 -0
  115. package/dist/plan-builder.js +139 -0
  116. package/dist/plan-builder.js.map +1 -0
  117. package/dist/plan-manifest.d.ts +6 -0
  118. package/dist/plan-manifest.d.ts.map +1 -0
  119. package/dist/plan-manifest.js +40 -0
  120. package/dist/plan-manifest.js.map +1 -0
  121. package/dist/project-discovery-app.d.ts +7 -0
  122. package/dist/project-discovery-app.d.ts.map +1 -0
  123. package/dist/project-discovery-app.js +122 -0
  124. package/dist/project-discovery-app.js.map +1 -0
  125. package/dist/project-discovery-database.d.ts +11 -0
  126. package/dist/project-discovery-database.d.ts.map +1 -0
  127. package/dist/project-discovery-database.js +46 -0
  128. package/dist/project-discovery-database.js.map +1 -0
  129. package/dist/project-discovery-types.d.ts +45 -0
  130. package/dist/project-discovery-types.d.ts.map +1 -0
  131. package/dist/project-discovery-types.js +2 -0
  132. package/dist/project-discovery-types.js.map +1 -0
  133. package/dist/project-discovery.d.ts +4 -0
  134. package/dist/project-discovery.d.ts.map +1 -0
  135. package/dist/project-discovery.js +152 -0
  136. package/dist/project-discovery.js.map +1 -0
  137. package/dist/prompt-driver.d.ts +32 -0
  138. package/dist/prompt-driver.d.ts.map +1 -0
  139. package/dist/prompt-driver.js +42 -0
  140. package/dist/prompt-driver.js.map +1 -0
  141. package/dist/provider-planning-definitions.d.ts +35 -0
  142. package/dist/provider-planning-definitions.d.ts.map +1 -0
  143. package/dist/provider-planning-definitions.js +109 -0
  144. package/dist/provider-planning-definitions.js.map +1 -0
  145. package/dist/provider-planning.d.ts +11 -0
  146. package/dist/provider-planning.d.ts.map +1 -0
  147. package/dist/provider-planning.js +131 -0
  148. package/dist/provider-planning.js.map +1 -0
  149. package/dist/render-ai-sources.d.ts +18 -0
  150. package/dist/render-ai-sources.d.ts.map +1 -0
  151. package/dist/render-ai-sources.js +49 -0
  152. package/dist/render-ai-sources.js.map +1 -0
  153. package/dist/render-ai.d.ts +11 -0
  154. package/dist/render-ai.d.ts.map +1 -0
  155. package/dist/render-ai.js +61 -0
  156. package/dist/render-ai.js.map +1 -0
  157. package/dist/render-auth-schema.d.ts +3 -0
  158. package/dist/render-auth-schema.d.ts.map +1 -0
  159. package/dist/render-auth-schema.js +85 -0
  160. package/dist/render-auth-schema.js.map +1 -0
  161. package/dist/render-auth-sources.d.ts +3 -0
  162. package/dist/render-auth-sources.d.ts.map +1 -0
  163. package/dist/render-auth-sources.js +17 -0
  164. package/dist/render-auth-sources.js.map +1 -0
  165. package/dist/render-auth.d.ts +6 -0
  166. package/dist/render-auth.d.ts.map +1 -0
  167. package/dist/render-auth.js +84 -0
  168. package/dist/render-auth.js.map +1 -0
  169. package/dist/render-database-sources.d.ts +5 -0
  170. package/dist/render-database-sources.d.ts.map +1 -0
  171. package/dist/render-database-sources.js +65 -0
  172. package/dist/render-database-sources.js.map +1 -0
  173. package/dist/render-database.d.ts +13 -0
  174. package/dist/render-database.d.ts.map +1 -0
  175. package/dist/render-database.js +40 -0
  176. package/dist/render-database.js.map +1 -0
  177. package/dist/render-domain-register.d.ts +4 -0
  178. package/dist/render-domain-register.d.ts.map +1 -0
  179. package/dist/render-domain-register.js +11 -0
  180. package/dist/render-domain-register.js.map +1 -0
  181. package/dist/render-domain-sources.d.ts +19 -0
  182. package/dist/render-domain-sources.d.ts.map +1 -0
  183. package/dist/render-domain-sources.js +132 -0
  184. package/dist/render-domain-sources.js.map +1 -0
  185. package/dist/render-domain.d.ts +26 -0
  186. package/dist/render-domain.d.ts.map +1 -0
  187. package/dist/render-domain.js +98 -0
  188. package/dist/render-domain.js.map +1 -0
  189. package/dist/render-resource-sources.d.ts +4 -0
  190. package/dist/render-resource-sources.d.ts.map +1 -0
  191. package/dist/render-resource-sources.js +31 -0
  192. package/dist/render-resource-sources.js.map +1 -0
  193. package/dist/render-resources.d.ts +11 -0
  194. package/dist/render-resources.d.ts.map +1 -0
  195. package/dist/render-resources.js +53 -0
  196. package/dist/render-resources.js.map +1 -0
  197. package/dist/render-route-sources.d.ts +6 -0
  198. package/dist/render-route-sources.d.ts.map +1 -0
  199. package/dist/render-route-sources.js +35 -0
  200. package/dist/render-route-sources.js.map +1 -0
  201. package/dist/render-routes.d.ts +13 -0
  202. package/dist/render-routes.d.ts.map +1 -0
  203. package/dist/render-routes.js +48 -0
  204. package/dist/render-routes.js.map +1 -0
  205. package/dist/render-service.d.ts +6 -0
  206. package/dist/render-service.d.ts.map +1 -0
  207. package/dist/render-service.js +124 -0
  208. package/dist/render-service.js.map +1 -0
  209. package/dist/scaffold-catalog.d.ts +75 -0
  210. package/dist/scaffold-catalog.d.ts.map +1 -0
  211. package/dist/scaffold-catalog.js +24 -0
  212. package/dist/scaffold-catalog.js.map +1 -0
  213. package/dist/source-edit.d.ts +10 -0
  214. package/dist/source-edit.d.ts.map +1 -0
  215. package/dist/source-edit.js +136 -0
  216. package/dist/source-edit.js.map +1 -0
  217. package/dist/source-import-edit.d.ts +3 -0
  218. package/dist/source-import-edit.d.ts.map +1 -0
  219. package/dist/source-import-edit.js +52 -0
  220. package/dist/source-import-edit.js.map +1 -0
  221. package/dist/template-root.d.ts +5 -0
  222. package/dist/template-root.d.ts.map +1 -0
  223. package/dist/template-root.js +15 -0
  224. package/dist/template-root.js.map +1 -0
  225. package/dist/templates/default/README.md +1 -1
  226. package/dist/templates/default/v1/agent/.env.example +0 -2
  227. package/dist/templates/default/v1/agent/README.md +7 -8
  228. package/dist/templates/default/v1/agent/package.json +6 -4
  229. package/dist/templates/default/v1/agent/relkit.config.ts +5 -15
  230. package/dist/templates/default/v1/agent/src/hello/agents/assistant.agent.ts +37 -4
  231. package/dist/templates/default/v1/agent/src/hello/agents/review.agent.ts +43 -0
  232. package/dist/templates/default/v1/agent/src/platform/env.ts +0 -2
  233. package/dist/templates/default/v1/agent/tests/integration/hello.route.test.ts +17 -6
  234. package/dist/templates/default/v1/agent/tests/unit/assistant.agent.test.ts +14 -36
  235. package/dist/templates/default/v1/agent/tests/unit/review.agent.test.ts +19 -0
  236. package/dist/templates/default/v1/api/package.json +3 -3
  237. package/dist/templates/default/v1/fullstack/.env.example +1 -0
  238. package/dist/templates/default/v1/fullstack/README.md +15 -0
  239. package/dist/templates/default/v1/fullstack/gitignore +4 -0
  240. package/dist/templates/default/v1/fullstack/package.json +35 -0
  241. package/dist/templates/default/v1/fullstack/relkit.config.ts +15 -0
  242. package/dist/templates/default/v1/fullstack/src/hello/agents/assistant.agent.ts +46 -0
  243. package/dist/templates/default/v1/fullstack/src/hello/agents/review.agent.ts +43 -0
  244. package/dist/templates/default/v1/fullstack/src/hello/functions/hello.function.ts +10 -0
  245. package/dist/templates/default/v1/fullstack/src/hello/service.ts +4 -0
  246. package/dist/templates/default/v1/fullstack/src/hello/tools/lookup.tool.ts +9 -0
  247. package/dist/templates/default/v1/fullstack/src/platform/env.ts +5 -0
  248. package/dist/templates/default/v1/fullstack/src/routes/hello/route.ts +4 -0
  249. package/dist/templates/default/v1/fullstack/tests/integration/hello.route.test.ts +29 -0
  250. package/dist/templates/default/v1/fullstack/tests/unit/assistant.agent.test.ts +18 -0
  251. package/dist/templates/default/v1/fullstack/tests/unit/hello.function.test.ts +7 -0
  252. package/dist/templates/default/v1/fullstack/tests/unit/review.agent.test.ts +19 -0
  253. package/dist/templates/default/v1/fullstack/tsconfig.json +21 -0
  254. package/dist/templates/default/v1/fullstack/web/app/agent-config.ts +7 -0
  255. package/dist/templates/default/v1/fullstack/web/app/layout.tsx +12 -0
  256. package/dist/templates/default/v1/fullstack/web/app/page.tsx +85 -0
  257. package/dist/templates/default/v1/fullstack/web/app/providers.tsx +29 -0
  258. package/dist/templates/default/v1/fullstack/web/next-env.d.ts +2 -0
  259. package/dist/templates/default/v1/fullstack/web/next.config.ts +5 -0
  260. package/dist/templates/default/v1/fullstack/web/tsconfig.json +10 -0
  261. package/dist/templates/default/v1/minimal/package.json +3 -3
  262. package/dist/templates/default/v1/tasks/.env.example +2 -0
  263. package/dist/templates/default/v1/tasks/README.md +27 -0
  264. package/dist/templates/default/v1/tasks/examples/export-orders.json +1 -0
  265. package/dist/templates/default/v1/tasks/gitignore +10 -0
  266. package/dist/templates/default/v1/tasks/package.json +24 -0
  267. package/dist/templates/default/v1/tasks/relkit.config.ts +11 -0
  268. package/dist/templates/default/v1/tasks/src/orders/functions/health.function.ts +9 -0
  269. package/dist/templates/default/v1/tasks/src/orders/jobs/export-orders.job.ts +15 -0
  270. package/dist/templates/default/v1/tasks/src/orders/service.ts +10 -0
  271. package/dist/templates/default/v1/tasks/src/orders/tasks/export-orders.task.ts +19 -0
  272. package/dist/templates/default/v1/tasks/src/platform/env.ts +3 -0
  273. package/dist/templates/default/v1/tasks/src/routes/health/route.ts +4 -0
  274. package/dist/templates/default/v1/tasks/tests/unit/export-orders.task.test.ts +7 -0
  275. package/dist/templates/default/v1/tasks/tsconfig.json +14 -0
  276. package/package.json +6 -1
  277. package/dist/templates/default/v1/agent/src/hello/functions/ask-assistant.function.ts +0 -16
@@ -1,16 +1,49 @@
1
1
  import { defineAgent } from "@relkit/app/agents";
2
2
  import { z } from "@relkit/app/schema";
3
+ import { FakeToolCallingModel, todoListMiddleware, tool } from "langchain";
3
4
  import lookup from "@app/hello/tools/lookup.tool.js";
4
5
 
6
+ export const uppercase = tool(async ({ text }) => text.toUpperCase(), {
7
+ name: "uppercase",
8
+ description: "Uppercase text without leaving the process",
9
+ schema: z.object({ text: z.string() }),
10
+ });
11
+
5
12
  export default defineAgent({
6
13
  id: "hello.assistant",
7
14
 
8
- input: z.object({ question: z.string().min(1) }),
15
+ input: z.object({ message: z.string().min(1) }),
9
16
  output: z.object({ answer: z.string() }),
10
17
 
11
- model: "openai:gpt-5-mini",
12
- instructions: "Answer greeting questions with the read-only lookup tool.",
13
- tools: [lookup],
18
+ model: new FakeToolCallingModel({
19
+ toolCalls: [
20
+ [
21
+ {
22
+ name: "write_todos",
23
+ args: { todos: [{ content: "Answer the greeting", status: "completed" }] },
24
+ id: "todo-1",
25
+ },
26
+ ],
27
+ [
28
+ {
29
+ name: "relkit_output",
30
+ args: { value: { answer: "Hello from the offline LangChain model." } },
31
+ id: "output-1",
32
+ },
33
+ ],
34
+ ],
35
+ }),
36
+ instructions: "Answer greeting questions with the available tools.",
37
+ tools: [lookup, uppercase],
38
+ middleware: [todoListMiddleware()],
39
+ stateProfile: "default",
40
+ client: {
41
+ public: true,
42
+ state: ["todos"],
43
+ events: { notice: z.object({ message: z.string() }) },
44
+ },
45
+ chat: { input: "message", output: "answer" },
46
+ controls: ["stop"],
14
47
 
15
48
  limits: {
16
49
  maxSteps: 4,
@@ -0,0 +1,43 @@
1
+ import { defineGraph, defineGraphNode } from "@relkit/app/agents";
2
+ import { z } from "@relkit/app/schema";
3
+ import { END, MemorySaver, START, StateSchema, interrupt } from "@langchain/langgraph";
4
+
5
+ const state = new StateSchema({
6
+ request: z.string(),
7
+ approved: z.boolean().optional(),
8
+ result: z.string().optional(),
9
+ });
10
+
11
+ const review = defineGraphNode({
12
+ id: "review",
13
+ input: z.object({ request: z.string() }),
14
+ output: z.object({ approved: z.boolean() }),
15
+ resume: z.boolean(),
16
+ handler: ({ request }) => ({
17
+ approved: interrupt({ question: `Approve "${request}"?` }) as boolean,
18
+ }),
19
+ });
20
+
21
+ const finish = defineGraphNode({
22
+ id: "finish",
23
+ input: z.object({ request: z.string(), approved: z.boolean() }),
24
+ output: z.object({ result: z.string() }),
25
+ handler: ({ request, approved }) => ({
26
+ result: approved ? `${request} approved` : `${request} rejected`,
27
+ }),
28
+ });
29
+
30
+ export default defineGraph({
31
+ id: "hello.review",
32
+ state,
33
+ input: z.object({ request: z.string().min(1) }),
34
+ output: z.object({ result: z.string() }),
35
+ nodes: [review, finish],
36
+ edges: (graph) =>
37
+ graph.addEdge(START, "review").addEdge("review", "finish").addEdge("finish", END),
38
+ checkpointer: new MemorySaver(),
39
+ stateProfile: "default",
40
+ client: { public: true, state: ["approved", "result"] },
41
+ controls: ["stop"],
42
+ limits: { maxSteps: 4, maxToolCalls: 1, timeoutMs: 10_000 },
43
+ });
@@ -2,6 +2,4 @@ import { defineEnv, env } from "@relkit/app/config";
2
2
 
3
3
  export default defineEnv({
4
4
  LOG_LEVEL: env.literal("trace", "debug", "info", "warn", "error").default("info"),
5
- OPENAI_API_KEY: env.secret().requiredIn("production"),
6
- ANTHROPIC_API_KEY: env.secret().requiredIn("production"),
7
5
  });
@@ -1,12 +1,20 @@
1
1
  import { afterAll, expect, test } from "bun:test";
2
- import { createTestApplication } from "@relkit/testing";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ createTestAgentStateProvider,
7
+ createTestApplication,
8
+ createTestRealtimeProvider,
9
+ } from "@relkit/testing";
3
10
  import config from "../../relkit.config.js";
4
11
 
5
- const model = {
6
- resolveModel: (selector = "test:model") => ({ id: selector, model: {} }),
7
- };
12
+ const providerRoot = await mkdtemp(join(tmpdir(), "relkit-agent-template-"));
8
13
  const testApp = await createTestApplication(config, {
9
- providers: { model: { openai: model, anthropic: model } },
14
+ providers: {
15
+ "agent-state": { default: createTestAgentStateProvider(join(providerRoot, "agents")) },
16
+ realtime: { default: createTestRealtimeProvider(join(providerRoot, "realtime")) },
17
+ },
10
18
  });
11
19
 
12
20
  test("GET /hello", async () => {
@@ -15,4 +23,7 @@ test("GET /hello", async () => {
15
23
  expect(await response.json()).toEqual({ message: "Hello, Mustafa!" });
16
24
  });
17
25
 
18
- afterAll(() => testApp.close());
26
+ afterAll(async () => {
27
+ await testApp.close();
28
+ await rm(providerRoot, { force: true, recursive: true });
29
+ });
@@ -1,43 +1,21 @@
1
1
  import { expect, test } from "bun:test";
2
- import { createTestAgent, invokeFunction } from "@relkit/testing";
3
- import assistant from "@app/hello/agents/assistant.agent.js";
4
- import askAssistant from "@app/hello/functions/ask-assistant.function.js";
5
- import hello from "@app/hello/functions/hello.function.js";
2
+ import { invokeAgent } from "@relkit/app/agents";
3
+ import assistant, { uppercase } from "@app/hello/agents/assistant.agent.js";
6
4
  import lookup from "@app/hello/tools/lookup.tool.js";
7
5
 
8
- test("assistant uses a function-derived tool with an offline AI SDK model", async () => {
9
- expect(assistant.model).toBe("openai:gpt-5-mini");
10
- expect(assistant.tools.map((tool) => tool.ref.id)).toEqual(["hello.lookup"]);
6
+ test("assistant runs native tools and todo middleware without a paid provider", async () => {
7
+ expect(assistant.tools).toEqual([expect.objectContaining({ ref: lookup.ref }), uppercase]);
8
+ expect(assistant.middleware.map((middleware) => middleware.name)).toEqual(["todoListMiddleware"]);
9
+ expect(assistant.client?.state).toEqual(["todos"]);
11
10
  expect(assistant.limits).toEqual({ maxSteps: 4, maxToolCalls: 4, timeoutMs: 10_000 });
12
11
 
13
- const agent = createTestAgent({
14
- agent: assistant,
15
- tools: [lookup],
16
- engine: {
17
- invoke: (request) =>
18
- invokeFunction(hello, request.input as Parameters<typeof hello.invoke>[0]),
19
- },
20
- model: { provider: "openai", modelId: "gpt-5-mini" },
21
- // Script the model's choices while running the real greeting function.
22
- script: [
23
- {
24
- type: "tool-call",
25
- callId: "call-1",
26
- toolId: "hello.lookup",
27
- input: { name: "Ada" },
28
- },
29
- { type: "final", output: { answer: "Hello, Ada!" } },
30
- ],
31
- });
32
-
33
12
  await expect(
34
- invokeFunction(
35
- askAssistant,
36
- { question: "greet Ada" },
37
- {
38
- clients: { agents: { "hello.assistant": agent.invoke } },
39
- },
40
- ),
41
- ).resolves.toEqual({ answer: "Hello, Ada!" });
42
- expect(agent.model.calls).toHaveLength(2);
13
+ invokeAgent({
14
+ agent: assistant,
15
+ input: { message: "Greet Ada" },
16
+ threadId: "test:assistant",
17
+ tools: [lookup],
18
+ engine: { invoke: async () => ({ message: "Hello, Ada!" }) },
19
+ }),
20
+ ).resolves.toEqual({ answer: "Hello from the offline LangChain model." });
43
21
  });
@@ -0,0 +1,19 @@
1
+ import { expect, test } from "bun:test";
2
+ import { GraphInterruptedError, invokeAgent } from "@relkit/app/agents";
3
+ import review from "@app/hello/agents/review.agent.js";
4
+
5
+ const runtime = {
6
+ agent: review,
7
+ tools: {},
8
+ engine: { invoke: async () => Promise.reject(new Error("Unexpected function invocation")) },
9
+ };
10
+
11
+ test("review graph interrupts and resumes on an explicit thread", async () => {
12
+ const threadId = "test:review";
13
+ await expect(
14
+ invokeAgent({ ...runtime, input: { request: "Publish" }, threadId }),
15
+ ).rejects.toBeInstanceOf(GraphInterruptedError);
16
+ await expect(invokeAgent({ ...runtime, input: true, threadId, resume: true })).resolves.toEqual({
17
+ result: "Publish approved",
18
+ });
19
+ });
@@ -15,12 +15,12 @@
15
15
  "graph": "relkit graph print"
16
16
  },
17
17
  "dependencies": {
18
- "@relkit/app": "0.4.0"
18
+ "@relkit/app": "0.5.1"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/bun": "1.3.10",
22
- "@relkit/cli": "0.4.0",
23
- "@relkit/testing": "0.4.0",
22
+ "@relkit/cli": "0.5.1",
23
+ "@relkit/testing": "0.5.1",
24
24
  "typescript": "5.9.3"
25
25
  }
26
26
  }
@@ -0,0 +1 @@
1
+ APP_ENV=development
@@ -0,0 +1,15 @@
1
+ # my-app
2
+
3
+ A Relkit backend and Next.js typed client. The example includes an offline
4
+ native LangChain agent with typed todo/tool state, a LangGraph interrupt and
5
+ resume flow, explicit application-owned thread IDs, and selectable SSE or
6
+ WebSocket streaming. `bun run dev` serves the API on port 3000, the web app on
7
+ port 3001, and the Inspector on port 3210.
8
+
9
+ ```sh
10
+ bun install
11
+ bun run dev
12
+ bun run test
13
+ bun run typecheck
14
+ bun run build
15
+ ```
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ .next
3
+ .relkit
4
+ .env
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "my-app",
3
+ "private": true,
4
+ "type": "module",
5
+ "packageManager": "bun@1.3.10",
6
+ "scripts": {
7
+ "dev": "bun run check && bun run --parallel dev:api dev:web",
8
+ "dev:api": "RELKIT_ALLOWED_ORIGINS=http://127.0.0.1:3001 relkit dev",
9
+ "dev:web": "next dev web --hostname 127.0.0.1 --port 3001",
10
+ "check": "relkit check",
11
+ "typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json",
12
+ "test": "bun test",
13
+ "build": "relkit build && next build web",
14
+ "start": "relkit start"
15
+ },
16
+ "dependencies": {
17
+ "@langchain/langgraph": "1.4.14",
18
+ "@relkit/app": "0.5.1",
19
+ "@relkit/client": "0.5.1",
20
+ "@relkit/local": "0.5.1",
21
+ "@tanstack/react-query": "5.102.3",
22
+ "langchain": "1.5.10",
23
+ "next": "16.3.3",
24
+ "react": "19.2.8",
25
+ "react-dom": "19.2.8"
26
+ },
27
+ "devDependencies": {
28
+ "@relkit/cli": "0.5.1",
29
+ "@relkit/testing": "0.5.1",
30
+ "@types/bun": "1.3.10",
31
+ "@types/react": "19.2.18",
32
+ "@types/react-dom": "19.2.7",
33
+ "typescript": "5.9.3"
34
+ }
35
+ }
@@ -0,0 +1,15 @@
1
+ import { defineApp } from "@relkit/app/config";
2
+ import { localAgentState, localRealtime } from "@relkit/local";
3
+ import env from "@app/platform/env.js";
4
+ // relkit:create:deployment-imports
5
+
6
+ export default defineApp({
7
+ env,
8
+ realtime: localRealtime(),
9
+ "agent-state": localAgentState(),
10
+ defaults: { realtime: "default", "agent-state": "default" },
11
+ telemetry: { redaction: { mode: "development-redacted", maxBytes: 65_536 } },
12
+ server: { port: 3000, maxBodyBytes: 1_048_576 },
13
+ inspector: { port: 3210 },
14
+ // relkit:create:deployment
15
+ });
@@ -0,0 +1,46 @@
1
+ import { defineAgent } from "@relkit/app/agents";
2
+ import { z } from "@relkit/app/schema";
3
+ import { FakeToolCallingModel, todoListMiddleware, tool } from "langchain";
4
+ import lookup from "@app/hello/tools/lookup.tool.js";
5
+
6
+ const uppercase = tool(async ({ text }) => text.toUpperCase(), {
7
+ name: "uppercase",
8
+ description: "Uppercase text without leaving the process",
9
+ schema: z.object({ text: z.string() }),
10
+ });
11
+
12
+ export default defineAgent({
13
+ id: "hello.assistant",
14
+ input: z.object({ message: z.string().min(1) }),
15
+ output: z.object({ answer: z.string() }),
16
+ model: new FakeToolCallingModel({
17
+ toolCalls: [
18
+ [
19
+ {
20
+ name: "write_todos",
21
+ args: { todos: [{ content: "Answer the greeting", status: "completed" }] },
22
+ id: "todo-1",
23
+ },
24
+ ],
25
+ [
26
+ {
27
+ name: "relkit_output",
28
+ args: { value: { answer: "Hello from the offline LangChain model." } },
29
+ id: "output-1",
30
+ },
31
+ ],
32
+ ],
33
+ }),
34
+ instructions: "Answer greeting questions with the available tools.",
35
+ tools: [lookup, uppercase],
36
+ middleware: [todoListMiddleware()],
37
+ stateProfile: "default",
38
+ client: {
39
+ public: true,
40
+ state: ["todos"],
41
+ events: { notice: z.object({ message: z.string() }) },
42
+ },
43
+ chat: { input: "message", output: "answer" },
44
+ controls: ["stop"],
45
+ limits: { maxSteps: 4, maxToolCalls: 4, timeoutMs: 10_000 },
46
+ });
@@ -0,0 +1,43 @@
1
+ import { defineGraph, defineGraphNode } from "@relkit/app/agents";
2
+ import { z } from "@relkit/app/schema";
3
+ import { END, MemorySaver, START, StateSchema, interrupt } from "@langchain/langgraph";
4
+
5
+ const state = new StateSchema({
6
+ request: z.string(),
7
+ approved: z.boolean().optional(),
8
+ result: z.string().optional(),
9
+ });
10
+
11
+ const review = defineGraphNode({
12
+ id: "review",
13
+ input: z.object({ request: z.string() }),
14
+ output: z.object({ approved: z.boolean() }),
15
+ resume: z.boolean(),
16
+ handler: ({ request }) => ({
17
+ approved: interrupt({ question: `Approve "${request}"?` }) as boolean,
18
+ }),
19
+ });
20
+
21
+ const finish = defineGraphNode({
22
+ id: "finish",
23
+ input: z.object({ request: z.string(), approved: z.boolean() }),
24
+ output: z.object({ result: z.string() }),
25
+ handler: ({ request, approved }) => ({
26
+ result: approved ? `${request} approved` : `${request} rejected`,
27
+ }),
28
+ });
29
+
30
+ export default defineGraph({
31
+ id: "hello.review",
32
+ state,
33
+ input: z.object({ request: z.string().min(1) }),
34
+ output: z.object({ result: z.string() }),
35
+ nodes: [review, finish],
36
+ edges: (graph) =>
37
+ graph.addEdge(START, "review").addEdge("review", "finish").addEdge("finish", END),
38
+ checkpointer: new MemorySaver(),
39
+ stateProfile: "default",
40
+ client: { public: true, state: ["approved", "result"] },
41
+ controls: ["stop"],
42
+ limits: { maxSteps: 4, maxToolCalls: 1, timeoutMs: 10_000 },
43
+ });
@@ -0,0 +1,10 @@
1
+ import { defineFunction } from "@relkit/app/functions";
2
+ import { z } from "@relkit/app/schema";
3
+
4
+ const hello = defineFunction({
5
+ input: z.object({ name: z.string().min(1).default("world") }),
6
+ output: z.object({ message: z.string() }),
7
+ handler: async ({ name }) => ({ message: `Hello, ${name}!` }),
8
+ });
9
+
10
+ export default hello;
@@ -0,0 +1,4 @@
1
+ import { defineService } from "@relkit/app/services";
2
+ import hello from "@app/hello/functions/hello.function.js";
3
+
4
+ export default defineService({ functions: { hello } });
@@ -0,0 +1,9 @@
1
+ import hello from "@app/hello/functions/hello.function.js";
2
+
3
+ export default hello.asTool({
4
+ id: "hello.lookup",
5
+ description: "Read a greeting for a supplied name",
6
+ sideEffect: "read",
7
+ approval: "never",
8
+ timeoutMs: 2_000,
9
+ });
@@ -0,0 +1,5 @@
1
+ import { defineEnv, env } from "@relkit/app/config";
2
+
3
+ export default defineEnv({
4
+ APP_ENV: env.literal("development", "test", "production").default("development"),
5
+ });
@@ -0,0 +1,4 @@
1
+ import { defineRoute } from "@relkit/app/routes";
2
+ import hello from "@app/hello/service.js";
3
+
4
+ export const GET = defineRoute({ target: hello.hello, client: { operation: "query" } });
@@ -0,0 +1,29 @@
1
+ import { afterAll, expect, test } from "bun:test";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ createTestAgentStateProvider,
7
+ createTestApplication,
8
+ createTestRealtimeProvider,
9
+ } from "@relkit/testing";
10
+ import config from "../../relkit.config.js";
11
+
12
+ const providerRoot = await mkdtemp(join(tmpdir(), "relkit-fullstack-template-"));
13
+ const testApp = await createTestApplication(config, {
14
+ providers: {
15
+ "agent-state": { default: createTestAgentStateProvider(join(providerRoot, "agents")) },
16
+ realtime: { default: createTestRealtimeProvider(join(providerRoot, "realtime")) },
17
+ },
18
+ });
19
+
20
+ test("GET /hello", async () => {
21
+ const response = await testApp.http.request("/hello?name=Mustafa");
22
+ expect(response.status).toBe(200);
23
+ expect(await response.json()).toEqual({ message: "Hello, Mustafa!" });
24
+ });
25
+
26
+ afterAll(async () => {
27
+ await testApp.close();
28
+ await rm(providerRoot, { force: true, recursive: true });
29
+ });
@@ -0,0 +1,18 @@
1
+ import { expect, test } from "bun:test";
2
+ import { invokeAgent } from "@relkit/app/agents";
3
+ import assistant from "@app/hello/agents/assistant.agent.js";
4
+ import lookup from "@app/hello/tools/lookup.tool.js";
5
+
6
+ test("assistant runs with native todo middleware and the offline model", async () => {
7
+ expect(assistant.client?.state).toEqual(["todos"]);
8
+ expect(assistant.middleware.map((middleware) => middleware.name)).toEqual(["todoListMiddleware"]);
9
+ await expect(
10
+ invokeAgent({
11
+ agent: assistant,
12
+ input: { message: "Greet Ada" },
13
+ threadId: "test:assistant",
14
+ tools: [lookup],
15
+ engine: { invoke: async () => ({ message: "Hello, Ada!" }) },
16
+ }),
17
+ ).resolves.toEqual({ answer: "Hello from the offline LangChain model." });
18
+ });
@@ -0,0 +1,7 @@
1
+ import { expect, test } from "bun:test";
2
+ import { invokeFunction } from "@relkit/testing";
3
+ import hello from "@app/hello/functions/hello.function.js";
4
+
5
+ test("greets by name", async () => {
6
+ await expect(invokeFunction(hello, { name: "Ada" })).resolves.toEqual({ message: "Hello, Ada!" });
7
+ });
@@ -0,0 +1,19 @@
1
+ import { expect, test } from "bun:test";
2
+ import { GraphInterruptedError, invokeAgent } from "@relkit/app/agents";
3
+ import review from "@app/hello/agents/review.agent.js";
4
+
5
+ const runtime = {
6
+ agent: review,
7
+ tools: {},
8
+ engine: { invoke: async () => Promise.reject(new Error("Unexpected function invocation")) },
9
+ };
10
+
11
+ test("review graph interrupts and resumes on an explicit thread", async () => {
12
+ const threadId = "test:review";
13
+ await expect(
14
+ invokeAgent({ ...runtime, input: { request: "Publish" }, threadId }),
15
+ ).rejects.toBeInstanceOf(GraphInterruptedError);
16
+ await expect(invokeAgent({ ...runtime, input: true, threadId, resume: true })).resolves.toEqual({
17
+ result: "Publish approved",
18
+ });
19
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "baseUrl": ".",
8
+ "paths": { "@app/*": ["src/*"] },
9
+ "lib": ["ES2022"],
10
+ "types": ["bun"],
11
+ "strict": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "verbatimModuleSyntax": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "skipLibCheck": true
18
+ },
19
+ "include": ["src/**/*.ts", "tests/**/*.ts", "relkit.config.ts"],
20
+ "exclude": ["node_modules", ".relkit", "web"]
21
+ }
@@ -0,0 +1,7 @@
1
+ export const agentThreadIds = {
2
+ assistant: "demo:assistant",
3
+ review: "demo:review",
4
+ } as const;
5
+
6
+ export const agentTransports = ["sse", "websocket"] as const;
7
+ export type AgentTransport = (typeof agentTransports)[number];
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import { Providers } from "./providers";
3
+
4
+ export default function Layout({ children }: { readonly children: ReactNode }) {
5
+ return (
6
+ <html lang="en">
7
+ <body>
8
+ <Providers>{children}</Providers>
9
+ </body>
10
+ </html>
11
+ );
12
+ }
@@ -0,0 +1,85 @@
1
+ "use client";
2
+
3
+ import { useAgent, useRoute } from "@relkit/client/react";
4
+ import { agentThreadIds } from "./agent-config";
5
+
6
+ function interruptQuestion(value: unknown): string {
7
+ if (typeof value === "string") return value;
8
+ if (
9
+ value !== null &&
10
+ typeof value === "object" &&
11
+ "question" in value &&
12
+ typeof value.question === "string"
13
+ ) {
14
+ return value.question;
15
+ }
16
+ return "Approve?";
17
+ }
18
+
19
+ export default function Page() {
20
+ const greeting = useRoute("route.get.hello", { input: { name: "Relkit" } });
21
+ const assistant = useAgent("hello.assistant");
22
+ const review = useAgent("hello.review");
23
+
24
+ return (
25
+ <main>
26
+ <h1>{greeting.data?.message ?? "Loading…"}</h1>
27
+ <section>
28
+ <h2>Typed agent</h2>
29
+ <button
30
+ onClick={() => void assistant.send("Greet Ada", { threadId: agentThreadIds.assistant })}
31
+ >
32
+ Run assistant
33
+ </button>
34
+ <button onClick={() => void assistant.observe({ threadId: agentThreadIds.assistant })}>
35
+ Reconnect
36
+ </button>
37
+ <button onClick={() => void assistant.stop({ threadId: agentThreadIds.assistant })}>
38
+ Stop
39
+ </button>
40
+ <p>{assistant.output?.answer ?? assistant.status}</p>
41
+ {assistant.events.map((event) =>
42
+ event.kind === "custom" ? <p key={event.eventId}>{event.data.message}</p> : null,
43
+ )}
44
+ <ul>
45
+ {assistant.values?.todos.map((todo) => (
46
+ <li key={todo.content}>{`${todo.status}: ${todo.content}`}</li>
47
+ ))}
48
+ {assistant.toolCalls.map((call) => (
49
+ <li key={call.toolCallId}>{`${call.toolId}: ${call.state}`}</li>
50
+ ))}
51
+ </ul>
52
+ </section>
53
+ <section>
54
+ <h2>Interrupt and resume</h2>
55
+ <button
56
+ onClick={() =>
57
+ void review.run({ request: "Publish" }, { threadId: agentThreadIds.review })
58
+ }
59
+ >
60
+ Start review
61
+ </button>
62
+ {review.waiting ? (
63
+ <fieldset>
64
+ <legend>{interruptQuestion(review.waiting.requests[0]?.value)}</legend>
65
+ <button
66
+ onClick={() =>
67
+ void review.run(true, { threadId: agentThreadIds.review, resume: true })
68
+ }
69
+ >
70
+ Approve
71
+ </button>
72
+ <button
73
+ onClick={() =>
74
+ void review.run(false, { threadId: agentThreadIds.review, resume: true })
75
+ }
76
+ >
77
+ Reject
78
+ </button>
79
+ </fieldset>
80
+ ) : null}
81
+ <p>{review.output?.result ?? review.status}</p>
82
+ </section>
83
+ </main>
84
+ );
85
+ }