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