arcie 0.1.2 → 0.1.4
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 +34 -6
- package/dist/agent/index.d.ts +6 -3
- package/dist/agent/index.js +6 -6
- package/dist/agent/index.js.map +1 -1
- package/dist/auth/index.d.ts +6 -5
- package/dist/auth/index.js +8 -17
- package/dist/auth/index.js.map +1 -1
- package/dist/channels/index.d.ts +8 -5
- package/dist/channels/index.js +10 -12
- package/dist/channels/index.js.map +1 -1
- package/dist/chunk-2ZSKZYWK.js +1901 -0
- package/dist/chunk-2ZSKZYWK.js.map +1 -0
- package/dist/chunk-3CNQ6HQV.js +12 -0
- package/dist/chunk-3CNQ6HQV.js.map +1 -0
- package/dist/chunk-4L7WVCTU.js +12 -0
- package/dist/chunk-4L7WVCTU.js.map +1 -0
- package/dist/chunk-6XETPLIF.js +176 -0
- package/dist/chunk-6XETPLIF.js.map +1 -0
- package/dist/chunk-CKHBEZCT.js +24 -0
- package/dist/chunk-CKHBEZCT.js.map +1 -0
- package/dist/chunk-FQ7KDNV5.js +12 -0
- package/dist/chunk-FQ7KDNV5.js.map +1 -0
- package/dist/chunk-L3BTL5FM.js +39 -0
- package/dist/chunk-L3BTL5FM.js.map +1 -0
- package/dist/chunk-LQC65UHE.js +34 -0
- package/dist/chunk-LQC65UHE.js.map +1 -0
- package/dist/chunk-PHTJLJOV.js +25 -0
- package/dist/chunk-PHTJLJOV.js.map +1 -0
- package/dist/chunk-QE46QFID.js +92 -0
- package/dist/chunk-QE46QFID.js.map +1 -0
- package/dist/chunk-WAD7VLNI.js +51 -0
- package/dist/chunk-WAD7VLNI.js.map +1 -0
- package/dist/chunk-ZJNHQXMO.js +20 -0
- package/dist/chunk-ZJNHQXMO.js.map +1 -0
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +252 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/context/index.d.ts +14 -11
- package/dist/context/index.js +22 -37
- package/dist/context/index.js.map +1 -1
- package/dist/discover/index.d.ts +17 -7
- package/dist/discover/index.js +6 -132
- package/dist/discover/index.js.map +1 -1
- package/dist/hooks/index.d.ts +6 -3
- package/dist/hooks/index.js +6 -6
- package/dist/hooks/index.js.map +1 -1
- package/dist/index-b4NcDl3m.d.ts +136 -0
- package/dist/index.d.ts +297 -18
- package/dist/index.js +158 -13
- package/dist/index.js.map +1 -1
- package/dist/instructions/index.d.ts +7 -4
- package/dist/instructions/index.js +8 -32
- package/dist/instructions/index.js.map +1 -1
- package/dist/protocol/events.d.ts +47 -39
- package/dist/protocol/events.js +46 -45
- package/dist/protocol/events.js.map +1 -1
- package/dist/runner/index.d.ts +4 -18
- package/dist/runner/index.js +10 -277
- package/dist/runner/index.js.map +1 -1
- package/dist/schedules/index.d.ts +6 -3
- package/dist/schedules/index.js +6 -6
- package/dist/schedules/index.js.map +1 -1
- package/dist/skills/index.d.ts +7 -4
- package/dist/skills/index.js +8 -27
- package/dist/skills/index.js.map +1 -1
- package/dist/tools/index.d.ts +13 -3
- package/dist/tools/index.js +8 -9
- package/dist/tools/index.js.map +1 -1
- package/dist/types-BxOQTzx1.d.ts +151 -0
- package/docs/getting-started.md +2 -2
- package/docs/index.md +3 -2
- package/docs/project-layout.md +2 -2
- package/docs/subagents.md +95 -0
- package/package.json +15 -10
- package/templates/default/agent/agent.ts +2 -2
- package/templates/default/package.json +3 -3
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/channels/index.d.ts.map +0 -1
- package/dist/cli/banner.d.ts +0 -2
- package/dist/cli/banner.d.ts.map +0 -1
- package/dist/cli/banner.js +0 -10
- package/dist/cli/banner.js.map +0 -1
- package/dist/cli/build.d.ts +0 -5
- package/dist/cli/build.d.ts.map +0 -1
- package/dist/cli/build.js +0 -54
- package/dist/cli/build.js.map +0 -1
- package/dist/cli/dev.d.ts +0 -5
- package/dist/cli/dev.d.ts.map +0 -1
- package/dist/cli/dev.js +0 -74
- package/dist/cli/dev.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/init.d.ts +0 -4
- package/dist/cli/init.d.ts.map +0 -1
- package/dist/cli/init.js +0 -75
- package/dist/cli/init.js.map +0 -1
- package/dist/context/index.d.ts.map +0 -1
- package/dist/discover/index.d.ts.map +0 -1
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/instructions/index.d.ts.map +0 -1
- package/dist/loader.d.ts +0 -7
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -102
- package/dist/loader.js.map +0 -1
- package/dist/protocol/events.d.ts.map +0 -1
- package/dist/runner/index.d.ts.map +0 -1
- package/dist/schedules/index.d.ts.map +0 -1
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -118
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,1901 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMessageAppended,
|
|
3
|
+
createMessageCompleted,
|
|
4
|
+
createMessageReceived,
|
|
5
|
+
createSessionCompleted,
|
|
6
|
+
createSessionStarted,
|
|
7
|
+
createSessionWaiting,
|
|
8
|
+
createStepCompleted,
|
|
9
|
+
createStepStarted,
|
|
10
|
+
createSubagentCalled,
|
|
11
|
+
createSubagentCompleted,
|
|
12
|
+
createToolCallCompleted,
|
|
13
|
+
createToolCallStarted,
|
|
14
|
+
createTurnCompleted,
|
|
15
|
+
createTurnStarted
|
|
16
|
+
} from "./chunk-QE46QFID.js";
|
|
17
|
+
import {
|
|
18
|
+
toModelOutput
|
|
19
|
+
} from "./chunk-PHTJLJOV.js";
|
|
20
|
+
|
|
21
|
+
// src/loader.ts
|
|
22
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
23
|
+
import { resolve, basename, extname } from "path";
|
|
24
|
+
async function loadAgent(agentDir) {
|
|
25
|
+
const absDir = resolve(process.cwd(), agentDir);
|
|
26
|
+
if (!existsSync(absDir)) {
|
|
27
|
+
throw new Error(`Agent directory not found: ${absDir}`);
|
|
28
|
+
}
|
|
29
|
+
const agentFile = resolve(absDir, "agent.ts");
|
|
30
|
+
let config = { model: "gpt-4o" };
|
|
31
|
+
if (existsSync(agentFile)) {
|
|
32
|
+
try {
|
|
33
|
+
const mod = await import(agentFile);
|
|
34
|
+
if (mod.default) {
|
|
35
|
+
config = { ...config, ...mod.default };
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.warn(`[arcie] Failed to load agent.ts: ${err.message}. Using defaults.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const instructions = loadInstructionsFile(absDir);
|
|
42
|
+
const tools = await loadDirectory(absDir, "tools");
|
|
43
|
+
const skills = await loadDirectory(absDir, "skills");
|
|
44
|
+
const hooks = await loadDirectory(absDir, "hooks");
|
|
45
|
+
const channels = await loadDirectory(absDir, "channels");
|
|
46
|
+
const connections = await loadDirectory(absDir, "connections");
|
|
47
|
+
const schedules = await loadDirectory(absDir, "schedules");
|
|
48
|
+
const subagents = await loadSubagents(absDir);
|
|
49
|
+
const session = await loadSessionConfig(absDir);
|
|
50
|
+
const policy = await loadPolicyConfig(absDir);
|
|
51
|
+
return {
|
|
52
|
+
agentDir: absDir,
|
|
53
|
+
manifest: {
|
|
54
|
+
config,
|
|
55
|
+
instructions,
|
|
56
|
+
tools,
|
|
57
|
+
skills,
|
|
58
|
+
hooks,
|
|
59
|
+
channels,
|
|
60
|
+
connections,
|
|
61
|
+
schedules,
|
|
62
|
+
subagents,
|
|
63
|
+
session: session ?? void 0,
|
|
64
|
+
policy: policy ?? void 0
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function loadSubagents(agentDir) {
|
|
69
|
+
const dirPath = resolve(agentDir, "subagents");
|
|
70
|
+
if (!existsSync(dirPath)) return {};
|
|
71
|
+
const result = {};
|
|
72
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
73
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
74
|
+
const id = entry.name;
|
|
75
|
+
const subDir = resolve(dirPath, id);
|
|
76
|
+
const agentFile = resolve(subDir, "agent.ts");
|
|
77
|
+
if (!existsSync(agentFile)) {
|
|
78
|
+
throw new Error(`Subagent "${id}" is missing agent.ts`);
|
|
79
|
+
}
|
|
80
|
+
let mod;
|
|
81
|
+
try {
|
|
82
|
+
mod = await import(agentFile);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
throw new Error(`Failed to load subagent "${id}": ${err.message}`);
|
|
85
|
+
}
|
|
86
|
+
const config = mod.default;
|
|
87
|
+
if (!config) {
|
|
88
|
+
throw new Error(`Subagent "${id}" agent.ts must have a default export`);
|
|
89
|
+
}
|
|
90
|
+
if (!config.description) {
|
|
91
|
+
throw new Error(`Subagent "${id}" must declare a description in agent.ts`);
|
|
92
|
+
}
|
|
93
|
+
result[id] = {
|
|
94
|
+
config,
|
|
95
|
+
instructions: loadInstructionsFile(subDir),
|
|
96
|
+
tools: await loadDirectory(subDir, "tools"),
|
|
97
|
+
skills: await loadDirectory(subDir, "skills")
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
function loadInstructionsFile(agentDir) {
|
|
103
|
+
const mdPath = resolve(agentDir, "instructions.md");
|
|
104
|
+
if (existsSync(mdPath)) {
|
|
105
|
+
return readFileSync(mdPath, "utf-8");
|
|
106
|
+
}
|
|
107
|
+
return "You are a helpful AI agent.";
|
|
108
|
+
}
|
|
109
|
+
async function loadDirectory(agentDir, dirName) {
|
|
110
|
+
const dirPath = resolve(agentDir, dirName);
|
|
111
|
+
if (!existsSync(dirPath)) return {};
|
|
112
|
+
const entries = readdirSync(dirPath).filter(
|
|
113
|
+
(f) => !f.startsWith(".") && (f.endsWith(".ts") || f.endsWith(".js"))
|
|
114
|
+
);
|
|
115
|
+
const result = {};
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const name = basename(entry, extname(entry));
|
|
118
|
+
const filePath = resolve(dirPath, entry);
|
|
119
|
+
if (statSync(filePath).isFile()) {
|
|
120
|
+
try {
|
|
121
|
+
const mod = await import(filePath);
|
|
122
|
+
if (mod.default) {
|
|
123
|
+
result[name] = mod.default;
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
async function loadSessionConfig(agentDir) {
|
|
132
|
+
const tsPath = resolve(agentDir, "sessions", "config.ts");
|
|
133
|
+
if (existsSync(tsPath)) {
|
|
134
|
+
try {
|
|
135
|
+
const mod = await import(tsPath);
|
|
136
|
+
return mod.default ?? null;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
async function loadPolicyConfig(agentDir) {
|
|
144
|
+
const tsPath = resolve(agentDir, "policies", "index.ts");
|
|
145
|
+
if (existsSync(tsPath)) {
|
|
146
|
+
try {
|
|
147
|
+
const mod = await import(tsPath);
|
|
148
|
+
return mod.default ?? null;
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/memory/store.ts
|
|
157
|
+
var InMemoryStore = class {
|
|
158
|
+
entries = /* @__PURE__ */ new Map();
|
|
159
|
+
threads = /* @__PURE__ */ new Map();
|
|
160
|
+
key(resourceId, threadId) {
|
|
161
|
+
return `${resourceId}:${threadId}`;
|
|
162
|
+
}
|
|
163
|
+
async save(resourceId, threadId, entries) {
|
|
164
|
+
const k = this.key(resourceId, threadId);
|
|
165
|
+
const existing = this.entries.get(k) ?? [];
|
|
166
|
+
existing.push(...entries);
|
|
167
|
+
this.entries.set(k, existing);
|
|
168
|
+
}
|
|
169
|
+
async load(resourceId, threadId, limit) {
|
|
170
|
+
const entries = this.entries.get(this.key(resourceId, threadId)) ?? [];
|
|
171
|
+
if (limit === void 0 || limit >= entries.length) return [...entries];
|
|
172
|
+
return entries.slice(entries.length - limit);
|
|
173
|
+
}
|
|
174
|
+
async search(resourceId, threadId, query, limit) {
|
|
175
|
+
const entries = this.entries.get(this.key(resourceId, threadId)) ?? [];
|
|
176
|
+
const lower = query.toLowerCase();
|
|
177
|
+
const matched = entries.filter((e) => e.content.toLowerCase().includes(lower));
|
|
178
|
+
if (limit !== void 0 && matched.length > limit) return matched.slice(0, limit);
|
|
179
|
+
return matched;
|
|
180
|
+
}
|
|
181
|
+
async clear(resourceId, threadId) {
|
|
182
|
+
this.entries.delete(this.key(resourceId, threadId));
|
|
183
|
+
}
|
|
184
|
+
async close() {
|
|
185
|
+
}
|
|
186
|
+
async createThread(thread) {
|
|
187
|
+
this.threads.set(thread.id, thread);
|
|
188
|
+
}
|
|
189
|
+
async getThread(threadId, _resourceId) {
|
|
190
|
+
return this.threads.get(threadId) ?? null;
|
|
191
|
+
}
|
|
192
|
+
async listThreads(resourceId) {
|
|
193
|
+
return [...this.threads.values()].filter((t) => t.resourceId === resourceId);
|
|
194
|
+
}
|
|
195
|
+
async updateThread(thread) {
|
|
196
|
+
const existing = this.threads.get(thread.id);
|
|
197
|
+
if (!existing) throw new Error(`Thread not found: ${thread.id}`);
|
|
198
|
+
this.threads.set(thread.id, { ...existing, ...thread, updatedAt: Date.now() });
|
|
199
|
+
}
|
|
200
|
+
async deleteThread(threadId, _resourceId) {
|
|
201
|
+
this.threads.delete(threadId);
|
|
202
|
+
this.entries.delete(this.key(_resourceId, threadId));
|
|
203
|
+
}
|
|
204
|
+
async deleteMessages(opts) {
|
|
205
|
+
const k = this.key(opts.resourceId, opts.threadId);
|
|
206
|
+
const existing = this.entries.get(k) ?? [];
|
|
207
|
+
let keep = [...existing];
|
|
208
|
+
if (opts.messageIds && opts.messageIds.length > 0) {
|
|
209
|
+
const ids = new Set(opts.messageIds);
|
|
210
|
+
keep = keep.filter((e) => !(e.turnId && ids.has(e.turnId)));
|
|
211
|
+
}
|
|
212
|
+
if (opts.beforeTimestamp !== void 0) {
|
|
213
|
+
keep = keep.filter((e) => e.timestamp >= opts.beforeTimestamp);
|
|
214
|
+
}
|
|
215
|
+
if (opts.afterTimestamp !== void 0) {
|
|
216
|
+
keep = keep.filter((e) => e.timestamp <= opts.afterTimestamp);
|
|
217
|
+
}
|
|
218
|
+
const deleted = existing.length - keep.length;
|
|
219
|
+
this.entries.set(k, keep);
|
|
220
|
+
return deleted;
|
|
221
|
+
}
|
|
222
|
+
async cloneThread(source, dest) {
|
|
223
|
+
const src = this.entries.get(this.key(source.resourceId, source.threadId));
|
|
224
|
+
if (src) {
|
|
225
|
+
this.entries.set(this.key(dest.resourceId, dest.threadId), src.map((e) => ({ ...e })));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/memory/sqlite-store.ts
|
|
231
|
+
import { createClient } from "@libsql/client";
|
|
232
|
+
import { resolve as resolve2, dirname } from "path";
|
|
233
|
+
import { homedir } from "os";
|
|
234
|
+
import { mkdirSync } from "fs";
|
|
235
|
+
var SqliteStore = class {
|
|
236
|
+
client;
|
|
237
|
+
constructor(dbPath) {
|
|
238
|
+
const path = dbPath ?? resolve2(homedir(), ".arcie", "memory.db");
|
|
239
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
240
|
+
this.client = createClient({ url: `file:${path}` });
|
|
241
|
+
this.init();
|
|
242
|
+
}
|
|
243
|
+
init() {
|
|
244
|
+
this.client.execute(`
|
|
245
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
246
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
247
|
+
resource_id TEXT NOT NULL,
|
|
248
|
+
thread_id TEXT NOT NULL,
|
|
249
|
+
role TEXT NOT NULL,
|
|
250
|
+
content TEXT NOT NULL,
|
|
251
|
+
timestamp INTEGER NOT NULL,
|
|
252
|
+
turn_id TEXT
|
|
253
|
+
)
|
|
254
|
+
`);
|
|
255
|
+
this.client.execute(`
|
|
256
|
+
CREATE INDEX IF NOT EXISTS idx_memories_lookup
|
|
257
|
+
ON memories(resource_id, thread_id, timestamp)
|
|
258
|
+
`);
|
|
259
|
+
this.client.execute(`
|
|
260
|
+
CREATE TABLE IF NOT EXISTS threads (
|
|
261
|
+
id TEXT PRIMARY KEY,
|
|
262
|
+
resource_id TEXT NOT NULL,
|
|
263
|
+
title TEXT,
|
|
264
|
+
metadata TEXT,
|
|
265
|
+
created_at INTEGER NOT NULL,
|
|
266
|
+
updated_at INTEGER NOT NULL
|
|
267
|
+
)
|
|
268
|
+
`);
|
|
269
|
+
this.client.execute(`
|
|
270
|
+
CREATE INDEX IF NOT EXISTS idx_threads_resource
|
|
271
|
+
ON threads(resource_id)
|
|
272
|
+
`);
|
|
273
|
+
}
|
|
274
|
+
async save(resourceId, threadId, entries) {
|
|
275
|
+
const stmt = `INSERT INTO memories (resource_id, thread_id, role, content, timestamp, turn_id) VALUES (?, ?, ?, ?, ?, ?)`;
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
await this.client.execute({
|
|
278
|
+
sql: stmt,
|
|
279
|
+
args: [resourceId, threadId, entry.role, entry.content, entry.timestamp, entry.turnId ?? null]
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async load(resourceId, threadId, limit) {
|
|
284
|
+
const rows = await this.client.execute({
|
|
285
|
+
sql: `SELECT role, content, timestamp, turn_id FROM memories WHERE resource_id = ? AND thread_id = ? ORDER BY timestamp ASC, id ASC`,
|
|
286
|
+
args: [resourceId, threadId]
|
|
287
|
+
});
|
|
288
|
+
let entries = rows.rows.map((r) => ({
|
|
289
|
+
role: r.role,
|
|
290
|
+
content: r.content,
|
|
291
|
+
timestamp: r.timestamp,
|
|
292
|
+
turnId: r.turn_id
|
|
293
|
+
}));
|
|
294
|
+
if (limit !== void 0 && entries.length > limit) {
|
|
295
|
+
entries = entries.slice(entries.length - limit);
|
|
296
|
+
}
|
|
297
|
+
return entries;
|
|
298
|
+
}
|
|
299
|
+
async clear(resourceId, threadId) {
|
|
300
|
+
await this.client.execute({
|
|
301
|
+
sql: `DELETE FROM memories WHERE resource_id = ? AND thread_id = ?`,
|
|
302
|
+
args: [resourceId, threadId]
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
async search(resourceId, threadId, query, limit) {
|
|
306
|
+
const rows = await this.client.execute({
|
|
307
|
+
sql: `SELECT role, content, timestamp, turn_id FROM memories WHERE resource_id = ? AND thread_id = ? AND content LIKE ? ORDER BY timestamp DESC`,
|
|
308
|
+
args: [resourceId, threadId, `%${query}%`]
|
|
309
|
+
});
|
|
310
|
+
let entries = rows.rows.map((r) => ({
|
|
311
|
+
role: r.role,
|
|
312
|
+
content: r.content,
|
|
313
|
+
timestamp: r.timestamp,
|
|
314
|
+
turnId: r.turn_id
|
|
315
|
+
}));
|
|
316
|
+
if (limit !== void 0 && entries.length > limit) {
|
|
317
|
+
entries = entries.slice(0, limit);
|
|
318
|
+
}
|
|
319
|
+
return entries;
|
|
320
|
+
}
|
|
321
|
+
async close() {
|
|
322
|
+
this.client.close();
|
|
323
|
+
}
|
|
324
|
+
async createThread(thread) {
|
|
325
|
+
await this.client.execute({
|
|
326
|
+
sql: `INSERT INTO threads (id, resource_id, title, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
327
|
+
args: [
|
|
328
|
+
thread.id,
|
|
329
|
+
thread.resourceId,
|
|
330
|
+
thread.title ?? null,
|
|
331
|
+
thread.metadata ? JSON.stringify(thread.metadata) : null,
|
|
332
|
+
thread.createdAt,
|
|
333
|
+
thread.updatedAt
|
|
334
|
+
]
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
async getThread(threadId, _resourceId) {
|
|
338
|
+
const rows = await this.client.execute({
|
|
339
|
+
sql: `SELECT id, resource_id, title, metadata, created_at, updated_at FROM threads WHERE id = ?`,
|
|
340
|
+
args: [threadId]
|
|
341
|
+
});
|
|
342
|
+
if (rows.rows.length === 0) return null;
|
|
343
|
+
const r = rows.rows[0];
|
|
344
|
+
return {
|
|
345
|
+
id: r.id,
|
|
346
|
+
resourceId: r.resource_id,
|
|
347
|
+
title: r.title,
|
|
348
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : void 0,
|
|
349
|
+
createdAt: r.created_at,
|
|
350
|
+
updatedAt: r.updated_at
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
async listThreads(resourceId) {
|
|
354
|
+
const rows = await this.client.execute({
|
|
355
|
+
sql: `SELECT id, resource_id, title, metadata, created_at, updated_at FROM threads WHERE resource_id = ? ORDER BY updated_at DESC`,
|
|
356
|
+
args: [resourceId]
|
|
357
|
+
});
|
|
358
|
+
return rows.rows.map((r) => ({
|
|
359
|
+
id: r.id,
|
|
360
|
+
resourceId: r.resource_id,
|
|
361
|
+
title: r.title,
|
|
362
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : void 0,
|
|
363
|
+
createdAt: r.created_at,
|
|
364
|
+
updatedAt: r.updated_at
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
async updateThread(thread) {
|
|
368
|
+
const fields = [];
|
|
369
|
+
const args = [];
|
|
370
|
+
if (thread.title !== void 0) {
|
|
371
|
+
fields.push("title = ?");
|
|
372
|
+
args.push(thread.title);
|
|
373
|
+
}
|
|
374
|
+
if (thread.metadata !== void 0) {
|
|
375
|
+
fields.push("metadata = ?");
|
|
376
|
+
args.push(JSON.stringify(thread.metadata));
|
|
377
|
+
}
|
|
378
|
+
fields.push("updated_at = ?");
|
|
379
|
+
args.push(Date.now());
|
|
380
|
+
args.push(thread.id);
|
|
381
|
+
await this.client.execute({
|
|
382
|
+
sql: `UPDATE threads SET ${fields.join(", ")} WHERE id = ?`,
|
|
383
|
+
args
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
async deleteThread(threadId, _resourceId) {
|
|
387
|
+
await this.client.execute({
|
|
388
|
+
sql: `DELETE FROM memories WHERE thread_id = ?`,
|
|
389
|
+
args: [threadId]
|
|
390
|
+
});
|
|
391
|
+
await this.client.execute({
|
|
392
|
+
sql: `DELETE FROM threads WHERE id = ?`,
|
|
393
|
+
args: [threadId]
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
async deleteMessages(opts) {
|
|
397
|
+
const conditions = ["resource_id = ?", "thread_id = ?"];
|
|
398
|
+
const args = [opts.resourceId, opts.threadId];
|
|
399
|
+
if (opts.messageIds && opts.messageIds.length > 0) {
|
|
400
|
+
const placeholders = opts.messageIds.map(() => "?").join(",");
|
|
401
|
+
conditions.push(`turn_id IN (${placeholders})`);
|
|
402
|
+
args.push(...opts.messageIds);
|
|
403
|
+
}
|
|
404
|
+
if (opts.beforeTimestamp !== void 0) {
|
|
405
|
+
conditions.push("timestamp >= ?");
|
|
406
|
+
args.push(opts.beforeTimestamp);
|
|
407
|
+
}
|
|
408
|
+
if (opts.afterTimestamp !== void 0) {
|
|
409
|
+
conditions.push("timestamp <= ?");
|
|
410
|
+
args.push(opts.afterTimestamp);
|
|
411
|
+
}
|
|
412
|
+
const result = await this.client.execute({
|
|
413
|
+
sql: `DELETE FROM memories WHERE ${conditions.join(" AND ")}`,
|
|
414
|
+
args
|
|
415
|
+
});
|
|
416
|
+
return Number(result.rowsAffected);
|
|
417
|
+
}
|
|
418
|
+
async cloneThread(source, dest) {
|
|
419
|
+
const rows = await this.client.execute({
|
|
420
|
+
sql: `SELECT role, content, timestamp, turn_id FROM memories WHERE resource_id = ? AND thread_id = ? ORDER BY id ASC`,
|
|
421
|
+
args: [source.resourceId, source.threadId]
|
|
422
|
+
});
|
|
423
|
+
const stmt = `INSERT INTO memories (resource_id, thread_id, role, content, timestamp, turn_id) VALUES (?, ?, ?, ?, ?, ?)`;
|
|
424
|
+
for (const r of rows.rows) {
|
|
425
|
+
await this.client.execute({
|
|
426
|
+
sql: stmt,
|
|
427
|
+
args: [
|
|
428
|
+
dest.resourceId,
|
|
429
|
+
dest.threadId,
|
|
430
|
+
r.role,
|
|
431
|
+
r.content,
|
|
432
|
+
r.timestamp,
|
|
433
|
+
r.turn_id
|
|
434
|
+
]
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/memory/file-store.ts
|
|
441
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
442
|
+
import { resolve as resolve3, dirname as dirname2 } from "path";
|
|
443
|
+
import { homedir as homedir2 } from "os";
|
|
444
|
+
var FileStore = class {
|
|
445
|
+
baseDir;
|
|
446
|
+
constructor(baseDir) {
|
|
447
|
+
this.baseDir = baseDir ?? resolve3(homedir2(), ".arcie", "memory");
|
|
448
|
+
}
|
|
449
|
+
filePath(resourceId, threadId) {
|
|
450
|
+
const dir = resolve3(this.baseDir, resourceId);
|
|
451
|
+
return resolve3(dir, `${threadId}.json`);
|
|
452
|
+
}
|
|
453
|
+
threadsPath(resourceId) {
|
|
454
|
+
return resolve3(this.baseDir, resourceId, "__threads.json");
|
|
455
|
+
}
|
|
456
|
+
read(resourceId, threadId) {
|
|
457
|
+
const path = this.filePath(resourceId, threadId);
|
|
458
|
+
if (!existsSync2(path)) return [];
|
|
459
|
+
try {
|
|
460
|
+
const raw = readFileSync2(path, "utf-8");
|
|
461
|
+
return JSON.parse(raw);
|
|
462
|
+
} catch {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
write(resourceId, threadId, entries) {
|
|
467
|
+
const path = this.filePath(resourceId, threadId);
|
|
468
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
469
|
+
writeFileSync(path, JSON.stringify(entries, null, 2), "utf-8");
|
|
470
|
+
}
|
|
471
|
+
readThreads(resourceId) {
|
|
472
|
+
const path = this.threadsPath(resourceId);
|
|
473
|
+
if (!existsSync2(path)) return [];
|
|
474
|
+
try {
|
|
475
|
+
const raw = readFileSync2(path, "utf-8");
|
|
476
|
+
return JSON.parse(raw);
|
|
477
|
+
} catch {
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
writeThreads(resourceId, threads) {
|
|
482
|
+
const path = this.threadsPath(resourceId);
|
|
483
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
484
|
+
writeFileSync(path, JSON.stringify(threads, null, 2), "utf-8");
|
|
485
|
+
}
|
|
486
|
+
async save(resourceId, threadId, entries) {
|
|
487
|
+
const existing = this.read(resourceId, threadId);
|
|
488
|
+
existing.push(...entries);
|
|
489
|
+
this.write(resourceId, threadId, existing);
|
|
490
|
+
}
|
|
491
|
+
async load(resourceId, threadId, limit) {
|
|
492
|
+
const entries = this.read(resourceId, threadId);
|
|
493
|
+
if (limit === void 0 || limit >= entries.length) return entries;
|
|
494
|
+
return entries.slice(entries.length - limit);
|
|
495
|
+
}
|
|
496
|
+
async clear(resourceId, threadId) {
|
|
497
|
+
this.write(resourceId, threadId, []);
|
|
498
|
+
}
|
|
499
|
+
async close() {
|
|
500
|
+
}
|
|
501
|
+
async createThread(thread) {
|
|
502
|
+
const threads = this.readThreads(thread.resourceId);
|
|
503
|
+
threads.push(thread);
|
|
504
|
+
this.writeThreads(thread.resourceId, threads);
|
|
505
|
+
}
|
|
506
|
+
async getThread(threadId, resourceId) {
|
|
507
|
+
const threads = this.readThreads(resourceId);
|
|
508
|
+
return threads.find((t) => t.id === threadId) ?? null;
|
|
509
|
+
}
|
|
510
|
+
async listThreads(resourceId) {
|
|
511
|
+
return this.readThreads(resourceId);
|
|
512
|
+
}
|
|
513
|
+
async updateThread(thread) {
|
|
514
|
+
const threads = this.readThreads(thread.resourceId);
|
|
515
|
+
const idx = threads.findIndex((t) => t.id === thread.id);
|
|
516
|
+
if (idx === -1) throw new Error(`Thread not found: ${thread.id}`);
|
|
517
|
+
threads[idx] = { ...threads[idx], ...thread, updatedAt: Date.now() };
|
|
518
|
+
this.writeThreads(thread.resourceId, threads);
|
|
519
|
+
}
|
|
520
|
+
async deleteThread(threadId, resourceId) {
|
|
521
|
+
const threads = this.readThreads(resourceId);
|
|
522
|
+
this.writeThreads(resourceId, threads.filter((t) => t.id !== threadId));
|
|
523
|
+
const path = this.filePath(resourceId, threadId);
|
|
524
|
+
if (existsSync2(path)) writeFileSync(path, "[]", "utf-8");
|
|
525
|
+
}
|
|
526
|
+
async deleteMessages(opts) {
|
|
527
|
+
let entries = this.read(opts.resourceId, opts.threadId);
|
|
528
|
+
const before = entries.length;
|
|
529
|
+
const { messageIds, beforeTimestamp, afterTimestamp } = opts;
|
|
530
|
+
if (messageIds && messageIds.length > 0) {
|
|
531
|
+
const ids = new Set(messageIds);
|
|
532
|
+
entries = entries.filter((e) => !(e.turnId && ids.has(e.turnId)));
|
|
533
|
+
}
|
|
534
|
+
if (beforeTimestamp !== void 0) {
|
|
535
|
+
entries = entries.filter((e) => e.timestamp >= beforeTimestamp);
|
|
536
|
+
}
|
|
537
|
+
if (afterTimestamp !== void 0) {
|
|
538
|
+
entries = entries.filter((e) => e.timestamp <= afterTimestamp);
|
|
539
|
+
}
|
|
540
|
+
this.write(opts.resourceId, opts.threadId, entries);
|
|
541
|
+
return before - entries.length;
|
|
542
|
+
}
|
|
543
|
+
async cloneThread(source, dest) {
|
|
544
|
+
const entries = this.read(source.resourceId, source.threadId);
|
|
545
|
+
this.write(dest.resourceId, dest.threadId, entries.map((e) => ({ ...e })));
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// src/memory/cencori-store.ts
|
|
550
|
+
var CencoriMemoryStore = class {
|
|
551
|
+
client;
|
|
552
|
+
constructor(client) {
|
|
553
|
+
this.client = client;
|
|
554
|
+
}
|
|
555
|
+
async save(resourceId, threadId, entries) {
|
|
556
|
+
for (const entry of entries) {
|
|
557
|
+
await this.client.store({
|
|
558
|
+
namespace: resourceId,
|
|
559
|
+
content: entry.content,
|
|
560
|
+
metadata: {
|
|
561
|
+
threadId,
|
|
562
|
+
role: entry.role,
|
|
563
|
+
timestamp: entry.timestamp,
|
|
564
|
+
turnId: entry.turnId
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async load(resourceId, threadId, limit) {
|
|
570
|
+
const result = await this.client.search({
|
|
571
|
+
namespace: resourceId,
|
|
572
|
+
query: "*",
|
|
573
|
+
limit: limit ?? 100,
|
|
574
|
+
threshold: 0,
|
|
575
|
+
filter: { threadId }
|
|
576
|
+
});
|
|
577
|
+
return result.results.map((r) => ({
|
|
578
|
+
role: r.metadata.role ?? "user",
|
|
579
|
+
content: r.content,
|
|
580
|
+
timestamp: r.metadata.timestamp ?? Date.now(),
|
|
581
|
+
turnId: r.metadata.turnId,
|
|
582
|
+
score: r.similarity
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
async clear(resourceId, threadId) {
|
|
586
|
+
if (this.client.delete) {
|
|
587
|
+
await this.client.delete({
|
|
588
|
+
namespace: resourceId,
|
|
589
|
+
filter: { threadId }
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async search(resourceId, threadId, query, limit) {
|
|
594
|
+
const result = await this.client.search({
|
|
595
|
+
namespace: resourceId,
|
|
596
|
+
query,
|
|
597
|
+
limit: limit ?? 5,
|
|
598
|
+
threshold: 0.5,
|
|
599
|
+
filter: { threadId }
|
|
600
|
+
});
|
|
601
|
+
return result.results.map((r) => ({
|
|
602
|
+
role: r.metadata.role ?? "user",
|
|
603
|
+
content: r.content,
|
|
604
|
+
timestamp: r.metadata.timestamp ?? Date.now(),
|
|
605
|
+
turnId: r.metadata.turnId,
|
|
606
|
+
score: r.similarity
|
|
607
|
+
}));
|
|
608
|
+
}
|
|
609
|
+
async close() {
|
|
610
|
+
}
|
|
611
|
+
async createThread(_thread) {
|
|
612
|
+
throw new Error("Thread management not supported by CencoriMemoryStore");
|
|
613
|
+
}
|
|
614
|
+
async getThread(_threadId, _resourceId) {
|
|
615
|
+
throw new Error("Thread management not supported by CencoriMemoryStore");
|
|
616
|
+
}
|
|
617
|
+
async listThreads(_resourceId) {
|
|
618
|
+
throw new Error("Thread management not supported by CencoriMemoryStore");
|
|
619
|
+
}
|
|
620
|
+
async updateThread(_thread) {
|
|
621
|
+
throw new Error("Thread management not supported by CencoriMemoryStore");
|
|
622
|
+
}
|
|
623
|
+
async deleteThread(_threadId, _resourceId) {
|
|
624
|
+
throw new Error("Thread management not supported by CencoriMemoryStore");
|
|
625
|
+
}
|
|
626
|
+
async deleteMessages(opts) {
|
|
627
|
+
if (!this.client.delete) throw new Error("CencoriMemoryClient does not support delete");
|
|
628
|
+
const filter = { threadId: opts.threadId };
|
|
629
|
+
if (opts.messageIds && opts.messageIds.length > 0) {
|
|
630
|
+
filter.turnId = { $in: opts.messageIds };
|
|
631
|
+
}
|
|
632
|
+
if (opts.beforeTimestamp !== void 0) {
|
|
633
|
+
filter.timestamp = { $gte: opts.beforeTimestamp };
|
|
634
|
+
}
|
|
635
|
+
if (opts.afterTimestamp !== void 0) {
|
|
636
|
+
filter.timestamp = { ...filter.timestamp || {}, $lte: opts.afterTimestamp };
|
|
637
|
+
}
|
|
638
|
+
await this.client.delete({ namespace: opts.resourceId, filter });
|
|
639
|
+
return -1;
|
|
640
|
+
}
|
|
641
|
+
async cloneThread(source, dest) {
|
|
642
|
+
const entries = await this.load(source.resourceId, source.threadId);
|
|
643
|
+
for (const entry of entries) {
|
|
644
|
+
await this.client.store({
|
|
645
|
+
namespace: dest.resourceId,
|
|
646
|
+
content: entry.content,
|
|
647
|
+
metadata: {
|
|
648
|
+
threadId: dest.threadId,
|
|
649
|
+
role: entry.role,
|
|
650
|
+
timestamp: entry.timestamp,
|
|
651
|
+
turnId: entry.turnId
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// src/memory/strategies/lastN.ts
|
|
659
|
+
var LastNStrategy = class {
|
|
660
|
+
limit;
|
|
661
|
+
constructor(limit = 10) {
|
|
662
|
+
this.limit = limit;
|
|
663
|
+
}
|
|
664
|
+
async getInputContext(store, resourceId, threadId) {
|
|
665
|
+
const entries = await store.load(resourceId, threadId, this.limit * 2);
|
|
666
|
+
if (entries.length === 0) return "";
|
|
667
|
+
const lines = entries.map((e) => {
|
|
668
|
+
const prefix = e.role === "user" ? "User" : e.role === "assistant" ? "Assistant" : "Tool";
|
|
669
|
+
return `${prefix}: ${e.content}`;
|
|
670
|
+
});
|
|
671
|
+
return `## Recent conversation history
|
|
672
|
+
|
|
673
|
+
${lines.join("\n")}`;
|
|
674
|
+
}
|
|
675
|
+
async recordTurn(store, resourceId, threadId, input, output, toolCalls) {
|
|
676
|
+
const entries = [
|
|
677
|
+
{ role: "user", content: input, timestamp: Date.now() },
|
|
678
|
+
{ role: "assistant", content: output, timestamp: Date.now() }
|
|
679
|
+
];
|
|
680
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
681
|
+
for (const tc of toolCalls) {
|
|
682
|
+
entries.push({
|
|
683
|
+
role: "tool",
|
|
684
|
+
content: JSON.stringify({ tool: tc.tool, result: tc.output }),
|
|
685
|
+
timestamp: Date.now()
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
await store.save(resourceId, threadId, entries);
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/memory/strategies/keyFacts.ts
|
|
694
|
+
var FACT_PATTERNS = [
|
|
695
|
+
/(?:I am|I'm|my name is)\s+(\w+)/i,
|
|
696
|
+
/(?:I (?:work|am)\s+(?:at|for))\s+([\w\s]+?)(?=[.!,]|$)/i,
|
|
697
|
+
/(?:I (?:like|love|enjoy|prefer)\s+)([\w\s]+?)(?=[.!,]|$)/i,
|
|
698
|
+
/(?:I (?:live|am based|am located)\s+(?:in|at))\s+([\w\s,]+?)(?=[.!,]|$)/i,
|
|
699
|
+
/(?:my (?:favorite|favourite)\s+\w+\s+is)\s+([\w\s]+?)(?=[.!,]|$)/i,
|
|
700
|
+
/(?:I (?:have|has)\s+(?:a|an|the)\s+)([\w\s]+?)(?=[.!,]|$)/i,
|
|
701
|
+
/(?:I (?:don't|do not)\s+)([\w\s]+?)(?=[.!,]|$)/i,
|
|
702
|
+
/(?:I (?:use|built|created|developed)\s+)([\w\s]+?)(?=[.!,]|$)/i
|
|
703
|
+
];
|
|
704
|
+
function extractFacts(text) {
|
|
705
|
+
const facts = /* @__PURE__ */ new Set();
|
|
706
|
+
for (const pattern of FACT_PATTERNS) {
|
|
707
|
+
const match = text.match(pattern);
|
|
708
|
+
if (match && match[1].trim().length > 1) {
|
|
709
|
+
facts.add(match[1].trim());
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return [...facts];
|
|
713
|
+
}
|
|
714
|
+
var FACTS_PREFIX = "___keyfacts___:";
|
|
715
|
+
var KeyFactsStrategy = class {
|
|
716
|
+
facts = /* @__PURE__ */ new Map();
|
|
717
|
+
loaded = false;
|
|
718
|
+
async ensureLoaded(store, resourceId, threadId) {
|
|
719
|
+
if (this.loaded) return;
|
|
720
|
+
this.loaded = true;
|
|
721
|
+
try {
|
|
722
|
+
const entries = await store.load(resourceId, threadId);
|
|
723
|
+
const factEntries = entries.filter((e) => e.content.startsWith(FACTS_PREFIX)).sort((a, b) => b.timestamp - a.timestamp);
|
|
724
|
+
if (factEntries.length > 0) {
|
|
725
|
+
this.facts = new Map(JSON.parse(factEntries[0].content.slice(FACTS_PREFIX.length)));
|
|
726
|
+
}
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async getInputContext(store, resourceId, threadId) {
|
|
731
|
+
await this.ensureLoaded(store, resourceId, threadId);
|
|
732
|
+
if (this.facts.size === 0) return "";
|
|
733
|
+
const lines = [...this.facts.entries()].sort((a, b) => b[1] - a[1]).map(([fact]) => `- ${fact}`);
|
|
734
|
+
return `## Known facts about the user
|
|
735
|
+
|
|
736
|
+
${lines.join("\n")}`;
|
|
737
|
+
}
|
|
738
|
+
async recordTurn(store, resourceId, threadId, input, output, _toolCalls) {
|
|
739
|
+
await this.ensureLoaded(store, resourceId, threadId);
|
|
740
|
+
const extracted = [...extractFacts(input), ...extractFacts(output)];
|
|
741
|
+
for (const fact of extracted) {
|
|
742
|
+
this.facts.set(fact, (this.facts.get(fact) ?? 0) + 1);
|
|
743
|
+
}
|
|
744
|
+
await store.save(resourceId, threadId, [
|
|
745
|
+
{ role: "user", content: input, timestamp: Date.now() },
|
|
746
|
+
{ role: "assistant", content: output, timestamp: Date.now() },
|
|
747
|
+
{
|
|
748
|
+
role: "tool",
|
|
749
|
+
content: `${FACTS_PREFIX}${JSON.stringify([...this.facts.entries()])}`,
|
|
750
|
+
timestamp: Date.now()
|
|
751
|
+
}
|
|
752
|
+
]);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// src/memory/strategies/summary.ts
|
|
757
|
+
function truncate(text, maxChars = 2e3) {
|
|
758
|
+
if (text.length <= maxChars) return text;
|
|
759
|
+
return text.slice(0, maxChars) + "...";
|
|
760
|
+
}
|
|
761
|
+
var SummaryStrategy = class {
|
|
762
|
+
limit;
|
|
763
|
+
summary = "";
|
|
764
|
+
summarize;
|
|
765
|
+
mutex = Promise.resolve();
|
|
766
|
+
constructor(limit = 10, summarize) {
|
|
767
|
+
this.limit = limit;
|
|
768
|
+
this.summarize = summarize ?? this.defaultSummarize;
|
|
769
|
+
}
|
|
770
|
+
async exclusive(fn) {
|
|
771
|
+
const prev = this.mutex;
|
|
772
|
+
let release;
|
|
773
|
+
this.mutex = new Promise((resolve5) => {
|
|
774
|
+
release = resolve5;
|
|
775
|
+
});
|
|
776
|
+
await prev;
|
|
777
|
+
try {
|
|
778
|
+
return await fn();
|
|
779
|
+
} finally {
|
|
780
|
+
release();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
async defaultSummarize(text) {
|
|
784
|
+
const truncated = truncate(text);
|
|
785
|
+
const apiKey = process.env.CENCORI_API_KEY;
|
|
786
|
+
const endpoint = process.env.CENCORI_API_URL || "https://cencori.com/v1";
|
|
787
|
+
if (!apiKey) {
|
|
788
|
+
return `Previous conversation: ${truncated.slice(0, 500)}`;
|
|
789
|
+
}
|
|
790
|
+
try {
|
|
791
|
+
const res = await fetch(`${endpoint}/responses`, {
|
|
792
|
+
method: "POST",
|
|
793
|
+
headers: {
|
|
794
|
+
"Content-Type": "application/json",
|
|
795
|
+
Authorization: `Bearer ${apiKey}`
|
|
796
|
+
},
|
|
797
|
+
body: JSON.stringify({
|
|
798
|
+
model: "gpt-4o-mini",
|
|
799
|
+
input: `Summarize this conversation concisely:
|
|
800
|
+
|
|
801
|
+
${truncated}`,
|
|
802
|
+
max_output_tokens: 300
|
|
803
|
+
})
|
|
804
|
+
});
|
|
805
|
+
if (!res.ok) return `Previous conversation: ${truncated.slice(0, 500)}`;
|
|
806
|
+
const data = await res.json();
|
|
807
|
+
const text2 = data.output?.output?.[0]?.content?.[0]?.text;
|
|
808
|
+
return text2 ?? `Previous conversation: ${truncated.slice(0, 500)}`;
|
|
809
|
+
} catch {
|
|
810
|
+
return `Previous conversation: ${truncated.slice(0, 500)}`;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
async getInputContext(store, resourceId, threadId) {
|
|
814
|
+
const entries = await store.load(resourceId, threadId);
|
|
815
|
+
const recentEntries = [];
|
|
816
|
+
const summarizeEntries = [];
|
|
817
|
+
if (this.summary) {
|
|
818
|
+
summarizeEntries.push(`Summary of earlier conversation: ${this.summary}`);
|
|
819
|
+
}
|
|
820
|
+
for (const e of entries) {
|
|
821
|
+
const prefix = e.role === "user" ? "User" : e.role === "assistant" ? "Assistant" : "Tool";
|
|
822
|
+
recentEntries.push(`${prefix}: ${e.content}`);
|
|
823
|
+
}
|
|
824
|
+
const parts = [...summarizeEntries, ...recentEntries];
|
|
825
|
+
if (parts.length === 0) return "";
|
|
826
|
+
return `## Conversation history
|
|
827
|
+
|
|
828
|
+
${parts.join("\n")}`;
|
|
829
|
+
}
|
|
830
|
+
async recordTurn(store, resourceId, threadId, input, output, toolCalls) {
|
|
831
|
+
await this.exclusive(async () => {
|
|
832
|
+
const entries = [
|
|
833
|
+
{ role: "user", content: input, timestamp: Date.now() },
|
|
834
|
+
{ role: "assistant", content: output, timestamp: Date.now() }
|
|
835
|
+
];
|
|
836
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
837
|
+
for (const tc of toolCalls) {
|
|
838
|
+
entries.push({
|
|
839
|
+
role: "tool",
|
|
840
|
+
content: JSON.stringify({ tool: tc.tool, result: tc.output }),
|
|
841
|
+
timestamp: Date.now()
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
await store.save(resourceId, threadId, entries);
|
|
846
|
+
const allEntries = await store.load(resourceId, threadId);
|
|
847
|
+
if (allEntries.length > this.limit * 2) {
|
|
848
|
+
const textToSummarize = allEntries.slice(0, allEntries.length - this.limit).map((e) => `${e.role}: ${e.content}`).join("\n");
|
|
849
|
+
this.summary = await this.summarize(textToSummarize);
|
|
850
|
+
const recentEntries = allEntries.slice(allEntries.length - this.limit);
|
|
851
|
+
await store.clear(resourceId, threadId);
|
|
852
|
+
await store.save(resourceId, threadId, recentEntries);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// src/memory/semantic.ts
|
|
859
|
+
var SemanticRecall = class {
|
|
860
|
+
topK;
|
|
861
|
+
constructor(topK = 5) {
|
|
862
|
+
this.topK = topK;
|
|
863
|
+
}
|
|
864
|
+
async getInputContext(store, resourceId, threadId) {
|
|
865
|
+
if (!store.search) return "";
|
|
866
|
+
try {
|
|
867
|
+
const entries = await store.search(resourceId, threadId, "*", this.topK);
|
|
868
|
+
if (entries.length === 0) return "";
|
|
869
|
+
const sorted = [...entries].sort((a, b) => {
|
|
870
|
+
const sa = a.score ?? -Infinity;
|
|
871
|
+
const sb = b.score ?? -Infinity;
|
|
872
|
+
if (sa !== sb) return sb - sa;
|
|
873
|
+
return b.timestamp - a.timestamp;
|
|
874
|
+
});
|
|
875
|
+
const lines = sorted.map((e) => {
|
|
876
|
+
const prefix = e.role === "user" ? "User" : e.role === "assistant" ? "Assistant" : "Tool";
|
|
877
|
+
const scoreStr = e.score !== void 0 ? ` [relevance: ${(e.score * 100).toFixed(0)}%]` : "";
|
|
878
|
+
return `${prefix}${scoreStr}: ${e.content}`;
|
|
879
|
+
});
|
|
880
|
+
return `## Relevant memories
|
|
881
|
+
|
|
882
|
+
${lines.join("\n")}`;
|
|
883
|
+
} catch {
|
|
884
|
+
return "";
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
async recordTurn(store, resourceId, threadId, input, output, toolCalls) {
|
|
888
|
+
const entries = [
|
|
889
|
+
{ role: "user", content: input, timestamp: Date.now() },
|
|
890
|
+
{ role: "assistant", content: output, timestamp: Date.now() }
|
|
891
|
+
];
|
|
892
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
893
|
+
for (const tc of toolCalls) {
|
|
894
|
+
entries.push({
|
|
895
|
+
role: "tool",
|
|
896
|
+
content: JSON.stringify({ tool: tc.tool, result: tc.output }),
|
|
897
|
+
timestamp: Date.now()
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
await store.save(resourceId, threadId, entries);
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// src/memory/working-memory.ts
|
|
906
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
907
|
+
import { resolve as resolve4 } from "path";
|
|
908
|
+
import { homedir as homedir3 } from "os";
|
|
909
|
+
import { z } from "zod";
|
|
910
|
+
var updateWorkingMemorySchema = z.object({
|
|
911
|
+
section: z.string().describe("The markdown section heading to update (e.g. 'Interests', 'Goals')"),
|
|
912
|
+
content: z.string().describe("The new bullet-list content for the section")
|
|
913
|
+
});
|
|
914
|
+
var DEFAULT_TEMPLATE = `# User Profile
|
|
915
|
+
|
|
916
|
+
- **Name**:
|
|
917
|
+
- **Interests**:
|
|
918
|
+
- **Goals**:
|
|
919
|
+
- **Preferences**:
|
|
920
|
+
`;
|
|
921
|
+
var WORKING_MEMORY_SYSTEM_INSTRUCTION = `You have access to a persistent working memory that stores information about the user.
|
|
922
|
+
You can read the current working memory below and update it using the \`updateWorkingMemory\` tool.
|
|
923
|
+
|
|
924
|
+
When the user tells you something about themselves, update the working memory to reflect it.
|
|
925
|
+
Use the \`updateWorkingMemory\` tool with \`section\` set to the markdown heading (e.g. "Interests") and \`content\` set to the new bullet list content for that section.`;
|
|
926
|
+
var WorkingMemory = class {
|
|
927
|
+
template;
|
|
928
|
+
content;
|
|
929
|
+
baseDir;
|
|
930
|
+
resourceId;
|
|
931
|
+
initialized = false;
|
|
932
|
+
constructor(template, baseDir, resourceId) {
|
|
933
|
+
this.template = template ?? DEFAULT_TEMPLATE;
|
|
934
|
+
this.content = this.template;
|
|
935
|
+
this.resourceId = resourceId ?? "default";
|
|
936
|
+
this.baseDir = baseDir ?? resolve4(homedir3(), ".arcie", "memory", "working");
|
|
937
|
+
}
|
|
938
|
+
ensureDir() {
|
|
939
|
+
if (!this.initialized) {
|
|
940
|
+
mkdirSync3(this.baseDir, { recursive: true });
|
|
941
|
+
this.initialized = true;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
filePath(resourceId) {
|
|
945
|
+
return resolve4(this.baseDir, `${resourceId}.md`);
|
|
946
|
+
}
|
|
947
|
+
loadFromDisk(resourceId) {
|
|
948
|
+
this.ensureDir();
|
|
949
|
+
const path = this.filePath(resourceId);
|
|
950
|
+
if (existsSync3(path)) {
|
|
951
|
+
this.content = readFileSync3(path, "utf-8");
|
|
952
|
+
} else {
|
|
953
|
+
this.content = this.template;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
saveToDisk(resourceId) {
|
|
957
|
+
this.ensureDir();
|
|
958
|
+
writeFileSync2(this.filePath(resourceId), this.content, "utf-8");
|
|
959
|
+
}
|
|
960
|
+
getSystemInstruction() {
|
|
961
|
+
return WORKING_MEMORY_SYSTEM_INSTRUCTION;
|
|
962
|
+
}
|
|
963
|
+
getCurrentContent(resourceId) {
|
|
964
|
+
this.loadFromDisk(resourceId);
|
|
965
|
+
return this.content;
|
|
966
|
+
}
|
|
967
|
+
updateSection(resourceId, section, content) {
|
|
968
|
+
this.loadFromDisk(resourceId);
|
|
969
|
+
const sectionRegex = new RegExp(`(##\\s*${escapeRegex(section)}[\\s\\S]*?)(?=\\n##\\s|\\n*$)`, "i");
|
|
970
|
+
const existing = this.content.match(sectionRegex);
|
|
971
|
+
if (existing) {
|
|
972
|
+
const headerMatch = existing[0].match(/^##\s+.+$/m);
|
|
973
|
+
const header = headerMatch ? headerMatch[0] : `## ${section}`;
|
|
974
|
+
this.content = this.content.replace(existing[0], `${header}
|
|
975
|
+
|
|
976
|
+
${content}
|
|
977
|
+
`);
|
|
978
|
+
} else {
|
|
979
|
+
this.content += `
|
|
980
|
+
## ${section}
|
|
981
|
+
|
|
982
|
+
${content}
|
|
983
|
+
`;
|
|
984
|
+
}
|
|
985
|
+
this.saveToDisk(resourceId);
|
|
986
|
+
}
|
|
987
|
+
getToolDefinitions() {
|
|
988
|
+
const resourceId = this.resourceId;
|
|
989
|
+
return {
|
|
990
|
+
updateWorkingMemory: {
|
|
991
|
+
description: "Update a section of the working memory with new content. Use this when the user shares personal information.",
|
|
992
|
+
inputSchema: updateWorkingMemorySchema,
|
|
993
|
+
execute: async (input) => {
|
|
994
|
+
const { section, content } = input;
|
|
995
|
+
this.updateSection(resourceId, section, content);
|
|
996
|
+
return { updated: true, section };
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
async getInputContext(_store, resourceId, _threadId) {
|
|
1002
|
+
this.loadFromDisk(resourceId);
|
|
1003
|
+
if (!this.content || this.content === this.template) return "";
|
|
1004
|
+
return `## Working Memory (persistent \u2014 edit ~/.arcie/memory/working/${resourceId}.md directly)
|
|
1005
|
+
|
|
1006
|
+
${this.content}`;
|
|
1007
|
+
}
|
|
1008
|
+
async recordTurn(_store, _resourceId, _threadId, _input, _output, _toolCalls) {
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
function escapeRegex(str) {
|
|
1012
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/memory/processors/message-history.ts
|
|
1016
|
+
var MessageHistoryProcessor = class {
|
|
1017
|
+
name = "message-history";
|
|
1018
|
+
limit;
|
|
1019
|
+
constructor(limit = 10) {
|
|
1020
|
+
this.limit = limit;
|
|
1021
|
+
}
|
|
1022
|
+
async processInput(store, resourceId, threadId) {
|
|
1023
|
+
const entries = await store.load(resourceId, threadId, this.limit);
|
|
1024
|
+
if (entries.length === 0) return "";
|
|
1025
|
+
const lines = entries.map((e) => {
|
|
1026
|
+
const prefix = e.role === "user" ? "User" : e.role === "assistant" ? "Assistant" : "Tool";
|
|
1027
|
+
return `${prefix}: ${e.content}`;
|
|
1028
|
+
});
|
|
1029
|
+
return `## Recent conversation history
|
|
1030
|
+
|
|
1031
|
+
${lines.join("\n")}`;
|
|
1032
|
+
}
|
|
1033
|
+
async processOutput(store, resourceId, threadId, input, output, toolCalls) {
|
|
1034
|
+
const entries = [
|
|
1035
|
+
{ role: "user", content: input, timestamp: Date.now() },
|
|
1036
|
+
{ role: "assistant", content: output, timestamp: Date.now() }
|
|
1037
|
+
];
|
|
1038
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
1039
|
+
for (const tc of toolCalls) {
|
|
1040
|
+
entries.push({
|
|
1041
|
+
role: "tool",
|
|
1042
|
+
content: JSON.stringify({ tool: tc.tool, result: tc.output }),
|
|
1043
|
+
timestamp: Date.now()
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
await store.save(resourceId, threadId, entries);
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
// src/memory/processors/working-memory.ts
|
|
1052
|
+
import { z as z2 } from "zod";
|
|
1053
|
+
var WORKING_MEMORY_KEY = "__working_memory__";
|
|
1054
|
+
var DEFAULT_TEMPLATE2 = `# User Profile
|
|
1055
|
+
|
|
1056
|
+
- **Name**:
|
|
1057
|
+
- **Interests**:
|
|
1058
|
+
- **Goals**:
|
|
1059
|
+
- **Preferences**:
|
|
1060
|
+
`;
|
|
1061
|
+
var TEMPLATE_SYSTEM_INSTRUCTION = `You have access to a persistent working memory that stores information about the user.
|
|
1062
|
+
You can read the current working memory below and update it using the \`updateWorkingMemory\` tool.
|
|
1063
|
+
|
|
1064
|
+
When the user tells you something about themselves, update the working memory to reflect it.
|
|
1065
|
+
Use the \`updateWorkingMemory\` tool with \`content\` set to the full updated markdown content.`;
|
|
1066
|
+
var SCHEMA_SYSTEM_INSTRUCTION = `You have access to a persistent working memory that stores structured data about the user.
|
|
1067
|
+
You can read the current working memory below and update it using the \`updateWorkingMemory\` tool.
|
|
1068
|
+
|
|
1069
|
+
When the user tells you something about themselves, update the working memory to reflect it.
|
|
1070
|
+
The tool uses merge semantics: send only the fields you want to add or change. Set a field to null to delete it.
|
|
1071
|
+
Objects are deep-merged. Arrays are replaced entirely.`;
|
|
1072
|
+
function deepMerge(target, source) {
|
|
1073
|
+
const result = { ...target };
|
|
1074
|
+
for (const key of Object.keys(source)) {
|
|
1075
|
+
const val = source[key];
|
|
1076
|
+
if (val === null) {
|
|
1077
|
+
delete result[key];
|
|
1078
|
+
} else if (val !== null && typeof val === "object" && !Array.isArray(val) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
|
|
1079
|
+
result[key] = deepMerge(result[key], val);
|
|
1080
|
+
} else {
|
|
1081
|
+
result[key] = val;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return result;
|
|
1085
|
+
}
|
|
1086
|
+
var WorkingMemoryProcessor = class {
|
|
1087
|
+
name = "working-memory";
|
|
1088
|
+
store;
|
|
1089
|
+
resourceId;
|
|
1090
|
+
threadId;
|
|
1091
|
+
config;
|
|
1092
|
+
mode;
|
|
1093
|
+
currentContent = "";
|
|
1094
|
+
constructor(store, resourceId, threadId, config) {
|
|
1095
|
+
this.store = store;
|
|
1096
|
+
this.resourceId = resourceId;
|
|
1097
|
+
this.threadId = threadId;
|
|
1098
|
+
this.config = {
|
|
1099
|
+
enabled: true,
|
|
1100
|
+
scope: "resource",
|
|
1101
|
+
...config
|
|
1102
|
+
};
|
|
1103
|
+
if (this.config.schema) {
|
|
1104
|
+
this.mode = { type: "schema", schema: this.config.schema };
|
|
1105
|
+
} else {
|
|
1106
|
+
this.mode = { type: "template", template: this.config.template ?? DEFAULT_TEMPLATE2 };
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
key() {
|
|
1110
|
+
return this.config.scope === "resource" ? this.resourceId : this.threadId;
|
|
1111
|
+
}
|
|
1112
|
+
defaultContent() {
|
|
1113
|
+
if (this.mode.type === "template") return this.mode.template;
|
|
1114
|
+
return JSON.stringify(this.mode.schema.parse({}), null, 2);
|
|
1115
|
+
}
|
|
1116
|
+
async loadContent() {
|
|
1117
|
+
const k = this.key();
|
|
1118
|
+
const entries = await this.store.load(k, WORKING_MEMORY_KEY);
|
|
1119
|
+
if (entries.length === 0) return this.defaultContent();
|
|
1120
|
+
const wm = entries.find((e) => e.role === "system");
|
|
1121
|
+
return wm?.content ?? this.defaultContent();
|
|
1122
|
+
}
|
|
1123
|
+
async processInput(_store, _resourceId, _threadId) {
|
|
1124
|
+
if (!this.config.enabled) return "";
|
|
1125
|
+
this.currentContent = await this.loadContent();
|
|
1126
|
+
if (!this.currentContent || this.currentContent === this.defaultContent()) return "";
|
|
1127
|
+
const label = this.mode.type === "schema" ? "## Working Memory\n\n```json" : "## Working Memory";
|
|
1128
|
+
const suffix = this.mode.type === "schema" ? "```" : "";
|
|
1129
|
+
return `${label}
|
|
1130
|
+
${this.currentContent}${suffix ? `
|
|
1131
|
+
${suffix}` : ""}`;
|
|
1132
|
+
}
|
|
1133
|
+
async processOutput(_store, _resourceId, _threadId, _input, _output, _toolCalls) {
|
|
1134
|
+
}
|
|
1135
|
+
getSystemInstruction() {
|
|
1136
|
+
if (!this.config.enabled) return "";
|
|
1137
|
+
return this.mode.type === "schema" ? SCHEMA_SYSTEM_INSTRUCTION : TEMPLATE_SYSTEM_INSTRUCTION;
|
|
1138
|
+
}
|
|
1139
|
+
getToolDefinitions() {
|
|
1140
|
+
if (!this.config.enabled) return {};
|
|
1141
|
+
const self = this;
|
|
1142
|
+
if (this.mode.type === "schema") {
|
|
1143
|
+
return {
|
|
1144
|
+
updateWorkingMemory: {
|
|
1145
|
+
description: "Update the working memory with new information about the user or conversation. Uses merge semantics: send only the fields to add or change.",
|
|
1146
|
+
inputSchema: z2.object({
|
|
1147
|
+
updates: z2.record(z2.unknown()).describe("Partial data to merge into working memory. Objects deep-merge, arrays replace, null deletes.")
|
|
1148
|
+
}),
|
|
1149
|
+
execute: async (input) => {
|
|
1150
|
+
const { updates } = input;
|
|
1151
|
+
const k = self.key();
|
|
1152
|
+
const raw = await self.loadContent();
|
|
1153
|
+
let current;
|
|
1154
|
+
try {
|
|
1155
|
+
current = JSON.parse(raw);
|
|
1156
|
+
} catch {
|
|
1157
|
+
current = {};
|
|
1158
|
+
}
|
|
1159
|
+
const merged = deepMerge(current, updates);
|
|
1160
|
+
const content = JSON.stringify(merged, null, 2);
|
|
1161
|
+
await self.store.save(k, WORKING_MEMORY_KEY, [
|
|
1162
|
+
{ role: "system", content, timestamp: Date.now() }
|
|
1163
|
+
]);
|
|
1164
|
+
return { updated: true };
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
const updateWmSchema = z2.object({
|
|
1170
|
+
content: z2.string().describe("The full updated markdown content for working memory")
|
|
1171
|
+
});
|
|
1172
|
+
return {
|
|
1173
|
+
updateWorkingMemory: {
|
|
1174
|
+
description: "Update the working memory with new content about the user.",
|
|
1175
|
+
inputSchema: updateWmSchema,
|
|
1176
|
+
execute: async (input) => {
|
|
1177
|
+
const { content } = input;
|
|
1178
|
+
const k = self.key();
|
|
1179
|
+
await self.store.save(k, WORKING_MEMORY_KEY, [
|
|
1180
|
+
{ role: "system", content, timestamp: Date.now() }
|
|
1181
|
+
]);
|
|
1182
|
+
return { updated: true };
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
// src/memory/processors/semantic-recall.ts
|
|
1190
|
+
var SemanticRecallProcessor = class {
|
|
1191
|
+
name = "semantic-recall";
|
|
1192
|
+
config;
|
|
1193
|
+
embedder;
|
|
1194
|
+
vectorStore;
|
|
1195
|
+
constructor(embedder, vectorStore, config) {
|
|
1196
|
+
this.embedder = embedder;
|
|
1197
|
+
this.vectorStore = vectorStore;
|
|
1198
|
+
this.config = {
|
|
1199
|
+
topK: 5,
|
|
1200
|
+
messageRange: 2,
|
|
1201
|
+
scope: "thread",
|
|
1202
|
+
...config
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
async processInput(store, resourceId, threadId) {
|
|
1206
|
+
const entries = await store.load(resourceId, threadId, this.config.topK);
|
|
1207
|
+
if (entries.length === 0) return "";
|
|
1208
|
+
const lines = entries.map((e) => {
|
|
1209
|
+
const prefix = e.role === "user" ? "User" : e.role === "assistant" ? "Assistant" : "Tool";
|
|
1210
|
+
const scoreStr = e.score !== void 0 ? ` [relevance: ${(e.score * 100).toFixed(0)}%]` : "";
|
|
1211
|
+
return `${prefix}${scoreStr}: ${e.content}`;
|
|
1212
|
+
});
|
|
1213
|
+
return `## Relevant past context
|
|
1214
|
+
|
|
1215
|
+
${lines.join("\n")}`;
|
|
1216
|
+
}
|
|
1217
|
+
async processOutput(store, resourceId, threadId, input, output, toolCalls) {
|
|
1218
|
+
const texts = [input, output];
|
|
1219
|
+
if (toolCalls) {
|
|
1220
|
+
for (const tc of toolCalls) {
|
|
1221
|
+
texts.push(JSON.stringify({ tool: tc.tool, result: tc.output }));
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const embeddings = await this.embedder.embedBatch(texts);
|
|
1225
|
+
const namespace = this.config.scope === "resource" ? resourceId : threadId;
|
|
1226
|
+
await this.vectorStore.upsert(namespace, embeddings.map((values, i) => ({
|
|
1227
|
+
id: `${threadId}:${Date.now()}:${i}`,
|
|
1228
|
+
values,
|
|
1229
|
+
metadata: { role: i === 0 ? "user" : i === 1 ? "assistant" : "tool", threadId }
|
|
1230
|
+
})));
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
// src/memory/processors/strategy-adapter.ts
|
|
1235
|
+
var StrategyAdapter = class {
|
|
1236
|
+
name;
|
|
1237
|
+
strategy;
|
|
1238
|
+
constructor(strategy) {
|
|
1239
|
+
this.strategy = strategy;
|
|
1240
|
+
this.name = `${strategy.constructor.name}Adapter`;
|
|
1241
|
+
}
|
|
1242
|
+
async processInput(store, resourceId, threadId) {
|
|
1243
|
+
return this.strategy.getInputContext(store, resourceId, threadId);
|
|
1244
|
+
}
|
|
1245
|
+
async processOutput(store, resourceId, threadId, input, output, toolCalls) {
|
|
1246
|
+
await this.strategy.recordTurn(store, resourceId, threadId, input, output, toolCalls);
|
|
1247
|
+
}
|
|
1248
|
+
getToolDefinitions() {
|
|
1249
|
+
if ("getToolDefinitions" in this.strategy) {
|
|
1250
|
+
return this.strategy.getToolDefinitions();
|
|
1251
|
+
}
|
|
1252
|
+
return {};
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
// src/memory/index.ts
|
|
1257
|
+
var Memory = class _Memory {
|
|
1258
|
+
store;
|
|
1259
|
+
resourceId;
|
|
1260
|
+
threadId;
|
|
1261
|
+
processors;
|
|
1262
|
+
readOnly;
|
|
1263
|
+
buildSource;
|
|
1264
|
+
constructor(configOrOptions, options) {
|
|
1265
|
+
let config;
|
|
1266
|
+
let opts;
|
|
1267
|
+
if (configOrOptions && "store" in configOrOptions) {
|
|
1268
|
+
opts = configOrOptions;
|
|
1269
|
+
config = void 0;
|
|
1270
|
+
} else {
|
|
1271
|
+
config = configOrOptions;
|
|
1272
|
+
opts = options ?? {};
|
|
1273
|
+
}
|
|
1274
|
+
this.buildSource = config ? { type: "config", config, opts } : { type: "options", opts };
|
|
1275
|
+
this.store = opts.store ?? new InMemoryStore();
|
|
1276
|
+
this.resourceId = opts.resourceId ?? "default";
|
|
1277
|
+
this.threadId = opts.threadId ?? "default";
|
|
1278
|
+
this.readOnly = opts.readOnly ?? false;
|
|
1279
|
+
this.processors = [];
|
|
1280
|
+
if (opts.processors) {
|
|
1281
|
+
for (const p of opts.processors) {
|
|
1282
|
+
if ("getInputContext" in p && "recordTurn" in p) {
|
|
1283
|
+
this.processors.push(new StrategyAdapter(p));
|
|
1284
|
+
} else {
|
|
1285
|
+
this.processors.push(p);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
} else if (config) {
|
|
1289
|
+
this.buildFromConfig(config, opts);
|
|
1290
|
+
} else if (opts.lastMessages) {
|
|
1291
|
+
this.processors.push(new MessageHistoryProcessor(opts.lastMessages));
|
|
1292
|
+
} else {
|
|
1293
|
+
this.processors.push(new MessageHistoryProcessor(10));
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
buildFromConfig(config, opts) {
|
|
1297
|
+
if (!config) return;
|
|
1298
|
+
switch (config.strategy) {
|
|
1299
|
+
case "lastN": {
|
|
1300
|
+
const strategy = new LastNStrategy(config.limit ?? 10);
|
|
1301
|
+
this.processors.push(new StrategyAdapter(strategy));
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
1304
|
+
case "summary": {
|
|
1305
|
+
const strategy = new SummaryStrategy(config.limit ?? 10);
|
|
1306
|
+
this.processors.push(new StrategyAdapter(strategy));
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
case "keyFacts": {
|
|
1310
|
+
const strategy = new KeyFactsStrategy();
|
|
1311
|
+
this.processors.push(new StrategyAdapter(strategy));
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
case "semantic": {
|
|
1315
|
+
if (opts.embedder && opts.vectorStore) {
|
|
1316
|
+
this.processors.push(new SemanticRecallProcessor(opts.embedder, opts.vectorStore, {
|
|
1317
|
+
topK: config.limit ?? 5,
|
|
1318
|
+
scope: "resource"
|
|
1319
|
+
}));
|
|
1320
|
+
} else {
|
|
1321
|
+
const strategy = new SemanticRecall(config.limit ?? 5);
|
|
1322
|
+
this.processors.push(new StrategyAdapter(strategy));
|
|
1323
|
+
}
|
|
1324
|
+
break;
|
|
1325
|
+
}
|
|
1326
|
+
default:
|
|
1327
|
+
this.processors.push(new MessageHistoryProcessor(config.limit ?? 10));
|
|
1328
|
+
}
|
|
1329
|
+
if (config.workingMemory) {
|
|
1330
|
+
this.processors.push(new WorkingMemoryProcessor(this.store, this.resourceId, this.threadId, {
|
|
1331
|
+
enabled: true,
|
|
1332
|
+
template: config.workingMemoryTemplate,
|
|
1333
|
+
scope: "resource"
|
|
1334
|
+
}));
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Create a new Memory instance sharing the same store and configuration
|
|
1339
|
+
* but with overridden resourceId, threadId, and/or readOnly.
|
|
1340
|
+
* Useful for per-request memory config.
|
|
1341
|
+
*/
|
|
1342
|
+
with(overrides) {
|
|
1343
|
+
if (this.buildSource.type === "config") {
|
|
1344
|
+
return new _Memory(this.buildSource.config, {
|
|
1345
|
+
...this.buildSource.opts,
|
|
1346
|
+
resourceId: overrides.resourceId ?? this.resourceId,
|
|
1347
|
+
threadId: overrides.threadId ?? this.threadId,
|
|
1348
|
+
readOnly: overrides.readOnly ?? this.readOnly
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
return new _Memory({
|
|
1352
|
+
...this.buildSource.opts,
|
|
1353
|
+
resourceId: overrides.resourceId ?? this.resourceId,
|
|
1354
|
+
threadId: overrides.threadId ?? this.threadId,
|
|
1355
|
+
readOnly: overrides.readOnly ?? this.readOnly
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
async destroy() {
|
|
1359
|
+
await this.store.close?.();
|
|
1360
|
+
}
|
|
1361
|
+
async getInputContext() {
|
|
1362
|
+
const parts = [];
|
|
1363
|
+
for (const p of this.processors) {
|
|
1364
|
+
const ctx = await p.processInput(this.store, this.resourceId, this.threadId);
|
|
1365
|
+
if (ctx) parts.push(ctx);
|
|
1366
|
+
}
|
|
1367
|
+
return parts.join("\n\n");
|
|
1368
|
+
}
|
|
1369
|
+
getSystemInstruction() {
|
|
1370
|
+
const parts = [];
|
|
1371
|
+
for (const p of this.processors) {
|
|
1372
|
+
if ("getSystemInstruction" in p) {
|
|
1373
|
+
const si = p.getSystemInstruction();
|
|
1374
|
+
if (si) parts.push(si);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return parts.join("\n\n");
|
|
1378
|
+
}
|
|
1379
|
+
getToolDefinitions() {
|
|
1380
|
+
if (this.readOnly) return {};
|
|
1381
|
+
for (const p of this.processors) {
|
|
1382
|
+
if ("getToolDefinitions" in p) {
|
|
1383
|
+
const tools = p.getToolDefinitions();
|
|
1384
|
+
if (Object.keys(tools).length > 0) return tools;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return {};
|
|
1388
|
+
}
|
|
1389
|
+
async recordTurn(input, output, toolCalls) {
|
|
1390
|
+
if (this.readOnly) return;
|
|
1391
|
+
for (const p of this.processors) {
|
|
1392
|
+
if (p.processOutput) {
|
|
1393
|
+
await p.processOutput(this.store, this.resourceId, this.threadId, input, output, toolCalls);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
async clear() {
|
|
1398
|
+
await this.store.clear(this.resourceId, this.threadId);
|
|
1399
|
+
}
|
|
1400
|
+
// ── Thread API ──
|
|
1401
|
+
async createThread(thread) {
|
|
1402
|
+
if (!this.store.createThread) throw new Error("Store does not support thread creation");
|
|
1403
|
+
await this.store.createThread(thread);
|
|
1404
|
+
}
|
|
1405
|
+
async getThread(threadId, resourceId) {
|
|
1406
|
+
if (!this.store.getThread) throw new Error("Store does not support thread retrieval");
|
|
1407
|
+
return this.store.getThread(threadId, resourceId);
|
|
1408
|
+
}
|
|
1409
|
+
async listThreads(resourceId) {
|
|
1410
|
+
if (!this.store.listThreads) throw new Error("Store does not support thread listing");
|
|
1411
|
+
return this.store.listThreads(resourceId);
|
|
1412
|
+
}
|
|
1413
|
+
async updateThread(thread) {
|
|
1414
|
+
if (!this.store.updateThread) throw new Error("Store does not support thread updates");
|
|
1415
|
+
await this.store.updateThread(thread);
|
|
1416
|
+
}
|
|
1417
|
+
async deleteThread(threadId, resourceId) {
|
|
1418
|
+
if (!this.store.deleteThread) throw new Error("Store does not support thread deletion");
|
|
1419
|
+
await this.store.deleteThread(threadId, resourceId);
|
|
1420
|
+
}
|
|
1421
|
+
// ── Delete messages ──
|
|
1422
|
+
async deleteMessages(opts) {
|
|
1423
|
+
if (!this.store.deleteMessages) throw new Error("Store does not support message deletion");
|
|
1424
|
+
return this.store.deleteMessages(opts);
|
|
1425
|
+
}
|
|
1426
|
+
// ── Clone thread ──
|
|
1427
|
+
async cloneThread(source, dest) {
|
|
1428
|
+
if (!this.store.cloneThread) throw new Error("Store does not support thread cloning");
|
|
1429
|
+
await this.store.cloneThread(source, dest);
|
|
1430
|
+
}
|
|
1431
|
+
// ── Recall API ──
|
|
1432
|
+
async recall(opts) {
|
|
1433
|
+
let entries = await this.store.load(opts.resourceId, opts.threadId);
|
|
1434
|
+
if (opts.dateRange) {
|
|
1435
|
+
const start2 = opts.dateRange.start?.getTime() ?? 0;
|
|
1436
|
+
const end = opts.dateRange.end?.getTime() ?? Infinity;
|
|
1437
|
+
entries = entries.filter((e) => e.timestamp >= start2 && e.timestamp <= end);
|
|
1438
|
+
}
|
|
1439
|
+
if (opts.include && opts.include.length > 0) {
|
|
1440
|
+
const included = [];
|
|
1441
|
+
for (const inc of opts.include) {
|
|
1442
|
+
const idx = entries.findIndex((e) => e.turnId === inc.id);
|
|
1443
|
+
if (idx === -1) continue;
|
|
1444
|
+
const start2 = Math.max(0, idx - (inc.withPreviousMessages ?? 0));
|
|
1445
|
+
const end = Math.min(entries.length, idx + 1 + (inc.withNextMessages ?? 0));
|
|
1446
|
+
for (let i = start2; i < end; i++) {
|
|
1447
|
+
included.push(entries[i]);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
entries = included;
|
|
1451
|
+
}
|
|
1452
|
+
if (opts.vectorSearchString && this.store.search) {
|
|
1453
|
+
entries = await this.store.search(opts.resourceId, opts.threadId, opts.vectorSearchString);
|
|
1454
|
+
}
|
|
1455
|
+
const total = entries.length;
|
|
1456
|
+
const perPage = opts.perPage ?? 50;
|
|
1457
|
+
const page = opts.page ?? 0;
|
|
1458
|
+
const start = page * perPage;
|
|
1459
|
+
const hasMore = start + perPage < total;
|
|
1460
|
+
return {
|
|
1461
|
+
messages: entries.slice(start, start + perPage),
|
|
1462
|
+
total,
|
|
1463
|
+
hasMore
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
// src/runner/index.ts
|
|
1469
|
+
var DEFAULT_ENDPOINT = "https://cencori.com/v1";
|
|
1470
|
+
async function createChildSession(endpoint, apiKey, agent, subagentId) {
|
|
1471
|
+
const sub = agent.manifest.subagents[subagentId];
|
|
1472
|
+
const res = await fetch(`${endpoint}/sessions`, {
|
|
1473
|
+
method: "POST",
|
|
1474
|
+
headers: {
|
|
1475
|
+
"Content-Type": "application/json",
|
|
1476
|
+
Authorization: `Bearer ${apiKey}`
|
|
1477
|
+
},
|
|
1478
|
+
body: JSON.stringify({
|
|
1479
|
+
agent_id: `${agent.manifest.config.name ?? "unnamed"}.${subagentId}`,
|
|
1480
|
+
metadata: {
|
|
1481
|
+
model: sub.config.model,
|
|
1482
|
+
instructions: sub.instructions
|
|
1483
|
+
}
|
|
1484
|
+
})
|
|
1485
|
+
});
|
|
1486
|
+
if (!res.ok) {
|
|
1487
|
+
const err = await res.text();
|
|
1488
|
+
throw new Error(`Subagent session error (${res.status}): ${err}`);
|
|
1489
|
+
}
|
|
1490
|
+
const data = await res.json();
|
|
1491
|
+
return data.id;
|
|
1492
|
+
}
|
|
1493
|
+
async function executeSubagent(endpoint, apiKey, agent, subagentId, input, childSessionId) {
|
|
1494
|
+
const sub = agent.manifest.subagents[subagentId];
|
|
1495
|
+
childSessionId ??= await createChildSession(endpoint, apiKey, agent, subagentId);
|
|
1496
|
+
const inputStr = typeof input === "string" ? input : JSON.stringify(input);
|
|
1497
|
+
const tools = Object.entries(sub.tools).map(([name, tool]) => ({
|
|
1498
|
+
name,
|
|
1499
|
+
description: tool.description,
|
|
1500
|
+
input_schema: {},
|
|
1501
|
+
type: "function"
|
|
1502
|
+
}));
|
|
1503
|
+
const res = await fetch(`${endpoint}/sessions/${childSessionId}/turns`, {
|
|
1504
|
+
method: "POST",
|
|
1505
|
+
headers: {
|
|
1506
|
+
"Content-Type": "application/json",
|
|
1507
|
+
Authorization: `Bearer ${apiKey}`
|
|
1508
|
+
},
|
|
1509
|
+
body: JSON.stringify({
|
|
1510
|
+
model: sub.config.model,
|
|
1511
|
+
input: inputStr,
|
|
1512
|
+
instructions: sub.instructions,
|
|
1513
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
1514
|
+
stream: true,
|
|
1515
|
+
pause_on_tool_calls: false
|
|
1516
|
+
})
|
|
1517
|
+
});
|
|
1518
|
+
if (!res.ok) {
|
|
1519
|
+
const err = await res.text();
|
|
1520
|
+
throw new Error(`Subagent turn error (${res.status}): ${err}`);
|
|
1521
|
+
}
|
|
1522
|
+
let output = "";
|
|
1523
|
+
let currentEvent = "";
|
|
1524
|
+
const reader = res.body?.getReader();
|
|
1525
|
+
if (reader) {
|
|
1526
|
+
const decoder = new TextDecoder();
|
|
1527
|
+
let buffer = "";
|
|
1528
|
+
while (true) {
|
|
1529
|
+
const { done, value } = await reader.read();
|
|
1530
|
+
if (done) break;
|
|
1531
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1532
|
+
const lines = buffer.split("\n");
|
|
1533
|
+
buffer = lines.pop() ?? "";
|
|
1534
|
+
for (const line of lines) {
|
|
1535
|
+
if (line.startsWith("event: ")) {
|
|
1536
|
+
currentEvent = line.slice(7);
|
|
1537
|
+
} else if (line.startsWith("data: ")) {
|
|
1538
|
+
const payload = line.slice(6);
|
|
1539
|
+
if (payload === "[DONE]") continue;
|
|
1540
|
+
try {
|
|
1541
|
+
const parsed = JSON.parse(payload);
|
|
1542
|
+
const eventType = currentEvent || parsed.type;
|
|
1543
|
+
if (eventType === "message.appended" && parsed.delta) {
|
|
1544
|
+
output += parsed.delta;
|
|
1545
|
+
}
|
|
1546
|
+
} catch {
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return output || inputStr;
|
|
1553
|
+
}
|
|
1554
|
+
function headers(apiKey, agent) {
|
|
1555
|
+
return {
|
|
1556
|
+
"Content-Type": "application/json",
|
|
1557
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1558
|
+
...agent.manifest.config.cencori?.project ? { "X-Project-ID": agent.manifest.config.cencori.project } : {}
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
async function createSession(endpoint, apiKey, agent) {
|
|
1562
|
+
const res = await fetch(`${endpoint}/sessions`, {
|
|
1563
|
+
method: "POST",
|
|
1564
|
+
headers: headers(apiKey, agent),
|
|
1565
|
+
body: JSON.stringify({
|
|
1566
|
+
agent_id: agent.manifest.config.name,
|
|
1567
|
+
metadata: {
|
|
1568
|
+
model: agent.manifest.config.model,
|
|
1569
|
+
instructions: agent.manifest.instructions
|
|
1570
|
+
}
|
|
1571
|
+
})
|
|
1572
|
+
});
|
|
1573
|
+
if (!res.ok) {
|
|
1574
|
+
const err = await res.text();
|
|
1575
|
+
throw new Error(`Cencori Sessions API error (${res.status}): ${err}`);
|
|
1576
|
+
}
|
|
1577
|
+
const data = await res.json();
|
|
1578
|
+
return data.id;
|
|
1579
|
+
}
|
|
1580
|
+
async function runAgent(agentDir, input, options = {}) {
|
|
1581
|
+
const agent = await loadAgent(agentDir);
|
|
1582
|
+
const endpoint = options.endpoint || process.env.CENCORI_API_URL || DEFAULT_ENDPOINT;
|
|
1583
|
+
const apiKey = options.apiKey || process.env.CENCORI_API_KEY || "";
|
|
1584
|
+
const events = [];
|
|
1585
|
+
let outputText = "";
|
|
1586
|
+
if (!apiKey) {
|
|
1587
|
+
throw new Error(
|
|
1588
|
+
"Cencori API key required. Set CENCORI_API_KEY env var or pass apiKey in options."
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
const sessionId = options.sessionId || await createSession(endpoint, apiKey, agent);
|
|
1592
|
+
for await (const event of streamAgent(agentDir, input, { ...options, sessionId, endpoint, apiKey })) {
|
|
1593
|
+
events.push(event);
|
|
1594
|
+
options.onEvent?.(event);
|
|
1595
|
+
if (event.type === "message.completed" && event.data.text) {
|
|
1596
|
+
outputText = event.data.text;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
const toolCalls = [];
|
|
1600
|
+
for (const e of events) {
|
|
1601
|
+
if (e.type === "tool.started") {
|
|
1602
|
+
const completed = events.find(
|
|
1603
|
+
(ev) => ev.type === "tool.completed" && ev.data.callId === e.data.callId
|
|
1604
|
+
);
|
|
1605
|
+
const error = completed?.data.status === "failed" || completed?.data.status === "rejected" ? new Error(completed.data.error?.message ?? "Tool failed") : void 0;
|
|
1606
|
+
toolCalls.push({
|
|
1607
|
+
tool: e.data.name,
|
|
1608
|
+
input: e.data.input,
|
|
1609
|
+
output: completed?.data.output,
|
|
1610
|
+
durationMs: void 0,
|
|
1611
|
+
...error ? { error } : {}
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
return {
|
|
1616
|
+
output: outputText,
|
|
1617
|
+
turns: [{
|
|
1618
|
+
id: events.find((e) => e.type === "turn.started")?.data.turnId ?? "",
|
|
1619
|
+
sessionId,
|
|
1620
|
+
input,
|
|
1621
|
+
output: outputText,
|
|
1622
|
+
toolCalls
|
|
1623
|
+
}],
|
|
1624
|
+
events,
|
|
1625
|
+
sessionId
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
async function* readTurnSSE(response, turnId) {
|
|
1629
|
+
if (!response.ok) {
|
|
1630
|
+
const error = await response.text();
|
|
1631
|
+
throw new Error(`Cencori Sessions API error (${response.status}): ${error}`);
|
|
1632
|
+
}
|
|
1633
|
+
const reader = response.body?.getReader();
|
|
1634
|
+
if (!reader) return { status: "completed", text: "" };
|
|
1635
|
+
const decoder = new TextDecoder();
|
|
1636
|
+
let buffer = "";
|
|
1637
|
+
let currentEvent = "";
|
|
1638
|
+
let textSoFar = "";
|
|
1639
|
+
const toolCalls = [];
|
|
1640
|
+
while (true) {
|
|
1641
|
+
const { done, value } = await reader.read();
|
|
1642
|
+
if (done) break;
|
|
1643
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1644
|
+
const lines = buffer.split("\n");
|
|
1645
|
+
buffer = lines.pop() ?? "";
|
|
1646
|
+
for (const line of lines) {
|
|
1647
|
+
if (line.startsWith("event: ")) {
|
|
1648
|
+
currentEvent = line.slice(7);
|
|
1649
|
+
} else if (line.startsWith("data: ")) {
|
|
1650
|
+
const payload = line.slice(6);
|
|
1651
|
+
if (payload === "[DONE]") continue;
|
|
1652
|
+
try {
|
|
1653
|
+
const parsed = JSON.parse(payload);
|
|
1654
|
+
const eventType = currentEvent || parsed.type;
|
|
1655
|
+
switch (eventType) {
|
|
1656
|
+
case "turn.started": {
|
|
1657
|
+
yield createTurnStarted(parsed.turn_number ?? 1, turnId);
|
|
1658
|
+
yield createMessageReceived(parsed.input_text ?? "", parsed.turn_number ?? 1, turnId);
|
|
1659
|
+
yield createStepStarted(1, 0, turnId);
|
|
1660
|
+
break;
|
|
1661
|
+
}
|
|
1662
|
+
case "output_text.delta": {
|
|
1663
|
+
if (parsed.delta) {
|
|
1664
|
+
textSoFar += parsed.delta;
|
|
1665
|
+
yield createMessageAppended(parsed.delta, textSoFar, 1, 0, turnId);
|
|
1666
|
+
}
|
|
1667
|
+
break;
|
|
1668
|
+
}
|
|
1669
|
+
case "tool_call.started": {
|
|
1670
|
+
toolCalls.push({
|
|
1671
|
+
name: parsed.tool,
|
|
1672
|
+
args: parsed.arguments,
|
|
1673
|
+
actionId: parsed.action_id
|
|
1674
|
+
});
|
|
1675
|
+
yield createToolCallStarted(
|
|
1676
|
+
parsed.tool,
|
|
1677
|
+
parsed.arguments,
|
|
1678
|
+
parsed.action_id,
|
|
1679
|
+
1,
|
|
1680
|
+
0,
|
|
1681
|
+
turnId
|
|
1682
|
+
);
|
|
1683
|
+
break;
|
|
1684
|
+
}
|
|
1685
|
+
case "turn.paused": {
|
|
1686
|
+
currentEvent = "";
|
|
1687
|
+
return {
|
|
1688
|
+
status: "paused",
|
|
1689
|
+
toolCalls: [...toolCalls],
|
|
1690
|
+
text: textSoFar
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
case "turn.completed": {
|
|
1694
|
+
const out = parsed.output;
|
|
1695
|
+
let finalText = textSoFar;
|
|
1696
|
+
let finishReason = "stop";
|
|
1697
|
+
if (out?.error) {
|
|
1698
|
+
finishReason = "error";
|
|
1699
|
+
} else if (Array.isArray(out?.output)) {
|
|
1700
|
+
for (const item of out.output) {
|
|
1701
|
+
if (item.type === "message") {
|
|
1702
|
+
const tc = item.content?.find((c) => c.type === "output_text");
|
|
1703
|
+
if (tc?.text) finalText = tc.text;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
textSoFar = finalText;
|
|
1708
|
+
yield createMessageCompleted(finalText, finishReason, 1, 0, turnId);
|
|
1709
|
+
yield createStepCompleted(finishReason, 1, 0, turnId);
|
|
1710
|
+
yield createTurnCompleted(1, turnId);
|
|
1711
|
+
currentEvent = "";
|
|
1712
|
+
return { status: "completed", text: finalText };
|
|
1713
|
+
}
|
|
1714
|
+
case "turn.resumed": {
|
|
1715
|
+
break;
|
|
1716
|
+
}
|
|
1717
|
+
default: {
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
currentEvent = "";
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return { status: "completed", text: textSoFar };
|
|
1728
|
+
}
|
|
1729
|
+
async function* streamAgent(agentDir, input, options = {}) {
|
|
1730
|
+
const agent = await loadAgent(agentDir);
|
|
1731
|
+
const endpoint = options.endpoint || process.env.CENCORI_API_URL || DEFAULT_ENDPOINT;
|
|
1732
|
+
const apiKey = options.apiKey || process.env.CENCORI_API_KEY || "";
|
|
1733
|
+
if (!apiKey) {
|
|
1734
|
+
throw new Error(
|
|
1735
|
+
"Cencori API key required. Set CENCORI_API_KEY env var or pass apiKey in options."
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
const sessionId = options.sessionId || await createSession(endpoint, apiKey, agent);
|
|
1739
|
+
const model = agent.manifest.config.model;
|
|
1740
|
+
const memory = agent.manifest.session?.memory ? new Memory(agent.manifest.session.memory, {
|
|
1741
|
+
store: options.memoryStore,
|
|
1742
|
+
resourceId: options.resourceId,
|
|
1743
|
+
threadId: options.threadId
|
|
1744
|
+
}) : null;
|
|
1745
|
+
const memoryTools = memory ? memory.getToolDefinitions() : {};
|
|
1746
|
+
const allTools = { ...agent.manifest.tools, ...memoryTools };
|
|
1747
|
+
const tools = Object.entries(allTools).map(
|
|
1748
|
+
([name, tool]) => toModelOutput(name, tool)
|
|
1749
|
+
);
|
|
1750
|
+
const memoryContext = memory ? await memory.getInputContext() : "";
|
|
1751
|
+
const memoryInstruction = memory ? memory.getSystemInstruction() : "";
|
|
1752
|
+
const instructions = [
|
|
1753
|
+
agent.manifest.instructions,
|
|
1754
|
+
memoryInstruction,
|
|
1755
|
+
memoryContext
|
|
1756
|
+
].filter(Boolean).join("\n\n");
|
|
1757
|
+
yield createSessionStarted(sessionId, {
|
|
1758
|
+
agentId: agent.manifest.config.name ?? "unnamed",
|
|
1759
|
+
modelId: model,
|
|
1760
|
+
arcieVersion: "0.1.2"
|
|
1761
|
+
});
|
|
1762
|
+
const turnId = crypto.randomUUID();
|
|
1763
|
+
const maxToolLoops = options.maxTurns ?? 25;
|
|
1764
|
+
let response = await fetch(`${endpoint}/sessions/${sessionId}/turns`, {
|
|
1765
|
+
method: "POST",
|
|
1766
|
+
headers: headers(apiKey, agent),
|
|
1767
|
+
body: JSON.stringify({
|
|
1768
|
+
model,
|
|
1769
|
+
input,
|
|
1770
|
+
instructions,
|
|
1771
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
1772
|
+
stream: true,
|
|
1773
|
+
pause_on_tool_calls: true
|
|
1774
|
+
})
|
|
1775
|
+
});
|
|
1776
|
+
if (!response.ok) {
|
|
1777
|
+
const error = await response.text();
|
|
1778
|
+
yield createTurnCompleted(1, turnId);
|
|
1779
|
+
yield createSessionCompleted();
|
|
1780
|
+
throw new Error(`Cencori Sessions API error (${response.status}): ${error}`);
|
|
1781
|
+
}
|
|
1782
|
+
const approvedTools = /* @__PURE__ */ new Set();
|
|
1783
|
+
const allToolCalls = [];
|
|
1784
|
+
for (let loop = 0; loop < maxToolLoops; loop++) {
|
|
1785
|
+
const eventGen = readTurnSSE(response, turnId);
|
|
1786
|
+
let result;
|
|
1787
|
+
let iter = await eventGen.next();
|
|
1788
|
+
while (!iter.done) {
|
|
1789
|
+
yield iter.value;
|
|
1790
|
+
iter = await eventGen.next();
|
|
1791
|
+
}
|
|
1792
|
+
result = iter.value;
|
|
1793
|
+
if (result.status === "completed") {
|
|
1794
|
+
if (memory) {
|
|
1795
|
+
await memory.recordTurn(input, result.text, allToolCalls.length > 0 ? allToolCalls : void 0);
|
|
1796
|
+
}
|
|
1797
|
+
break;
|
|
1798
|
+
}
|
|
1799
|
+
if (result.status !== "paused" || result.toolCalls.length === 0) break;
|
|
1800
|
+
const toolResults = [];
|
|
1801
|
+
let needsUserApproval = false;
|
|
1802
|
+
for (const tc of result.toolCalls) {
|
|
1803
|
+
const toolDef = agent.manifest.tools[tc.name] ?? memoryTools[tc.name];
|
|
1804
|
+
const subDef = agent.manifest.subagents[tc.name];
|
|
1805
|
+
const strategy = toolDef?.needsApproval;
|
|
1806
|
+
if (strategy === "always" || strategy === "once" && !approvedTools.has(tc.name)) {
|
|
1807
|
+
needsUserApproval = true;
|
|
1808
|
+
yield createToolCallCompleted(
|
|
1809
|
+
tc.name,
|
|
1810
|
+
null,
|
|
1811
|
+
tc.actionId,
|
|
1812
|
+
"pending",
|
|
1813
|
+
{ code: "needs_approval", message: `Tool "${tc.name}" requires approval` },
|
|
1814
|
+
1,
|
|
1815
|
+
0,
|
|
1816
|
+
turnId
|
|
1817
|
+
);
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
if (strategy === "once") approvedTools.add(tc.name);
|
|
1821
|
+
if (toolDef?.execute) {
|
|
1822
|
+
try {
|
|
1823
|
+
const output = await toolDef.execute(tc.args);
|
|
1824
|
+
const outputStr = typeof output === "string" ? output : JSON.stringify(output);
|
|
1825
|
+
yield createToolCallCompleted(tc.name, outputStr, tc.actionId, "completed", void 0, 1, 0, turnId);
|
|
1826
|
+
toolResults.push({ action_id: tc.actionId, output: outputStr });
|
|
1827
|
+
allToolCalls.push({ tool: tc.name, input: tc.args, output });
|
|
1828
|
+
} catch (err) {
|
|
1829
|
+
const msg = err instanceof Error ? err.message : "Tool execution failed";
|
|
1830
|
+
yield createToolCallCompleted(tc.name, msg, tc.actionId, "failed", { code: "execution_error", message: msg }, 1, 0, turnId);
|
|
1831
|
+
toolResults.push({ action_id: tc.actionId, output: `Error: ${msg}` });
|
|
1832
|
+
allToolCalls.push({ tool: tc.name, input: tc.args, output: `Error: ${msg}` });
|
|
1833
|
+
}
|
|
1834
|
+
} else if (subDef) {
|
|
1835
|
+
const callId = tc.actionId;
|
|
1836
|
+
try {
|
|
1837
|
+
const childSessionId = await createChildSession(endpoint, apiKey, agent, tc.name);
|
|
1838
|
+
yield createSubagentCalled(tc.name, callId, childSessionId, turnId);
|
|
1839
|
+
const output = await executeSubagent(endpoint, apiKey, agent, tc.name, tc.args, childSessionId);
|
|
1840
|
+
yield createSubagentCompleted(tc.name, callId, output);
|
|
1841
|
+
yield createToolCallCompleted(tc.name, output, tc.actionId, "completed", void 0, 1, 0, turnId);
|
|
1842
|
+
toolResults.push({ action_id: tc.actionId, output });
|
|
1843
|
+
allToolCalls.push({ tool: tc.name, input: tc.args, output });
|
|
1844
|
+
} catch (err) {
|
|
1845
|
+
const msg = err instanceof Error ? err.message : "Subagent execution failed";
|
|
1846
|
+
yield createSubagentCompleted(tc.name, callId, `Error: ${msg}`);
|
|
1847
|
+
yield createToolCallCompleted(tc.name, msg, tc.actionId, "failed", { code: "execution_error", message: msg }, 1, 0, turnId);
|
|
1848
|
+
toolResults.push({ action_id: tc.actionId, output: `Error: ${msg}` });
|
|
1849
|
+
allToolCalls.push({ tool: tc.name, input: tc.args, output: `Error: ${msg}` });
|
|
1850
|
+
}
|
|
1851
|
+
} else {
|
|
1852
|
+
yield createToolCallCompleted(tc.name, "Tool not found in agent manifest", tc.actionId, "failed", { code: "tool_not_found", message: `Tool "${tc.name}" not defined in agent` }, 1, 0, turnId);
|
|
1853
|
+
toolResults.push({ action_id: tc.actionId, output: "Error: tool not defined" });
|
|
1854
|
+
allToolCalls.push({ tool: tc.name, input: tc.args, output: "Error: tool not defined" });
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
if (needsUserApproval && toolResults.length === 0) {
|
|
1858
|
+
yield createSessionWaiting();
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
const firstActionId = toolResults[0].action_id;
|
|
1862
|
+
const approveRes = await fetch(
|
|
1863
|
+
`${endpoint}/sessions/${sessionId}/approve`,
|
|
1864
|
+
{
|
|
1865
|
+
method: "POST",
|
|
1866
|
+
headers: headers(apiKey, agent),
|
|
1867
|
+
body: JSON.stringify({
|
|
1868
|
+
action_id: firstActionId,
|
|
1869
|
+
tool_results: toolResults
|
|
1870
|
+
})
|
|
1871
|
+
}
|
|
1872
|
+
);
|
|
1873
|
+
if (!approveRes.ok) {
|
|
1874
|
+
const errText = await approveRes.text();
|
|
1875
|
+
yield createTurnCompleted(1, turnId);
|
|
1876
|
+
yield createSessionCompleted();
|
|
1877
|
+
throw new Error(`Cencori Sessions approve error (${approveRes.status}): ${errText}`);
|
|
1878
|
+
}
|
|
1879
|
+
response = approveRes;
|
|
1880
|
+
}
|
|
1881
|
+
yield createSessionWaiting();
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
export {
|
|
1885
|
+
InMemoryStore,
|
|
1886
|
+
SqliteStore,
|
|
1887
|
+
FileStore,
|
|
1888
|
+
CencoriMemoryStore,
|
|
1889
|
+
LastNStrategy,
|
|
1890
|
+
KeyFactsStrategy,
|
|
1891
|
+
SummaryStrategy,
|
|
1892
|
+
SemanticRecall,
|
|
1893
|
+
DEFAULT_TEMPLATE,
|
|
1894
|
+
WORKING_MEMORY_SYSTEM_INSTRUCTION,
|
|
1895
|
+
WorkingMemory,
|
|
1896
|
+
Memory,
|
|
1897
|
+
loadAgent,
|
|
1898
|
+
runAgent,
|
|
1899
|
+
streamAgent
|
|
1900
|
+
};
|
|
1901
|
+
//# sourceMappingURL=chunk-2ZSKZYWK.js.map
|