@tabbybyte/kimten 0.1.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 +251 -0
- package/index.js +4 -0
- package/lib/kimten.js +106 -0
- package/lib/memory.js +26 -0
- package/lib/prompt.js +6 -0
- package/lib/tools.js +58 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Project Authors
|
|
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
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# 🐈 kimten
|
|
2
|
+
|
|
3
|
+
A micro-agent library: thin wrapper over the **[Agent interface in Vercel AI SDK Core v6+](https://ai-sdk.dev/docs/agents)**
|
|
4
|
+
|
|
5
|
+
Small surface area, sharp claws, zero fluff (well… almost).
|
|
6
|
+
|
|
7
|
+
Think:
|
|
8
|
+
|
|
9
|
+
> minimal agent loop + tools + short-term memory
|
|
10
|
+
> but delivered as a smol terminal cat 🐾
|
|
11
|
+
|
|
12
|
+
Kimten doesn’t try to be a “framework”.
|
|
13
|
+
It’s just a neat little helper that runs prompts, calls tools, remembers a little, and gets out of your way.
|
|
14
|
+
|
|
15
|
+
No planners.
|
|
16
|
+
No graphs.
|
|
17
|
+
No magic state machines.
|
|
18
|
+
Just *play → result → nap*. 😼
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ✨ Why Kimten?
|
|
23
|
+
|
|
24
|
+
Sometimes you don’t want:
|
|
25
|
+
|
|
26
|
+
- 15 abstractions
|
|
27
|
+
- 6 middlewares
|
|
28
|
+
- 4 “agent runtimes”
|
|
29
|
+
- 200MB of dependencies
|
|
30
|
+
|
|
31
|
+
You just want:
|
|
32
|
+
|
|
33
|
+
✔ call an LLM
|
|
34
|
+
✔ give it tools
|
|
35
|
+
✔ keep a bit of convo memory
|
|
36
|
+
✔ maybe get structured output
|
|
37
|
+
✔ done
|
|
38
|
+
|
|
39
|
+
Kimten = **tiny agent loop with paws** 🐾
|
|
40
|
+
|
|
41
|
+
Perfect for:
|
|
42
|
+
|
|
43
|
+
- CLI helpers
|
|
44
|
+
- small automations
|
|
45
|
+
- local tools
|
|
46
|
+
- scripting
|
|
47
|
+
- quick AI utilities
|
|
48
|
+
- “just let the model call a function” use cases
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 📦 Install
|
|
53
|
+
|
|
54
|
+
Feed the cat some treats:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm i kimten ai zod @ai-sdk/openai
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That’s it. No ceremony. No rituals. 🍗
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🚀 Usage
|
|
65
|
+
|
|
66
|
+
Summon your little helper (with or without `toys`) and let it `play`.
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { openai } from '@ai-sdk/openai'; // or, any other provider
|
|
70
|
+
import { z } from 'zod';
|
|
71
|
+
import Kimten from 'kimten';
|
|
72
|
+
|
|
73
|
+
const cat = Kimten({
|
|
74
|
+
brain: openai('gpt-4o-mini'), // or, any other available model
|
|
75
|
+
|
|
76
|
+
toys: {
|
|
77
|
+
add: async ({ a, b }) => a + b,
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
personality: 'Helpful terminal cat',
|
|
81
|
+
|
|
82
|
+
hops: 10,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// free-form text
|
|
86
|
+
const text = await cat.play('summarize this repo');
|
|
87
|
+
|
|
88
|
+
// structured output
|
|
89
|
+
const structured = await cat.play(
|
|
90
|
+
'extract the name',
|
|
91
|
+
z.object({ name: z.string() })
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// wipe short-term memory
|
|
95
|
+
cat.forget();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Done.
|
|
99
|
+
No lifecycle hooks. No config jungle. 🧘
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🧠 Mental Model
|
|
104
|
+
|
|
105
|
+
Kimten is basically:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
loop:
|
|
109
|
+
include short-term conversation memory
|
|
110
|
+
prompt LLM
|
|
111
|
+
maybe call a tool
|
|
112
|
+
repeat (max hops)
|
|
113
|
+
return result
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
That’s the whole thing.
|
|
117
|
+
|
|
118
|
+
Each instance keeps a **small, short-term chat memory** 🧠
|
|
119
|
+
So follow-up prompts naturally reference earlier messages:
|
|
120
|
+
|
|
121
|
+
> “summarize this” → “make it shorter” → “now extract bullets”
|
|
122
|
+
|
|
123
|
+
When you’re done, call `forget()` and the brain goes blank again. 🫧
|
|
124
|
+
|
|
125
|
+
It’s intentionally:
|
|
126
|
+
|
|
127
|
+
* tiny
|
|
128
|
+
* predictable
|
|
129
|
+
* hackable
|
|
130
|
+
* easy to read in one sitting
|
|
131
|
+
|
|
132
|
+
If you can read the source in ~5 minutes, we did it right 😺
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## ⚙️ API
|
|
137
|
+
|
|
138
|
+
### `Kimten(config)`
|
|
139
|
+
|
|
140
|
+
Create a new cat.
|
|
141
|
+
|
|
142
|
+
### Required (must-haves)
|
|
143
|
+
|
|
144
|
+
* `brain` → AI SDK model instance
|
|
145
|
+
|
|
146
|
+
### Optional (extra whiskers)
|
|
147
|
+
|
|
148
|
+
* `toys` → object map of async functions (tools the model may call), default: `{}`
|
|
149
|
+
* `personality` → system prompt / behavior description (default: `"You are a helpful assistant."`)
|
|
150
|
+
* `hops` → max agent loop steps (default: `10`)
|
|
151
|
+
prevents infinite zoomies 🌀
|
|
152
|
+
|
|
153
|
+
### Returns
|
|
154
|
+
|
|
155
|
+
* `play(input, schema?)`
|
|
156
|
+
|
|
157
|
+
* runs the agent
|
|
158
|
+
* uses short-term memory automatically
|
|
159
|
+
* optional Zod schema for structured output
|
|
160
|
+
|
|
161
|
+
* `forget()`
|
|
162
|
+
|
|
163
|
+
* clears short-term memory/context
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 🧩 Design Philosophy
|
|
168
|
+
|
|
169
|
+
Kimten intentionally avoids “big agent framework energy”.
|
|
170
|
+
|
|
171
|
+
No:
|
|
172
|
+
|
|
173
|
+
* streaming APIs
|
|
174
|
+
* planners or graphs
|
|
175
|
+
* middleware/plugins
|
|
176
|
+
* long-term memory
|
|
177
|
+
* persistence/storage
|
|
178
|
+
* hidden background processes
|
|
179
|
+
* TypeScript runtime/build nonsense
|
|
180
|
+
|
|
181
|
+
If you need those… use something heavier.
|
|
182
|
+
|
|
183
|
+
If you want **simple + fast + composable**, Kimten fits nicely.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🛠 Tips
|
|
188
|
+
|
|
189
|
+
### Providers & models
|
|
190
|
+
|
|
191
|
+
For the `brain` part, feel free to use any compatible provider and their models.
|
|
192
|
+
|
|
193
|
+
Refer to https://ai-sdk.dev/docs/foundations/providers-and-models
|
|
194
|
+
|
|
195
|
+
### Add tools freely
|
|
196
|
+
|
|
197
|
+
Tools are just async functions:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
toys: {
|
|
201
|
+
readFile,
|
|
202
|
+
writeFile,
|
|
203
|
+
fetchJson,
|
|
204
|
+
runCommand,
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The model decides when to use them.
|
|
209
|
+
|
|
210
|
+
### Structured output = sanity
|
|
211
|
+
|
|
212
|
+
Use Zod schemas whenever possible.
|
|
213
|
+
LLMs lie less when types exist 😼
|
|
214
|
+
|
|
215
|
+
### Keep hops low
|
|
216
|
+
|
|
217
|
+
If you need 50+ steps, you probably want a planner, not Kimten.
|
|
218
|
+
|
|
219
|
+
### Reset when needed
|
|
220
|
+
|
|
221
|
+
Fresh task? Call `forget()`.
|
|
222
|
+
Cats don’t hold grudges (or context). 🐾
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 🐾 Vibes
|
|
227
|
+
|
|
228
|
+
Kimten is:
|
|
229
|
+
|
|
230
|
+
* small
|
|
231
|
+
* opinionated
|
|
232
|
+
* dependency-light
|
|
233
|
+
* short-memory by design
|
|
234
|
+
* easy to embed anywhere
|
|
235
|
+
|
|
236
|
+
It’s not trying to be LangChain or a full orchestration system.
|
|
237
|
+
|
|
238
|
+
It’s just a cat.
|
|
239
|
+
|
|
240
|
+
A helpful one.
|
|
241
|
+
|
|
242
|
+
In your terminal.
|
|
243
|
+
|
|
244
|
+
Typing. 🐈⬛
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT
|
|
251
|
+
Pet responsibly.
|
package/index.js
ADDED
package/lib/kimten.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ToolLoopAgent, stepCountIs, Output } from 'ai';
|
|
2
|
+
import { createMemory } from './memory.js';
|
|
3
|
+
import { buildMessages } from './prompt.js';
|
|
4
|
+
import { normalizeToys } from './tools.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_PERSONALITY = 'You are a helpful assistant.';
|
|
7
|
+
|
|
8
|
+
function validateConfig(config) {
|
|
9
|
+
if (config === null || typeof config !== 'object' || Array.isArray(config)) {
|
|
10
|
+
throw new TypeError('Kimten requires a config object.');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { brain, toys = {}, personality = null, hops = 10 } = config;
|
|
14
|
+
|
|
15
|
+
if (!brain || typeof brain !== 'object') {
|
|
16
|
+
throw new TypeError('Kimten config "brain" is required and must be an AI SDK model instance.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const resolvedPersonality = personality ?? DEFAULT_PERSONALITY;
|
|
20
|
+
if (typeof resolvedPersonality !== 'string' || resolvedPersonality.trim() === '') {
|
|
21
|
+
throw new TypeError('Kimten config "personality" must be a non-empty string when provided.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!Number.isInteger(hops) || hops <= 0) {
|
|
25
|
+
throw new TypeError('Kimten config "hops" must be a positive integer.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
brain,
|
|
30
|
+
toys,
|
|
31
|
+
personality: resolvedPersonality,
|
|
32
|
+
hops,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Kimten(config) {
|
|
37
|
+
const { brain, toys, personality, hops } = validateConfig(config);
|
|
38
|
+
const memory = createMemory();
|
|
39
|
+
const tools = normalizeToys(toys);
|
|
40
|
+
const structuredAgents = new WeakMap(); // Cache for agents based on output schema
|
|
41
|
+
|
|
42
|
+
const textAgent = new ToolLoopAgent({
|
|
43
|
+
model: brain,
|
|
44
|
+
instructions: personality,
|
|
45
|
+
tools,
|
|
46
|
+
stopWhen: stepCountIs(hops),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function createStructuredAgent(schema) {
|
|
50
|
+
return new ToolLoopAgent({
|
|
51
|
+
model: brain,
|
|
52
|
+
instructions: personality,
|
|
53
|
+
tools,
|
|
54
|
+
stopWhen: stepCountIs(hops),
|
|
55
|
+
output: Output.object({ schema }),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getStructuredAgent(schema) {
|
|
60
|
+
if (schema !== null && (typeof schema === 'object' || typeof schema === 'function')) {
|
|
61
|
+
const cached = structuredAgents.get(schema);
|
|
62
|
+
if (cached) {
|
|
63
|
+
return cached;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const created = createStructuredAgent(schema);
|
|
67
|
+
structuredAgents.set(schema, created);
|
|
68
|
+
return created;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return createStructuredAgent(schema);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function play(input, schema = null) {
|
|
75
|
+
if (typeof input !== 'string') {
|
|
76
|
+
throw new TypeError('Kimten play(input) expects input to be a string.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
memory.add({ role: 'user', content: input });
|
|
80
|
+
|
|
81
|
+
const agent = schema ? getStructuredAgent(schema) : textAgent;
|
|
82
|
+
const result = await agent.generate({
|
|
83
|
+
messages: buildMessages(personality, memory.list()),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const assistantContent =
|
|
87
|
+
schema
|
|
88
|
+
? (typeof result.text === 'string' && result.text.trim() !== ''
|
|
89
|
+
? result.text
|
|
90
|
+
: JSON.stringify(result.output ?? null))
|
|
91
|
+
: (typeof result.text === 'string' ? result.text : '');
|
|
92
|
+
|
|
93
|
+
memory.add({ role: 'assistant', content: assistantContent });
|
|
94
|
+
|
|
95
|
+
return schema ? result.output : assistantContent;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function forget() {
|
|
99
|
+
memory.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
play,
|
|
104
|
+
forget,
|
|
105
|
+
};
|
|
106
|
+
}
|
package/lib/memory.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const MEMORY_LIMIT = 10;
|
|
2
|
+
|
|
3
|
+
export function createMemory(limit = MEMORY_LIMIT) {
|
|
4
|
+
const history = [];
|
|
5
|
+
|
|
6
|
+
function add(message) {
|
|
7
|
+
history.push(message);
|
|
8
|
+
if (history.length > limit) {
|
|
9
|
+
history.shift();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function list() {
|
|
14
|
+
return history.slice();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function clear() {
|
|
18
|
+
history.length = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
add,
|
|
23
|
+
list,
|
|
24
|
+
clear,
|
|
25
|
+
};
|
|
26
|
+
}
|
package/lib/prompt.js
ADDED
package/lib/tools.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { tool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const proto = Object.getPrototypeOf(value);
|
|
10
|
+
return proto === Object.prototype || proto === null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeToys(toys) {
|
|
14
|
+
if (toys === undefined || toys === null) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!isPlainObject(toys)) {
|
|
19
|
+
throw new TypeError('Kimten config "toys" must be an object map of functions.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const wrapped = {};
|
|
23
|
+
|
|
24
|
+
for (const [name, fn] of Object.entries(toys)) {
|
|
25
|
+
if (typeof fn !== 'function') {
|
|
26
|
+
throw new TypeError(`Kimten tool "${name}" must be a function.`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
wrapped[name] = tool({
|
|
30
|
+
inputSchema: z.any(),
|
|
31
|
+
async execute(args) {
|
|
32
|
+
try {
|
|
33
|
+
const result = await fn(args);
|
|
34
|
+
return toJsonSafe(result);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
error: error instanceof Error ? error.message : String(error),
|
|
38
|
+
toolName: name,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return wrapped;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toJsonSafe(value) {
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(JSON.stringify(value));
|
|
55
|
+
} catch {
|
|
56
|
+
return { value: String(value) };
|
|
57
|
+
}
|
|
58
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tabbybyte/kimten",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "A micro-agent library: thin wrapper over the Agent interface from Vercel's AI SDK Core v6+",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"index.js",
|
|
15
|
+
"lib"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --test"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"agent",
|
|
23
|
+
"llm",
|
|
24
|
+
"tools",
|
|
25
|
+
"vercel-ai"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/tabbybyte-technologies/kimten.git"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"ai": ">=6",
|
|
33
|
+
"zod": ">=3"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"ai": "^6.0.0-beta.89",
|
|
37
|
+
"zod": "^3.25.76"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=22"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT"
|
|
43
|
+
}
|