@codeworksh/aikit 0.2.0 → 0.2.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.
- package/README.md +117 -260
- package/index.d.mts +507 -528
- package/index.mjs +58 -231
- package/index.mjs.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @codeworksh/aikit
|
|
2
2
|
|
|
3
|
-
Toolkit for
|
|
3
|
+
Toolkit for building AI agents and streaming LLM workflows.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,341 +8,198 @@ Toolkit for Building AI Agents and Workflows.
|
|
|
8
8
|
npm install @codeworksh/aikit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Node `>=24.14.1` is required.
|
|
12
|
+
|
|
13
|
+
## What You Get
|
|
14
|
+
|
|
15
|
+
- `llm(...)` to resolve a model from the built-in registry
|
|
16
|
+
- `stream(...)` and `stream.complete(...)` for provider-normalized LLM streaming
|
|
17
|
+
- `Agent.create(...)` for a stateful agent instance
|
|
18
|
+
- `agent.loop(...)` and `agent.loopContinue(...)` for lower-level turn orchestration
|
|
19
|
+
- `Message` helpers for typed messages and generated `messageId` values
|
|
20
|
+
|
|
21
|
+
## Root API
|
|
12
22
|
|
|
13
23
|
```ts
|
|
14
|
-
import { Message, llm, stream } from "@codeworksh/aikit";
|
|
24
|
+
import { Agent, Message, agent, llm, stream } from "@codeworksh/aikit";
|
|
25
|
+
```
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
if (!model) throw new Error("Model not found");
|
|
27
|
+
## Quick Start
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
messages: [
|
|
22
|
-
{
|
|
23
|
-
role: "user",
|
|
24
|
-
time: { created: Date.now() },
|
|
25
|
-
parts: [{ type: "text", text: "Say hello in one line." }],
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
+
```ts
|
|
30
|
+
import { Agent } from "@codeworksh/aikit";
|
|
29
31
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
+
const agent = await Agent.create({
|
|
33
|
+
provider: "anthropic",
|
|
34
|
+
model: "claude-haiku-4-5-20251001",
|
|
35
|
+
getApiKey: async () => process.env.ANTHROPIC_API_KEY,
|
|
32
36
|
});
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
if (event.type === "text.delta") {
|
|
36
|
-
process.stdout.write(event.delta);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
38
|
+
agent.setSystemPrompt("Be concise.");
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
console.log(message.parts);
|
|
42
|
-
```
|
|
40
|
+
await agent.prompt([{ type: "text", text: "Reply with exactly: hello" }]);
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
const lastMessage = agent.state.messages.at(-1);
|
|
43
|
+
console.log(lastMessage?.messageId);
|
|
44
|
+
console.log(lastMessage?.role);
|
|
45
|
+
```
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
## Stream Events
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
Use `stream(...)` when you want token and part-level events.
|
|
49
50
|
|
|
50
51
|
```ts
|
|
51
|
-
import { llm, stream } from "@codeworksh/aikit";
|
|
52
|
+
import { Message, llm, stream } from "@codeworksh/aikit";
|
|
52
53
|
|
|
53
54
|
const model = await llm("anthropic", "claude-haiku-4-5-20251001");
|
|
54
|
-
if (!model) throw new Error("
|
|
55
|
+
if (!model) throw new Error("Model not found");
|
|
55
56
|
|
|
56
|
-
const
|
|
57
|
+
const s = stream(
|
|
57
58
|
model,
|
|
58
59
|
{
|
|
59
60
|
messages: [
|
|
60
|
-
{
|
|
61
|
+
Message.createUserMessage({
|
|
61
62
|
role: "user",
|
|
62
63
|
time: { created: Date.now() },
|
|
63
|
-
parts: [{ type: "text", text: "
|
|
64
|
-
},
|
|
64
|
+
parts: [{ type: "text", text: "Count from 1 to 3" }],
|
|
65
|
+
}),
|
|
65
66
|
],
|
|
66
67
|
},
|
|
67
68
|
{ apiKey: process.env.ANTHROPIC_API_KEY },
|
|
68
69
|
);
|
|
70
|
+
|
|
71
|
+
for await (const event of s) {
|
|
72
|
+
if (event.type === "text.delta") {
|
|
73
|
+
process.stdout.write(event.delta);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const finalMessage = await s.result();
|
|
78
|
+
console.log(finalMessage.messageId);
|
|
69
79
|
```
|
|
70
80
|
|
|
71
|
-
|
|
81
|
+
## Agent Instance
|
|
72
82
|
|
|
73
|
-
`
|
|
83
|
+
`Agent.create(...)` is the simplest way to work with a stateful agent.
|
|
74
84
|
|
|
75
85
|
```ts
|
|
76
|
-
import {
|
|
77
|
-
|
|
86
|
+
import { Agent } from "@codeworksh/aikit";
|
|
87
|
+
|
|
88
|
+
const agent = await Agent.create({
|
|
89
|
+
provider: "anthropic",
|
|
90
|
+
model: "claude-haiku-4-5-20251001",
|
|
91
|
+
getApiKey: async () => process.env.ANTHROPIC_API_KEY,
|
|
92
|
+
});
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
agent.setSystemPrompt("Be concise.");
|
|
95
|
+
|
|
96
|
+
agent.subscribe((event) => {
|
|
97
|
+
if (event.type === "message.end") {
|
|
98
|
+
console.log(event.message.role, event.message.messageId);
|
|
99
|
+
}
|
|
81
100
|
});
|
|
82
101
|
|
|
102
|
+
await agent.prompt([{ type: "text", text: "Say hello in one line." }]);
|
|
103
|
+
|
|
104
|
+
console.log(agent.state.messages);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## With Tools
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { Type } from "@sinclair/typebox";
|
|
111
|
+
import { Agent } from "@codeworksh/aikit";
|
|
112
|
+
|
|
83
113
|
const calculatorTool = Agent.defineTool({
|
|
84
114
|
name: "calculator",
|
|
85
115
|
label: "Calculator",
|
|
86
|
-
description: "Evaluate
|
|
87
|
-
parameters:
|
|
88
|
-
|
|
116
|
+
description: "Evaluate arithmetic expressions",
|
|
117
|
+
parameters: Type.Object({
|
|
118
|
+
expression: Type.String(),
|
|
119
|
+
}),
|
|
120
|
+
async execute(_callID, params) {
|
|
89
121
|
return {
|
|
90
122
|
status: "completed",
|
|
91
123
|
result: {
|
|
92
|
-
content: [{ type: "text", text:
|
|
124
|
+
content: [{ type: "text", text: `result for ${params.expression}` }],
|
|
93
125
|
isError: false,
|
|
94
126
|
},
|
|
95
127
|
};
|
|
96
128
|
},
|
|
97
129
|
});
|
|
98
130
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
time: { created: Date.now() },
|
|
105
|
-
parts: [{ type: "text", text: "Use the calculator tool for 2 + 2." }],
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const run = agent.run(
|
|
109
|
-
{
|
|
110
|
-
model,
|
|
111
|
-
convertToLlm: async (messages) => messages,
|
|
112
|
-
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
113
|
-
beforeToolExecution: async ({ toolCall }) => {
|
|
114
|
-
console.log("validated args", toolCall.args);
|
|
115
|
-
},
|
|
116
|
-
afterToolExecution: async ({ result }) => result,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
systemPrompt: "Use tools when helpful.",
|
|
120
|
-
messages: [],
|
|
131
|
+
const instance = await Agent.create({
|
|
132
|
+
provider: "anthropic",
|
|
133
|
+
model: "claude-haiku-4-5-20251001",
|
|
134
|
+
getApiKey: async () => process.env.ANTHROPIC_API_KEY,
|
|
135
|
+
initialState: {
|
|
121
136
|
tools: [calculatorTool],
|
|
122
137
|
},
|
|
123
|
-
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
for await (const event of run) {
|
|
127
|
-
if (event.type === "message.part.update" && event.source === "tool") {
|
|
128
|
-
console.log(event.part);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const messages = await run.result();
|
|
133
|
-
console.log(messages.at(-1));
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Agent Vs LLM
|
|
137
|
-
|
|
138
|
-
Use `stream(...)` when you want raw provider events and manual control over tool handling.
|
|
139
|
-
|
|
140
|
-
Use `agent.run(...)` when you want a higher-level runtime that:
|
|
141
|
-
|
|
142
|
-
- streams assistant message lifecycle events
|
|
143
|
-
- validates tool arguments
|
|
144
|
-
- executes tools sequentially or in parallel
|
|
145
|
-
- supports `beforeToolExecution` and `afterToolExecution` callbacks
|
|
146
|
-
|
|
147
|
-
## Event Flow
|
|
148
|
-
|
|
149
|
-
LLM streams emit low-level provider-normalized events:
|
|
138
|
+
});
|
|
150
139
|
|
|
151
|
-
|
|
152
|
-
start
|
|
153
|
-
text.start / thinking.start / toolcall.start
|
|
154
|
-
text.delta / thinking.delta / toolcall.delta
|
|
155
|
-
text.end / thinking.end / toolcall.end
|
|
156
|
-
done | error
|
|
140
|
+
await instance.prompt([{ type: "text", text: "Use the calculator tool for 25 * 18." }]);
|
|
157
141
|
```
|
|
158
142
|
|
|
159
|
-
##
|
|
160
|
-
|
|
161
|
-
Agent loops emit higher-level lifecycle events around messages, parts, turns, and tools:
|
|
162
|
-
|
|
163
|
-
```text
|
|
164
|
-
agent.start
|
|
165
|
-
turn.start
|
|
166
|
-
message.start
|
|
167
|
-
message.part.start
|
|
168
|
-
message.part.update
|
|
169
|
-
message.part.end
|
|
170
|
-
message.update
|
|
171
|
-
message.end
|
|
172
|
-
tool.execution.start
|
|
173
|
-
tool.execution.update
|
|
174
|
-
tool.execution.end
|
|
175
|
-
turn.end
|
|
176
|
-
agent.end
|
|
177
|
-
```
|
|
143
|
+
## Lower-Level Loop
|
|
178
144
|
|
|
179
|
-
|
|
145
|
+
Use `agent.loop(...)` when you want loop execution without creating a stateful instance.
|
|
180
146
|
|
|
181
147
|
```ts
|
|
182
|
-
import {
|
|
183
|
-
import { Agent, Message, agent, llm } from "@codeworksh/aikit";
|
|
184
|
-
|
|
185
|
-
const searchParams = Type.Object({
|
|
186
|
-
query: Type.String(),
|
|
187
|
-
limit: Type.Optional(Type.Number()),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const searchTool: Agent.AgentTool<typeof searchParams, { progress: number }, { hits: number }> = {
|
|
191
|
-
name: "search",
|
|
192
|
-
label: "Search",
|
|
193
|
-
description: "Search indexed documents",
|
|
194
|
-
parameters: searchParams,
|
|
195
|
-
async execute(callID, params, signal, onUpdate) {
|
|
196
|
-
await onUpdate?.({
|
|
197
|
-
status: "running",
|
|
198
|
-
partial: {
|
|
199
|
-
content: [{ type: "text", text: `Searching for ${params.query}` }],
|
|
200
|
-
details: { progress: 50 },
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
status: "completed",
|
|
206
|
-
result: {
|
|
207
|
-
content: [{ type: "text", text: `Found results for ${params.query}` }],
|
|
208
|
-
details: { hits: 3 },
|
|
209
|
-
isError: false,
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
},
|
|
213
|
-
};
|
|
148
|
+
import { Message, agent, llm } from "@codeworksh/aikit";
|
|
214
149
|
|
|
215
150
|
const model = await llm("anthropic", "claude-haiku-4-5-20251001");
|
|
216
|
-
if (!model) throw new Error("
|
|
217
|
-
|
|
218
|
-
const prompt: Message.UserMessage = {
|
|
219
|
-
role: "user",
|
|
220
|
-
time: { created: Date.now() },
|
|
221
|
-
parts: [{ type: "text", text: "Search for agent loop documentation." }],
|
|
222
|
-
};
|
|
151
|
+
if (!model) throw new Error("Model not found");
|
|
223
152
|
|
|
224
|
-
const run = agent.
|
|
153
|
+
const run = agent.loop(
|
|
225
154
|
{
|
|
226
155
|
model,
|
|
227
156
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
228
157
|
convertToLlm: async (messages) => messages,
|
|
229
|
-
toolExecution: "parallel",
|
|
230
158
|
},
|
|
231
159
|
{
|
|
232
|
-
systemPrompt: "
|
|
160
|
+
systemPrompt: "Be concise.",
|
|
233
161
|
messages: [],
|
|
234
|
-
tools: [
|
|
162
|
+
tools: [],
|
|
235
163
|
},
|
|
236
|
-
[
|
|
164
|
+
[
|
|
165
|
+
Message.createUserMessage({
|
|
166
|
+
role: "user",
|
|
167
|
+
time: { created: Date.now() },
|
|
168
|
+
parts: [{ type: "text", text: "Reply with exactly: ok" }],
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
237
171
|
);
|
|
238
172
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log("tool started", event.name, event.args ?? event.rawArgs);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (event.type === "tool.execution.update") {
|
|
245
|
-
console.log("tool update", event.partial?.details);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (event.type === "tool.execution.end") {
|
|
249
|
-
console.log("tool finished", event.result.details);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
173
|
+
const messages = await run.result();
|
|
174
|
+
console.log(messages.at(-1)?.role);
|
|
252
175
|
```
|
|
253
176
|
|
|
254
|
-
|
|
177
|
+
## Message IDs
|
|
255
178
|
|
|
256
|
-
|
|
179
|
+
Every user and assistant message has a `messageId`.
|
|
257
180
|
|
|
258
|
-
|
|
259
|
-
- `sequential`: prepares and executes each tool call one by one. Use this when tools depend on shared state, rate limits, or side effects that should not overlap.
|
|
181
|
+
Use the helpers when constructing messages yourself:
|
|
260
182
|
|
|
261
183
|
```ts
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
model,
|
|
265
|
-
convertToLlm: async (messages) => messages,
|
|
266
|
-
toolExecution: "sequential",
|
|
267
|
-
},
|
|
268
|
-
context,
|
|
269
|
-
[prompt],
|
|
270
|
-
);
|
|
271
|
-
```
|
|
184
|
+
import { Message } from "@codeworksh/aikit";
|
|
272
185
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
186
|
+
const userMessage = Message.createUserMessage({
|
|
187
|
+
role: "user",
|
|
188
|
+
time: { created: Date.now() },
|
|
189
|
+
parts: [{ type: "text", text: "hello" }],
|
|
190
|
+
});
|
|
278
191
|
|
|
279
|
-
|
|
280
|
-
const run = agent.run(
|
|
281
|
-
{
|
|
282
|
-
model,
|
|
283
|
-
convertToLlm: async (messages) => messages,
|
|
284
|
-
beforeToolExecution: async ({ toolCall }) => {
|
|
285
|
-
if (toolCall.name === "search" && toolCall.args?.query === "secret") {
|
|
286
|
-
return {
|
|
287
|
-
block: true,
|
|
288
|
-
reason: "This query is not allowed",
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
context,
|
|
294
|
-
[prompt],
|
|
295
|
-
);
|
|
192
|
+
console.log(userMessage.messageId);
|
|
296
193
|
```
|
|
297
194
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
### afterToolExecution
|
|
301
|
-
|
|
302
|
-
`afterToolExecution` runs after the tool finishes and before the terminal tool result is emitted.
|
|
303
|
-
|
|
304
|
-
Use it to normalize output, attach metadata, or override the final tool result.
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
const run = agent.run(
|
|
308
|
-
{
|
|
309
|
-
model,
|
|
310
|
-
convertToLlm: async (messages) => messages,
|
|
311
|
-
afterToolExecution: async ({ result }) => {
|
|
312
|
-
if (result.status === "completed") {
|
|
313
|
-
return {
|
|
314
|
-
status: "completed",
|
|
315
|
-
result: {
|
|
316
|
-
...result.result,
|
|
317
|
-
details: {
|
|
318
|
-
...result.result.details,
|
|
319
|
-
source: "post-processed",
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
context,
|
|
327
|
-
[prompt],
|
|
328
|
-
);
|
|
329
|
-
```
|
|
195
|
+
This is useful for persistence layers like SQLite, event reconciliation, and updating stored messages by identity instead of array position.
|
|
330
196
|
|
|
331
|
-
##
|
|
197
|
+
## Lower-Level APIs
|
|
332
198
|
|
|
333
|
-
|
|
199
|
+
- `stream(...)`: raw provider-normalized stream
|
|
200
|
+
- `stream.complete(...)`: final assistant message without manual iteration
|
|
201
|
+
- `Agent.create(...)`: stateful agent instance
|
|
202
|
+
- `agent.loop(...)`: lower-level agent loop
|
|
203
|
+
- `agent.loopContinue(...)`: continue from an existing context
|
|
334
204
|
|
|
335
|
-
|
|
336
|
-
import {
|
|
337
|
-
agent,
|
|
338
|
-
llm,
|
|
339
|
-
stream,
|
|
340
|
-
Agent,
|
|
341
|
-
Event,
|
|
342
|
-
Message,
|
|
343
|
-
Model,
|
|
344
|
-
Stream,
|
|
345
|
-
validateToolArguments,
|
|
346
|
-
validateToolCall,
|
|
347
|
-
} from "@codeworksh/aikit";
|
|
348
|
-
```
|
|
205
|
+
API docs can be added later. For now, the tests under `packages/aikit/test/` are the best reference for concrete usage.
|