@getenki/ai-darwin-arm64 0.2.5 → 0.2.7
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 +440 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,441 @@
|
|
|
1
|
-
# `@getenki/ai
|
|
1
|
+
# `@getenki/ai`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Node.js bindings for Enki's Rust agent runtime, published as a native package via `napi-rs`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @getenki/ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package ships prebuilt native binaries for:
|
|
12
|
+
|
|
13
|
+
- Windows x64 and arm64
|
|
14
|
+
- macOS x64 and arm64
|
|
15
|
+
- Linux x64 and arm64 (GNU libc)
|
|
16
|
+
|
|
17
|
+
## What It Exports
|
|
18
|
+
|
|
19
|
+
The current package surface is intentionally small:
|
|
20
|
+
|
|
21
|
+
- `NativeEnkiAgent`
|
|
22
|
+
- `JsMemoryKind`
|
|
23
|
+
- `JsMemoryModule`
|
|
24
|
+
- `JsMemoryEntry`
|
|
25
|
+
|
|
26
|
+
`NativeEnkiAgent` is the main entrypoint. It can be created in four modes:
|
|
27
|
+
|
|
28
|
+
- `new(...)` for a plain agent
|
|
29
|
+
- `NativeEnkiAgent.withTools(...)`
|
|
30
|
+
- `NativeEnkiAgent.withMemory(...)`
|
|
31
|
+
- `NativeEnkiAgent.withToolsAndMemory(...)`
|
|
32
|
+
|
|
33
|
+
## Basic Agent
|
|
34
|
+
|
|
35
|
+
Use the constructor when you only need a session-based agent backed by the native runtime.
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
const { NativeEnkiAgent } = require('@getenki/ai')
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
const agent = new NativeEnkiAgent(
|
|
42
|
+
'Assistant',
|
|
43
|
+
'Answer clearly and keep responses short.',
|
|
44
|
+
'ollama::qwen3.5:latest',
|
|
45
|
+
20,
|
|
46
|
+
process.cwd(),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const output = await agent.run('session-1', 'Explain what this project does.')
|
|
50
|
+
console.log(output)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main().catch(console.error)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
TypeScript version:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { NativeEnkiAgent } from '@getenki/ai'
|
|
60
|
+
|
|
61
|
+
const agent = new NativeEnkiAgent(
|
|
62
|
+
'Assistant',
|
|
63
|
+
'Answer clearly and keep responses short.',
|
|
64
|
+
'ollama::qwen3.5:latest',
|
|
65
|
+
20,
|
|
66
|
+
process.cwd(),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const output = await agent.run('session-1', 'Explain what this project does.')
|
|
70
|
+
console.log(output)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Constructor arguments:
|
|
74
|
+
|
|
75
|
+
- `name?: string`
|
|
76
|
+
- `systemPromptPreamble?: string`
|
|
77
|
+
- `model?: string`
|
|
78
|
+
- `maxIterations?: number`
|
|
79
|
+
- `workspaceHome?: string`
|
|
80
|
+
|
|
81
|
+
If omitted, the runtime falls back to built-in defaults for name, prompt, and max iterations.
|
|
82
|
+
|
|
83
|
+
## Tools
|
|
84
|
+
|
|
85
|
+
Tools can be attached with `NativeEnkiAgent.withTools(...)`. Each tool object must provide:
|
|
86
|
+
|
|
87
|
+
- `id` or `name`
|
|
88
|
+
- `description`
|
|
89
|
+
- one of `inputSchema`, `inputSchemaJson`, `parameters`, or `parametersJson`
|
|
90
|
+
- either `execute(inputJson, contextJson)` or a shared `toolHandler`
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
const { NativeEnkiAgent } = require('@getenki/ai')
|
|
96
|
+
|
|
97
|
+
const tools = [
|
|
98
|
+
{
|
|
99
|
+
id: 'calculate_sum',
|
|
100
|
+
description: 'Add two numbers and return a short text result.',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
a: { type: 'number' },
|
|
105
|
+
b: { type: 'number' },
|
|
106
|
+
},
|
|
107
|
+
required: ['a', 'b'],
|
|
108
|
+
},
|
|
109
|
+
execute: (inputJson, contextJson) => {
|
|
110
|
+
const args = inputJson ? JSON.parse(inputJson) : {}
|
|
111
|
+
const ctx = contextJson ? JSON.parse(contextJson) : {}
|
|
112
|
+
const result = Number(args.a) + Number(args.b)
|
|
113
|
+
|
|
114
|
+
return JSON.stringify({
|
|
115
|
+
result,
|
|
116
|
+
workspaceDir: ctx.workspaceDir,
|
|
117
|
+
text: `${args.a} + ${args.b} = ${result}`,
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
const agent = NativeEnkiAgent.withTools(
|
|
124
|
+
'Tool Agent',
|
|
125
|
+
'Use tools when they help.',
|
|
126
|
+
'ollama::qwen3.5:latest',
|
|
127
|
+
20,
|
|
128
|
+
process.cwd(),
|
|
129
|
+
tools,
|
|
130
|
+
null,
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Per-tool `execute` receives:
|
|
135
|
+
|
|
136
|
+
- `inputJson`: serialized tool arguments
|
|
137
|
+
- `contextJson`: serialized runtime context with `agentDir`, `workspaceDir`, and `sessionsDir`
|
|
138
|
+
|
|
139
|
+
TypeScript tool example:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { NativeEnkiAgent } from '@getenki/ai'
|
|
143
|
+
|
|
144
|
+
type SumArgs = {
|
|
145
|
+
a?: number
|
|
146
|
+
b?: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type ExampleTool = {
|
|
150
|
+
id: string
|
|
151
|
+
description: string
|
|
152
|
+
inputSchema: Record<string, unknown>
|
|
153
|
+
execute: (inputJson: string, contextJson: string) => string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const tools: ExampleTool[] = [
|
|
157
|
+
{
|
|
158
|
+
id: 'calculate_sum',
|
|
159
|
+
description: 'Add two numbers and return a short text result.',
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
a: { type: 'number' },
|
|
164
|
+
b: { type: 'number' },
|
|
165
|
+
},
|
|
166
|
+
required: ['a', 'b'],
|
|
167
|
+
},
|
|
168
|
+
execute: (inputJson: string, contextJson: string): string => {
|
|
169
|
+
const args = inputJson ? (JSON.parse(inputJson) as SumArgs) : {}
|
|
170
|
+
const ctx = contextJson
|
|
171
|
+
? (JSON.parse(contextJson) as { workspaceDir?: string })
|
|
172
|
+
: {}
|
|
173
|
+
const result = Number(args.a) + Number(args.b)
|
|
174
|
+
|
|
175
|
+
return JSON.stringify({
|
|
176
|
+
result,
|
|
177
|
+
workspaceDir: ctx.workspaceDir,
|
|
178
|
+
text: `${args.a} + ${args.b} = ${result}`,
|
|
179
|
+
})
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
const agent = NativeEnkiAgent.withTools(
|
|
185
|
+
'Tool Agent',
|
|
186
|
+
'Use tools when they help.',
|
|
187
|
+
'ollama::qwen3.5:latest',
|
|
188
|
+
20,
|
|
189
|
+
process.cwd(),
|
|
190
|
+
tools,
|
|
191
|
+
null,
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Instead of putting `execute` on every tool, you can pass a shared `toolHandler` as the final argument to `withTools(...)` or `withToolsAndMemory(...)`. The shared handler receives:
|
|
196
|
+
|
|
197
|
+
- `toolName`
|
|
198
|
+
- `inputJson`
|
|
199
|
+
- `agentDir`
|
|
200
|
+
- `workspaceDir`
|
|
201
|
+
- `sessionsDir`
|
|
202
|
+
|
|
203
|
+
## Memory
|
|
204
|
+
|
|
205
|
+
Memory modules are plain objects:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
const memories = [{ name: 'example-memory' }]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
When using `withMemory(...)` or `withToolsAndMemory(...)`, you supply four callbacks:
|
|
212
|
+
|
|
213
|
+
- `recordHandler(memoryName, sessionId, userMsg, assistantMsg)`
|
|
214
|
+
- `recallHandler(memoryName, sessionId, query, maxEntries)`
|
|
215
|
+
- `flushHandler(memoryName, sessionId)`
|
|
216
|
+
- `consolidateHandler(memoryName, sessionId)`
|
|
217
|
+
|
|
218
|
+
`recallHandler` must return an array of `JsMemoryEntry` objects:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
type JsMemoryEntry = {
|
|
222
|
+
key: string
|
|
223
|
+
content: string
|
|
224
|
+
kind: JsMemoryKind
|
|
225
|
+
relevance: number
|
|
226
|
+
timestampNs: string
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Supported memory kinds:
|
|
231
|
+
|
|
232
|
+
- `JsMemoryKind.RecentMessage`
|
|
233
|
+
- `JsMemoryKind.Summary`
|
|
234
|
+
- `JsMemoryKind.Entity`
|
|
235
|
+
- `JsMemoryKind.Preference`
|
|
236
|
+
|
|
237
|
+
TypeScript memory typing example:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import {
|
|
241
|
+
JsMemoryKind,
|
|
242
|
+
type JsMemoryEntry,
|
|
243
|
+
type JsMemoryModule,
|
|
244
|
+
} from '@getenki/ai'
|
|
245
|
+
|
|
246
|
+
const memories: JsMemoryModule[] = [{ name: 'example-memory' }]
|
|
247
|
+
const memoryStore = new Map<string, JsMemoryEntry[]>()
|
|
248
|
+
|
|
249
|
+
function memoryKey(memoryName: string, sessionId: string): string {
|
|
250
|
+
return `${memoryName}:${sessionId}`
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getMemoryEntries(memoryName: string, sessionId: string): JsMemoryEntry[] {
|
|
254
|
+
const key = memoryKey(memoryName, sessionId)
|
|
255
|
+
const existing = memoryStore.get(key)
|
|
256
|
+
if (existing) {
|
|
257
|
+
return existing
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const empty: JsMemoryEntry[] = []
|
|
261
|
+
memoryStore.set(key, empty)
|
|
262
|
+
return empty
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const recordHandler = (
|
|
266
|
+
memoryName: string,
|
|
267
|
+
sessionId: string,
|
|
268
|
+
userMsg: string,
|
|
269
|
+
assistantMsg: string,
|
|
270
|
+
): void => {
|
|
271
|
+
const entries = getMemoryEntries(memoryName, sessionId)
|
|
272
|
+
entries.push({
|
|
273
|
+
key: `entry-${entries.length + 1}`,
|
|
274
|
+
content: `User: ${userMsg}\nAssistant: ${assistantMsg}`,
|
|
275
|
+
kind: JsMemoryKind.RecentMessage,
|
|
276
|
+
relevance: 1,
|
|
277
|
+
timestampNs: `${Date.now() * 1000000}`,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Tools And Memory Example
|
|
283
|
+
|
|
284
|
+
The repository examples in [`example/basic-js/index.js`](/I:/projects/enki/core-next/example/basic-js/index.js) and [`example/basic-ts/index.ts`](/I:/projects/enki/core-next/example/basic-ts/index.ts) use `NativeEnkiAgent.withToolsAndMemory(...)` with:
|
|
285
|
+
|
|
286
|
+
- a `calculate_sum` tool
|
|
287
|
+
- a `get_today` tool
|
|
288
|
+
- an in-memory `Map` for session memory storage
|
|
289
|
+
|
|
290
|
+
Minimal JavaScript version:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
const { JsMemoryKind, NativeEnkiAgent } = require('@getenki/ai')
|
|
294
|
+
|
|
295
|
+
const tools = [
|
|
296
|
+
{
|
|
297
|
+
id: 'get_today',
|
|
298
|
+
description: 'Return the current local date in ISO format.',
|
|
299
|
+
inputSchema: { type: 'object', properties: {} },
|
|
300
|
+
execute: () => JSON.stringify({ today: new Date().toISOString().slice(0, 10) }),
|
|
301
|
+
},
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
const memories = [{ name: 'example-memory' }]
|
|
305
|
+
const memoryStore = new Map()
|
|
306
|
+
|
|
307
|
+
function memoryKey(memoryName, sessionId) {
|
|
308
|
+
return `${memoryName}:${sessionId}`
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const agent = NativeEnkiAgent.withToolsAndMemory(
|
|
312
|
+
'Basic JS Agent',
|
|
313
|
+
'Answer clearly and keep responses short.',
|
|
314
|
+
'ollama::qwen3.5:latest',
|
|
315
|
+
20,
|
|
316
|
+
process.cwd(),
|
|
317
|
+
tools,
|
|
318
|
+
null,
|
|
319
|
+
memories,
|
|
320
|
+
(memoryName, sessionId, userMsg, assistantMsg) => {
|
|
321
|
+
const key = memoryKey(memoryName, sessionId)
|
|
322
|
+
const entries = memoryStore.get(key) ?? []
|
|
323
|
+
entries.push({
|
|
324
|
+
key: `entry-${entries.length + 1}`,
|
|
325
|
+
content: `User: ${userMsg}\nAssistant: ${assistantMsg}`,
|
|
326
|
+
kind: JsMemoryKind.RecentMessage,
|
|
327
|
+
relevance: 1,
|
|
328
|
+
timestampNs: `${Date.now() * 1000000}`,
|
|
329
|
+
})
|
|
330
|
+
memoryStore.set(key, entries)
|
|
331
|
+
},
|
|
332
|
+
(memoryName, sessionId, query, maxEntries) => {
|
|
333
|
+
const entries = memoryStore.get(memoryKey(memoryName, sessionId)) ?? []
|
|
334
|
+
return entries.filter((entry) => entry.content.includes(query)).slice(-maxEntries)
|
|
335
|
+
},
|
|
336
|
+
(memoryName, sessionId) => {
|
|
337
|
+
memoryStore.delete(memoryKey(memoryName, sessionId))
|
|
338
|
+
},
|
|
339
|
+
() => {},
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Minimal TypeScript version:
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import {
|
|
347
|
+
JsMemoryKind,
|
|
348
|
+
type JsMemoryEntry,
|
|
349
|
+
type JsMemoryModule,
|
|
350
|
+
NativeEnkiAgent,
|
|
351
|
+
} from '@getenki/ai'
|
|
352
|
+
|
|
353
|
+
type ExampleTool = {
|
|
354
|
+
id: string
|
|
355
|
+
description: string
|
|
356
|
+
inputSchema: Record<string, unknown>
|
|
357
|
+
execute: (inputJson: string, contextJson: string) => string
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const tools: ExampleTool[] = [
|
|
361
|
+
{
|
|
362
|
+
id: 'get_today',
|
|
363
|
+
description: 'Return the current local date in ISO format.',
|
|
364
|
+
inputSchema: { type: 'object', properties: {} },
|
|
365
|
+
execute: (): string =>
|
|
366
|
+
JSON.stringify({ today: new Date().toISOString().slice(0, 10) }),
|
|
367
|
+
},
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
const memories: JsMemoryModule[] = [{ name: 'example-memory' }]
|
|
371
|
+
const memoryStore = new Map<string, JsMemoryEntry[]>()
|
|
372
|
+
|
|
373
|
+
const agent = NativeEnkiAgent.withToolsAndMemory(
|
|
374
|
+
'Basic TS Agent',
|
|
375
|
+
'Answer clearly and keep responses short.',
|
|
376
|
+
'ollama::qwen3.5:latest',
|
|
377
|
+
20,
|
|
378
|
+
process.cwd(),
|
|
379
|
+
tools,
|
|
380
|
+
null,
|
|
381
|
+
memories,
|
|
382
|
+
(memoryName: string, sessionId: string, userMsg: string, assistantMsg: string): void => {
|
|
383
|
+
const key = `${memoryName}:${sessionId}`
|
|
384
|
+
const entries = memoryStore.get(key) ?? []
|
|
385
|
+
entries.push({
|
|
386
|
+
key: `entry-${entries.length + 1}`,
|
|
387
|
+
content: `User: ${userMsg}\nAssistant: ${assistantMsg}`,
|
|
388
|
+
kind: JsMemoryKind.RecentMessage,
|
|
389
|
+
relevance: 1,
|
|
390
|
+
timestampNs: `${Date.now() * 1000000}`,
|
|
391
|
+
})
|
|
392
|
+
memoryStore.set(key, entries)
|
|
393
|
+
},
|
|
394
|
+
(memoryName: string, sessionId: string, query: string, maxEntries: number): JsMemoryEntry[] => {
|
|
395
|
+
const entries = memoryStore.get(`${memoryName}:${sessionId}`) ?? []
|
|
396
|
+
return entries.filter((entry) => entry.content.includes(query)).slice(-maxEntries)
|
|
397
|
+
},
|
|
398
|
+
(memoryName: string, sessionId: string): void => {
|
|
399
|
+
memoryStore.delete(`${memoryName}:${sessionId}`)
|
|
400
|
+
},
|
|
401
|
+
(): void => {},
|
|
402
|
+
)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Running The Examples
|
|
406
|
+
|
|
407
|
+
JavaScript example:
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
cd example/basic-js
|
|
411
|
+
npm install
|
|
412
|
+
npm start
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
TypeScript example:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
cd example/basic-ts
|
|
419
|
+
npm install
|
|
420
|
+
npm start
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
The checked-in examples currently hardcode `ollama::qwen3.5:latest` as the model, so make sure that model is available in your local provider before running them.
|
|
424
|
+
|
|
425
|
+
## Development
|
|
426
|
+
|
|
427
|
+
From [`crates/bindings/enki-js`](/I:/projects/enki/core-next/crates/bindings/enki-js):
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
npm install
|
|
431
|
+
npm run build
|
|
432
|
+
npm test
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Useful scripts:
|
|
436
|
+
|
|
437
|
+
- `npm run build`: build the native addon in release mode
|
|
438
|
+
- `npm run build:debug`: build without release optimizations
|
|
439
|
+
- `npm test`: run the AVA test suite
|
|
440
|
+
- `npm run lint`: run `oxlint`
|
|
441
|
+
- `npm run format`: run Prettier, `cargo fmt`, and `taplo format`
|