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
@@ -0,0 +1,534 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { STOPPED, RUNNING, DEAD, IN_PROGRESS } from "@kernl-sdk/protocol";
3
+
4
+ import { InMemoryStorage, InMemoryThreadStore } from "../in-memory";
5
+ import { Agent } from "@/agent";
6
+ import { Thread } from "@/thread";
7
+ import type { NewThread } from "@/storage";
8
+ import type { ThreadEvent } from "@/types/thread";
9
+ import { createMockModel } from "@/thread/__tests__/fixtures/mock-model";
10
+
11
+ describe("InMemoryThreadStore", () => {
12
+ let store: InMemoryThreadStore;
13
+ let agent: Agent;
14
+ let model: ReturnType<typeof createMockModel>;
15
+
16
+ beforeEach(() => {
17
+ store = new InMemoryThreadStore();
18
+ model = createMockModel(async () => ({
19
+ content: [
20
+ {
21
+ kind: "message" as const,
22
+ id: "msg_1",
23
+ role: "assistant" as const,
24
+ content: [{ kind: "text" as const, text: "Hello" }],
25
+ },
26
+ ],
27
+ finishReason: "stop",
28
+ usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
29
+ warnings: [],
30
+ }));
31
+
32
+ agent = new Agent({
33
+ id: "test-agent",
34
+ name: "Test Agent",
35
+ instructions: "Test instructions",
36
+ model,
37
+ });
38
+
39
+ // Bind registries
40
+ const agents = new Map([[agent.id, agent]]);
41
+ const models = new Map([[`${model.provider}/${model.modelId}`, model]]);
42
+ store.bind({ agents, models });
43
+ });
44
+
45
+ describe("insert", () => {
46
+ it("should insert a new thread", async () => {
47
+ const newThread: NewThread = {
48
+ id: "thread-1",
49
+ namespace: "test",
50
+ agentId: agent.id,
51
+ model: `${model.provider}/${model.modelId}`,
52
+ context: { foo: "bar" },
53
+ };
54
+
55
+ const thread = await store.insert(newThread);
56
+
57
+ expect(thread).toBeInstanceOf(Thread);
58
+ expect(thread.tid).toBe("thread-1");
59
+ expect(thread._tick).toBe(0);
60
+ expect(thread.state).toBe(STOPPED);
61
+ });
62
+
63
+ it("should use provided defaults", async () => {
64
+ const createdAt = new Date("2024-01-01");
65
+ const newThread: NewThread = {
66
+ id: "thread-2",
67
+ namespace: "test",
68
+ agentId: agent.id,
69
+ model: `${model.provider}/${model.modelId}`,
70
+ tick: 5,
71
+ state: RUNNING,
72
+ createdAt,
73
+ };
74
+
75
+ const thread = await store.insert(newThread);
76
+
77
+ expect(thread._tick).toBe(5);
78
+ expect(thread.state).toBe(RUNNING);
79
+ });
80
+ });
81
+
82
+ describe("get", () => {
83
+ it("should get a thread by id", async () => {
84
+ const newThread: NewThread = {
85
+ id: "thread-1",
86
+ namespace: "test",
87
+ agentId: agent.id,
88
+ model: `${model.provider}/${model.modelId}`,
89
+ };
90
+
91
+ await store.insert(newThread);
92
+ const thread = await store.get("thread-1");
93
+
94
+ expect(thread).not.toBeNull();
95
+ expect(thread?.tid).toBe("thread-1");
96
+ });
97
+
98
+ it("should return null for non-existent thread", async () => {
99
+ const thread = await store.get("non-existent");
100
+ expect(thread).toBeNull();
101
+ });
102
+
103
+ it("should include history when requested", async () => {
104
+ const newThread: NewThread = {
105
+ id: "thread-1",
106
+ namespace: "test",
107
+ agentId: agent.id,
108
+ model: `${model.provider}/${model.modelId}`,
109
+ };
110
+
111
+ await store.insert(newThread);
112
+
113
+ const events: ThreadEvent[] = [
114
+ {
115
+ kind: "message",
116
+ id: "msg-1",
117
+ tid: "thread-1",
118
+ seq: 0,
119
+ timestamp: new Date(),
120
+ role: "user",
121
+ content: [{ kind: "text", text: "Hello" }],
122
+ metadata: {},
123
+ },
124
+ ];
125
+
126
+ await store.append(events);
127
+
128
+ const thread = await store.get("thread-1", { history: true });
129
+ expect(thread).not.toBeNull();
130
+
131
+ // Access private history for testing
132
+ const history = (thread as any).history as ThreadEvent[];
133
+ expect(history).toHaveLength(1);
134
+ expect(history[0].id).toBe("msg-1");
135
+ });
136
+ });
137
+
138
+ describe("update", () => {
139
+ it("should update thread state", async () => {
140
+ const newThread: NewThread = {
141
+ id: "thread-1",
142
+ namespace: "test",
143
+ agentId: agent.id,
144
+ model: `${model.provider}/${model.modelId}`,
145
+ };
146
+
147
+ await store.insert(newThread);
148
+ const updated = await store.update("thread-1", {
149
+ state: DEAD,
150
+ tick: 10,
151
+ });
152
+
153
+ expect(updated.state).toBe(DEAD);
154
+ expect(updated._tick).toBe(10);
155
+ });
156
+
157
+ it("should throw on non-existent thread", async () => {
158
+ await expect(
159
+ store.update("non-existent", { state: DEAD }),
160
+ ).rejects.toThrow("Thread non-existent not found");
161
+ });
162
+ });
163
+
164
+ describe("delete", () => {
165
+ it("should delete a thread and its events", async () => {
166
+ const newThread: NewThread = {
167
+ id: "thread-1",
168
+ namespace: "test",
169
+ agentId: agent.id,
170
+ model: `${model.provider}/${model.modelId}`,
171
+ };
172
+
173
+ await store.insert(newThread);
174
+ await store.append([
175
+ {
176
+ kind: "message",
177
+ id: "msg-1",
178
+ tid: "thread-1",
179
+ seq: 0,
180
+ timestamp: new Date(),
181
+ role: "user",
182
+ content: [{ kind: "text", text: "Hello" }],
183
+ metadata: {},
184
+ },
185
+ ]);
186
+
187
+ await store.delete("thread-1");
188
+
189
+ const thread = await store.get("thread-1");
190
+ const history = await store.history("thread-1");
191
+
192
+ expect(thread).toBeNull();
193
+ expect(history).toHaveLength(0);
194
+ });
195
+ });
196
+
197
+ describe("append", () => {
198
+ it("should append events to thread history", async () => {
199
+ const newThread: NewThread = {
200
+ id: "thread-1",
201
+ namespace: "test",
202
+ agentId: agent.id,
203
+ model: `${model.provider}/${model.modelId}`,
204
+ };
205
+
206
+ await store.insert(newThread);
207
+
208
+ const events: ThreadEvent[] = [
209
+ {
210
+ kind: "message",
211
+ id: "msg-1",
212
+ tid: "thread-1",
213
+ seq: 0,
214
+ timestamp: new Date(),
215
+ role: "user",
216
+ content: [{ kind: "text", text: "Hello" }],
217
+ metadata: {},
218
+ },
219
+ {
220
+ kind: "message",
221
+ id: "msg-2",
222
+ tid: "thread-1",
223
+ seq: 1,
224
+ timestamp: new Date(),
225
+ role: "assistant",
226
+ content: [{ kind: "text", text: "Hi" }],
227
+ metadata: {},
228
+ },
229
+ ];
230
+
231
+ await store.append(events);
232
+
233
+ const history = await store.history("thread-1");
234
+ expect(history).toHaveLength(2);
235
+ expect(history[0].seq).toBe(0);
236
+ expect(history[1].seq).toBe(1);
237
+ });
238
+
239
+ it("should be idempotent on event.id", async () => {
240
+ const newThread: NewThread = {
241
+ id: "thread-1",
242
+ namespace: "test",
243
+ agentId: agent.id,
244
+ model: `${model.provider}/${model.modelId}`,
245
+ };
246
+
247
+ await store.insert(newThread);
248
+
249
+ const event: ThreadEvent = {
250
+ kind: "message",
251
+ id: "msg-1",
252
+ tid: "thread-1",
253
+ seq: 0,
254
+ timestamp: new Date(),
255
+ role: "user",
256
+ content: [{ kind: "text", text: "Hello" }],
257
+ metadata: {},
258
+ };
259
+
260
+ await store.append([event]);
261
+ await store.append([event]); // duplicate
262
+
263
+ const history = await store.history("thread-1");
264
+ expect(history).toHaveLength(1);
265
+ });
266
+
267
+ it("should maintain seq ordering", async () => {
268
+ const newThread: NewThread = {
269
+ id: "thread-1",
270
+ namespace: "test",
271
+ agentId: agent.id,
272
+ model: `${model.provider}/${model.modelId}`,
273
+ };
274
+
275
+ await store.insert(newThread);
276
+
277
+ // Insert out of order
278
+ await store.append([
279
+ {
280
+ kind: "message",
281
+ id: "msg-2",
282
+ tid: "thread-1",
283
+ seq: 2,
284
+ timestamp: new Date(),
285
+ role: "user",
286
+ content: [{ kind: "text", text: "Second" }],
287
+ metadata: {},
288
+ },
289
+ ]);
290
+
291
+ await store.append([
292
+ {
293
+ kind: "message",
294
+ id: "msg-1",
295
+ tid: "thread-1",
296
+ seq: 1,
297
+ timestamp: new Date(),
298
+ role: "user",
299
+ content: [{ kind: "text", text: "First" }],
300
+ metadata: {},
301
+ },
302
+ ]);
303
+
304
+ const history = await store.history("thread-1");
305
+ expect(history).toHaveLength(2);
306
+ expect(history[0].seq).toBe(1);
307
+ expect(history[1].seq).toBe(2);
308
+ });
309
+ });
310
+
311
+ describe("history", () => {
312
+ beforeEach(async () => {
313
+ const newThread: NewThread = {
314
+ id: "thread-1",
315
+ namespace: "test",
316
+ agentId: agent.id,
317
+ model: `${model.provider}/${model.modelId}`,
318
+ };
319
+
320
+ await store.insert(newThread);
321
+
322
+ await store.append([
323
+ {
324
+ kind: "message",
325
+ id: "msg-1",
326
+ tid: "thread-1",
327
+ seq: 0,
328
+ timestamp: new Date(),
329
+ role: "user",
330
+ content: [{ kind: "text", text: "First" }],
331
+ metadata: {},
332
+ },
333
+ {
334
+ kind: "message",
335
+ id: "msg-2",
336
+ tid: "thread-1",
337
+ seq: 1,
338
+ timestamp: new Date(),
339
+ role: "assistant",
340
+ content: [{ kind: "text", text: "Second" }],
341
+ metadata: {},
342
+ },
343
+ {
344
+ kind: "message",
345
+ id: "msg-3",
346
+ tid: "thread-1",
347
+ seq: 2,
348
+ timestamp: new Date(),
349
+ role: "user",
350
+ content: [{ kind: "text", text: "Third" }],
351
+ metadata: {},
352
+ },
353
+ ]);
354
+ });
355
+
356
+ it("should return all events by default", async () => {
357
+ const history = await store.history("thread-1");
358
+ expect(history).toHaveLength(3);
359
+ });
360
+
361
+ it("should filter by after seq", async () => {
362
+ const history = await store.history("thread-1", { after: 0 });
363
+ expect(history).toHaveLength(2);
364
+ expect(history[0].seq).toBe(1);
365
+ });
366
+
367
+ it("should filter by kinds", async () => {
368
+ await store.append([
369
+ {
370
+ kind: "tool-call",
371
+ id: "tc-1",
372
+ tid: "thread-1",
373
+ seq: 3,
374
+ timestamp: new Date(),
375
+ callId: "call-1",
376
+ toolId: "test-tool",
377
+ state: IN_PROGRESS,
378
+ arguments: "{}",
379
+ metadata: {},
380
+ },
381
+ ]);
382
+
383
+ const history = await store.history("thread-1", {
384
+ kinds: ["tool-call"],
385
+ });
386
+ expect(history).toHaveLength(1);
387
+ expect(history[0].kind).toBe("tool-call");
388
+ });
389
+
390
+ it("should apply limit", async () => {
391
+ const history = await store.history("thread-1", { limit: 2 });
392
+ expect(history).toHaveLength(2);
393
+ });
394
+
395
+ it("should support desc ordering", async () => {
396
+ const history = await store.history("thread-1", { order: "desc" });
397
+ expect(history).toHaveLength(3);
398
+ expect(history[0].seq).toBe(2);
399
+ expect(history[2].seq).toBe(0);
400
+ });
401
+ });
402
+
403
+ describe("list", () => {
404
+ beforeEach(async () => {
405
+ await store.insert({
406
+ id: "thread-1",
407
+ namespace: "test",
408
+ agentId: agent.id,
409
+ model: `${model.provider}/${model.modelId}`,
410
+ state: STOPPED,
411
+ createdAt: new Date("2024-01-01"),
412
+ });
413
+
414
+ await store.insert({
415
+ id: "thread-2",
416
+ namespace: "test",
417
+ agentId: agent.id,
418
+ model: `${model.provider}/${model.modelId}`,
419
+ state: RUNNING,
420
+ createdAt: new Date("2024-01-02"),
421
+ });
422
+
423
+ await store.insert({
424
+ id: "thread-3",
425
+ namespace: "test",
426
+ agentId: agent.id,
427
+ model: `${model.provider}/${model.modelId}`,
428
+ state: DEAD,
429
+ createdAt: new Date("2024-01-03"),
430
+ });
431
+ });
432
+
433
+ it("should list all threads", async () => {
434
+ const threads = await store.list();
435
+ expect(threads).toHaveLength(3);
436
+ });
437
+
438
+ it("should filter by state", async () => {
439
+ const threads = await store.list({
440
+ filter: { state: RUNNING },
441
+ });
442
+ expect(threads).toHaveLength(1);
443
+ expect(threads[0].tid).toBe("thread-2");
444
+ });
445
+
446
+ it("should filter by multiple states", async () => {
447
+ const threads = await store.list({
448
+ filter: { state: [STOPPED, DEAD] },
449
+ });
450
+ expect(threads).toHaveLength(2);
451
+ });
452
+
453
+ it("should filter by agentId", async () => {
454
+ const threads = await store.list({
455
+ filter: { agentId: agent.id },
456
+ });
457
+ expect(threads).toHaveLength(3);
458
+ });
459
+
460
+ it("should filter by createdAfter", async () => {
461
+ const threads = await store.list({
462
+ filter: { createdAfter: new Date("2024-01-01T12:00:00") },
463
+ });
464
+ expect(threads).toHaveLength(2);
465
+ });
466
+
467
+ it("should filter by createdBefore", async () => {
468
+ const threads = await store.list({
469
+ filter: { createdBefore: new Date("2024-01-02T12:00:00") },
470
+ });
471
+ expect(threads).toHaveLength(2);
472
+ });
473
+
474
+ it("should apply limit", async () => {
475
+ const threads = await store.list({ limit: 2 });
476
+ expect(threads).toHaveLength(2);
477
+ });
478
+
479
+ it("should apply offset", async () => {
480
+ const threads = await store.list({ offset: 1, limit: 2 });
481
+ expect(threads).toHaveLength(2);
482
+ });
483
+
484
+ it("should sort by createdAt asc", async () => {
485
+ const threads = await store.list({
486
+ order: { createdAt: "asc" },
487
+ });
488
+ expect(threads[0].tid).toBe("thread-1");
489
+ expect(threads[2].tid).toBe("thread-3");
490
+ });
491
+
492
+ it("should sort by createdAt desc (default)", async () => {
493
+ const threads = await store.list({
494
+ order: { createdAt: "desc" },
495
+ });
496
+ expect(threads[0].tid).toBe("thread-3");
497
+ expect(threads[2].tid).toBe("thread-1");
498
+ });
499
+ });
500
+ });
501
+
502
+ describe("InMemoryStorage", () => {
503
+ it("should create storage with thread store", () => {
504
+ const storage = new InMemoryStorage();
505
+ expect(storage.threads).toBeInstanceOf(InMemoryThreadStore);
506
+ });
507
+
508
+ it("should bind registries to thread store", () => {
509
+ const storage = new InMemoryStorage();
510
+ const agents = new Map();
511
+ const models = new Map();
512
+
513
+ storage.bind({ agents, models });
514
+
515
+ // Verify binding by checking thread store can hydrate (would throw if not bound)
516
+ expect(() => {
517
+ (storage.threads as any).registries;
518
+ }).not.toThrow();
519
+ });
520
+
521
+ it("should have no-op lifecycle methods", async () => {
522
+ const storage = new InMemoryStorage();
523
+ await expect(storage.init()).resolves.toBeUndefined();
524
+ await expect(storage.close()).resolves.toBeUndefined();
525
+ await expect(storage.migrate()).resolves.toBeUndefined();
526
+ });
527
+
528
+ it("should throw on transaction", async () => {
529
+ const storage = new InMemoryStorage();
530
+ await expect(storage.transaction(async () => {})).rejects.toThrow(
531
+ "Transactions not supported",
532
+ );
533
+ });
534
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Core storage contracts.
3
+ */
4
+
5
+ import type { AgentRegistry, ModelRegistry } from "@/types/kernl";
6
+ import type { ThreadStore } from "./thread";
7
+
8
+ /**
9
+ * The main storage interface for Kernl.
10
+ *
11
+ * Provides access to system stores (threads, tasks, traces) and transaction support.
12
+ */
13
+ export interface KernlStorage {
14
+ /**
15
+ * Thread store - manages thread execution records and event history.
16
+ */
17
+ threads: ThreadStore;
18
+
19
+ // tasks: TaskStore;
20
+ // traces: TraceStore;
21
+
22
+ /**
23
+ * Bind runtime registries to storage.
24
+ *
25
+ * Called by Kernl after construction to wire up agent/model lookups.
26
+ */
27
+ bind(registries: { agents: AgentRegistry; models: ModelRegistry }): void;
28
+
29
+ /**
30
+ * Execute a function within a transaction.
31
+ *
32
+ * All operations performed using the transaction-scoped stores will be
33
+ * committed atomically or rolled back on error.
34
+ */
35
+ transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
36
+
37
+ /**
38
+ * Initialize the storage backend.
39
+ *
40
+ * Connects to the database and ensures all required schemas/tables exist.
41
+ */
42
+ init(): Promise<void>;
43
+
44
+ /**
45
+ * Close the storage backend and cleanup resources.
46
+ */
47
+ close(): Promise<void>;
48
+
49
+ /**
50
+ * Runs the migrations in order to ensure all required tables exist.
51
+ */
52
+ migrate(): Promise<void>;
53
+ }
54
+
55
+ /**
56
+ * Transaction context providing transactional access to stores.
57
+ */
58
+ export interface Transaction {
59
+ /**
60
+ * Thread store within this transaction.
61
+ */
62
+ threads: ThreadStore;
63
+
64
+ // Future stores (deferred)
65
+ // tasks: TaskStore;
66
+ // traces: TraceStore;
67
+
68
+ /**
69
+ * Commit the transaction.
70
+ */
71
+ commit(): Promise<void>;
72
+
73
+ /**
74
+ * Rollback the transaction.
75
+ */
76
+ rollback(): Promise<void>;
77
+ }