kernl 0.2.0 → 0.6.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 (267) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/.turbo/turbo-check-types.log +4 -0
  3. package/CHANGELOG.md +147 -0
  4. package/LICENSE +1 -1
  5. package/dist/agent/__tests__/concurrency.test.d.ts +2 -0
  6. package/dist/agent/__tests__/concurrency.test.d.ts.map +1 -0
  7. package/dist/agent/__tests__/concurrency.test.js +152 -0
  8. package/dist/agent/__tests__/run.test.d.ts +2 -0
  9. package/dist/agent/__tests__/run.test.d.ts.map +1 -0
  10. package/dist/agent/__tests__/run.test.js +357 -0
  11. package/dist/agent/index.d.ts +1 -0
  12. package/dist/agent/index.d.ts.map +1 -0
  13. package/dist/agent.d.ts +32 -9
  14. package/dist/agent.d.ts.map +1 -1
  15. package/dist/agent.js +102 -14
  16. package/dist/api/__tests__/cursor-page.test.d.ts +2 -0
  17. package/dist/api/__tests__/cursor-page.test.d.ts.map +1 -0
  18. package/dist/api/__tests__/cursor-page.test.js +414 -0
  19. package/dist/api/__tests__/offset-page.test.d.ts +2 -0
  20. package/dist/api/__tests__/offset-page.test.d.ts.map +1 -0
  21. package/dist/api/__tests__/offset-page.test.js +510 -0
  22. package/dist/api/__tests__/threads.test.d.ts +2 -0
  23. package/dist/api/__tests__/threads.test.d.ts.map +1 -0
  24. package/dist/api/__tests__/threads.test.js +338 -0
  25. package/dist/api/models/index.d.ts +2 -0
  26. package/dist/api/models/index.d.ts.map +1 -0
  27. package/dist/api/models/thread.d.ts +120 -0
  28. package/dist/api/models/thread.d.ts.map +1 -0
  29. package/dist/api/pagination/base.d.ts +48 -0
  30. package/dist/api/pagination/base.d.ts.map +1 -0
  31. package/dist/api/pagination/base.js +45 -0
  32. package/dist/api/pagination/cursor.d.ts +44 -0
  33. package/dist/api/pagination/cursor.d.ts.map +1 -0
  34. package/dist/api/pagination/cursor.js +52 -0
  35. package/dist/api/pagination/offset.d.ts +42 -0
  36. package/dist/api/pagination/offset.d.ts.map +1 -0
  37. package/dist/api/pagination/offset.js +55 -0
  38. package/dist/api/resources/threads/events.d.ts +21 -0
  39. package/dist/api/resources/threads/events.d.ts.map +1 -0
  40. package/dist/api/resources/threads/events.js +24 -0
  41. package/dist/api/resources/threads/index.d.ts +4 -0
  42. package/dist/api/resources/threads/index.d.ts.map +1 -0
  43. package/dist/api/resources/threads/index.js +2 -0
  44. package/dist/api/resources/threads/threads.d.ts +57 -0
  45. package/dist/api/resources/threads/threads.d.ts.map +1 -0
  46. package/dist/api/resources/threads/threads.js +199 -0
  47. package/dist/api/resources/threads/types.d.ts +123 -0
  48. package/dist/api/resources/threads/types.d.ts.map +1 -0
  49. package/dist/api/resources/threads/utils.d.ts +18 -0
  50. package/dist/api/resources/threads/utils.d.ts.map +1 -0
  51. package/dist/api/resources/threads/utils.js +78 -0
  52. package/dist/context.d.ts +5 -1
  53. package/dist/context.d.ts.map +1 -1
  54. package/dist/context.js +6 -1
  55. package/dist/index.d.ts +9 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +7 -0
  58. package/dist/internal.d.ts +4 -0
  59. package/dist/internal.d.ts.map +1 -0
  60. package/dist/internal.js +2 -0
  61. package/dist/kernl/index.d.ts +3 -0
  62. package/dist/kernl/index.d.ts.map +1 -0
  63. package/dist/kernl/index.js +2 -0
  64. package/dist/kernl/kernl.d.ts +64 -0
  65. package/dist/kernl/kernl.d.ts.map +1 -0
  66. package/dist/kernl/kernl.js +116 -0
  67. package/dist/kernl/threads.d.ts +110 -0
  68. package/dist/kernl/threads.d.ts.map +1 -0
  69. package/dist/kernl/threads.js +126 -0
  70. package/dist/kernl.d.ts +22 -6
  71. package/dist/kernl.d.ts.map +1 -1
  72. package/dist/kernl.js +73 -10
  73. package/dist/lib/env.d.ts +3 -3
  74. package/dist/lib/env.js +1 -1
  75. package/dist/mcp/__tests__/integration.test.js +8 -8
  76. package/dist/mcp/__tests__/utils.test.js +6 -6
  77. package/dist/mcp/http.d.ts +1 -1
  78. package/dist/mcp/http.d.ts.map +1 -1
  79. package/dist/mcp/http.js +9 -9
  80. package/dist/mcp/sse.d.ts +1 -1
  81. package/dist/mcp/sse.d.ts.map +1 -1
  82. package/dist/mcp/sse.js +7 -7
  83. package/dist/mcp/utils.d.ts +1 -1
  84. package/dist/mcp/utils.d.ts.map +1 -1
  85. package/dist/mcp/utils.js +4 -5
  86. package/dist/storage/__tests__/in-memory.test.d.ts +2 -0
  87. package/dist/storage/__tests__/in-memory.test.d.ts.map +1 -0
  88. package/dist/storage/__tests__/in-memory.test.js +455 -0
  89. package/dist/storage/base.d.ts +64 -0
  90. package/dist/storage/base.d.ts.map +1 -0
  91. package/dist/storage/base.js +4 -0
  92. package/dist/storage/in-memory.d.ts +62 -0
  93. package/dist/storage/in-memory.d.ts.map +1 -0
  94. package/dist/storage/in-memory.js +283 -0
  95. package/dist/storage/index.d.ts +10 -0
  96. package/dist/storage/index.d.ts.map +1 -0
  97. package/dist/storage/index.js +7 -0
  98. package/dist/storage/thread.d.ts +123 -0
  99. package/dist/storage/thread.d.ts.map +1 -0
  100. package/dist/storage/thread.js +4 -0
  101. package/dist/task.d.ts +5 -3
  102. package/dist/task.d.ts.map +1 -1
  103. package/dist/task.js +10 -8
  104. package/dist/thread/__tests__/fixtures/mock-model.d.ts +1 -2
  105. package/dist/thread/__tests__/fixtures/mock-model.d.ts.map +1 -1
  106. package/dist/thread/__tests__/integration.test.js +73 -5
  107. package/dist/thread/__tests__/namespace.test.d.ts +2 -0
  108. package/dist/thread/__tests__/namespace.test.d.ts.map +1 -0
  109. package/dist/thread/__tests__/namespace.test.js +131 -0
  110. package/dist/thread/__tests__/thread-persistence.test.d.ts +2 -0
  111. package/dist/thread/__tests__/thread-persistence.test.d.ts.map +1 -0
  112. package/dist/thread/__tests__/thread-persistence.test.js +351 -0
  113. package/dist/thread/__tests__/thread.test.js +49 -51
  114. package/dist/thread/thread.d.ts +70 -18
  115. package/dist/thread/thread.d.ts.map +1 -1
  116. package/dist/thread/thread.js +211 -73
  117. package/dist/thread/utils.d.ts +36 -8
  118. package/dist/thread/utils.d.ts.map +1 -1
  119. package/dist/thread/utils.js +52 -8
  120. package/dist/tool/__tests__/fixtures.js +1 -1
  121. package/dist/tool/__tests__/toolkit.test.js +15 -12
  122. package/dist/tool/tool.js +3 -3
  123. package/dist/types/kernl.d.ts +42 -0
  124. package/dist/types/kernl.d.ts.map +1 -0
  125. package/dist/types/thread.d.ts +108 -22
  126. package/dist/types/thread.d.ts.map +1 -1
  127. package/dist/types/thread.js +12 -0
  128. package/package.json +11 -7
  129. package/src/agent/__tests__/concurrency.test.ts +194 -0
  130. package/src/agent/__tests__/run.test.ts +441 -0
  131. package/src/agent/index.ts +0 -0
  132. package/src/agent.ts +141 -24
  133. package/src/api/__tests__/cursor-page.test.ts +512 -0
  134. package/src/api/__tests__/offset-page.test.ts +624 -0
  135. package/src/api/__tests__/threads.test.ts +415 -0
  136. package/src/api/models/index.ts +6 -0
  137. package/src/api/models/thread.ts +138 -0
  138. package/src/api/pagination/base.ts +79 -0
  139. package/src/api/pagination/cursor.ts +86 -0
  140. package/src/api/pagination/offset.ts +89 -0
  141. package/src/api/resources/threads/events.ts +26 -0
  142. package/src/api/resources/threads/index.ts +9 -0
  143. package/src/api/resources/threads/threads.ts +256 -0
  144. package/src/api/resources/threads/types.ts +143 -0
  145. package/src/api/resources/threads/utils.ts +104 -0
  146. package/src/context.ts +10 -1
  147. package/src/index.ts +49 -1
  148. package/src/internal.ts +15 -0
  149. package/src/kernl.ts +86 -17
  150. package/src/mcp/__tests__/integration.test.ts +8 -9
  151. package/src/mcp/__tests__/utils.test.ts +6 -6
  152. package/src/mcp/http.ts +9 -9
  153. package/src/mcp/sse.ts +7 -7
  154. package/src/mcp/utils.ts +6 -5
  155. package/src/storage/__tests__/in-memory.test.ts +534 -0
  156. package/src/storage/base.ts +77 -0
  157. package/src/storage/in-memory.ts +372 -0
  158. package/src/storage/index.ts +21 -0
  159. package/src/storage/thread.ts +141 -0
  160. package/src/task.ts +12 -10
  161. package/src/thread/__tests__/fixtures/mock-model.ts +2 -4
  162. package/src/thread/__tests__/integration.test.ts +111 -10
  163. package/src/thread/__tests__/namespace.test.ts +158 -0
  164. package/src/thread/__tests__/thread-persistence.test.ts +367 -0
  165. package/src/thread/__tests__/thread.test.ts +52 -54
  166. package/src/thread/thread.ts +247 -96
  167. package/src/thread/utils.ts +76 -13
  168. package/src/tool/__tests__/fixtures.ts +1 -1
  169. package/src/tool/__tests__/toolkit.test.ts +15 -12
  170. package/src/tool/tool.ts +3 -3
  171. package/src/types/kernl.ts +51 -0
  172. package/src/types/thread.ts +139 -25
  173. package/vitest.config.ts +1 -0
  174. package/dist/env.d.ts +0 -45
  175. package/dist/env.d.ts.map +0 -1
  176. package/dist/env.js +0 -31
  177. package/dist/error.d.ts +0 -1
  178. package/dist/error.d.ts.map +0 -1
  179. package/dist/kernel.d.ts +0 -7
  180. package/dist/kernel.d.ts.map +0 -1
  181. package/dist/kernel.js +0 -7
  182. package/dist/lib/serde/__tests__/codec.test.d.ts +0 -2
  183. package/dist/lib/serde/__tests__/codec.test.d.ts.map +0 -1
  184. package/dist/lib/serde/__tests__/codec.test.js +0 -75
  185. package/dist/lib/serde/codec.d.ts +0 -12
  186. package/dist/lib/serde/codec.d.ts.map +0 -1
  187. package/dist/lib/serde/codec.js +0 -54
  188. package/dist/lib/serde/thread.d.ts +0 -1
  189. package/dist/lib/serde/thread.d.ts.map +0 -1
  190. package/dist/lib/serde/thread.js +0 -172
  191. package/dist/lib/serde/tool.d.ts +0 -36
  192. package/dist/lib/serde/tool.d.ts.map +0 -1
  193. package/dist/lib/utils.d.ts +0 -19
  194. package/dist/lib/utils.d.ts.map +0 -1
  195. package/dist/lib/utils.js +0 -41
  196. package/dist/logger.d.ts +0 -36
  197. package/dist/logger.d.ts.map +0 -1
  198. package/dist/logger.js +0 -43
  199. package/dist/mcp/__tests__/fixtures/echo-server.d.ts +0 -3
  200. package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +0 -1
  201. package/dist/mcp/__tests__/fixtures/echo-server.js +0 -92
  202. package/dist/mcp/__tests__/fixtures/math-server.d.ts +0 -3
  203. package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +0 -1
  204. package/dist/mcp/__tests__/fixtures/math-server.js +0 -98
  205. package/dist/mcp/__tests__/fixtures/test-server.d.ts +0 -3
  206. package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +0 -1
  207. package/dist/mcp/__tests__/fixtures/test-server.js +0 -163
  208. package/dist/mcp/__tests__/test-utils.d.ts +0 -17
  209. package/dist/mcp/__tests__/test-utils.d.ts.map +0 -1
  210. package/dist/mcp/__tests__/test-utils.js +0 -42
  211. package/dist/mcp/node.d.ts +0 -60
  212. package/dist/mcp/node.d.ts.map +0 -1
  213. package/dist/mcp/node.js +0 -297
  214. package/dist/model.d.ts +0 -175
  215. package/dist/model.d.ts.map +0 -1
  216. package/dist/providers/ai.d.ts +0 -1
  217. package/dist/providers/ai.d.ts.map +0 -1
  218. package/dist/providers/ai.js +0 -1
  219. package/dist/providers/default.d.ts +0 -16
  220. package/dist/providers/default.d.ts.map +0 -1
  221. package/dist/providers/default.js +0 -17
  222. package/dist/providers/registry.d.ts +0 -1
  223. package/dist/providers/registry.d.ts.map +0 -1
  224. package/dist/providers/registry.js +0 -1
  225. package/dist/sched/scheduler.d.ts +0 -20
  226. package/dist/sched/scheduler.d.ts.map +0 -1
  227. package/dist/sched/task.d.ts +0 -92
  228. package/dist/sched/task.d.ts.map +0 -1
  229. package/dist/sched/task.js +0 -102
  230. package/dist/serde/__tests__/codec.test.d.ts +0 -2
  231. package/dist/serde/__tests__/codec.test.d.ts.map +0 -1
  232. package/dist/serde/__tests__/codec.test.js +0 -75
  233. package/dist/serde/codec.d.ts +0 -12
  234. package/dist/serde/codec.d.ts.map +0 -1
  235. package/dist/serde/codec.js +0 -54
  236. package/dist/serde/json.d.ts +0 -8
  237. package/dist/serde/json.d.ts.map +0 -1
  238. package/dist/serde/json.js +0 -13
  239. package/dist/serde/thread.d.ts +0 -687
  240. package/dist/serde/thread.d.ts.map +0 -1
  241. package/dist/serde/thread.js +0 -158
  242. package/dist/serde/tool.d.ts +0 -36
  243. package/dist/serde/tool.d.ts.map +0 -1
  244. package/dist/session.d.ts +0 -1
  245. package/dist/session.d.ts.map +0 -1
  246. package/dist/session.js +0 -1
  247. package/dist/thread/__tests__/stream.test.d.ts +0 -2
  248. package/dist/thread/__tests__/stream.test.d.ts.map +0 -1
  249. package/dist/thread/__tests__/stream.test.js +0 -244
  250. package/dist/tool/mcp.d.ts +0 -75
  251. package/dist/tool/mcp.d.ts.map +0 -1
  252. package/dist/tool/mcp.js +0 -111
  253. package/dist/tools.d.ts +0 -362
  254. package/dist/tools.d.ts.map +0 -1
  255. package/dist/tools.js +0 -220
  256. package/dist/types/proto.d.ts +0 -1551
  257. package/dist/types/proto.d.ts.map +0 -1
  258. package/dist/types/proto.js +0 -531
  259. package/dist/usage.d.ts +0 -43
  260. package/dist/usage.d.ts.map +0 -1
  261. package/dist/usage.js +0 -61
  262. package/src/lib/serde/thread.ts +0 -188
  263. /package/dist/{error.js → agent/index.js} +0 -0
  264. /package/dist/{lib/serde/tool.js → api/models/index.js} +0 -0
  265. /package/dist/{model.js → api/models/thread.js} +0 -0
  266. /package/dist/{sched/scheduler.js → api/resources/threads/types.js} +0 -0
  267. /package/dist/{serde/tool.js → types/kernl.js} +0 -0
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { Thread } from "../thread";
3
+ import { Agent } from "../../agent";
4
+ import { Kernl } from "../../kernl";
5
+ import { InMemoryStorage } from "../../storage/in-memory";
6
+ import { createMockModel } from "./fixtures/mock-model";
7
+ // Helper to create user message input
8
+ function userMessage(text) {
9
+ return [
10
+ {
11
+ kind: "message",
12
+ id: "msg-test",
13
+ role: "user",
14
+ content: [{ kind: "text", text }],
15
+ },
16
+ ];
17
+ }
18
+ describe("Thread Namespaces", () => {
19
+ it("should use 'kernl' namespace by default", () => {
20
+ const model = createMockModel(async () => ({
21
+ content: [],
22
+ finishReason: "stop",
23
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
24
+ warnings: [],
25
+ }));
26
+ const agent = new Agent({
27
+ id: "test",
28
+ name: "Test",
29
+ instructions: "Test agent",
30
+ model,
31
+ });
32
+ const thread = new Thread({ agent });
33
+ expect(thread.namespace).toBe("kernl");
34
+ expect(thread.context.namespace).toBe("kernl");
35
+ });
36
+ it("should accept custom namespace in constructor", () => {
37
+ const model = createMockModel(async () => ({
38
+ content: [],
39
+ finishReason: "stop",
40
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
41
+ warnings: [],
42
+ }));
43
+ const agent = new Agent({
44
+ id: "test",
45
+ name: "Test",
46
+ instructions: "Test agent",
47
+ model,
48
+ });
49
+ const thread = new Thread({
50
+ agent,
51
+ namespace: "tenant-123",
52
+ });
53
+ expect(thread.namespace).toBe("tenant-123");
54
+ expect(thread.context.namespace).toBe("tenant-123");
55
+ });
56
+ it("should propagate namespace from agent.run() options", async () => {
57
+ const model = createMockModel(async (req) => {
58
+ return {
59
+ content: [
60
+ {
61
+ kind: "message",
62
+ id: "msg_1",
63
+ role: "assistant",
64
+ content: [{ kind: "text", text: "Hello" }],
65
+ },
66
+ ],
67
+ finishReason: "stop",
68
+ usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
69
+ warnings: [],
70
+ };
71
+ });
72
+ const agent = new Agent({
73
+ id: "test",
74
+ name: "Test",
75
+ instructions: "Test agent",
76
+ model,
77
+ });
78
+ // Mock storage to intercept the thread creation
79
+ const storage = new InMemoryStorage();
80
+ const kernl = new Kernl({ storage: { db: storage } });
81
+ kernl.register(agent);
82
+ const result = await agent.run("Hello", {
83
+ namespace: "custom-ns",
84
+ });
85
+ // Verify storage persistence
86
+ const threads = await storage.threads.list();
87
+ const persistedThread = threads[0];
88
+ expect(persistedThread).toBeDefined();
89
+ expect(persistedThread.namespace).toBe("custom-ns");
90
+ expect(persistedThread.context.namespace).toBe("custom-ns");
91
+ });
92
+ it("should filter threads by namespace in storage", async () => {
93
+ const model = createMockModel(async () => ({
94
+ content: [
95
+ {
96
+ kind: "message",
97
+ id: "msg_ok",
98
+ role: "assistant",
99
+ content: [{ kind: "text", text: "ok" }],
100
+ },
101
+ ],
102
+ finishReason: "stop",
103
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
104
+ warnings: [],
105
+ }));
106
+ const agent = new Agent({
107
+ id: "test",
108
+ name: "Test",
109
+ instructions: "Test agent",
110
+ model,
111
+ });
112
+ const storage = new InMemoryStorage();
113
+ const kernl = new Kernl({ storage: { db: storage } });
114
+ kernl.register(agent);
115
+ // Create threads in different namespaces
116
+ await agent.run("Thread 1", { namespace: "ns-a" });
117
+ await agent.run("Thread 2", { namespace: "ns-b" });
118
+ await agent.run("Thread 3", { namespace: "ns-a" });
119
+ // Verify filtering
120
+ const nsAThreads = await storage.threads.list({
121
+ filter: { namespace: "ns-a" },
122
+ });
123
+ expect(nsAThreads).toHaveLength(2);
124
+ expect(nsAThreads.every(t => t.namespace === "ns-a")).toBe(true);
125
+ const nsBThreads = await storage.threads.list({
126
+ filter: { namespace: "ns-b" },
127
+ });
128
+ expect(nsBThreads).toHaveLength(1);
129
+ expect(nsBThreads[0].namespace).toBe("ns-b");
130
+ });
131
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thread-persistence.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-persistence.test.d.ts","sourceRoot":"","sources":["../../../src/thread/__tests__/thread-persistence.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,351 @@
1
+ import { describe, it } from "vitest";
2
+ describe("Thread Persistence", () => {
3
+ describe("Per-tick persistence (no tools)", () => {
4
+ it.skip("should persist on first tick: insert thread + append events + update state", async () => {
5
+ // TODO: Enable once InMemoryStorage is implemented
6
+ // const storage = new InMemoryStorage();
7
+ // const model = createMockModel(async () => ({
8
+ // content: [message({ role: "assistant", text: "Response" })],
9
+ // finishReason: "stop",
10
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
11
+ // warnings: [],
12
+ // }));
13
+ //
14
+ // const agent = new Agent({
15
+ // id: "test-agent",
16
+ // name: "Test",
17
+ // instructions: "Test",
18
+ // model,
19
+ // });
20
+ //
21
+ // const thread = new Thread({
22
+ // agent,
23
+ // input: [message({ role: "user", text: "Hello" })],
24
+ // storage: storage.threads,
25
+ // });
26
+ //
27
+ // await thread.execute();
28
+ //
29
+ // // Verify insert called once (thread record created)
30
+ // expect(storage.calls.insert).toHaveLength(1);
31
+ // expect(storage.calls.insert[0].id).toBe(thread.tid);
32
+ // expect(storage.calls.insert[0].agentId).toBe("test-agent");
33
+ //
34
+ // // Verify state updates (RUNNING → STOPPED)
35
+ // expect(storage.calls.update.length).toBeGreaterThanOrEqual(2);
36
+ // const runningUpdate = storage.calls.update[0];
37
+ // expect(runningUpdate.patch.state).toBe(RUNNING);
38
+ //
39
+ // const stoppedUpdate = storage.calls.update[storage.calls.update.length - 1];
40
+ // expect(stoppedUpdate.patch.state).toBe(STOPPED);
41
+ //
42
+ // // Verify append called with events from tick
43
+ // expect(storage.calls.append.length).toBeGreaterThan(0);
44
+ // const allAppendedEvents = storage.calls.append.flat();
45
+ //
46
+ // // Should have user message + assistant message
47
+ // expect(allAppendedEvents.length).toBeGreaterThanOrEqual(2);
48
+ // expect(allAppendedEvents).toEqual(
49
+ // expect.arrayContaining([
50
+ // expect.objectContaining({ kind: "message", role: "user" }),
51
+ // expect.objectContaining({ kind: "message", role: "assistant" }),
52
+ // ])
53
+ // );
54
+ //
55
+ // // Verify tick and seq are correct
56
+ // expect(thread._tick).toBe(1);
57
+ // expect(thread._seq).toBeGreaterThanOrEqual(1);
58
+ });
59
+ it.skip("should persist events with monotonically increasing seq", async () => {
60
+ // TODO: Enable once InMemoryStorage is implemented
61
+ // const storage = new InMemoryStorage();
62
+ // let callCount = 0;
63
+ //
64
+ // const model = createMockModel(async () => {
65
+ // callCount++;
66
+ // if (callCount === 1) {
67
+ // return {
68
+ // content: [
69
+ // message({ role: "assistant", text: "" }),
70
+ // { kind: "tool-call", toolId: "test", callId: "call_1", state: IN_PROGRESS, arguments: "{}" },
71
+ // ],
72
+ // finishReason: "stop",
73
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
74
+ // warnings: [],
75
+ // };
76
+ // }
77
+ // return {
78
+ // content: [message({ role: "assistant", text: "Done" })],
79
+ // finishReason: "stop",
80
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
81
+ // warnings: [],
82
+ // };
83
+ // });
84
+ //
85
+ // const testTool = tool({
86
+ // id: "test",
87
+ // description: "Test",
88
+ // parameters: undefined,
89
+ // execute: async () => "result",
90
+ // });
91
+ //
92
+ // const agent = new Agent({
93
+ // id: "test-agent",
94
+ // name: "Test",
95
+ // instructions: "Test",
96
+ // model,
97
+ // toolkits: [new FunctionToolkit({ id: "tools", tools: [testTool] })],
98
+ // });
99
+ //
100
+ // const thread = new Thread({
101
+ // agent,
102
+ // input: [message({ role: "user", text: "Test" })],
103
+ // storage: storage.threads,
104
+ // });
105
+ //
106
+ // await thread.execute();
107
+ //
108
+ // // Verify all events have monotonically increasing seq
109
+ // const allEvents = storage.calls.append.flat();
110
+ // const seqNumbers = allEvents.map(e => e.seq);
111
+ //
112
+ // expect(seqNumbers).toEqual([...seqNumbers].sort((a, b) => a - b));
113
+ // expect(new Set(seqNumbers).size).toBe(seqNumbers.length); // No duplicates
114
+ // expect(seqNumbers[0]).toBe(0); // Starts at 0
115
+ // expect(seqNumbers[seqNumbers.length - 1]).toBe(seqNumbers.length - 1); // No gaps
116
+ });
117
+ });
118
+ describe("Per-tick persistence (with tools)", () => {
119
+ it.skip("should persist model events and tool results together in same append", async () => {
120
+ // TODO: Enable once InMemoryStorage is implemented
121
+ // const storage = new InMemoryStorage();
122
+ // let callCount = 0;
123
+ //
124
+ // const model = createMockModel(async () => {
125
+ // callCount++;
126
+ // if (callCount === 1) {
127
+ // return {
128
+ // content: [
129
+ // message({ role: "assistant", text: "" }),
130
+ // { kind: "tool-call", toolId: "echo", callId: "call_1", state: IN_PROGRESS, arguments: '{"text":"test"}' },
131
+ // ],
132
+ // finishReason: "stop",
133
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
134
+ // warnings: [],
135
+ // };
136
+ // }
137
+ // return {
138
+ // content: [message({ role: "assistant", text: "Done" })],
139
+ // finishReason: "stop",
140
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
141
+ // warnings: [],
142
+ // };
143
+ // });
144
+ //
145
+ // const echoTool = tool({
146
+ // id: "echo",
147
+ // description: "Echo",
148
+ // parameters: z.object({ text: z.string() }),
149
+ // execute: async (ctx, { text }) => `Echo: ${text}`,
150
+ // });
151
+ //
152
+ // const agent = new Agent({
153
+ // id: "test-agent",
154
+ // name: "Test",
155
+ // instructions: "Test",
156
+ // model,
157
+ // toolkits: [new FunctionToolkit({ id: "tools", tools: [echoTool] })],
158
+ // });
159
+ //
160
+ // const thread = new Thread({
161
+ // agent,
162
+ // input: [message({ role: "user", text: "Test" })],
163
+ // storage: storage.threads,
164
+ // });
165
+ //
166
+ // await thread.execute();
167
+ //
168
+ // // Find the append call for tick 1 (should include model message, tool-call, and tool-result)
169
+ // const tick1Events = storage.calls.append.find(batch =>
170
+ // batch.some(e => e.kind === "tool-result")
171
+ // );
172
+ //
173
+ // expect(tick1Events).toBeDefined();
174
+ // expect(tick1Events!.length).toBeGreaterThanOrEqual(3);
175
+ //
176
+ // // Verify tick 1 has: assistant message, tool-call, tool-result
177
+ // expect(tick1Events).toEqual(
178
+ // expect.arrayContaining([
179
+ // expect.objectContaining({ kind: "message", role: "assistant" }),
180
+ // expect.objectContaining({ kind: "tool-call", toolId: "echo" }),
181
+ // expect.objectContaining({ kind: "tool-result", toolId: "echo", result: "Echo: test" }),
182
+ // ])
183
+ // );
184
+ });
185
+ });
186
+ describe("Run lifecycle state transitions", () => {
187
+ it.skip("should persist RUNNING state at start, STOPPED at finish", async () => {
188
+ // TODO: Enable once InMemoryStorage is implemented
189
+ // const storage = new InMemoryStorage();
190
+ // const model = createMockModel(async () => ({
191
+ // content: [message({ role: "assistant", text: "Done" })],
192
+ // finishReason: "stop",
193
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
194
+ // warnings: [],
195
+ // }));
196
+ //
197
+ // const agent = new Agent({
198
+ // id: "test-agent",
199
+ // name: "Test",
200
+ // instructions: "Test",
201
+ // model,
202
+ // });
203
+ //
204
+ // const thread = new Thread({
205
+ // agent,
206
+ // input: [message({ role: "user", text: "Test" })],
207
+ // storage: storage.threads,
208
+ // });
209
+ //
210
+ // const events = [];
211
+ // for await (const event of thread.stream()) {
212
+ // events.push(event);
213
+ // }
214
+ //
215
+ // // First state update should be RUNNING
216
+ // expect(storage.calls.update[0].patch.state).toBe(RUNNING);
217
+ //
218
+ // // Last state update should be STOPPED
219
+ // const lastUpdate = storage.calls.update[storage.calls.update.length - 1];
220
+ // expect(lastUpdate.patch.state).toBe(STOPPED);
221
+ //
222
+ // // Verify stream-start event
223
+ // expect(events[0]).toEqual({ kind: "stream-start" });
224
+ });
225
+ it.skip("should persist STOPPED state even on model error", async () => {
226
+ // TODO: Enable once InMemoryStorage is implemented
227
+ // const storage = new InMemoryStorage();
228
+ // const model = createMockModel(async () => {
229
+ // throw new Error("Model error");
230
+ // });
231
+ //
232
+ // const agent = new Agent({
233
+ // id: "test-agent",
234
+ // name: "Test",
235
+ // instructions: "Test",
236
+ // model,
237
+ // });
238
+ //
239
+ // const thread = new Thread({
240
+ // agent,
241
+ // input: [message({ role: "user", text: "Test" })],
242
+ // storage: storage.threads,
243
+ // });
244
+ //
245
+ // // Execute should complete without throwing (error becomes event)
246
+ // const events = [];
247
+ // for await (const event of thread.stream()) {
248
+ // events.push(event);
249
+ // }
250
+ //
251
+ // // Should have error event
252
+ // expect(events.some(e => e.kind === "error")).toBe(true);
253
+ //
254
+ // // Should still transition to STOPPED
255
+ // const lastUpdate = storage.calls.update[storage.calls.update.length - 1];
256
+ // expect(lastUpdate.patch.state).toBe(STOPPED);
257
+ });
258
+ });
259
+ describe("Storage failure propagation", () => {
260
+ it.skip("should throw and halt execution when insert fails", async () => {
261
+ // TODO: Enable once InMemoryStorage is implemented
262
+ // const storage = new InMemoryStorage();
263
+ // storage.shouldFailOnPersist = true;
264
+ //
265
+ // const model = createMockModel(async () => ({
266
+ // content: [message({ role: "assistant", text: "Done" })],
267
+ // finishReason: "stop",
268
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
269
+ // warnings: [],
270
+ // }));
271
+ //
272
+ // const agent = new Agent({
273
+ // id: "test-agent",
274
+ // name: "Test",
275
+ // instructions: "Test",
276
+ // model,
277
+ // });
278
+ //
279
+ // const thread = new Thread({
280
+ // agent,
281
+ // input: [message({ role: "user", text: "Test" })],
282
+ // storage: storage.threads,
283
+ // });
284
+ //
285
+ // // Should reject with storage error
286
+ // await expect(thread.execute()).rejects.toThrow("Simulated insert failure");
287
+ //
288
+ // // Should not have persisted events after insert failure
289
+ // expect(storage.calls.append).toHaveLength(0);
290
+ });
291
+ it.skip("should throw and halt execution when append fails", async () => {
292
+ // TODO: Enable once InMemoryStorage is implemented
293
+ // const storage = new InMemoryStorage();
294
+ //
295
+ // const model = createMockModel(async () => ({
296
+ // content: [message({ role: "assistant", text: "Done" })],
297
+ // finishReason: "stop",
298
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
299
+ // warnings: [],
300
+ // }));
301
+ //
302
+ // const agent = new Agent({
303
+ // id: "test-agent",
304
+ // name: "Test",
305
+ // instructions: "Test",
306
+ // model,
307
+ // });
308
+ //
309
+ // const thread = new Thread({
310
+ // agent,
311
+ // input: [message({ role: "user", text: "Test" })],
312
+ // storage: storage.threads,
313
+ // });
314
+ //
315
+ // // Fail after insert succeeds
316
+ // storage.shouldFailOnAppend = true;
317
+ //
318
+ // await expect(thread.execute()).rejects.toThrow("Simulated append failure");
319
+ });
320
+ it.skip("should not retry persist on failure (fail hard)", async () => {
321
+ // TODO: Enable once InMemoryStorage is implemented
322
+ // const storage = new InMemoryStorage();
323
+ // storage.shouldFailOnPersist = true;
324
+ //
325
+ // const model = createMockModel(async () => ({
326
+ // content: [message({ role: "assistant", text: "Done" })],
327
+ // finishReason: "stop",
328
+ // usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
329
+ // warnings: [],
330
+ // }));
331
+ //
332
+ // const agent = new Agent({
333
+ // id: "test-agent",
334
+ // name: "Test",
335
+ // instructions: "Test",
336
+ // model,
337
+ // });
338
+ //
339
+ // const thread = new Thread({
340
+ // agent,
341
+ // input: [message({ role: "user", text: "Test" })],
342
+ // storage: storage.threads,
343
+ // });
344
+ //
345
+ // await expect(thread.execute()).rejects.toThrow();
346
+ //
347
+ // // Should have only tried once
348
+ // expect(storage.calls.insert).toHaveLength(1);
349
+ });
350
+ });
351
+ });