@tabbybyte/kimten 0.1.5 → 0.3.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/README.md +41 -37
- package/index.d.ts +9 -6
- package/lib/kimten.js +71 -10
- package/lib/tools.js +7 -15
- package/package.json +1 -1
- package/lib/prompt.js +0 -13
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ It’s meant to feel like a smart helper, not a framework.
|
|
|
14
14
|
|
|
15
15
|
## ✅ What it does
|
|
16
16
|
|
|
17
|
-
- Runs a simple agent loop (bounded by `hops`)
|
|
18
|
-
- Lets the model call your
|
|
17
|
+
- Runs a simple, single-agent loop (bounded by `hops`)
|
|
18
|
+
- Lets the LLM model (the brain) call your tool functions (the toys)
|
|
19
19
|
- Keeps short-term conversation memory (in-process, per instance)
|
|
20
20
|
- Supports optional structured output via Zod
|
|
21
21
|
|
|
@@ -24,19 +24,19 @@ It’s meant to feel like a smart helper, not a framework.
|
|
|
24
24
|
- No planners/graphs/state machines
|
|
25
25
|
- No streaming API surface
|
|
26
26
|
- No persistence or long-term memory
|
|
27
|
-
- No plugin system or orchestration
|
|
27
|
+
- No plugin system or multi-agent orchestration
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
31
|
## ✨ Why Kimten?
|
|
32
32
|
|
|
33
|
-
Use it when you just want an agent loop with
|
|
33
|
+
Use it when you just want an agent loop with toys and a little memory, without adopting a larger framework.
|
|
34
34
|
|
|
35
35
|
Good fits:
|
|
36
36
|
|
|
37
37
|
- CLI helpers
|
|
38
38
|
- small automations
|
|
39
|
-
- local
|
|
39
|
+
- local toys
|
|
40
40
|
- scripting
|
|
41
41
|
- quick AI utilities
|
|
42
42
|
- “just let the model call a function” use cases
|
|
@@ -68,9 +68,19 @@ const cat = Kimten({
|
|
|
68
68
|
brain: openai('gpt-4o-mini'), // or, any other available model
|
|
69
69
|
|
|
70
70
|
toys: {
|
|
71
|
-
|
|
71
|
+
randomNumber: {
|
|
72
|
+
description: 'Generate a random integer between min and max (inclusive).',
|
|
73
|
+
inputSchema: z.object({ min: z.number().int(), max: z.number().int() }),
|
|
74
|
+
async execute({ min, max }) {
|
|
75
|
+
const low = Math.min(min, max);
|
|
76
|
+
const high = Math.max(min, max);
|
|
77
|
+
return Math.floor(Math.random() * (high - low + 1)) + low;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
72
80
|
},
|
|
73
81
|
|
|
82
|
+
personality: 'You are a helpful assistant.',
|
|
83
|
+
|
|
74
84
|
hops: 10,
|
|
75
85
|
});
|
|
76
86
|
|
|
@@ -89,7 +99,7 @@ cat.forget();
|
|
|
89
99
|
|
|
90
100
|
---
|
|
91
101
|
|
|
92
|
-
##
|
|
102
|
+
## 💭 Mental Model
|
|
93
103
|
|
|
94
104
|
Kimten is basically:
|
|
95
105
|
|
|
@@ -115,35 +125,36 @@ Each instance keeps short-term chat memory, so follow-up prompts naturally refer
|
|
|
115
125
|
Create a new instance.
|
|
116
126
|
|
|
117
127
|
#### Required
|
|
118
|
-
* `brain` → AI SDK model instance
|
|
128
|
+
* 🧠 `brain` → AI SDK model instance
|
|
119
129
|
|
|
120
130
|
#### Optional
|
|
121
131
|
|
|
122
|
-
* `toys` → object map of tool definitions. Each entry
|
|
123
|
-
* async function shorthand: `async (args) => result`
|
|
132
|
+
* 🎱 `toys` → object map of toy (tool) definitions. Each entry is:
|
|
124
133
|
* object form: `{ inputSchema?, description?, strict?, execute }`
|
|
125
134
|
default: `{}`
|
|
126
|
-
* `personality` → system
|
|
127
|
-
* `hops` → max agent loop steps (default: `10`)
|
|
128
|
-
prevents infinite zoomies 🌀
|
|
135
|
+
* 🕵️♂️ `personality` → system instructions / prompt for overall behavior description (default: `'You are a helpful assistant.'`)
|
|
136
|
+
* 🌀 `hops` → max agent loop steps (default: `10`) - prevents infinite zoomies
|
|
129
137
|
|
|
130
|
-
####
|
|
138
|
+
#### Toy semantics
|
|
131
139
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
- If a
|
|
140
|
+
- Toy inputs are validated only if you provide `inputSchema`.
|
|
141
|
+
- Toy results should be JSON-serializable; `undefined` becomes `null`.
|
|
142
|
+
- If a toy function throws, Kimten returns `{ error, toolName }` as the toy result (it does not re-throw).
|
|
143
|
+
- Under the hood, each toy is implemented as an AI SDK tool.
|
|
135
144
|
|
|
136
145
|
#### Returns
|
|
137
146
|
|
|
138
|
-
* `play(input, schema?)`
|
|
147
|
+
* `play(input, schema?, context?)`
|
|
139
148
|
|
|
140
149
|
* runs the agent
|
|
141
150
|
* uses short-term memory automatically
|
|
142
151
|
* optional Zod schema for structured output
|
|
152
|
+
* optional plain object context injected into the current call prompt as JSON (with basic redaction/truncation guards)
|
|
153
|
+
* context is ephemeral per `play()` call and is not persisted in memory
|
|
143
154
|
|
|
144
155
|
* `forget()`
|
|
145
156
|
|
|
146
|
-
* clears short-term memory
|
|
157
|
+
* clears short-term memory
|
|
147
158
|
|
|
148
159
|
---
|
|
149
160
|
|
|
@@ -153,32 +164,25 @@ Create a new instance.
|
|
|
153
164
|
|
|
154
165
|
For the `brain` part, feel free to use any compatible provider and their models.
|
|
155
166
|
|
|
156
|
-
|
|
167
|
+
❗ Note that not all providers (and models) may work out the box with Kimten, particularly for structured output.
|
|
157
168
|
|
|
158
|
-
|
|
169
|
+
💡 Refer to the AI SDK docs for details: **[providers and models](https://ai-sdk.dev/docs/foundations/providers-and-models)**.
|
|
159
170
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
```js
|
|
163
|
-
toys: {
|
|
164
|
-
readFile,
|
|
165
|
-
writeFile,
|
|
166
|
-
fetchJson,
|
|
167
|
-
runCommand,
|
|
168
|
-
}
|
|
169
|
-
```
|
|
171
|
+
### Add toys freely
|
|
170
172
|
|
|
171
|
-
|
|
173
|
+
Define `toys` in object form for strong arg validation and proper selection by the LLM:
|
|
172
174
|
|
|
173
175
|
```js
|
|
174
176
|
import { z } from 'zod';
|
|
175
177
|
|
|
176
178
|
toys: {
|
|
177
|
-
|
|
178
|
-
description: '
|
|
179
|
-
inputSchema: z.object({
|
|
180
|
-
async execute({
|
|
181
|
-
|
|
179
|
+
randomNumber: {
|
|
180
|
+
description: 'Generate a random integer between min and max (inclusive).',
|
|
181
|
+
inputSchema: z.object({ min: z.number().int(), max: z.number().int() }),
|
|
182
|
+
async execute({ min, max }) {
|
|
183
|
+
const low = Math.min(min, max);
|
|
184
|
+
const high = Math.max(min, max);
|
|
185
|
+
return Math.floor(Math.random() * (high - low + 1)) + low;
|
|
182
186
|
},
|
|
183
187
|
},
|
|
184
188
|
}
|
package/index.d.ts
CHANGED
|
@@ -2,16 +2,16 @@ import type { ZodTypeAny, infer as ZodInfer } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
export type BrainModel = Record<string, unknown>;
|
|
4
4
|
|
|
5
|
-
export type
|
|
5
|
+
export type ToolExecute = (args: any) => any | Promise<any>;
|
|
6
6
|
|
|
7
7
|
export type ToyDefinition = {
|
|
8
8
|
inputSchema?: ZodTypeAny;
|
|
9
9
|
description?: string;
|
|
10
10
|
strict?: boolean;
|
|
11
|
-
execute:
|
|
11
|
+
execute: ToolExecute;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export type Toys = Record<string,
|
|
14
|
+
export type Toys = Record<string, ToyDefinition>;
|
|
15
15
|
|
|
16
16
|
export type KimtenConfig = {
|
|
17
17
|
brain: BrainModel;
|
|
@@ -21,8 +21,12 @@ export type KimtenConfig = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export type KimtenAgent = {
|
|
24
|
-
play(input: string): Promise<string>;
|
|
25
|
-
play<S extends ZodTypeAny>(
|
|
24
|
+
play(input: string, schema?: null, context?: Record<string, unknown> | null): Promise<string>;
|
|
25
|
+
play<S extends ZodTypeAny>(
|
|
26
|
+
input: string,
|
|
27
|
+
schema: S,
|
|
28
|
+
context?: Record<string, unknown> | null
|
|
29
|
+
): Promise<ZodInfer<S>>;
|
|
26
30
|
forget(): void;
|
|
27
31
|
};
|
|
28
32
|
|
|
@@ -30,4 +34,3 @@ export declare function Kimten(config: KimtenConfig): KimtenAgent;
|
|
|
30
34
|
|
|
31
35
|
declare const _default: typeof Kimten;
|
|
32
36
|
export default _default;
|
|
33
|
-
|
package/lib/kimten.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ToolLoopAgent, stepCountIs, Output } from 'ai';
|
|
2
2
|
import { createMemory } from './memory.js';
|
|
3
|
-
import { buildMessages } from './prompt.js';
|
|
4
3
|
import { normalizeToys } from './tools.js';
|
|
5
4
|
|
|
6
5
|
const DEFAULT_PERSONALITY = 'You are a helpful assistant.';
|
|
6
|
+
const CONTEXT_CHAR_LIMIT = 4000;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {import('zod').ZodTypeAny} ZodSchema
|
|
@@ -23,9 +23,9 @@ const DEFAULT_PERSONALITY = 'You are a helpful assistant.';
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Tool execute function.
|
|
27
27
|
*
|
|
28
|
-
* @callback
|
|
28
|
+
* @callback ToolExecute
|
|
29
29
|
* @param {any} args
|
|
30
30
|
* @returns {any | Promise<any>}
|
|
31
31
|
*/
|
|
@@ -37,13 +37,13 @@ const DEFAULT_PERSONALITY = 'You are a helpful assistant.';
|
|
|
37
37
|
* @property {import('zod').ZodTypeAny} [inputSchema]
|
|
38
38
|
* @property {string} [description]
|
|
39
39
|
* @property {boolean} [strict]
|
|
40
|
-
* @property {
|
|
40
|
+
* @property {ToolExecute} execute
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Tool registry map.
|
|
45
45
|
*
|
|
46
|
-
* @typedef {Record<string,
|
|
46
|
+
* @typedef {Record<string, ToyDefinition>} Toys
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -60,7 +60,7 @@ const DEFAULT_PERSONALITY = 'You are a helpful assistant.';
|
|
|
60
60
|
* Returned Kimten instance.
|
|
61
61
|
*
|
|
62
62
|
* @typedef {object} KimtenAgent
|
|
63
|
-
* @property {(input: string, schema?: ZodSchema | null) => Promise<any>} play Run the agent loop.
|
|
63
|
+
* @property {(input: string, schema?: ZodSchema | null, context?: Record<string, unknown> | null) => Promise<any>} play Run the agent loop.
|
|
64
64
|
* @property {() => void} forget Clear short-term memory.
|
|
65
65
|
*/
|
|
66
66
|
|
|
@@ -92,6 +92,45 @@ function validateConfig(config) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function serializeContext(context) {
|
|
96
|
+
if (context === null || context === undefined) {
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (typeof context !== 'object' || Array.isArray(context)) {
|
|
101
|
+
throw new TypeError('Kimten play(input, schema, context) expects context to be a plain object when provided.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const redacted = JSON.stringify(
|
|
105
|
+
context,
|
|
106
|
+
(key, value) => {
|
|
107
|
+
const lowered = String(key).toLowerCase();
|
|
108
|
+
if (
|
|
109
|
+
lowered.includes('password') ||
|
|
110
|
+
lowered.includes('token') ||
|
|
111
|
+
lowered.includes('secret') ||
|
|
112
|
+
lowered.includes('apikey') ||
|
|
113
|
+
lowered.includes('api_key')
|
|
114
|
+
) {
|
|
115
|
+
return '[REDACTED]';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return value;
|
|
119
|
+
},
|
|
120
|
+
2
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (typeof redacted !== 'string') {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (redacted.length <= CONTEXT_CHAR_LIMIT) {
|
|
128
|
+
return redacted;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return `${redacted.slice(0, CONTEXT_CHAR_LIMIT)}\n...(truncated)`;
|
|
132
|
+
}
|
|
133
|
+
|
|
95
134
|
/**
|
|
96
135
|
* Create a tiny tool-using agent with short-term memory.
|
|
97
136
|
*
|
|
@@ -144,19 +183,41 @@ export function Kimten(config) {
|
|
|
144
183
|
*
|
|
145
184
|
* @param {string} input
|
|
146
185
|
* @param {ZodSchema | null} [schema]
|
|
186
|
+
* @param {Record<string, unknown> | null} [context]
|
|
147
187
|
* @returns {Promise<any>}
|
|
148
188
|
*/
|
|
149
|
-
async function play(input, schema = null) {
|
|
189
|
+
async function play(input, schema = null, context = null) {
|
|
150
190
|
if (typeof input !== 'string') {
|
|
151
191
|
throw new TypeError('Kimten play(input) expects input to be a string.');
|
|
152
192
|
}
|
|
153
193
|
|
|
194
|
+
// Serialize provided context (redacts sensitive keys and truncates if too long).
|
|
195
|
+
const serializedContext = serializeContext(context);
|
|
196
|
+
|
|
197
|
+
// If we have context, embed it before the user's message so the agent sees both.
|
|
198
|
+
const effectiveInput = serializedContext
|
|
199
|
+
? `Context (JSON):\n${serializedContext}\n\nUser message:\n${input}`
|
|
200
|
+
: input;
|
|
201
|
+
|
|
202
|
+
// Store the raw user message (no context) in short-term memory.
|
|
154
203
|
memory.add({ role: 'user', content: input });
|
|
155
204
|
|
|
205
|
+
// Retrieve conversation so far from memory.
|
|
206
|
+
const fetchedMessages = memory.list();
|
|
207
|
+
|
|
208
|
+
// If the last message is the user message we just added, we may need to replace it with
|
|
209
|
+
// a version that includes the serialized context, so the agent can see that information.
|
|
210
|
+
// This way we keep the raw user message in memory, but provide the agent with the enriched version.
|
|
211
|
+
const lastMessage = fetchedMessages[fetchedMessages.length - 1];
|
|
212
|
+
const messages = serializedContext && lastMessage?.role === 'user'
|
|
213
|
+
? [...fetchedMessages.slice(0, -1), { ...lastMessage, content: effectiveInput }]
|
|
214
|
+
: fetchedMessages;
|
|
215
|
+
|
|
216
|
+
// Choose a structured agent when a schema is provided, otherwise use the text agent.
|
|
156
217
|
const agent = schema ? getStructuredAgent(schema) : textAgent;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
});
|
|
218
|
+
|
|
219
|
+
// Run the agent loop with the prepared messages.
|
|
220
|
+
const result = await agent.generate({ messages });
|
|
160
221
|
|
|
161
222
|
const assistantContent =
|
|
162
223
|
schema
|
package/lib/tools.js
CHANGED
|
@@ -6,9 +6,9 @@ import { z } from 'zod';
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Tool execute function.
|
|
10
10
|
*
|
|
11
|
-
* @callback
|
|
11
|
+
* @callback ToolExecute
|
|
12
12
|
* @param {any} args
|
|
13
13
|
* @returns {any | Promise<any>}
|
|
14
14
|
*/
|
|
@@ -20,13 +20,13 @@ import { z } from 'zod';
|
|
|
20
20
|
* @property {ZodSchema} [inputSchema]
|
|
21
21
|
* @property {string} [description]
|
|
22
22
|
* @property {boolean} [strict]
|
|
23
|
-
* @property {
|
|
23
|
+
* @property {ToolExecute} execute
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Tool registry map.
|
|
28
28
|
*
|
|
29
|
-
* @typedef {Record<string,
|
|
29
|
+
* @typedef {Record<string, ToyDefinition>} Toys
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
function isPlainObject(value) {
|
|
@@ -41,8 +41,7 @@ function isPlainObject(value) {
|
|
|
41
41
|
/**
|
|
42
42
|
* Normalize a toy registry into AI SDK tool objects.
|
|
43
43
|
*
|
|
44
|
-
* -
|
|
45
|
-
* - Object form: `{ inputSchema?, description?, strict?, execute }`
|
|
44
|
+
* - Object form only: `{ inputSchema?, description?, strict?, execute }`
|
|
46
45
|
*
|
|
47
46
|
* Tool execution is wrapped so thrown errors become JSON-safe results:
|
|
48
47
|
* `{ error, toolName }` (Kimten does not re-throw tool errors).
|
|
@@ -56,7 +55,7 @@ export function normalizeToys(toys) {
|
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
if (!isPlainObject(toys)) {
|
|
59
|
-
throw new TypeError('Kimten config "toys" must be an object map of
|
|
58
|
+
throw new TypeError('Kimten config "toys" must be an object map of tool definitions.');
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
const wrapped = {};
|
|
@@ -86,16 +85,9 @@ export function normalizeToys(toys) {
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
function normalizeToyDefinition(name, entry) {
|
|
89
|
-
if (typeof entry === 'function') {
|
|
90
|
-
return {
|
|
91
|
-
inputSchema: z.any(),
|
|
92
|
-
execute: entry,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
88
|
if (!isPlainObject(entry)) {
|
|
97
89
|
throw new TypeError(
|
|
98
|
-
`Kimten tool "${name}" must be
|
|
90
|
+
`Kimten tool "${name}" must be an object with execute(args).`
|
|
99
91
|
);
|
|
100
92
|
}
|
|
101
93
|
|
package/package.json
CHANGED
package/lib/prompt.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build AI SDK messages for a generation call.
|
|
3
|
-
*
|
|
4
|
-
* @param {string} personality System prompt / instructions.
|
|
5
|
-
* @param {Array<{ role: string, content: any }>} history Prior conversation messages.
|
|
6
|
-
* @returns {Array<{ role: string, content: any }>}
|
|
7
|
-
*/
|
|
8
|
-
export function buildMessages(personality, history) {
|
|
9
|
-
return [
|
|
10
|
-
{ role: 'system', content: personality },
|
|
11
|
-
...history,
|
|
12
|
-
];
|
|
13
|
-
}
|