kernl 0.2.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-check-types.log +4 -0
- package/CHANGELOG.md +138 -0
- package/LICENSE +1 -1
- package/dist/agent/__tests__/concurrency.test.d.ts +2 -0
- package/dist/agent/__tests__/concurrency.test.d.ts.map +1 -0
- package/dist/agent/__tests__/concurrency.test.js +152 -0
- package/dist/agent/__tests__/run.test.d.ts +2 -0
- package/dist/agent/__tests__/run.test.d.ts.map +1 -0
- package/dist/agent/__tests__/run.test.js +357 -0
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent.d.ts +32 -9
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +101 -14
- package/dist/api/__tests__/cursor-page.test.d.ts +2 -0
- package/dist/api/__tests__/cursor-page.test.d.ts.map +1 -0
- package/dist/api/__tests__/cursor-page.test.js +414 -0
- package/dist/api/__tests__/offset-page.test.d.ts +2 -0
- package/dist/api/__tests__/offset-page.test.d.ts.map +1 -0
- package/dist/api/__tests__/offset-page.test.js +510 -0
- package/dist/api/__tests__/threads.test.d.ts +2 -0
- package/dist/api/__tests__/threads.test.d.ts.map +1 -0
- package/dist/api/__tests__/threads.test.js +338 -0
- package/dist/api/models/index.d.ts +2 -0
- package/dist/api/models/index.d.ts.map +1 -0
- package/dist/api/models/thread.d.ts +120 -0
- package/dist/api/models/thread.d.ts.map +1 -0
- package/dist/api/pagination/base.d.ts +48 -0
- package/dist/api/pagination/base.d.ts.map +1 -0
- package/dist/api/pagination/base.js +45 -0
- package/dist/api/pagination/cursor.d.ts +44 -0
- package/dist/api/pagination/cursor.d.ts.map +1 -0
- package/dist/api/pagination/cursor.js +52 -0
- package/dist/api/pagination/offset.d.ts +42 -0
- package/dist/api/pagination/offset.d.ts.map +1 -0
- package/dist/api/pagination/offset.js +55 -0
- package/dist/api/resources/threads/events.d.ts +21 -0
- package/dist/api/resources/threads/events.d.ts.map +1 -0
- package/dist/api/resources/threads/events.js +24 -0
- package/dist/api/resources/threads/index.d.ts +4 -0
- package/dist/api/resources/threads/index.d.ts.map +1 -0
- package/dist/api/resources/threads/index.js +2 -0
- package/dist/api/resources/threads/threads.d.ts +57 -0
- package/dist/api/resources/threads/threads.d.ts.map +1 -0
- package/dist/api/resources/threads/threads.js +199 -0
- package/dist/api/resources/threads/types.d.ts +123 -0
- package/dist/api/resources/threads/types.d.ts.map +1 -0
- package/dist/api/resources/threads/utils.d.ts +18 -0
- package/dist/api/resources/threads/utils.d.ts.map +1 -0
- package/dist/api/resources/threads/utils.js +78 -0
- package/dist/context.d.ts +5 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/internal.d.ts +4 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +2 -0
- package/dist/kernl/index.d.ts +3 -0
- package/dist/kernl/index.d.ts.map +1 -0
- package/dist/kernl/index.js +2 -0
- package/dist/kernl/kernl.d.ts +64 -0
- package/dist/kernl/kernl.d.ts.map +1 -0
- package/dist/kernl/kernl.js +116 -0
- package/dist/kernl/threads.d.ts +110 -0
- package/dist/kernl/threads.d.ts.map +1 -0
- package/dist/kernl/threads.js +126 -0
- package/dist/kernl.d.ts +22 -6
- package/dist/kernl.d.ts.map +1 -1
- package/dist/kernl.js +73 -10
- package/dist/lib/env.d.ts +3 -3
- package/dist/lib/env.js +1 -1
- package/dist/mcp/__tests__/integration.test.js +8 -8
- package/dist/mcp/__tests__/utils.test.js +6 -6
- package/dist/mcp/http.d.ts +1 -1
- package/dist/mcp/http.d.ts.map +1 -1
- package/dist/mcp/http.js +9 -9
- package/dist/mcp/sse.d.ts +1 -1
- package/dist/mcp/sse.d.ts.map +1 -1
- package/dist/mcp/sse.js +7 -7
- package/dist/mcp/utils.d.ts +1 -1
- package/dist/mcp/utils.d.ts.map +1 -1
- package/dist/mcp/utils.js +4 -5
- package/dist/storage/__tests__/in-memory.test.d.ts +2 -0
- package/dist/storage/__tests__/in-memory.test.d.ts.map +1 -0
- package/dist/storage/__tests__/in-memory.test.js +455 -0
- package/dist/storage/base.d.ts +64 -0
- package/dist/storage/base.d.ts.map +1 -0
- package/dist/storage/base.js +4 -0
- package/dist/storage/in-memory.d.ts +62 -0
- package/dist/storage/in-memory.d.ts.map +1 -0
- package/dist/storage/in-memory.js +283 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +7 -0
- package/dist/storage/thread.d.ts +123 -0
- package/dist/storage/thread.d.ts.map +1 -0
- package/dist/storage/thread.js +4 -0
- package/dist/task.d.ts +5 -3
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +10 -8
- package/dist/thread/__tests__/fixtures/mock-model.d.ts +1 -2
- package/dist/thread/__tests__/fixtures/mock-model.d.ts.map +1 -1
- package/dist/thread/__tests__/integration.test.js +6 -6
- package/dist/thread/__tests__/namespace.test.d.ts +2 -0
- package/dist/thread/__tests__/namespace.test.d.ts.map +1 -0
- package/dist/thread/__tests__/namespace.test.js +131 -0
- package/dist/thread/__tests__/thread-persistence.test.d.ts +2 -0
- package/dist/thread/__tests__/thread-persistence.test.d.ts.map +1 -0
- package/dist/thread/__tests__/thread-persistence.test.js +351 -0
- package/dist/thread/__tests__/thread.test.js +49 -51
- package/dist/thread/thread.d.ts +70 -18
- package/dist/thread/thread.d.ts.map +1 -1
- package/dist/thread/thread.js +211 -73
- package/dist/thread/utils.d.ts +36 -8
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/utils.js +52 -8
- package/dist/tool/__tests__/fixtures.js +1 -1
- package/dist/tool/__tests__/toolkit.test.js +15 -12
- package/dist/tool/tool.js +3 -3
- package/dist/types/kernl.d.ts +42 -0
- package/dist/types/kernl.d.ts.map +1 -0
- package/dist/types/thread.d.ts +108 -22
- package/dist/types/thread.d.ts.map +1 -1
- package/dist/types/thread.js +12 -0
- package/package.json +11 -7
- package/src/agent/__tests__/concurrency.test.ts +194 -0
- package/src/agent/__tests__/run.test.ts +441 -0
- package/src/agent/index.ts +0 -0
- package/src/agent.ts +139 -24
- package/src/api/__tests__/cursor-page.test.ts +512 -0
- package/src/api/__tests__/offset-page.test.ts +624 -0
- package/src/api/__tests__/threads.test.ts +415 -0
- package/src/api/models/index.ts +6 -0
- package/src/api/models/thread.ts +138 -0
- package/src/api/pagination/base.ts +79 -0
- package/src/api/pagination/cursor.ts +86 -0
- package/src/api/pagination/offset.ts +89 -0
- package/src/api/resources/threads/events.ts +26 -0
- package/src/api/resources/threads/index.ts +9 -0
- package/src/api/resources/threads/threads.ts +256 -0
- package/src/api/resources/threads/types.ts +143 -0
- package/src/api/resources/threads/utils.ts +104 -0
- package/src/context.ts +10 -1
- package/src/index.ts +49 -1
- package/src/internal.ts +15 -0
- package/src/kernl.ts +86 -17
- package/src/mcp/__tests__/integration.test.ts +8 -9
- package/src/mcp/__tests__/utils.test.ts +6 -6
- package/src/mcp/http.ts +9 -9
- package/src/mcp/sse.ts +7 -7
- package/src/mcp/utils.ts +6 -5
- package/src/storage/__tests__/in-memory.test.ts +534 -0
- package/src/storage/base.ts +77 -0
- package/src/storage/in-memory.ts +372 -0
- package/src/storage/index.ts +21 -0
- package/src/storage/thread.ts +141 -0
- package/src/task.ts +12 -10
- package/src/thread/__tests__/fixtures/mock-model.ts +2 -4
- package/src/thread/__tests__/integration.test.ts +13 -12
- package/src/thread/__tests__/namespace.test.ts +158 -0
- package/src/thread/__tests__/thread-persistence.test.ts +367 -0
- package/src/thread/__tests__/thread.test.ts +52 -54
- package/src/thread/thread.ts +247 -96
- package/src/thread/utils.ts +76 -13
- package/src/tool/__tests__/fixtures.ts +1 -1
- package/src/tool/__tests__/toolkit.test.ts +15 -12
- package/src/tool/tool.ts +3 -3
- package/src/types/kernl.ts +51 -0
- package/src/types/thread.ts +139 -25
- package/vitest.config.ts +1 -0
- package/dist/env.d.ts +0 -45
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js +0 -31
- package/dist/error.d.ts +0 -1
- package/dist/error.d.ts.map +0 -1
- package/dist/kernel.d.ts +0 -7
- package/dist/kernel.d.ts.map +0 -1
- package/dist/kernel.js +0 -7
- package/dist/lib/serde/__tests__/codec.test.d.ts +0 -2
- package/dist/lib/serde/__tests__/codec.test.d.ts.map +0 -1
- package/dist/lib/serde/__tests__/codec.test.js +0 -75
- package/dist/lib/serde/codec.d.ts +0 -12
- package/dist/lib/serde/codec.d.ts.map +0 -1
- package/dist/lib/serde/codec.js +0 -54
- package/dist/lib/serde/thread.d.ts +0 -1
- package/dist/lib/serde/thread.d.ts.map +0 -1
- package/dist/lib/serde/thread.js +0 -172
- package/dist/lib/serde/tool.d.ts +0 -36
- package/dist/lib/serde/tool.d.ts.map +0 -1
- package/dist/lib/utils.d.ts +0 -19
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/utils.js +0 -41
- package/dist/logger.d.ts +0 -36
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -43
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/echo-server.js +0 -92
- package/dist/mcp/__tests__/fixtures/math-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/math-server.js +0 -98
- package/dist/mcp/__tests__/fixtures/test-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/test-server.js +0 -163
- package/dist/mcp/__tests__/test-utils.d.ts +0 -17
- package/dist/mcp/__tests__/test-utils.d.ts.map +0 -1
- package/dist/mcp/__tests__/test-utils.js +0 -42
- package/dist/mcp/node.d.ts +0 -60
- package/dist/mcp/node.d.ts.map +0 -1
- package/dist/mcp/node.js +0 -297
- package/dist/model.d.ts +0 -175
- package/dist/model.d.ts.map +0 -1
- package/dist/providers/ai.d.ts +0 -1
- package/dist/providers/ai.d.ts.map +0 -1
- package/dist/providers/ai.js +0 -1
- package/dist/providers/default.d.ts +0 -16
- package/dist/providers/default.d.ts.map +0 -1
- package/dist/providers/default.js +0 -17
- package/dist/providers/registry.d.ts +0 -1
- package/dist/providers/registry.d.ts.map +0 -1
- package/dist/providers/registry.js +0 -1
- package/dist/sched/scheduler.d.ts +0 -20
- package/dist/sched/scheduler.d.ts.map +0 -1
- package/dist/sched/task.d.ts +0 -92
- package/dist/sched/task.d.ts.map +0 -1
- package/dist/sched/task.js +0 -102
- package/dist/serde/__tests__/codec.test.d.ts +0 -2
- package/dist/serde/__tests__/codec.test.d.ts.map +0 -1
- package/dist/serde/__tests__/codec.test.js +0 -75
- package/dist/serde/codec.d.ts +0 -12
- package/dist/serde/codec.d.ts.map +0 -1
- package/dist/serde/codec.js +0 -54
- package/dist/serde/json.d.ts +0 -8
- package/dist/serde/json.d.ts.map +0 -1
- package/dist/serde/json.js +0 -13
- package/dist/serde/thread.d.ts +0 -687
- package/dist/serde/thread.d.ts.map +0 -1
- package/dist/serde/thread.js +0 -158
- package/dist/serde/tool.d.ts +0 -36
- package/dist/serde/tool.d.ts.map +0 -1
- package/dist/session.d.ts +0 -1
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -1
- package/dist/thread/__tests__/stream.test.d.ts +0 -2
- package/dist/thread/__tests__/stream.test.d.ts.map +0 -1
- package/dist/thread/__tests__/stream.test.js +0 -244
- package/dist/tool/mcp.d.ts +0 -75
- package/dist/tool/mcp.d.ts.map +0 -1
- package/dist/tool/mcp.js +0 -111
- package/dist/tools.d.ts +0 -362
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -220
- package/dist/types/proto.d.ts +0 -1551
- package/dist/types/proto.d.ts.map +0 -1
- package/dist/types/proto.js +0 -531
- package/dist/usage.d.ts +0 -43
- package/dist/usage.d.ts.map +0 -1
- package/dist/usage.js +0 -61
- package/src/lib/serde/thread.ts +0 -188
- /package/dist/{error.js → agent/index.js} +0 -0
- /package/dist/{lib/serde/tool.js → api/models/index.js} +0 -0
- /package/dist/{model.js → api/models/thread.js} +0 -0
- /package/dist/{sched/scheduler.js → api/resources/threads/types.js} +0 -0
- /package/dist/{serde/tool.js → types/kernl.js} +0 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Agent } from "@/agent";
|
|
3
|
+
import { Kernl } from "@/kernl";
|
|
4
|
+
import { createMockModel } from "@/thread/__tests__/fixtures/mock-model";
|
|
5
|
+
import { MisconfiguredError } from "@/lib/error";
|
|
6
|
+
import { message } from "@kernl-sdk/protocol";
|
|
7
|
+
import { InMemoryStorage } from "@/storage/in-memory";
|
|
8
|
+
|
|
9
|
+
describe("Agent.run() lifecycle", () => {
|
|
10
|
+
describe("Storage wiring", () => {
|
|
11
|
+
it("should pass storage to new Thread when creating", async () => {
|
|
12
|
+
const storage = new InMemoryStorage();
|
|
13
|
+
const model = createMockModel(async () => ({
|
|
14
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
15
|
+
finishReason: "stop",
|
|
16
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
17
|
+
warnings: [],
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const agent = new Agent({
|
|
21
|
+
id: "test-agent",
|
|
22
|
+
name: "Test",
|
|
23
|
+
instructions: "Test",
|
|
24
|
+
model,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
28
|
+
kernl.register(agent);
|
|
29
|
+
|
|
30
|
+
await agent.run("Hello");
|
|
31
|
+
|
|
32
|
+
// Verify storage was used - check that events were appended
|
|
33
|
+
const threads = await storage.threads.list();
|
|
34
|
+
expect(threads).toHaveLength(1);
|
|
35
|
+
|
|
36
|
+
const history = await storage.threads.history(threads[0].tid);
|
|
37
|
+
expect(history.length).toBeGreaterThan(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should work without storage (persist is no-op)", async () => {
|
|
41
|
+
const model = createMockModel(async () => ({
|
|
42
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
43
|
+
finishReason: "stop",
|
|
44
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
45
|
+
warnings: [],
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const agent = new Agent({
|
|
49
|
+
id: "test-agent",
|
|
50
|
+
name: "Test",
|
|
51
|
+
instructions: "Test",
|
|
52
|
+
model,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const kernl = new Kernl(); // No storage
|
|
56
|
+
kernl.register(agent);
|
|
57
|
+
|
|
58
|
+
const result = await agent.run("Hello");
|
|
59
|
+
|
|
60
|
+
// Should complete successfully
|
|
61
|
+
expect(result.response).toBe("Done");
|
|
62
|
+
expect(result.state).toBe("stopped");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should throw MisconfiguredError when agent not bound to kernl", async () => {
|
|
66
|
+
const model = createMockModel(async () => ({
|
|
67
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
68
|
+
finishReason: "stop",
|
|
69
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
70
|
+
warnings: [],
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
const agent = new Agent({
|
|
74
|
+
id: "test-agent",
|
|
75
|
+
name: "Test",
|
|
76
|
+
instructions: "Test",
|
|
77
|
+
model,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Don't register with kernl
|
|
81
|
+
await expect(agent.run("Hello")).rejects.toThrow(MisconfiguredError);
|
|
82
|
+
await expect(agent.run("Hello")).rejects.toThrow(/not bound to kernl/);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("New thread path", () => {
|
|
87
|
+
it("should create new thread when no threadId provided", async () => {
|
|
88
|
+
const model = createMockModel(async () => ({
|
|
89
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
90
|
+
finishReason: "stop",
|
|
91
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
92
|
+
warnings: [],
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
const agent = new Agent({
|
|
96
|
+
id: "test-agent",
|
|
97
|
+
name: "Test",
|
|
98
|
+
instructions: "Test",
|
|
99
|
+
model,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const kernl = new Kernl();
|
|
103
|
+
kernl.register(agent);
|
|
104
|
+
|
|
105
|
+
const result = await agent.run("Hello");
|
|
106
|
+
|
|
107
|
+
expect(result.response).toBe("Done");
|
|
108
|
+
expect(result.state).toBe("stopped");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should create new thread when threadId not found in storage", async () => {
|
|
112
|
+
const storage = new InMemoryStorage();
|
|
113
|
+
const model = createMockModel(async () => ({
|
|
114
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
115
|
+
finishReason: "stop",
|
|
116
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
117
|
+
warnings: [],
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const agent = new Agent({
|
|
121
|
+
id: "test-agent",
|
|
122
|
+
name: "Test",
|
|
123
|
+
instructions: "Test",
|
|
124
|
+
model,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
128
|
+
kernl.register(agent);
|
|
129
|
+
|
|
130
|
+
await agent.run("Hello", { threadId: "non-existent" });
|
|
131
|
+
|
|
132
|
+
// Should have created new thread with the specified tid
|
|
133
|
+
const threads = await storage.threads.list();
|
|
134
|
+
expect(threads).toHaveLength(1);
|
|
135
|
+
expect(threads[0].tid).toBe("non-existent");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should resume existing thread from storage", async () => {
|
|
139
|
+
const storage = new InMemoryStorage();
|
|
140
|
+
const model = createMockModel(async () => ({
|
|
141
|
+
content: [message({ role: "assistant", text: "Response" })],
|
|
142
|
+
finishReason: "stop",
|
|
143
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
144
|
+
warnings: [],
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
const agent = new Agent({
|
|
148
|
+
id: "test-agent",
|
|
149
|
+
name: "Test",
|
|
150
|
+
instructions: "Test",
|
|
151
|
+
model,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
155
|
+
kernl.register(agent);
|
|
156
|
+
|
|
157
|
+
const tid = "resume-thread";
|
|
158
|
+
|
|
159
|
+
// First run
|
|
160
|
+
await agent.run("First", { threadId: tid });
|
|
161
|
+
|
|
162
|
+
const firstHistory = await storage.threads.history(tid);
|
|
163
|
+
const firstEventCount = firstHistory.length;
|
|
164
|
+
expect(firstEventCount).toBeGreaterThanOrEqual(2); // user + assistant
|
|
165
|
+
|
|
166
|
+
// Second run (resume)
|
|
167
|
+
await agent.run("Second", { threadId: tid });
|
|
168
|
+
|
|
169
|
+
const secondHistory = await storage.threads.history(tid);
|
|
170
|
+
const secondEventCount = secondHistory.length;
|
|
171
|
+
|
|
172
|
+
// Should have more events (added user + assistant from second run)
|
|
173
|
+
expect(secondEventCount).toBeGreaterThanOrEqual(firstEventCount + 2);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("String vs array input", () => {
|
|
178
|
+
it("should handle string input (converted to message)", async () => {
|
|
179
|
+
const storage = new InMemoryStorage();
|
|
180
|
+
const model = createMockModel(async () => ({
|
|
181
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
182
|
+
finishReason: "stop",
|
|
183
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
184
|
+
warnings: [],
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
const agent = new Agent({
|
|
188
|
+
id: "test-agent",
|
|
189
|
+
name: "Test",
|
|
190
|
+
instructions: "Test",
|
|
191
|
+
model,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
195
|
+
kernl.register(agent);
|
|
196
|
+
|
|
197
|
+
await agent.run("Hello world");
|
|
198
|
+
|
|
199
|
+
const threads = await storage.threads.list();
|
|
200
|
+
const events = await storage.threads.history(threads[0].tid);
|
|
201
|
+
|
|
202
|
+
// Find the user message (first event should be user message)
|
|
203
|
+
const userMessage = events.find((e: any) => e.role === "user");
|
|
204
|
+
expect(userMessage).toMatchObject({
|
|
205
|
+
kind: "message",
|
|
206
|
+
role: "user",
|
|
207
|
+
content: [{ kind: "text", text: "Hello world" }],
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should handle array input (used as-is)", async () => {
|
|
212
|
+
const storage = new InMemoryStorage();
|
|
213
|
+
const model = createMockModel(async () => ({
|
|
214
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
215
|
+
finishReason: "stop",
|
|
216
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
217
|
+
warnings: [],
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
const agent = new Agent({
|
|
221
|
+
id: "test-agent",
|
|
222
|
+
name: "Test",
|
|
223
|
+
instructions: "Test",
|
|
224
|
+
model,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
228
|
+
kernl.register(agent);
|
|
229
|
+
|
|
230
|
+
const input = [message({ role: "user", text: "Custom" })];
|
|
231
|
+
await agent.run(input);
|
|
232
|
+
|
|
233
|
+
const threads = await storage.threads.list();
|
|
234
|
+
const events = await storage.threads.history(threads[0].tid);
|
|
235
|
+
|
|
236
|
+
// Find the user message
|
|
237
|
+
const userMessage = events.find((e: any) => e.role === "user");
|
|
238
|
+
expect(userMessage).toMatchObject({
|
|
239
|
+
kind: "message",
|
|
240
|
+
role: "user",
|
|
241
|
+
content: [{ kind: "text", text: "Custom" }],
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("Agent.stream() lifecycle", () => {
|
|
248
|
+
it("should yield stream-start event first", async () => {
|
|
249
|
+
const model = createMockModel(async () => ({
|
|
250
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
251
|
+
finishReason: "stop",
|
|
252
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
253
|
+
warnings: [],
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
const agent = new Agent({
|
|
257
|
+
id: "test-agent",
|
|
258
|
+
name: "Test",
|
|
259
|
+
instructions: "Test",
|
|
260
|
+
model,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const kernl = new Kernl();
|
|
264
|
+
kernl.register(agent);
|
|
265
|
+
|
|
266
|
+
const events = [];
|
|
267
|
+
for await (const event of agent.stream("Hello")) {
|
|
268
|
+
events.push(event);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
expect(events[0]).toEqual({ kind: "stream-start" });
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should have same persistence behavior as run()", async () => {
|
|
275
|
+
const storage = new InMemoryStorage();
|
|
276
|
+
const model = createMockModel(async () => ({
|
|
277
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
278
|
+
finishReason: "stop",
|
|
279
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
280
|
+
warnings: [],
|
|
281
|
+
}));
|
|
282
|
+
|
|
283
|
+
const agent = new Agent({
|
|
284
|
+
id: "test-agent",
|
|
285
|
+
name: "Test",
|
|
286
|
+
instructions: "Test",
|
|
287
|
+
model,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
291
|
+
kernl.register(agent);
|
|
292
|
+
|
|
293
|
+
const events = [];
|
|
294
|
+
for await (const event of agent.stream("Hello")) {
|
|
295
|
+
events.push(event);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Should have persisted like run()
|
|
299
|
+
const threads = await storage.threads.list();
|
|
300
|
+
expect(threads).toHaveLength(1);
|
|
301
|
+
|
|
302
|
+
const history = await storage.threads.history(threads[0].tid);
|
|
303
|
+
expect(history.length).toBeGreaterThan(0);
|
|
304
|
+
|
|
305
|
+
// Should have streamed events
|
|
306
|
+
expect(events).toEqual(
|
|
307
|
+
expect.arrayContaining([
|
|
308
|
+
{ kind: "stream-start" },
|
|
309
|
+
expect.objectContaining({ kind: "message" }),
|
|
310
|
+
]),
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("Agent.threads helper", () => {
|
|
316
|
+
it("should throw MisconfiguredError when agent is not bound to kernl", () => {
|
|
317
|
+
const model = createMockModel(async () => ({
|
|
318
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
319
|
+
finishReason: "stop",
|
|
320
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
321
|
+
warnings: [],
|
|
322
|
+
}));
|
|
323
|
+
|
|
324
|
+
const agent = new Agent({
|
|
325
|
+
id: "test-agent",
|
|
326
|
+
name: "Test",
|
|
327
|
+
instructions: "Test",
|
|
328
|
+
model,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
expect(() => agent.threads).toThrow(MisconfiguredError);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should list only threads for this agent", async () => {
|
|
335
|
+
const storage = new InMemoryStorage();
|
|
336
|
+
const model = createMockModel(async () => ({
|
|
337
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
338
|
+
finishReason: "stop",
|
|
339
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
340
|
+
warnings: [],
|
|
341
|
+
}));
|
|
342
|
+
|
|
343
|
+
const agentA = new Agent({
|
|
344
|
+
id: "agent-a",
|
|
345
|
+
name: "Agent A",
|
|
346
|
+
instructions: "Test",
|
|
347
|
+
model,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const agentB = new Agent({
|
|
351
|
+
id: "agent-b",
|
|
352
|
+
name: "Agent B",
|
|
353
|
+
instructions: "Test",
|
|
354
|
+
model,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
358
|
+
kernl.register(agentA);
|
|
359
|
+
kernl.register(agentB);
|
|
360
|
+
|
|
361
|
+
await agentA.run("Hello from A");
|
|
362
|
+
await agentB.run("Hello from B");
|
|
363
|
+
await agentB.run("Another from B");
|
|
364
|
+
|
|
365
|
+
const threadsAPage = await agentA.threads.list();
|
|
366
|
+
const threadsBPage = await agentB.threads.list();
|
|
367
|
+
|
|
368
|
+
const threadsA = await threadsAPage.collect();
|
|
369
|
+
const threadsB = await threadsBPage.collect();
|
|
370
|
+
|
|
371
|
+
expect(threadsA).toHaveLength(1);
|
|
372
|
+
expect(threadsB.length).toBeGreaterThanOrEqual(2);
|
|
373
|
+
|
|
374
|
+
for (const thread of threadsA) {
|
|
375
|
+
expect(thread.agentId).toBe("agent-a");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for (const thread of threadsB) {
|
|
379
|
+
expect(thread.agentId).toBe("agent-b");
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should expose thread history via threads.history()", async () => {
|
|
384
|
+
const storage = new InMemoryStorage();
|
|
385
|
+
const model = createMockModel(async () => ({
|
|
386
|
+
content: [message({ role: "assistant", text: "Done" })],
|
|
387
|
+
finishReason: "stop",
|
|
388
|
+
usage: { inputTokens: 2, outputTokens: 2, totalTokens: 4 },
|
|
389
|
+
warnings: [],
|
|
390
|
+
}));
|
|
391
|
+
|
|
392
|
+
const agent = new Agent({
|
|
393
|
+
id: "test-agent",
|
|
394
|
+
name: "Test",
|
|
395
|
+
instructions: "Test",
|
|
396
|
+
model,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const kernl = new Kernl({ storage: { db: storage } });
|
|
400
|
+
kernl.register(agent);
|
|
401
|
+
|
|
402
|
+
await agent.run("Hello");
|
|
403
|
+
|
|
404
|
+
const threadsPage = await agent.threads.list();
|
|
405
|
+
const threads = await threadsPage.collect();
|
|
406
|
+
expect(threads).toHaveLength(1);
|
|
407
|
+
|
|
408
|
+
const tid = threads[0].tid;
|
|
409
|
+
const events = await agent.threads.history(tid, { order: "asc" });
|
|
410
|
+
|
|
411
|
+
// Expect exactly two events: user message then assistant message
|
|
412
|
+
expect(events).toHaveLength(2);
|
|
413
|
+
|
|
414
|
+
const [userEvent, assistantEvent] = events;
|
|
415
|
+
|
|
416
|
+
// Common headers
|
|
417
|
+
expect(userEvent.tid).toBe(tid);
|
|
418
|
+
expect(assistantEvent.tid).toBe(tid);
|
|
419
|
+
expect(userEvent.seq).toBe(0);
|
|
420
|
+
expect(assistantEvent.seq).toBe(1);
|
|
421
|
+
|
|
422
|
+
// User message
|
|
423
|
+
expect(userEvent.kind).toBe("message");
|
|
424
|
+
// @ts-expect-error ThreadEvent extends LanguageModelItem at runtime
|
|
425
|
+
expect(userEvent.role).toBe("user");
|
|
426
|
+
// @ts-expect-error ThreadEvent extends LanguageModelItem at runtime
|
|
427
|
+
expect(userEvent.content).toEqual([
|
|
428
|
+
{ kind: "text", text: "Hello" },
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
// Assistant message
|
|
432
|
+
expect(assistantEvent.kind).toBe("message");
|
|
433
|
+
// @ts-expect-error ThreadEvent extends LanguageModelItem at runtime
|
|
434
|
+
expect(assistantEvent.role).toBe("assistant");
|
|
435
|
+
// @ts-expect-error ThreadEvent extends LanguageModelItem at runtime
|
|
436
|
+
expect(assistantEvent.content).toEqual([
|
|
437
|
+
{ kind: "text", text: "Done" },
|
|
438
|
+
]);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
File without changes
|
package/src/agent.ts
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
+
message,
|
|
2
3
|
LanguageModel,
|
|
4
|
+
LanguageModelItem,
|
|
3
5
|
LanguageModelRequestSettings,
|
|
4
|
-
message,
|
|
5
6
|
} from "@kernl-sdk/protocol";
|
|
6
7
|
|
|
8
|
+
import { Thread } from "./thread";
|
|
9
|
+
import type { Kernl } from "./kernl";
|
|
10
|
+
import type {
|
|
11
|
+
RThreadsListParams,
|
|
12
|
+
RThreadCreateParams,
|
|
13
|
+
RThreadGetOptions,
|
|
14
|
+
RThreadHistoryParams,
|
|
15
|
+
RThreadUpdateParams,
|
|
16
|
+
} from "@/api/resources/threads/types";
|
|
7
17
|
import type { Context, UnknownContext } from "./context";
|
|
8
|
-
import { InputGuardrail, OutputGuardrail } from "./guardrail";
|
|
9
|
-
import { AgentHooks } from "./lifecycle";
|
|
10
|
-
import { BaseToolkit } from "./tool/toolkit";
|
|
11
18
|
import { Tool } from "./tool";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
import { BaseToolkit } from "./tool/toolkit";
|
|
20
|
+
import {
|
|
21
|
+
InputGuardrail,
|
|
22
|
+
OutputGuardrail,
|
|
23
|
+
type ResolvedAgentResponse,
|
|
24
|
+
} from "./guardrail";
|
|
25
|
+
import { AgentHooks } from "./lifecycle";
|
|
15
26
|
|
|
27
|
+
import { MisconfiguredError, RuntimeError } from "./lib/error";
|
|
16
28
|
import type { AgentConfig, AgentResponseType } from "./types/agent";
|
|
17
29
|
import type {
|
|
18
30
|
TextResponse,
|
|
19
|
-
|
|
31
|
+
ThreadExecuteOptions,
|
|
20
32
|
ThreadExecuteResult,
|
|
21
33
|
ThreadStreamEvent,
|
|
22
34
|
} from "./types/thread";
|
|
23
|
-
import type { Kernl } from "./kernl";
|
|
24
|
-
import type { ResolvedAgentResponse } from "./guardrail";
|
|
25
35
|
|
|
26
36
|
export class Agent<
|
|
27
37
|
TContext = UnknownContext,
|
|
@@ -113,10 +123,13 @@ export class Agent<
|
|
|
113
123
|
|
|
114
124
|
/**
|
|
115
125
|
* Blocking execution - spawns or resumes thread and waits for completion
|
|
126
|
+
*
|
|
127
|
+
* @throws {RuntimeError} If the specified thread is already running (concurrent execution not allowed)
|
|
128
|
+
* @throws {MisconfiguredError} If the agent is not bound to a kernl instance
|
|
116
129
|
*/
|
|
117
130
|
async run(
|
|
118
|
-
|
|
119
|
-
options?:
|
|
131
|
+
input: string | LanguageModelItem[],
|
|
132
|
+
options?: ThreadExecuteOptions<TContext>,
|
|
120
133
|
): Promise<ThreadExecuteResult<ResolvedAgentResponse<TResponse>>> {
|
|
121
134
|
if (!this.kernl) {
|
|
122
135
|
throw new MisconfiguredError(
|
|
@@ -124,17 +137,46 @@ export class Agent<
|
|
|
124
137
|
);
|
|
125
138
|
}
|
|
126
139
|
|
|
127
|
-
const
|
|
140
|
+
const items =
|
|
141
|
+
typeof input === "string"
|
|
142
|
+
? [message({ role: "user", text: input })]
|
|
143
|
+
: input;
|
|
128
144
|
const tid = options?.threadId;
|
|
129
145
|
|
|
130
|
-
|
|
131
|
-
|
|
146
|
+
let thread: Thread<TContext, TResponse> | null = null;
|
|
147
|
+
|
|
148
|
+
if (tid) {
|
|
149
|
+
// no concurrent execution of same thread - correctness contract
|
|
150
|
+
// TODO: race condition - need to check again after async storage.get()
|
|
151
|
+
if (this.kernl.athreads.has(tid)) {
|
|
152
|
+
throw new RuntimeError(`Thread ${tid} is already running.`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// try to resume from storage if available
|
|
156
|
+
if (this.kernl.storage?.threads) {
|
|
157
|
+
thread = (await this.kernl.storage.threads.get(tid, {
|
|
158
|
+
history: true,
|
|
159
|
+
})) as Thread<TContext, TResponse> | null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// create new thread if not found in storage or no tid provided
|
|
132
164
|
if (!thread) {
|
|
133
|
-
thread = new Thread(
|
|
165
|
+
thread = new Thread({
|
|
166
|
+
agent: this,
|
|
167
|
+
input: items,
|
|
168
|
+
context: options?.context,
|
|
169
|
+
model: options?.model,
|
|
170
|
+
task: options?.task,
|
|
171
|
+
tid: options?.threadId,
|
|
172
|
+
namespace: options?.namespace,
|
|
173
|
+
storage: this.kernl.storage?.threads,
|
|
174
|
+
});
|
|
134
175
|
return this.kernl.spawn(thread);
|
|
135
176
|
}
|
|
136
177
|
|
|
137
|
-
thread
|
|
178
|
+
// resume existing thread from storage
|
|
179
|
+
thread.append(...items);
|
|
138
180
|
return this.kernl.schedule(thread);
|
|
139
181
|
}
|
|
140
182
|
|
|
@@ -142,10 +184,13 @@ export class Agent<
|
|
|
142
184
|
* Streaming execution - spawns or resumes thread and returns async iterator
|
|
143
185
|
*
|
|
144
186
|
* NOTE: streaming probably won't make sense in scheduling contexts so spawnStream etc. won't make sense
|
|
187
|
+
*
|
|
188
|
+
* @throws {RuntimeError} If the specified thread is already running (concurrent execution not allowed)
|
|
189
|
+
* @throws {MisconfiguredError} If the agent is not bound to a kernl instance
|
|
145
190
|
*/
|
|
146
191
|
async *stream(
|
|
147
|
-
|
|
148
|
-
options?:
|
|
192
|
+
input: string | LanguageModelItem[],
|
|
193
|
+
options?: ThreadExecuteOptions<TContext>,
|
|
149
194
|
): AsyncIterable<ThreadStreamEvent> {
|
|
150
195
|
if (!this.kernl) {
|
|
151
196
|
throw new MisconfiguredError(
|
|
@@ -153,22 +198,53 @@ export class Agent<
|
|
|
153
198
|
);
|
|
154
199
|
}
|
|
155
200
|
|
|
156
|
-
const
|
|
201
|
+
const items =
|
|
202
|
+
typeof input === "string"
|
|
203
|
+
? [message({ role: "user", text: input })]
|
|
204
|
+
: input;
|
|
157
205
|
const tid = options?.threadId;
|
|
158
206
|
|
|
159
|
-
|
|
160
|
-
|
|
207
|
+
let thread: Thread<TContext, TResponse> | null = null;
|
|
208
|
+
|
|
209
|
+
if (tid) {
|
|
210
|
+
// no concurrent execution of same thread - correctness contract
|
|
211
|
+
// TODO: race condition - need to check again after async storage.get()
|
|
212
|
+
if (this.kernl.athreads.has(tid)) {
|
|
213
|
+
throw new RuntimeError(`Thread ${tid} is already running.`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// try to resume from storage if available
|
|
217
|
+
if (this.kernl.storage?.threads) {
|
|
218
|
+
thread = (await this.kernl.storage.threads.get(tid, {
|
|
219
|
+
history: true,
|
|
220
|
+
})) as Thread<TContext, TResponse> | null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// create new thread if not found in storage or no tid provided
|
|
161
225
|
if (!thread) {
|
|
162
|
-
thread = new Thread(
|
|
226
|
+
thread = new Thread({
|
|
227
|
+
agent: this,
|
|
228
|
+
input: items,
|
|
229
|
+
context: options?.context,
|
|
230
|
+
model: options?.model,
|
|
231
|
+
task: options?.task,
|
|
232
|
+
tid: options?.threadId,
|
|
233
|
+
namespace: options?.namespace,
|
|
234
|
+
storage: this.kernl.storage?.threads,
|
|
235
|
+
});
|
|
163
236
|
yield* this.kernl.spawnStream(thread);
|
|
164
237
|
return;
|
|
165
238
|
}
|
|
166
239
|
|
|
167
|
-
thread
|
|
240
|
+
// resume existing thread from storage
|
|
241
|
+
thread.append(...items);
|
|
168
242
|
yield* this.kernl.scheduleStream(thread);
|
|
169
243
|
}
|
|
170
244
|
|
|
171
245
|
/**
|
|
246
|
+
* @internal
|
|
247
|
+
*
|
|
172
248
|
* Get a specific tool by ID from all toolkits.
|
|
173
249
|
*
|
|
174
250
|
* @param id The tool ID to look up
|
|
@@ -183,6 +259,8 @@ export class Agent<
|
|
|
183
259
|
}
|
|
184
260
|
|
|
185
261
|
/**
|
|
262
|
+
* @internal
|
|
263
|
+
*
|
|
186
264
|
* Get all tools available from all toolkits for the given context.
|
|
187
265
|
* Checks for duplicate tool IDs across toolkits and throws an error if found.
|
|
188
266
|
*
|
|
@@ -213,4 +291,41 @@ export class Agent<
|
|
|
213
291
|
|
|
214
292
|
return allTools;
|
|
215
293
|
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Thread management scoped to this agent.
|
|
297
|
+
*
|
|
298
|
+
* Convenience wrapper around kernl.threads that automatically filters to this agent's threads.
|
|
299
|
+
*/
|
|
300
|
+
get threads() {
|
|
301
|
+
if (!this.kernl) {
|
|
302
|
+
throw new MisconfiguredError(
|
|
303
|
+
`Agent ${this.id} not bound to kernl. Call kernl.register(agent) first.`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const agentId = this.id;
|
|
308
|
+
const kthreads = this.kernl.threads;
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
get: (tid: string, options?: RThreadGetOptions) =>
|
|
312
|
+
kthreads.get(tid, options),
|
|
313
|
+
list: (params: Omit<RThreadsListParams, "agentId"> = {}) =>
|
|
314
|
+
kthreads.list({ ...params, agentId }),
|
|
315
|
+
delete: (tid: string) => kthreads.delete(tid),
|
|
316
|
+
history: (tid: string, params?: RThreadHistoryParams) =>
|
|
317
|
+
kthreads.history(tid, params),
|
|
318
|
+
create: (params: Omit<RThreadCreateParams, "agentId" | "model">) =>
|
|
319
|
+
kthreads.create({
|
|
320
|
+
...params,
|
|
321
|
+
agentId,
|
|
322
|
+
model: {
|
|
323
|
+
provider: this.model.provider,
|
|
324
|
+
modelId: this.model.modelId,
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
update: (tid: string, patch: RThreadUpdateParams) =>
|
|
328
|
+
kthreads.update(tid, patch),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
216
331
|
}
|