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,415 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+
3
+ import { message, RUNNING } from "@kernl-sdk/protocol";
4
+
5
+ import { Agent } from "@/agent";
6
+ import { InMemoryThreadStore } from "@/storage/in-memory";
7
+ import type { AgentRegistry, ModelRegistry } from "@/types/kernl";
8
+ import type { NewThread } from "@/storage";
9
+ import { tevent } from "@/thread/utils";
10
+ import type { ThreadEvent } from "@/types/thread";
11
+ import { RThreads } from "@/api/resources/threads";
12
+ import type { MThread } from "@/api/models";
13
+
14
+ function createTestStore() {
15
+ const store = new InMemoryThreadStore();
16
+
17
+ const agents: AgentRegistry & Map<string, Agent> = new Map();
18
+ const models: ModelRegistry & Map<string, any> = new Map();
19
+
20
+ const model = {
21
+ spec: "1.0" as const,
22
+ provider: "test",
23
+ modelId: "test-model",
24
+ // generate/stream are never called in these tests
25
+ } as any;
26
+
27
+ const agent = new Agent({
28
+ id: "agent-1",
29
+ name: "Test Agent",
30
+ instructions: "You are a test agent.",
31
+ model,
32
+ });
33
+
34
+ const modelKey = `${model.provider}/${model.modelId}`;
35
+
36
+ agents.set(agent.id, agent);
37
+ models.set(modelKey, model);
38
+
39
+ store.bind({ agents, models });
40
+
41
+ return { store, agent, modelKey };
42
+ }
43
+
44
+ async function insertThread(args: {
45
+ store: InMemoryThreadStore;
46
+ modelKey: string;
47
+ agentId: string;
48
+ tid: string;
49
+ namespace: string;
50
+ createdAt: Date;
51
+ }): Promise<MThread> {
52
+ const thread: NewThread = {
53
+ id: args.tid,
54
+ namespace: args.namespace,
55
+ agentId: args.agentId,
56
+ model: args.modelKey,
57
+ context: { foo: "bar" },
58
+ createdAt: args.createdAt,
59
+ updatedAt: args.createdAt,
60
+ };
61
+
62
+ const runtime = await args.store.insert(thread);
63
+
64
+ // Minimal mirror of RThreads.toModel to keep the test focused
65
+ const model: MThread = {
66
+ tid: runtime.tid,
67
+ namespace: runtime.namespace,
68
+ agentId: runtime.agent.id,
69
+ model: {
70
+ provider: runtime.model.provider,
71
+ modelId: runtime.model.modelId,
72
+ },
73
+ context: runtime.context.context as Record<string, unknown>,
74
+ parentTaskId: runtime.parent?.id ?? null,
75
+ state: runtime.state,
76
+ createdAt: runtime.createdAt,
77
+ updatedAt: runtime.updatedAt,
78
+ };
79
+
80
+ return model;
81
+ }
82
+
83
+ async function appendEvents(
84
+ store: InMemoryThreadStore,
85
+ tid: string,
86
+ seqs: number[],
87
+ ): Promise<ThreadEvent[]> {
88
+ const events: ThreadEvent[] = [];
89
+
90
+ for (const seq of seqs) {
91
+ const item = message({
92
+ role: "user",
93
+ text: `event-${seq}`,
94
+ });
95
+ const e = tevent({
96
+ tid,
97
+ seq,
98
+ kind: "message",
99
+ data: item,
100
+ });
101
+ events.push(e);
102
+ }
103
+
104
+ // Add one system event to verify filtering in history
105
+ const systemEvent = tevent({
106
+ tid,
107
+ seq: Math.max(...seqs) + 1,
108
+ kind: "system",
109
+ data: null,
110
+ });
111
+ events.push(systemEvent);
112
+
113
+ await store.append(events);
114
+ return events;
115
+ }
116
+
117
+ describe("RThreads", () => {
118
+ let store: InMemoryThreadStore;
119
+ let agentId: string;
120
+ let modelKey: string;
121
+ let threads: RThreads;
122
+
123
+ beforeEach(() => {
124
+ const setup = createTestStore();
125
+ store = setup.store;
126
+ agentId = setup.agent.id;
127
+ modelKey = setup.modelKey;
128
+ threads = new RThreads(store);
129
+ });
130
+
131
+ it("lists threads with filters and pagination", async () => {
132
+ const now = new Date();
133
+ const t1Created = new Date(now.getTime() - 10_000);
134
+ const t2Created = new Date(now.getTime() - 5_000);
135
+
136
+ await insertThread({
137
+ store,
138
+ modelKey,
139
+ agentId,
140
+ tid: "t1",
141
+ namespace: "ns-a",
142
+ createdAt: t1Created,
143
+ });
144
+ await insertThread({
145
+ store,
146
+ modelKey,
147
+ agentId,
148
+ tid: "t2",
149
+ namespace: "ns-a",
150
+ createdAt: t2Created,
151
+ });
152
+ await insertThread({
153
+ store,
154
+ modelKey,
155
+ agentId,
156
+ tid: "t3",
157
+ namespace: "ns-b",
158
+ createdAt: t2Created,
159
+ });
160
+
161
+ const page = await threads.list({
162
+ namespace: "ns-a",
163
+ limit: 1,
164
+ });
165
+
166
+ // First page should contain the most recently created thread in ns-a
167
+ expect(page.items.map((t) => t.tid)).toEqual(["t2"]);
168
+ expect(page.last).toBe(false);
169
+
170
+ const next = await page.next();
171
+ expect(next).not.toBeNull();
172
+ expect(next!.items.map((t) => t.tid)).toEqual(["t1"]);
173
+ expect(next!.last).toBe(true);
174
+
175
+ // collect should return both threads in the correct order
176
+ const all = await page.collect();
177
+ expect(all.map((t) => t.tid)).toEqual(["t2", "t1"]);
178
+ });
179
+
180
+ it("supports createdAt/updatedAt ordering and before/after filters", async () => {
181
+ const now = new Date();
182
+ const t1Created = new Date(now.getTime() - 30_000);
183
+ const t2Created = new Date(now.getTime() - 20_000);
184
+ const t3Created = new Date(now.getTime() - 10_000);
185
+
186
+ await insertThread({
187
+ store,
188
+ modelKey,
189
+ agentId,
190
+ tid: "o1",
191
+ namespace: "order-ns",
192
+ createdAt: t1Created,
193
+ });
194
+ await insertThread({
195
+ store,
196
+ modelKey,
197
+ agentId,
198
+ tid: "o2",
199
+ namespace: "order-ns",
200
+ createdAt: t2Created,
201
+ });
202
+ await insertThread({
203
+ store,
204
+ modelKey,
205
+ agentId,
206
+ tid: "o3",
207
+ namespace: "order-ns",
208
+ createdAt: t3Created,
209
+ });
210
+
211
+ // Ascending createdAt should return [o1, o2, o3]
212
+ const asc = await threads.list({
213
+ namespace: "order-ns",
214
+ order: { createdAt: "asc" },
215
+ });
216
+ const ascAll = await asc.collect();
217
+ expect(ascAll.map((t) => t.tid)).toEqual(["o1", "o2", "o3"]);
218
+
219
+ // Filter with after/before to include only the middle thread
220
+ const filtered = await threads.list({
221
+ namespace: "order-ns",
222
+ after: t1Created,
223
+ before: t3Created,
224
+ order: { createdAt: "asc" },
225
+ });
226
+ const filteredAll = await filtered.collect();
227
+ expect(filteredAll.map((t) => t.tid)).toEqual(["o2"]);
228
+ });
229
+
230
+ it("returns history in latest-first order by default and filters out system events", async () => {
231
+ const createdAt = new Date();
232
+ await insertThread({
233
+ store,
234
+ modelKey,
235
+ agentId,
236
+ tid: "hist-1",
237
+ namespace: "ns-hist",
238
+ createdAt,
239
+ });
240
+
241
+ const events = await appendEvents(store, "hist-1", [0, 1, 2]);
242
+
243
+ const history = await threads.history("hist-1");
244
+
245
+ // Should exclude the system event
246
+ const systemIds = events
247
+ .filter((e) => e.kind === "system")
248
+ .map((e) => e.id);
249
+ expect(history.every((e) => !systemIds.includes(e.id))).toBe(true);
250
+
251
+ // Default order is "desc" on seq, so we expect [2, 1, 0]
252
+ expect(history.map((e) => e.seq)).toEqual([2, 1, 0]);
253
+ });
254
+
255
+ it("supports history after/kinds/limit options", async () => {
256
+ const createdAt = new Date();
257
+ await insertThread({
258
+ store,
259
+ modelKey,
260
+ agentId,
261
+ tid: "hist-opts",
262
+ namespace: "ns-hist-opts",
263
+ createdAt,
264
+ });
265
+
266
+ await appendEvents(store, "hist-opts", [0, 1, 2]);
267
+
268
+ // After seq 0, ascending, limit 1 → only seq 1
269
+ const history = await threads.history("hist-opts", {
270
+ after: 0,
271
+ order: "asc",
272
+ limit: 1,
273
+ kinds: ["message"],
274
+ });
275
+
276
+ expect(history.map((e) => e.seq)).toEqual([1]);
277
+ expect(history.every((e) => e.kind === "message")).toBe(true);
278
+ });
279
+
280
+ it("get() can optionally include history with default and custom options", async () => {
281
+ const createdAt = new Date();
282
+ const model = await insertThread({
283
+ store,
284
+ modelKey,
285
+ agentId,
286
+ tid: "with-history",
287
+ namespace: "ns-get",
288
+ createdAt,
289
+ });
290
+
291
+ await appendEvents(store, "with-history", [0, 1, 2]);
292
+
293
+ // Mark one of the threads as RUNNING and ensure state filter works via list()
294
+ await store.update(model.tid, { state: RUNNING });
295
+ const byState = await threads.list({
296
+ namespace: "ns-get",
297
+ state: RUNNING,
298
+ });
299
+ const byStateAll = await byState.collect();
300
+ expect(byStateAll.map((t) => t.tid)).toEqual(["with-history"]);
301
+
302
+ // history: true → all events with default options
303
+ const base = await threads.get("with-history", {
304
+ history: true,
305
+ });
306
+
307
+ expect(base).not.toBeNull();
308
+ expect(base!.history?.map((e) => e.seq)).toEqual([2, 1, 0]);
309
+
310
+ // history: { limit: 1, order: "asc" } → oldest event only
311
+ const thread = await threads.get("with-history", {
312
+ history: { limit: 1, order: "asc" },
313
+ });
314
+
315
+ expect(thread!.history?.map((e) => e.seq)).toEqual([0]);
316
+ });
317
+
318
+ it("create() persists a thread with caller metadata and optional title", async () => {
319
+ const thread = await threads.create({
320
+ agentId,
321
+ model: {
322
+ provider: "test",
323
+ modelId: "test-model",
324
+ },
325
+ namespace: "ns-create",
326
+ context: { foo: "bar" },
327
+ metadata: { foo: "baz" },
328
+ title: "My Thread",
329
+ });
330
+
331
+ expect(thread.tid).toMatch(/^tid_/);
332
+ expect(thread.namespace).toBe("ns-create");
333
+ expect(thread.agentId).toBe(agentId);
334
+ expect(thread.title).toBe("My Thread");
335
+
336
+ // Verify metadata was persisted and title merged into it.
337
+ const runtime = await store.get(thread.tid, { history: false });
338
+ expect(runtime).not.toBeNull();
339
+ expect(runtime!.metadata).not.toBeNull();
340
+ expect(runtime!.metadata!.foo).toBe("baz");
341
+ expect(runtime!.metadata!.title).toBe("My Thread");
342
+ });
343
+
344
+ it("update() can set and clear a thread title", async () => {
345
+ const createdAt = new Date();
346
+ const model = await insertThread({
347
+ store,
348
+ modelKey,
349
+ agentId,
350
+ tid: "title-1",
351
+ namespace: "ns-title",
352
+ createdAt,
353
+ });
354
+
355
+ // Initially there is no title
356
+ expect(model.title).toBeUndefined();
357
+
358
+ const updated = await threads.update("title-1", {
359
+ title: " New Title ",
360
+ });
361
+ expect(updated).not.toBeNull();
362
+ expect(updated!.title).toBe("New Title");
363
+
364
+ const cleared = await threads.update("title-1", { title: "" });
365
+ expect(cleared).not.toBeNull();
366
+ expect(cleared!.title).toBeNull();
367
+ });
368
+
369
+ it("update() can modify context and metadata with title overlay", async () => {
370
+ const createdAt = new Date();
371
+ await insertThread({
372
+ store,
373
+ modelKey,
374
+ agentId,
375
+ tid: "update-fields",
376
+ namespace: "ns-update",
377
+ createdAt,
378
+ });
379
+
380
+ const updated = await threads.update("update-fields", {
381
+ context: { foo: "bar" },
382
+ metadata: { a: 1 },
383
+ title: "Thread Title",
384
+ });
385
+
386
+ expect(updated).not.toBeNull();
387
+ expect(updated!.title).toBe("Thread Title");
388
+
389
+ const runtime = await store.get("update-fields", { history: false });
390
+ expect(runtime).not.toBeNull();
391
+ expect(runtime!.context.context).toEqual({ foo: "bar" });
392
+ expect(runtime!.metadata).toEqual({ a: 1, title: "Thread Title" });
393
+ });
394
+
395
+ it("rejects context updates while thread is running", async () => {
396
+ const createdAt = new Date();
397
+ const model = await insertThread({
398
+ store,
399
+ modelKey,
400
+ agentId,
401
+ tid: "running-1",
402
+ namespace: "ns-running",
403
+ createdAt,
404
+ });
405
+
406
+ // Mark thread as RUNNING in the store
407
+ await store.update(model.tid, { state: RUNNING });
408
+
409
+ await expect(
410
+ threads.update("running-1", {
411
+ context: { should: "fail" },
412
+ } as any),
413
+ ).rejects.toThrow(/Cannot update thread context while thread is running/);
414
+ });
415
+ });
@@ -0,0 +1,6 @@
1
+ export type {
2
+ MThread,
3
+ MThreadModelInfo,
4
+ MThreadEvent,
5
+ MThreadEventBase,
6
+ } from "./thread";
@@ -0,0 +1,138 @@
1
+ import type { LanguageModelItem } from "@kernl-sdk/protocol";
2
+ import type { ThreadState } from "@/types/thread";
3
+
4
+ /**
5
+ * Model metadata for the language model used by a thread.
6
+ *
7
+ * Provides the two components of the composite modelId key: "{provider}/{modelId}"
8
+ */
9
+ export interface MThreadModelInfo {
10
+ /**
11
+ * Provider identifier, e.g. `"openai"`, `"anthropic"`, `"vertex"`.
12
+ */
13
+ provider: string;
14
+
15
+ /**
16
+ * Concrete model identifier within the provider, e.g. `"gpt-4.1"` or `"claude-3-opus"`.
17
+ */
18
+ modelId: string;
19
+ }
20
+
21
+ /**
22
+ * Thread model returned by Kernl APIs.
23
+ *
24
+ * This represents the persisted state of a thread – what you get back from
25
+ * `kernl.threads.get()` / `kernl.threads.list()`.
26
+ */
27
+ export interface MThread {
28
+ /**
29
+ * Globally-unique thread identifier.
30
+ *
31
+ * You can pass this back into agents (via `threadId`) to resume execution
32
+ * or into storage APIs to fetch history.
33
+ */
34
+ tid: string;
35
+
36
+ /**
37
+ * Logical namespace this thread belongs to, e.g. `"kernl"` or `"org-a"`.
38
+ *
39
+ * Namespaces let you partition threads by tenant, environment, or product.
40
+ */
41
+ namespace: string;
42
+
43
+ /**
44
+ * Optional human-readable title for the thread.
45
+ */
46
+ title?: string | null;
47
+
48
+ /**
49
+ * ID of the agent that owns this thread.
50
+ */
51
+ agentId: string;
52
+
53
+ /**
54
+ * Language model used for this thread.
55
+ */
56
+ model: MThreadModelInfo;
57
+
58
+ /**
59
+ * User-defined context object that was attached to this thread.
60
+ *
61
+ * This is the raw JSON-serializable context, not a `Context` instance.
62
+ */
63
+ context: Record<string, unknown>;
64
+
65
+ /**
66
+ * Optional parent task ID that spawned this thread, if any.
67
+ */
68
+ parentTaskId: string | null;
69
+
70
+ /**
71
+ * Current lifecycle state of the thread (running, stopped, etc.).
72
+ */
73
+ state: ThreadState;
74
+
75
+ /**
76
+ * When the thread record was first created.
77
+ */
78
+ createdAt: Date;
79
+
80
+ /**
81
+ * When the thread record was last updated (state, context, etc.).
82
+ */
83
+ updatedAt: Date;
84
+
85
+ /**
86
+ * Event history for this thread, when requested via options.
87
+ *
88
+ * Only present when you call APIs like `kernl.threads.get(id, { history: true })`
89
+ * or `kernl.threads.get(id, { history: { ... } })`. For list endpoints,
90
+ * history is omitted to keep responses lightweight.
91
+ */
92
+ history?: MThreadEvent[];
93
+ }
94
+
95
+ /**
96
+ * Common metadata for all thread events.
97
+ *
98
+ * These fields are added on top of the underlying `LanguageModelItem`
99
+ * when events are persisted to storage.
100
+ */
101
+ export interface MThreadEventBase {
102
+ /**
103
+ * Globally-unique event identifier within the thread.
104
+ */
105
+ id: string;
106
+
107
+ /**
108
+ * ID of the thread this event belongs to.
109
+ */
110
+ tid: string;
111
+
112
+ /**
113
+ * Monotonically-increasing sequence number within the thread.
114
+ *
115
+ * `seq` defines the total order of events for a given thread.
116
+ */
117
+ seq: number;
118
+
119
+ /**
120
+ * Timestamp when the event was recorded (wall-clock time).
121
+ */
122
+ timestamp: Date;
123
+
124
+ /**
125
+ * Arbitrary metadata attached to the event (implementation-specific).
126
+ */
127
+ metadata: Record<string, unknown>;
128
+ }
129
+
130
+ /**
131
+ * Thread event as returned by APIs like `kernl.threads.history()`.
132
+ *
133
+ * This is a `LanguageModelItem` (message, tool-call, tool-result, etc.)
134
+ * enriched with thread-specific metadata such as `tid` and `seq`.
135
+ *
136
+ * Internal system events are filtered out before exposing this type.
137
+ */
138
+ export type MThreadEvent = LanguageModelItem & MThreadEventBase;
@@ -0,0 +1,79 @@
1
+ export interface PageParamsBase {
2
+ /**
3
+ * Maximum number of items to return in a single page.
4
+ */
5
+ limit?: number;
6
+ }
7
+
8
+ /**
9
+ * Generic page abstraction shared by all pagination modes.
10
+ *
11
+ * Loader: function that, given params, returns a page response.
12
+ */
13
+ export abstract class AbstractPage<T, TParams extends PageParamsBase, TResponse>
14
+ implements AsyncIterable<T>
15
+ {
16
+ protected readonly params: TParams;
17
+ protected readonly response: TResponse;
18
+ protected readonly loader: (params: TParams) => Promise<TResponse>;
19
+
20
+ constructor(args: {
21
+ params: TParams;
22
+ response: TResponse;
23
+ loader: (params: TParams) => Promise<TResponse>;
24
+ }) {
25
+ this.params = args.params;
26
+ this.response = args.response;
27
+ this.loader = args.loader;
28
+ }
29
+
30
+ /**
31
+ * All items contained in this page.
32
+ */
33
+ abstract get items(): T[];
34
+
35
+ /**
36
+ * True if this is the last page in the sequence.
37
+ *
38
+ * When `last` is true, `next()` will return null.
39
+ */
40
+ abstract get last(): boolean;
41
+
42
+ /**
43
+ * Fetch the next page, or null if there is no next page.
44
+ */
45
+ abstract next(): Promise<this | null>;
46
+
47
+ /**
48
+ * Iterate over this page and all subsequent pages.
49
+ */
50
+ async *pages(): AsyncGenerator<this> {
51
+ let page: this | null = this;
52
+ while (page) {
53
+ yield page;
54
+ page = await page.next();
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Iterate over all items across this page and all subsequent pages.
60
+ */
61
+ async *[Symbol.asyncIterator](): AsyncGenerator<T> {
62
+ for await (const page of this.pages()) {
63
+ for (const item of page.items) {
64
+ yield item;
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Collect all items from this page and all subsequent pages into an array.
71
+ */
72
+ async collect(): Promise<T[]> {
73
+ const items: T[] = [];
74
+ for await (const item of this) {
75
+ items.push(item);
76
+ }
77
+ return items;
78
+ }
79
+ }