@codeworksh/aikit 0.1.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/LICENSE +21 -0
- package/README.md +119 -261
- package/index.d.mts +8913 -0
- package/index.mjs +2342 -0
- package/index.mjs.map +1 -0
- package/package.json +61 -58
- package/src/agent/agent.ts +0 -75
- package/src/agent/loop.ts +0 -786
- package/src/agent.ts +0 -11
- package/src/event/event.ts +0 -271
- package/src/index.ts +0 -10
- package/src/llm.ts +0 -29
- package/src/message/message.ts +0 -280
- package/src/model/catalog.ts +0 -95
- package/src/model/model.ts +0 -253
- package/src/provider/provider.ts +0 -43
- package/src/provider/providers/anthropic/index.ts +0 -769
- package/src/provider/register.ts +0 -14
- package/src/provider/stream.ts +0 -299
- package/src/stream.ts +0 -72
- package/src/utils/eventstream.ts +0 -95
- package/src/utils/helpers.ts +0 -24
- package/src/utils/jsonparse.ts +0 -32
- package/src/utils/overflow.ts +0 -121
- package/src/utils/sanitize.ts +0 -25
- package/src/utils/validation.ts +0 -63
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanchit Kudari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,347 +1,205 @@
|
|
|
1
1
|
# @codeworksh/aikit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Toolkit for building AI agents and streaming LLM workflows.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
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
|
);
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Agent
|
|
72
|
-
|
|
73
|
-
`agent.loop(...)` runs a full assistant turn loop on top of model streaming. It handles tool calls, validates arguments, mutates tool-call parts in-place, and emits higher-level agent events.
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
import { Type } from "@sinclair/typebox";
|
|
77
|
-
import { Agent, Message, agent, llm } from "@codeworksh/aikit";
|
|
78
|
-
|
|
79
|
-
const calculatorParams = Type.Object({
|
|
80
|
-
expression: Type.String(),
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const calculatorTool: Agent.AgentTool<typeof calculatorParams> = {
|
|
84
|
-
name: "calculator",
|
|
85
|
-
label: "Calculator",
|
|
86
|
-
description: "Evaluate a math expression",
|
|
87
|
-
parameters: calculatorParams,
|
|
88
|
-
async execute(callID, params) {
|
|
89
|
-
return {
|
|
90
|
-
status: "completed",
|
|
91
|
-
result: {
|
|
92
|
-
content: [{ type: "text", text: `${params.expression} = 4` }],
|
|
93
|
-
isError: false,
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const model = await llm("anthropic", "claude-haiku-4-5-20251001");
|
|
100
|
-
if (!model) throw new Error("Missing model");
|
|
101
|
-
|
|
102
|
-
const prompt: Message.UserMessage = {
|
|
103
|
-
role: "user",
|
|
104
|
-
time: { created: Date.now() },
|
|
105
|
-
parts: [{ type: "text", text: "Use the calculator tool for 2 + 2." }],
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const run = agent.loop(
|
|
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: [],
|
|
121
|
-
tools: [calculatorTool],
|
|
122
|
-
},
|
|
123
|
-
[prompt],
|
|
124
|
-
);
|
|
125
70
|
|
|
126
|
-
for await (const event of
|
|
127
|
-
if (event.type === "
|
|
128
|
-
|
|
71
|
+
for await (const event of s) {
|
|
72
|
+
if (event.type === "text.delta") {
|
|
73
|
+
process.stdout.write(event.delta);
|
|
129
74
|
}
|
|
130
75
|
}
|
|
131
76
|
|
|
132
|
-
const
|
|
133
|
-
console.log(
|
|
77
|
+
const finalMessage = await s.result();
|
|
78
|
+
console.log(finalMessage.messageId);
|
|
134
79
|
```
|
|
135
80
|
|
|
136
|
-
|
|
81
|
+
## Agent Instance
|
|
137
82
|
|
|
138
|
-
|
|
83
|
+
`Agent.create(...)` is the simplest way to work with a stateful agent.
|
|
139
84
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- validates tool arguments
|
|
143
|
-
- executes tools sequentially or in parallel
|
|
144
|
-
- supports `beforeToolExecution` and `afterToolExecution` callbacks
|
|
85
|
+
```ts
|
|
86
|
+
import { Agent } from "@codeworksh/aikit";
|
|
145
87
|
|
|
146
|
-
|
|
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
|
+
});
|
|
147
93
|
|
|
148
|
-
|
|
94
|
+
agent.setSystemPrompt("Be concise.");
|
|
149
95
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
done | error
|
|
156
|
-
```
|
|
96
|
+
agent.subscribe((event) => {
|
|
97
|
+
if (event.type === "message.end") {
|
|
98
|
+
console.log(event.message.role, event.message.messageId);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
157
101
|
|
|
158
|
-
|
|
102
|
+
await agent.prompt([{ type: "text", text: "Say hello in one line." }]);
|
|
159
103
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
```text
|
|
163
|
-
agent.start
|
|
164
|
-
turn.start
|
|
165
|
-
message.start
|
|
166
|
-
message.part.start
|
|
167
|
-
message.part.update
|
|
168
|
-
message.part.end
|
|
169
|
-
message.update
|
|
170
|
-
message.end
|
|
171
|
-
tool.execution.start
|
|
172
|
-
tool.execution.update
|
|
173
|
-
tool.execution.end
|
|
174
|
-
turn.end
|
|
175
|
-
agent.end
|
|
104
|
+
console.log(agent.state.messages);
|
|
176
105
|
```
|
|
177
106
|
|
|
178
|
-
|
|
107
|
+
## With Tools
|
|
179
108
|
|
|
180
109
|
```ts
|
|
181
110
|
import { Type } from "@sinclair/typebox";
|
|
182
|
-
import { Agent
|
|
183
|
-
|
|
184
|
-
const searchParams = Type.Object({
|
|
185
|
-
query: Type.String(),
|
|
186
|
-
limit: Type.Optional(Type.Number()),
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const searchTool: Agent.AgentTool<typeof searchParams, { progress: number }, { hits: number }> = {
|
|
190
|
-
name: "search",
|
|
191
|
-
label: "Search",
|
|
192
|
-
description: "Search indexed documents",
|
|
193
|
-
parameters: searchParams,
|
|
194
|
-
async execute(callID, params, signal, onUpdate) {
|
|
195
|
-
onUpdate?.({
|
|
196
|
-
status: "running",
|
|
197
|
-
partial: {
|
|
198
|
-
content: [{ type: "text", text: `Searching for ${params.query}` }],
|
|
199
|
-
details: { progress: 50 },
|
|
200
|
-
},
|
|
201
|
-
});
|
|
111
|
+
import { Agent } from "@codeworksh/aikit";
|
|
202
112
|
|
|
113
|
+
const calculatorTool = Agent.defineTool({
|
|
114
|
+
name: "calculator",
|
|
115
|
+
label: "Calculator",
|
|
116
|
+
description: "Evaluate arithmetic expressions",
|
|
117
|
+
parameters: Type.Object({
|
|
118
|
+
expression: Type.String(),
|
|
119
|
+
}),
|
|
120
|
+
async execute(_callID, params) {
|
|
203
121
|
return {
|
|
204
122
|
status: "completed",
|
|
205
123
|
result: {
|
|
206
|
-
content: [{ type: "text", text: `
|
|
207
|
-
details: { hits: 3 },
|
|
124
|
+
content: [{ type: "text", text: `result for ${params.expression}` }],
|
|
208
125
|
isError: false,
|
|
209
126
|
},
|
|
210
127
|
};
|
|
211
128
|
},
|
|
212
|
-
};
|
|
129
|
+
});
|
|
213
130
|
|
|
214
|
-
const
|
|
215
|
-
|
|
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: {
|
|
136
|
+
tools: [calculatorTool],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
216
139
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
140
|
+
await instance.prompt([{ type: "text", text: "Use the calculator tool for 25 * 18." }]);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Lower-Level Loop
|
|
144
|
+
|
|
145
|
+
Use `agent.loop(...)` when you want loop execution without creating a stateful instance.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { Message, agent, llm } from "@codeworksh/aikit";
|
|
149
|
+
|
|
150
|
+
const model = await llm("anthropic", "claude-haiku-4-5-20251001");
|
|
151
|
+
if (!model) throw new Error("Model not found");
|
|
222
152
|
|
|
223
153
|
const run = agent.loop(
|
|
224
154
|
{
|
|
225
155
|
model,
|
|
226
156
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
227
157
|
convertToLlm: async (messages) => messages,
|
|
228
|
-
toolExecution: "parallel",
|
|
229
158
|
},
|
|
230
159
|
{
|
|
231
|
-
systemPrompt: "
|
|
160
|
+
systemPrompt: "Be concise.",
|
|
232
161
|
messages: [],
|
|
233
|
-
tools: [
|
|
162
|
+
tools: [],
|
|
234
163
|
},
|
|
235
|
-
[
|
|
164
|
+
[
|
|
165
|
+
Message.createUserMessage({
|
|
166
|
+
role: "user",
|
|
167
|
+
time: { created: Date.now() },
|
|
168
|
+
parts: [{ type: "text", text: "Reply with exactly: ok" }],
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
236
171
|
);
|
|
237
172
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.log("tool started", event.name, event.args ?? event.rawArgs);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (event.type === "tool.execution.update") {
|
|
244
|
-
console.log("tool update", event.partial?.details);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (event.type === "tool.execution.end") {
|
|
248
|
-
console.log("tool finished", event.result.details);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
173
|
+
const messages = await run.result();
|
|
174
|
+
console.log(messages.at(-1)?.role);
|
|
251
175
|
```
|
|
252
176
|
|
|
253
|
-
|
|
177
|
+
## Message IDs
|
|
254
178
|
|
|
255
|
-
|
|
179
|
+
Every user and assistant message has a `messageId`.
|
|
256
180
|
|
|
257
|
-
|
|
258
|
-
- `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:
|
|
259
182
|
|
|
260
183
|
```ts
|
|
261
|
-
|
|
262
|
-
{
|
|
263
|
-
model,
|
|
264
|
-
convertToLlm: async (messages) => messages,
|
|
265
|
-
toolExecution: "sequential",
|
|
266
|
-
},
|
|
267
|
-
context,
|
|
268
|
-
[prompt],
|
|
269
|
-
);
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### beforeToolExecution
|
|
273
|
-
|
|
274
|
-
`beforeToolExecution` runs after tool arguments have been validated and before the tool executes.
|
|
184
|
+
import { Message } from "@codeworksh/aikit";
|
|
275
185
|
|
|
276
|
-
|
|
186
|
+
const userMessage = Message.createUserMessage({
|
|
187
|
+
role: "user",
|
|
188
|
+
time: { created: Date.now() },
|
|
189
|
+
parts: [{ type: "text", text: "hello" }],
|
|
190
|
+
});
|
|
277
191
|
|
|
278
|
-
|
|
279
|
-
const run = agent.loop(
|
|
280
|
-
{
|
|
281
|
-
model,
|
|
282
|
-
convertToLlm: async (messages) => messages,
|
|
283
|
-
beforeToolExecution: async ({ toolCall }) => {
|
|
284
|
-
if (toolCall.name === "search" && toolCall.args?.query === "secret") {
|
|
285
|
-
return {
|
|
286
|
-
block: true,
|
|
287
|
-
reason: "This query is not allowed",
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
context,
|
|
293
|
-
[prompt],
|
|
294
|
-
);
|
|
192
|
+
console.log(userMessage.messageId);
|
|
295
193
|
```
|
|
296
194
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
### afterToolExecution
|
|
300
|
-
|
|
301
|
-
`afterToolExecution` runs after the tool finishes and before the terminal tool result is emitted.
|
|
302
|
-
|
|
303
|
-
Use it to normalize output, attach metadata, or override the final tool result.
|
|
304
|
-
|
|
305
|
-
```ts
|
|
306
|
-
const run = agent.loop(
|
|
307
|
-
{
|
|
308
|
-
model,
|
|
309
|
-
convertToLlm: async (messages) => messages,
|
|
310
|
-
afterToolExecution: async ({ result }) => {
|
|
311
|
-
if (result.status === "completed") {
|
|
312
|
-
return {
|
|
313
|
-
status: "completed",
|
|
314
|
-
result: {
|
|
315
|
-
...result.result,
|
|
316
|
-
details: {
|
|
317
|
-
...result.result.details,
|
|
318
|
-
source: "post-processed",
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
context,
|
|
326
|
-
[prompt],
|
|
327
|
-
);
|
|
328
|
-
```
|
|
195
|
+
This is useful for persistence layers like SQLite, event reconciliation, and updating stored messages by identity instead of array position.
|
|
329
196
|
|
|
330
|
-
##
|
|
197
|
+
## Lower-Level APIs
|
|
331
198
|
|
|
332
|
-
|
|
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
|
|
333
204
|
|
|
334
|
-
|
|
335
|
-
import {
|
|
336
|
-
agent,
|
|
337
|
-
llm,
|
|
338
|
-
stream,
|
|
339
|
-
Agent,
|
|
340
|
-
Event,
|
|
341
|
-
Message,
|
|
342
|
-
Model,
|
|
343
|
-
Stream,
|
|
344
|
-
validateToolArguments,
|
|
345
|
-
validateToolCall,
|
|
346
|
-
} from "@codeworksh/aikit";
|
|
347
|
-
```
|
|
205
|
+
API docs can be added later. For now, the tests under `packages/aikit/test/` are the best reference for concrete usage.
|