@jussmor/sdk-ai 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-kIkMQdYK.d.cts +105 -0
- package/dist/conversation-kIkMQdYK.d.ts +105 -0
- package/dist/conversation-store-CAyPuBjk.d.ts +10 -0
- package/dist/conversation-store-Cl42jpsA.d.cts +10 -0
- package/dist/index.cjs +1630 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +251 -0
- package/dist/index.d.ts +251 -0
- package/dist/index.js +1536 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-uBLqrQRY.d.cts +28 -0
- package/dist/memory-uBLqrQRY.d.ts +28 -0
- package/dist/providers/llm/anthropic.cjs +275 -0
- package/dist/providers/llm/anthropic.cjs.map +1 -0
- package/dist/providers/llm/anthropic.d.cts +22 -0
- package/dist/providers/llm/anthropic.d.ts +22 -0
- package/dist/providers/llm/anthropic.js +240 -0
- package/dist/providers/llm/anthropic.js.map +1 -0
- package/dist/providers/llm/ollama.cjs +195 -0
- package/dist/providers/llm/ollama.cjs.map +1 -0
- package/dist/providers/llm/ollama.d.cts +23 -0
- package/dist/providers/llm/ollama.d.ts +23 -0
- package/dist/providers/llm/ollama.js +170 -0
- package/dist/providers/llm/ollama.js.map +1 -0
- package/dist/providers/llm/openai.cjs +213 -0
- package/dist/providers/llm/openai.cjs.map +1 -0
- package/dist/providers/llm/openai.d.cts +22 -0
- package/dist/providers/llm/openai.d.ts +22 -0
- package/dist/providers/llm/openai.js +178 -0
- package/dist/providers/llm/openai.js.map +1 -0
- package/dist/providers/memory/filesystem.cjs +112 -0
- package/dist/providers/memory/filesystem.cjs.map +1 -0
- package/dist/providers/memory/filesystem.d.cts +17 -0
- package/dist/providers/memory/filesystem.d.ts +17 -0
- package/dist/providers/memory/filesystem.js +87 -0
- package/dist/providers/memory/filesystem.js.map +1 -0
- package/dist/providers/store/filesystem.cjs +87 -0
- package/dist/providers/store/filesystem.cjs.map +1 -0
- package/dist/providers/store/filesystem.d.cts +14 -0
- package/dist/providers/store/filesystem.d.ts +14 -0
- package/dist/providers/store/filesystem.js +62 -0
- package/dist/providers/store/filesystem.js.map +1 -0
- package/dist/providers/thread/memory.cjs +81 -0
- package/dist/providers/thread/memory.cjs.map +1 -0
- package/dist/providers/thread/memory.d.cts +14 -0
- package/dist/providers/thread/memory.d.ts +14 -0
- package/dist/providers/thread/memory.js +56 -0
- package/dist/providers/thread/memory.js.map +1 -0
- package/dist/providers/thread/sqlite.cjs +917 -0
- package/dist/providers/thread/sqlite.cjs.map +1 -0
- package/dist/providers/thread/sqlite.d.cts +17 -0
- package/dist/providers/thread/sqlite.d.ts +17 -0
- package/dist/providers/thread/sqlite.js +911 -0
- package/dist/providers/thread/sqlite.js.map +1 -0
- package/dist/providers/tokenizers/auto.cjs +136 -0
- package/dist/providers/tokenizers/auto.cjs.map +1 -0
- package/dist/providers/tokenizers/auto.d.cts +24 -0
- package/dist/providers/tokenizers/auto.d.ts +24 -0
- package/dist/providers/tokenizers/auto.js +107 -0
- package/dist/providers/tokenizers/auto.js.map +1 -0
- package/dist/streaming-B-P6Fw_k.d.cts +372 -0
- package/dist/streaming-BtD23BE0.d.ts +372 -0
- package/dist/thread-C2b9xRMJ.d.cts +30 -0
- package/dist/thread-C2b9xRMJ.d.ts +30 -0
- package/dist/tokenizer-BhG_RGUk.d.cts +13 -0
- package/dist/tokenizer-BhG_RGUk.d.ts +13 -0
- package/package.json +84 -0
- package/src/agent-loop.ts +311 -0
- package/src/agent-source.ts +12 -0
- package/src/artifact.ts +31 -0
- package/src/compaction.ts +75 -0
- package/src/context-budget.ts +65 -0
- package/src/conversation-store.ts +8 -0
- package/src/conversation.ts +42 -0
- package/src/dispatch.ts +207 -0
- package/src/engine.ts +53 -0
- package/src/execution-context.ts +31 -0
- package/src/index.ts +37 -0
- package/src/interrupt-store.ts +25 -0
- package/src/interrupt.ts +55 -0
- package/src/llm-router.ts +34 -0
- package/src/llm.ts +100 -0
- package/src/memory-selector.ts +38 -0
- package/src/memory.ts +34 -0
- package/src/mode.ts +81 -0
- package/src/permissions.ts +104 -0
- package/src/protocol.ts +1 -0
- package/src/providers/llm/anthropic.ts +298 -0
- package/src/providers/llm/ollama.ts +219 -0
- package/src/providers/llm/openai.ts +215 -0
- package/src/providers/memory/filesystem.ts +99 -0
- package/src/providers/store/filesystem.ts +64 -0
- package/src/providers/thread/memory.ts +67 -0
- package/src/providers/thread/sqlite.ts +147 -0
- package/src/providers/tokenizers/auto.ts +26 -0
- package/src/providers/tokenizers/byte.ts +27 -0
- package/src/providers/tokenizers/tiktoken.ts +91 -0
- package/src/reasoning.ts +7 -0
- package/src/rule-matcher.ts +32 -0
- package/src/runtime.ts +416 -0
- package/src/safety.ts +56 -0
- package/src/sandbox.ts +23 -0
- package/src/session-context.ts +33 -0
- package/src/skill-source.ts +21 -0
- package/src/streaming.ts +124 -0
- package/src/system-prompt.ts +71 -0
- package/src/system-reminder.ts +9 -0
- package/src/thread.ts +33 -0
- package/src/tokenizer.ts +31 -0
- package/src/tool.ts +175 -0
- package/src/tracing.ts +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1536 @@
|
|
|
1
|
+
// src/protocol.ts
|
|
2
|
+
var PROTOCOL_VERSION = "1";
|
|
3
|
+
|
|
4
|
+
// src/tool.ts
|
|
5
|
+
function toToolDef(t) {
|
|
6
|
+
return {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: t.name,
|
|
10
|
+
description: t.description,
|
|
11
|
+
parameters: t.parameters
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function scoreToolMatch(t, query) {
|
|
16
|
+
let score = 0;
|
|
17
|
+
if (t.name.toLowerCase().includes(query)) score += 0.6;
|
|
18
|
+
if (t.searchHint?.toLowerCase().includes(query)) score += 0.4;
|
|
19
|
+
if (t.description.toLowerCase().includes(query)) score += 0.3;
|
|
20
|
+
if (t.category.toLowerCase().includes(query)) score += 0.1;
|
|
21
|
+
if (t.aliases?.some((a) => a.toLowerCase().includes(query))) score += 0.2;
|
|
22
|
+
return score;
|
|
23
|
+
}
|
|
24
|
+
var ToolRegistry = class {
|
|
25
|
+
tools = /* @__PURE__ */ new Map();
|
|
26
|
+
aliases = /* @__PURE__ */ new Map();
|
|
27
|
+
register(tool) {
|
|
28
|
+
this.tools.set(tool.name, tool);
|
|
29
|
+
for (const alias of tool.aliases ?? []) {
|
|
30
|
+
const trimmed = alias.trim();
|
|
31
|
+
if (trimmed && trimmed !== tool.name) {
|
|
32
|
+
this.aliases.set(trimmed, tool.name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
get(name) {
|
|
37
|
+
return this.tools.get(name) ?? this.tools.get(this.aliases.get(name) ?? "");
|
|
38
|
+
}
|
|
39
|
+
list() {
|
|
40
|
+
return Array.from(this.tools.values());
|
|
41
|
+
}
|
|
42
|
+
byCategory(category) {
|
|
43
|
+
return this.list().filter((t) => t.category === category);
|
|
44
|
+
}
|
|
45
|
+
names() {
|
|
46
|
+
return Array.from(this.tools.keys()).sort();
|
|
47
|
+
}
|
|
48
|
+
describeAvailable() {
|
|
49
|
+
const tools = this.list().sort((a, b) => a.name.localeCompare(b.name));
|
|
50
|
+
if (tools.length === 0) return "- none";
|
|
51
|
+
const lines = tools.filter((t) => t.name.trim()).map((t) => {
|
|
52
|
+
const desc = t.description.trim();
|
|
53
|
+
return desc ? `- ${t.name}: ${desc}` : `- ${t.name}`;
|
|
54
|
+
});
|
|
55
|
+
return lines.length === 0 ? "- none" : lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
toolDefs() {
|
|
58
|
+
return this.list().filter((t) => !t.hidden && !t.deferred).map(toToolDef);
|
|
59
|
+
}
|
|
60
|
+
async collectDynamicReminders(signal) {
|
|
61
|
+
const tools = this.list().filter((t) => t.dynamicReminder != null).sort((a, b) => a.name.localeCompare(b.name));
|
|
62
|
+
const results = [];
|
|
63
|
+
for (const t of tools) {
|
|
64
|
+
const block = await t.dynamicReminder(signal);
|
|
65
|
+
if (block.trim()) results.push(block);
|
|
66
|
+
}
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
search(query) {
|
|
70
|
+
const q = query.toLowerCase().trim();
|
|
71
|
+
if (!q) return [];
|
|
72
|
+
return this.list().map((t) => ({ tool: t, score: scoreToolMatch(t, q) })).filter((m) => m.score > 0).sort((a, b) => b.score - a.score);
|
|
73
|
+
}
|
|
74
|
+
reveal(name) {
|
|
75
|
+
const tool = this.tools.get(name);
|
|
76
|
+
if (!tool) throw new Error(`tool "${name}" not found`);
|
|
77
|
+
tool.hidden = false;
|
|
78
|
+
}
|
|
79
|
+
hide(name) {
|
|
80
|
+
const tool = this.tools.get(name);
|
|
81
|
+
if (!tool) throw new Error(`tool "${name}" not found`);
|
|
82
|
+
tool.hidden = true;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/streaming.ts
|
|
87
|
+
async function collectStream(events) {
|
|
88
|
+
let text = "";
|
|
89
|
+
let final;
|
|
90
|
+
let error;
|
|
91
|
+
for await (const ev of events) {
|
|
92
|
+
if (ev.type === "delta" && ev.delta) text += ev.delta;
|
|
93
|
+
if (ev.type === "done") final = ev.final;
|
|
94
|
+
if (ev.type === "error") error = ev.error;
|
|
95
|
+
}
|
|
96
|
+
return { text, final, error };
|
|
97
|
+
}
|
|
98
|
+
function fanOutStream(source, consumers) {
|
|
99
|
+
const queues = Array.from({ length: consumers }, () => []);
|
|
100
|
+
const resolvers = Array(consumers).fill(null);
|
|
101
|
+
let done = false;
|
|
102
|
+
void (async () => {
|
|
103
|
+
for await (const ev of source) {
|
|
104
|
+
for (let i = 0; i < consumers; i++) {
|
|
105
|
+
const resolve = resolvers[i];
|
|
106
|
+
if (resolve) {
|
|
107
|
+
resolvers[i] = null;
|
|
108
|
+
resolve({ value: ev, done: false });
|
|
109
|
+
} else {
|
|
110
|
+
queues[i].push(ev);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
done = true;
|
|
115
|
+
for (let i = 0; i < consumers; i++) {
|
|
116
|
+
const resolve = resolvers[i];
|
|
117
|
+
if (resolve) {
|
|
118
|
+
resolvers[i] = null;
|
|
119
|
+
resolve({ value: void 0, done: true });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
return Array.from({ length: consumers }, (_, i) => {
|
|
124
|
+
return (async function* () {
|
|
125
|
+
while (true) {
|
|
126
|
+
const queued = queues[i].shift();
|
|
127
|
+
if (queued !== void 0) {
|
|
128
|
+
yield queued;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (done) return;
|
|
132
|
+
const ev = await new Promise((resolve) => {
|
|
133
|
+
resolvers[i] = resolve;
|
|
134
|
+
});
|
|
135
|
+
if (ev.done) return;
|
|
136
|
+
yield ev.value;
|
|
137
|
+
}
|
|
138
|
+
})();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/conversation.ts
|
|
143
|
+
function newConversation(id) {
|
|
144
|
+
return {
|
|
145
|
+
id,
|
|
146
|
+
messages: [],
|
|
147
|
+
memoryRead: false,
|
|
148
|
+
turnCount: 0,
|
|
149
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function isCold(conv) {
|
|
153
|
+
return conv.turnCount === 0;
|
|
154
|
+
}
|
|
155
|
+
function appendUser(conv, content) {
|
|
156
|
+
conv.messages.push({ role: "user", content });
|
|
157
|
+
}
|
|
158
|
+
function appendAssistant(conv, content) {
|
|
159
|
+
conv.messages.push({ role: "assistant", content });
|
|
160
|
+
}
|
|
161
|
+
function incrementTurn(conv) {
|
|
162
|
+
conv.turnCount++;
|
|
163
|
+
conv.lastTurnAt = /* @__PURE__ */ new Date();
|
|
164
|
+
}
|
|
165
|
+
function messageCount(conv) {
|
|
166
|
+
return conv.messages.length;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/mode.ts
|
|
170
|
+
function isToolAllowed(mode, toolName) {
|
|
171
|
+
if (!mode.toolsMode || !mode.toolsList?.length) return true;
|
|
172
|
+
const listed = mode.toolsList.includes(toolName);
|
|
173
|
+
if (mode.toolsMode === "allowlist") return listed;
|
|
174
|
+
if (mode.toolsMode === "denylist") return !listed;
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
var StaticModeProvider = class {
|
|
178
|
+
modes;
|
|
179
|
+
modeList;
|
|
180
|
+
constructor(modes) {
|
|
181
|
+
this.modes = /* @__PURE__ */ new Map();
|
|
182
|
+
this.modeList = [];
|
|
183
|
+
for (const mode of modes) {
|
|
184
|
+
if (!mode.id) continue;
|
|
185
|
+
this.modes.set(mode.id, mode);
|
|
186
|
+
this.modeList.push(mode);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async get(modeId) {
|
|
190
|
+
const mode = this.modes.get(modeId);
|
|
191
|
+
if (!mode) throw new Error(`mode not found: ${modeId}`);
|
|
192
|
+
return mode;
|
|
193
|
+
}
|
|
194
|
+
async list() {
|
|
195
|
+
return this.modeList;
|
|
196
|
+
}
|
|
197
|
+
async create() {
|
|
198
|
+
throw new Error("mode creation not supported by static mode provider");
|
|
199
|
+
}
|
|
200
|
+
builtinModes() {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/memory.ts
|
|
206
|
+
var DEFAULT_MEMORY_ROOTS = [
|
|
207
|
+
{ scope: "user", path: "/", label: "User memory" },
|
|
208
|
+
{ scope: "project", path: "/", label: "Project memory" }
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// src/thread.ts
|
|
212
|
+
var ErrThreadAccessDenied = new Error("thread: access denied");
|
|
213
|
+
|
|
214
|
+
// src/artifact.ts
|
|
215
|
+
var ARTIFACT_EMITTER_KEY = /* @__PURE__ */ Symbol("artifactEmitter");
|
|
216
|
+
function withArtifactEmitter(ctx, emitter) {
|
|
217
|
+
return { ...ctx, [ARTIFACT_EMITTER_KEY]: emitter };
|
|
218
|
+
}
|
|
219
|
+
function emitArtifact(ctx, artifact) {
|
|
220
|
+
ctx[ARTIFACT_EMITTER_KEY]?.(artifact);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/interrupt.ts
|
|
224
|
+
var InterruptGate = class {
|
|
225
|
+
requester;
|
|
226
|
+
pending = /* @__PURE__ */ new Map();
|
|
227
|
+
constructor(requester) {
|
|
228
|
+
this.requester = requester;
|
|
229
|
+
}
|
|
230
|
+
async wait(req, signal) {
|
|
231
|
+
this.pending.set(req.id, req);
|
|
232
|
+
try {
|
|
233
|
+
return await this.requester(req, signal);
|
|
234
|
+
} finally {
|
|
235
|
+
this.pending.delete(req.id);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
pendingRequests() {
|
|
239
|
+
return Array.from(this.pending.values());
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/safety.ts
|
|
244
|
+
var DANGEROUS_PATTERNS = [
|
|
245
|
+
/rm\s+-rf\s+\/[^a-z]/i,
|
|
246
|
+
/curl.*\|\s*(?:bash|sh|python)/i,
|
|
247
|
+
/wget.*\|\s*(?:bash|sh|python)/i,
|
|
248
|
+
/mkfs/i,
|
|
249
|
+
/dd\s+if=/i,
|
|
250
|
+
/>\s*\/dev\/sd[a-z]/i
|
|
251
|
+
];
|
|
252
|
+
var SECRET_PATTERNS = [
|
|
253
|
+
/sk-[a-zA-Z0-9]{20,}/,
|
|
254
|
+
/AKIA[0-9A-Z]{16}/,
|
|
255
|
+
/ghp_[a-zA-Z0-9]{36}/,
|
|
256
|
+
/-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/,
|
|
257
|
+
/password\s*[:=]\s*['"][^'"]{8,}['"]/i
|
|
258
|
+
];
|
|
259
|
+
var DefaultSafetyFilter = class {
|
|
260
|
+
inspect(call) {
|
|
261
|
+
const args = call.arguments;
|
|
262
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
263
|
+
if (pattern.test(args)) {
|
|
264
|
+
return {
|
|
265
|
+
decision: "block",
|
|
266
|
+
reason: `dangerous command pattern detected: ${pattern.source}`
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
271
|
+
if (pattern.test(args)) {
|
|
272
|
+
return {
|
|
273
|
+
decision: "block",
|
|
274
|
+
reason: "potential secret leak detected in tool arguments"
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { decision: "allow" };
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/tracing.ts
|
|
283
|
+
import { randomUUID } from "crypto";
|
|
284
|
+
var Tracer = class {
|
|
285
|
+
_traceId;
|
|
286
|
+
spans = [];
|
|
287
|
+
stack = [];
|
|
288
|
+
constructor() {
|
|
289
|
+
this._traceId = randomUUID();
|
|
290
|
+
}
|
|
291
|
+
traceId() {
|
|
292
|
+
return this._traceId;
|
|
293
|
+
}
|
|
294
|
+
startSpan(name, attrs) {
|
|
295
|
+
const span = {
|
|
296
|
+
traceId: this._traceId,
|
|
297
|
+
spanId: randomUUID(),
|
|
298
|
+
parentId: this.stack[this.stack.length - 1],
|
|
299
|
+
name,
|
|
300
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
301
|
+
attrs
|
|
302
|
+
};
|
|
303
|
+
this.spans.push(span);
|
|
304
|
+
this.stack.push(span.spanId);
|
|
305
|
+
return span;
|
|
306
|
+
}
|
|
307
|
+
finishSpan(span, error) {
|
|
308
|
+
span.finishedAt = /* @__PURE__ */ new Date();
|
|
309
|
+
if (error) span.error = error.message;
|
|
310
|
+
const idx = this.stack.lastIndexOf(span.spanId);
|
|
311
|
+
if (idx !== -1) this.stack.splice(idx, 1);
|
|
312
|
+
}
|
|
313
|
+
allSpans() {
|
|
314
|
+
return this.spans.slice();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
function withTracer(ctx, tracer) {
|
|
318
|
+
return { ...ctx, __tracer: tracer };
|
|
319
|
+
}
|
|
320
|
+
function tracerFromContext(ctx) {
|
|
321
|
+
return ctx.__tracer;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/tokenizer.ts
|
|
325
|
+
var HeuristicTokenizer = class {
|
|
326
|
+
count(text) {
|
|
327
|
+
return Math.ceil(text.length / 4);
|
|
328
|
+
}
|
|
329
|
+
encode(_text) {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
decode(_tokens) {
|
|
333
|
+
return "";
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
function truncateToTokens(text, maxTokens, tok) {
|
|
337
|
+
if (tok.count(text) <= maxTokens) return text;
|
|
338
|
+
const tokens = tok.encode(text);
|
|
339
|
+
if (tokens.length > 0 && tokens.length > maxTokens) {
|
|
340
|
+
return tok.decode(tokens.slice(0, maxTokens));
|
|
341
|
+
}
|
|
342
|
+
const limit = Math.min(maxTokens * 4, text.length);
|
|
343
|
+
return text.slice(0, limit);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/system-prompt.ts
|
|
347
|
+
var LAYER_ORDER = [
|
|
348
|
+
"core",
|
|
349
|
+
"behavior",
|
|
350
|
+
"memory",
|
|
351
|
+
"session",
|
|
352
|
+
"mode"
|
|
353
|
+
];
|
|
354
|
+
var SystemPromptBuilder = class {
|
|
355
|
+
layers = /* @__PURE__ */ new Map();
|
|
356
|
+
maxLayerTokens = /* @__PURE__ */ new Map();
|
|
357
|
+
setMaxLayerTokens(layer, maxTokens) {
|
|
358
|
+
this.maxLayerTokens.set(layer, maxTokens);
|
|
359
|
+
}
|
|
360
|
+
build() {
|
|
361
|
+
return this.buildWithBudget();
|
|
362
|
+
}
|
|
363
|
+
buildWithBudget(tok) {
|
|
364
|
+
const parts = [];
|
|
365
|
+
for (const layer of LAYER_ORDER) {
|
|
366
|
+
let content = (this.layers.get(layer) ?? "").trim();
|
|
367
|
+
if (!content) continue;
|
|
368
|
+
if (tok) {
|
|
369
|
+
const cap = this.maxLayerTokens.get(layer);
|
|
370
|
+
if (cap && cap > 0 && tok.count(content) > cap) {
|
|
371
|
+
content = truncateToTokens(content, cap, tok);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
parts.push(content);
|
|
375
|
+
}
|
|
376
|
+
return parts.join("\n\n");
|
|
377
|
+
}
|
|
378
|
+
get(layer) {
|
|
379
|
+
return this.layers.get(layer) ?? "";
|
|
380
|
+
}
|
|
381
|
+
has(layer) {
|
|
382
|
+
return (this.layers.get(layer) ?? "").trim().length > 0;
|
|
383
|
+
}
|
|
384
|
+
set(layer, content) {
|
|
385
|
+
this.layers.set(layer, content.trim());
|
|
386
|
+
}
|
|
387
|
+
append(layer, content) {
|
|
388
|
+
const existing = this.layers.get(layer) ?? "";
|
|
389
|
+
if (!existing) {
|
|
390
|
+
this.layers.set(layer, content.trim());
|
|
391
|
+
} else {
|
|
392
|
+
this.layers.set(layer, existing + "\n\n" + content.trim());
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
clear(layer) {
|
|
396
|
+
this.layers.delete(layer);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/system-reminder.ts
|
|
401
|
+
function systemReminder(content) {
|
|
402
|
+
const trimmed = content.trim();
|
|
403
|
+
if (!trimmed) return "";
|
|
404
|
+
return `<system-reminder>
|
|
405
|
+
${trimmed}
|
|
406
|
+
</system-reminder>`;
|
|
407
|
+
}
|
|
408
|
+
function joinSystemReminders(...reminders) {
|
|
409
|
+
return reminders.filter(Boolean).join("\n\n");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/context-budget.ts
|
|
413
|
+
function defaultContextBudget(windowSize) {
|
|
414
|
+
return {
|
|
415
|
+
windowSize,
|
|
416
|
+
skillsReserve: Math.floor(windowSize * 0.1),
|
|
417
|
+
memoryReserve: Math.floor(windowSize * 0.15),
|
|
418
|
+
historyReserve: Math.floor(windowSize * 0.6),
|
|
419
|
+
reserveBuffer: Math.floor(windowSize * 0.05)
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function enforceContextBudget(budget, conv, memoryTokens, tok) {
|
|
423
|
+
const result = {
|
|
424
|
+
overflowTokens: 0,
|
|
425
|
+
truncatedHistory: false,
|
|
426
|
+
historyDropped: 0,
|
|
427
|
+
stillOverflow: false
|
|
428
|
+
};
|
|
429
|
+
const available = budget.historyReserve;
|
|
430
|
+
let used = 0;
|
|
431
|
+
const kept = [];
|
|
432
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
433
|
+
const msg = conv.messages[i];
|
|
434
|
+
const tokens = tok.count(msg.content);
|
|
435
|
+
if (used + tokens > available && kept.length > 0) {
|
|
436
|
+
result.truncatedHistory = true;
|
|
437
|
+
result.historyDropped = i + 1;
|
|
438
|
+
result.overflowTokens = used + tokens - available;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
used += tokens;
|
|
442
|
+
kept.unshift(msg);
|
|
443
|
+
}
|
|
444
|
+
if (result.truncatedHistory) {
|
|
445
|
+
conv.messages = kept;
|
|
446
|
+
}
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/compaction.ts
|
|
451
|
+
var LLMCompactor = class {
|
|
452
|
+
constructor(llm) {
|
|
453
|
+
this.llm = llm;
|
|
454
|
+
}
|
|
455
|
+
llm;
|
|
456
|
+
async summarize(messages, signal) {
|
|
457
|
+
const history = messages.map((m) => `${m.role.toUpperCase()}: ${m.content}`).join("\n\n");
|
|
458
|
+
const resp = await this.llm.chat(
|
|
459
|
+
{
|
|
460
|
+
messages: [
|
|
461
|
+
{
|
|
462
|
+
role: "user",
|
|
463
|
+
content: `Summarize the following conversation history concisely, preserving key decisions, facts, and context:
|
|
464
|
+
|
|
465
|
+
${history}`
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
maxTokens: 1024
|
|
469
|
+
},
|
|
470
|
+
signal
|
|
471
|
+
);
|
|
472
|
+
return resp.content;
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
var BulletCompactor = class {
|
|
476
|
+
async summarize(messages) {
|
|
477
|
+
const points = [];
|
|
478
|
+
for (const msg of messages) {
|
|
479
|
+
if (msg.role === "assistant" && msg.content.trim()) {
|
|
480
|
+
const first = msg.content.split("\n")[0]?.trim() ?? "";
|
|
481
|
+
if (first) points.push(`- ${first.slice(0, 120)}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return points.join("\n");
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
async function enforceWithCompaction(budget, compactor, conv, memoryTokens, tok = new HeuristicTokenizer(), signal) {
|
|
488
|
+
const enforcementResult = enforceContextBudget(budget, conv, memoryTokens, tok);
|
|
489
|
+
let summary = "";
|
|
490
|
+
if (compactor && enforcementResult.truncatedHistory) {
|
|
491
|
+
try {
|
|
492
|
+
summary = await compactor.summarize(conv.messages.slice(0, enforcementResult.historyDropped), signal);
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return { enforcementResult, summary };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/permissions.ts
|
|
500
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
501
|
+
var InterruptApprover = class {
|
|
502
|
+
constructor(requester) {
|
|
503
|
+
this.requester = requester;
|
|
504
|
+
}
|
|
505
|
+
requester;
|
|
506
|
+
async ask(tool, args, signal) {
|
|
507
|
+
const req = {
|
|
508
|
+
id: randomUUID2(),
|
|
509
|
+
kind: "approval",
|
|
510
|
+
title: `Allow ${tool.name}?`,
|
|
511
|
+
description: tool.description,
|
|
512
|
+
toolName: tool.name,
|
|
513
|
+
toolArgs: args,
|
|
514
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
515
|
+
};
|
|
516
|
+
const resp = await this.requester(req, signal);
|
|
517
|
+
if (resp.approved) {
|
|
518
|
+
return { behavior: "allow" };
|
|
519
|
+
}
|
|
520
|
+
return { behavior: "deny", reason: "user denied" };
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
var PermissionEngine = class {
|
|
524
|
+
rules = [];
|
|
525
|
+
approver;
|
|
526
|
+
withRules(rules) {
|
|
527
|
+
this.rules = rules;
|
|
528
|
+
return this;
|
|
529
|
+
}
|
|
530
|
+
withApprover(approver) {
|
|
531
|
+
this.approver = approver;
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
async decide(tool, args, signal) {
|
|
535
|
+
for (const rule of this.rules) {
|
|
536
|
+
if (rule.toolName && rule.toolName !== tool.name) continue;
|
|
537
|
+
if (rule.pathPrefix) {
|
|
538
|
+
const path = typeof args["path"] === "string" ? args["path"] : "";
|
|
539
|
+
if (!path.startsWith(rule.pathPrefix)) continue;
|
|
540
|
+
}
|
|
541
|
+
if (rule.behavior === "ask") break;
|
|
542
|
+
return { behavior: rule.behavior, reason: rule.reason };
|
|
543
|
+
}
|
|
544
|
+
if (tool.checkPermissions) {
|
|
545
|
+
const result = await tool.checkPermissions(signal, args);
|
|
546
|
+
if (result.decision === "deny" || result.decision === "ask_user") {
|
|
547
|
+
if (this.approver && result.decision === "ask_user") {
|
|
548
|
+
return this.approver.ask(tool, args, signal);
|
|
549
|
+
}
|
|
550
|
+
return { behavior: "deny", reason: result.reason };
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
behavior: "allow",
|
|
554
|
+
updatedInput: result.updatedArgs
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (this.approver && tool.isDestructive?.(args)) {
|
|
558
|
+
return this.approver.ask(tool, args, signal);
|
|
559
|
+
}
|
|
560
|
+
return { behavior: "allow" };
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/session-context.ts
|
|
565
|
+
function formatSessionContext(sc) {
|
|
566
|
+
const parts = [];
|
|
567
|
+
if (sc.currentTime) {
|
|
568
|
+
parts.push(`Current time: ${sc.currentTime.toISOString()}`);
|
|
569
|
+
}
|
|
570
|
+
if (sc.timezone) parts.push(`Timezone: ${sc.timezone}`);
|
|
571
|
+
if (sc.userId) parts.push(`User ID: ${sc.userId}`);
|
|
572
|
+
if (sc.username) parts.push(`Username: ${sc.username}`);
|
|
573
|
+
if (sc.projectId) parts.push(`Project: ${sc.projectId}`);
|
|
574
|
+
if (sc.activeArtifactId) parts.push(`Active artifact: ${sc.activeArtifactId}`);
|
|
575
|
+
if (sc.extras) {
|
|
576
|
+
for (const [k, v] of Object.entries(sc.extras)) {
|
|
577
|
+
parts.push(`${k}: ${v}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return parts.join("\n");
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/execution-context.ts
|
|
584
|
+
var InMemoryExecutionContext = class {
|
|
585
|
+
_todos = [];
|
|
586
|
+
todos() {
|
|
587
|
+
return this._todos.slice();
|
|
588
|
+
}
|
|
589
|
+
setTodos(todos) {
|
|
590
|
+
this._todos = todos.slice();
|
|
591
|
+
}
|
|
592
|
+
markDone(id) {
|
|
593
|
+
const todo = this._todos.find((t) => t.id === id);
|
|
594
|
+
if (todo) todo.status = "completed";
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// src/interrupt-store.ts
|
|
599
|
+
var InMemoryInterruptStore = class {
|
|
600
|
+
store = /* @__PURE__ */ new Map();
|
|
601
|
+
async put(req) {
|
|
602
|
+
this.store.set(req.id, req);
|
|
603
|
+
}
|
|
604
|
+
async get(id) {
|
|
605
|
+
const req = this.store.get(id);
|
|
606
|
+
if (!req) return { ok: false };
|
|
607
|
+
return { req, ok: true };
|
|
608
|
+
}
|
|
609
|
+
async delete(id) {
|
|
610
|
+
this.store.delete(id);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// src/memory-selector.ts
|
|
615
|
+
var KeywordMemorySelector = class {
|
|
616
|
+
async select(query, headers, limit) {
|
|
617
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
618
|
+
if (!terms.length || !headers.length) return [];
|
|
619
|
+
const scored = headers.map((h) => {
|
|
620
|
+
const text = `${h.title ?? ""} ${h.description ?? ""} ${h.type ?? ""} ${h.path}`.toLowerCase();
|
|
621
|
+
const score = terms.reduce((acc, t) => acc + (text.includes(t) ? 1 : 0), 0);
|
|
622
|
+
return { h, score };
|
|
623
|
+
});
|
|
624
|
+
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.h);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/rule-matcher.ts
|
|
629
|
+
function ruleMatcherFromFn(fn) {
|
|
630
|
+
return { match: fn };
|
|
631
|
+
}
|
|
632
|
+
function prefixMatcher(argKey) {
|
|
633
|
+
return {
|
|
634
|
+
match(rule, input) {
|
|
635
|
+
if (!rule) return true;
|
|
636
|
+
const val = input[argKey];
|
|
637
|
+
return typeof val === "string" && val.startsWith(rule);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function exactMatcher() {
|
|
642
|
+
return {
|
|
643
|
+
match(rule, input) {
|
|
644
|
+
if (!rule) return true;
|
|
645
|
+
const primary = Object.values(input)[0];
|
|
646
|
+
return String(primary) === rule;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/engine.ts
|
|
652
|
+
function newEngine(...opts) {
|
|
653
|
+
const e = {};
|
|
654
|
+
for (const opt of opts) opt(e);
|
|
655
|
+
return e;
|
|
656
|
+
}
|
|
657
|
+
function newEngineWithDefaults(windowSize) {
|
|
658
|
+
const budget = defaultContextBudget(windowSize);
|
|
659
|
+
return {
|
|
660
|
+
prompt: new SystemPromptBuilder(),
|
|
661
|
+
budget
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
var hasMemory = (e) => e.memory != null;
|
|
665
|
+
var hasSandbox = (e) => e.sandbox != null;
|
|
666
|
+
var hasTools = (e) => e.tools != null;
|
|
667
|
+
var hasThreads = (e) => e.threads != null;
|
|
668
|
+
var hasModes = (e) => e.modes != null;
|
|
669
|
+
var hasLLM = (e) => e.llm != null;
|
|
670
|
+
var hasPrompt = (e) => e.prompt != null;
|
|
671
|
+
var hasBudget = (e) => e.budget != null;
|
|
672
|
+
var withMemory = (p) => (e) => {
|
|
673
|
+
e.memory = p;
|
|
674
|
+
};
|
|
675
|
+
var withSandbox = (p) => (e) => {
|
|
676
|
+
e.sandbox = p;
|
|
677
|
+
};
|
|
678
|
+
var withTools = (r) => (e) => {
|
|
679
|
+
e.tools = r;
|
|
680
|
+
};
|
|
681
|
+
var withThreads = (p) => (e) => {
|
|
682
|
+
e.threads = p;
|
|
683
|
+
};
|
|
684
|
+
var withModes = (p) => (e) => {
|
|
685
|
+
e.modes = p;
|
|
686
|
+
};
|
|
687
|
+
var withLLM = (p) => (e) => {
|
|
688
|
+
e.llm = p;
|
|
689
|
+
};
|
|
690
|
+
var withPrompt = (b) => (e) => {
|
|
691
|
+
e.prompt = b;
|
|
692
|
+
};
|
|
693
|
+
var withBudget = (b) => (e) => {
|
|
694
|
+
e.budget = b;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// src/dispatch.ts
|
|
698
|
+
var ToolDispatcher = class {
|
|
699
|
+
constructor(tools, sandbox) {
|
|
700
|
+
this.tools = tools;
|
|
701
|
+
this.sandbox = sandbox;
|
|
702
|
+
}
|
|
703
|
+
tools;
|
|
704
|
+
sandbox;
|
|
705
|
+
permissions;
|
|
706
|
+
withPermissions(engine) {
|
|
707
|
+
this.permissions = engine;
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
async dispatch(call, sandboxId, signal) {
|
|
711
|
+
const tool = this.tools.get(call.name);
|
|
712
|
+
if (!tool) {
|
|
713
|
+
const err = new Error(`tool "${call.name}" not found`);
|
|
714
|
+
return {
|
|
715
|
+
toolCallId: call.id,
|
|
716
|
+
name: call.name,
|
|
717
|
+
content: `error: tool "${call.name}" not found in registry`,
|
|
718
|
+
error: err
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
let args = {};
|
|
722
|
+
if (call.arguments && call.arguments !== "{}") {
|
|
723
|
+
try {
|
|
724
|
+
args = JSON.parse(call.arguments);
|
|
725
|
+
} catch (e) {
|
|
726
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
727
|
+
return {
|
|
728
|
+
toolCallId: call.id,
|
|
729
|
+
name: call.name,
|
|
730
|
+
content: `error: invalid JSON arguments: ${err.message}`,
|
|
731
|
+
error: err
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (tool.validate) {
|
|
736
|
+
try {
|
|
737
|
+
await tool.validate(signal, args);
|
|
738
|
+
} catch (e) {
|
|
739
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
740
|
+
return {
|
|
741
|
+
toolCallId: call.id,
|
|
742
|
+
name: call.name,
|
|
743
|
+
content: `error: ${err.message}`,
|
|
744
|
+
error: err
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (this.permissions) {
|
|
749
|
+
const dec = await this.permissions.decide(tool, args, signal);
|
|
750
|
+
if (dec.behavior === "deny" || dec.behavior === "ask") {
|
|
751
|
+
const reason = dec.reason?.trim() || "permission denied";
|
|
752
|
+
const content = `<tool_use_error>The user did not approve this ${tool.name} call. Reason: ${reason}.
|
|
753
|
+
|
|
754
|
+
The tool was NOT executed. Do NOT claim success. Do NOT retry the same call. Acknowledge the rejection and ask the user how to proceed (or stop).</tool_use_error>`;
|
|
755
|
+
return {
|
|
756
|
+
toolCallId: call.id,
|
|
757
|
+
name: call.name,
|
|
758
|
+
content,
|
|
759
|
+
error: new Error(reason)
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
if (dec.updatedInput) args = dec.updatedInput;
|
|
763
|
+
} else if (tool.checkPermissions) {
|
|
764
|
+
try {
|
|
765
|
+
const result = await tool.checkPermissions(signal, args);
|
|
766
|
+
if (result.decision === "deny" || result.decision === "ask_user") {
|
|
767
|
+
const reason = result.reason?.trim() || "permission denied";
|
|
768
|
+
return {
|
|
769
|
+
toolCallId: call.id,
|
|
770
|
+
name: call.name,
|
|
771
|
+
content: `error: ${reason}`,
|
|
772
|
+
error: new Error(reason)
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
if (result.updatedArgs) args = result.updatedArgs;
|
|
776
|
+
} catch (e) {
|
|
777
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
778
|
+
return {
|
|
779
|
+
toolCallId: call.id,
|
|
780
|
+
name: call.name,
|
|
781
|
+
content: `error: permission check failed: ${err.message}`,
|
|
782
|
+
error: err
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
const content = await tool.execute(signal, sandboxId, args);
|
|
788
|
+
return { toolCallId: call.id, name: call.name, content };
|
|
789
|
+
} catch (e) {
|
|
790
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
791
|
+
return {
|
|
792
|
+
toolCallId: call.id,
|
|
793
|
+
name: call.name,
|
|
794
|
+
content: `error: ${err.message}`,
|
|
795
|
+
error: err
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async dispatchAll(calls, sandboxId, signal) {
|
|
800
|
+
const results = [];
|
|
801
|
+
for (const call of calls) {
|
|
802
|
+
results.push(await this.dispatch(call, sandboxId, signal));
|
|
803
|
+
}
|
|
804
|
+
return results;
|
|
805
|
+
}
|
|
806
|
+
async dispatchParallel(calls, sandboxId, signal) {
|
|
807
|
+
if (calls.length === 0) return [];
|
|
808
|
+
if (calls.length === 1) return [await this.dispatch(calls[0], sandboxId, signal)];
|
|
809
|
+
const results = new Array(calls.length);
|
|
810
|
+
const safe = [];
|
|
811
|
+
const unsafe = [];
|
|
812
|
+
for (let i = 0; i < calls.length; i++) {
|
|
813
|
+
const call = calls[i];
|
|
814
|
+
const tool = this.tools.get(call.name);
|
|
815
|
+
let isSafe = false;
|
|
816
|
+
if (tool?.isConcurrencySafe) {
|
|
817
|
+
let args = {};
|
|
818
|
+
try {
|
|
819
|
+
if (call.arguments && call.arguments !== "{}") {
|
|
820
|
+
args = JSON.parse(call.arguments);
|
|
821
|
+
}
|
|
822
|
+
} catch {
|
|
823
|
+
}
|
|
824
|
+
isSafe = tool.isConcurrencySafe(args);
|
|
825
|
+
}
|
|
826
|
+
if (isSafe) {
|
|
827
|
+
safe.push({ idx: i, call });
|
|
828
|
+
} else {
|
|
829
|
+
unsafe.push({ idx: i, call });
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
await Promise.all(
|
|
833
|
+
safe.map(async ({ idx, call }) => {
|
|
834
|
+
results[idx] = await this.dispatch(call, sandboxId, signal);
|
|
835
|
+
})
|
|
836
|
+
);
|
|
837
|
+
for (const { idx, call } of unsafe) {
|
|
838
|
+
results[idx] = await this.dispatch(call, sandboxId, signal);
|
|
839
|
+
}
|
|
840
|
+
return results;
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
function toolResultsToMessages(assistantToolCalls, results) {
|
|
844
|
+
const msgs = [
|
|
845
|
+
{ role: "assistant", content: "", toolCalls: assistantToolCalls }
|
|
846
|
+
];
|
|
847
|
+
for (const r of results) {
|
|
848
|
+
msgs.push({
|
|
849
|
+
role: "tool",
|
|
850
|
+
name: r.name,
|
|
851
|
+
toolCallId: r.toolCallId,
|
|
852
|
+
content: r.content
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
return msgs;
|
|
856
|
+
}
|
|
857
|
+
function areIndependent(calls) {
|
|
858
|
+
if (calls.length <= 1) return true;
|
|
859
|
+
return !calls.some(
|
|
860
|
+
(c) => c.arguments.includes("${") || c.arguments.includes("{{")
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/llm-router.ts
|
|
865
|
+
function parseModelRef(ref) {
|
|
866
|
+
const slash = ref.indexOf("/");
|
|
867
|
+
if (slash === -1) return ["", ref];
|
|
868
|
+
return [ref.slice(0, slash), ref.slice(slash + 1)];
|
|
869
|
+
}
|
|
870
|
+
var RoutedLLMProvider = class {
|
|
871
|
+
routes = /* @__PURE__ */ new Map();
|
|
872
|
+
fallback;
|
|
873
|
+
register(provider, llm) {
|
|
874
|
+
this.routes.set(provider, llm);
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
877
|
+
withFallback(llm) {
|
|
878
|
+
this.fallback = llm;
|
|
879
|
+
return this;
|
|
880
|
+
}
|
|
881
|
+
route(model) {
|
|
882
|
+
const [provider] = parseModelRef(model);
|
|
883
|
+
const resolved = provider ? this.routes.get(provider) : void 0;
|
|
884
|
+
if (resolved) return resolved;
|
|
885
|
+
if (this.fallback) return this.fallback;
|
|
886
|
+
throw new Error(`no provider registered for model: ${model}`);
|
|
887
|
+
}
|
|
888
|
+
async chat(req, signal) {
|
|
889
|
+
return this.route(req.model ?? "").chat(req, signal);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// src/agent-loop.ts
|
|
894
|
+
function previewText(value, limit) {
|
|
895
|
+
const trimmed = value.trim().replace(/\s+/g, " ");
|
|
896
|
+
if (trimmed.length <= limit || limit <= 3) return trimmed;
|
|
897
|
+
return trimmed.slice(0, limit - 3) + "...";
|
|
898
|
+
}
|
|
899
|
+
function previewJson(value, limit) {
|
|
900
|
+
const trimmed = value.trim();
|
|
901
|
+
if (!trimmed) return "";
|
|
902
|
+
try {
|
|
903
|
+
const parsed = JSON.parse(trimmed);
|
|
904
|
+
return previewText(JSON.stringify(parsed), limit);
|
|
905
|
+
} catch {
|
|
906
|
+
return previewText(trimmed, limit);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function buildDefaultRequest(cfg, messages) {
|
|
910
|
+
const req = {
|
|
911
|
+
model: cfg.model,
|
|
912
|
+
messages: []
|
|
913
|
+
};
|
|
914
|
+
if (cfg.systemPrompt) {
|
|
915
|
+
req.messages.push({ role: "system", content: cfg.systemPrompt });
|
|
916
|
+
}
|
|
917
|
+
req.messages.push(...messages);
|
|
918
|
+
if (cfg.tools) {
|
|
919
|
+
req.tools = cfg.tools.toolDefs();
|
|
920
|
+
}
|
|
921
|
+
return req;
|
|
922
|
+
}
|
|
923
|
+
function buildRequest(cfg, messages) {
|
|
924
|
+
if (cfg.buildRequest) {
|
|
925
|
+
return cfg.buildRequest(cfg.systemPrompt ?? "", messages, cfg.tools);
|
|
926
|
+
}
|
|
927
|
+
return buildDefaultRequest(cfg, messages);
|
|
928
|
+
}
|
|
929
|
+
function classifyError(err) {
|
|
930
|
+
const msg = err.message.toLowerCase();
|
|
931
|
+
const permanent = [
|
|
932
|
+
"unauthorized",
|
|
933
|
+
"401",
|
|
934
|
+
"403",
|
|
935
|
+
"forbidden",
|
|
936
|
+
"invalid api key",
|
|
937
|
+
"authentication",
|
|
938
|
+
"invalid request",
|
|
939
|
+
"400",
|
|
940
|
+
"not found",
|
|
941
|
+
"404",
|
|
942
|
+
"context length",
|
|
943
|
+
"context window",
|
|
944
|
+
"content_filter",
|
|
945
|
+
"content filter"
|
|
946
|
+
];
|
|
947
|
+
if (permanent.some((p) => msg.includes(p))) return "permanent";
|
|
948
|
+
const transient = [
|
|
949
|
+
"rate limit",
|
|
950
|
+
"429",
|
|
951
|
+
"too many requests",
|
|
952
|
+
"timeout",
|
|
953
|
+
"deadline",
|
|
954
|
+
"connection reset",
|
|
955
|
+
"connection refused",
|
|
956
|
+
"eof",
|
|
957
|
+
"broken pipe",
|
|
958
|
+
"500",
|
|
959
|
+
"502",
|
|
960
|
+
"503",
|
|
961
|
+
"504",
|
|
962
|
+
"server error",
|
|
963
|
+
"service unavailable",
|
|
964
|
+
"temporary"
|
|
965
|
+
];
|
|
966
|
+
if (transient.some((t) => msg.includes(t))) return "transient";
|
|
967
|
+
return "unknown";
|
|
968
|
+
}
|
|
969
|
+
async function callWithRetry(provider, req, maxRetries, onError, signal) {
|
|
970
|
+
let lastErr = new Error("unknown");
|
|
971
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
972
|
+
try {
|
|
973
|
+
return await provider.chat(req, signal);
|
|
974
|
+
} catch (e) {
|
|
975
|
+
lastErr = e instanceof Error ? e : new Error(String(e));
|
|
976
|
+
switch (classifyError(lastErr)) {
|
|
977
|
+
case "permanent":
|
|
978
|
+
throw lastErr;
|
|
979
|
+
case "transient":
|
|
980
|
+
if (attempt < maxRetries) {
|
|
981
|
+
const delay = Math.min(1e3 * 2 ** (attempt - 1), 3e4);
|
|
982
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
break;
|
|
986
|
+
default:
|
|
987
|
+
if (onError == null || !onError(lastErr, attempt)) throw lastErr;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
throw new Error(`max retries (${maxRetries}) exceeded: ${lastErr.message}`);
|
|
992
|
+
}
|
|
993
|
+
async function runAgentLoop(cfg, messages, signal) {
|
|
994
|
+
if (!cfg.provider) throw new Error("AgentLoopConfig.provider is required");
|
|
995
|
+
const maxTurns = cfg.maxTurns && cfg.maxTurns > 0 ? cfg.maxTurns : 50;
|
|
996
|
+
const maxRetries = cfg.maxRetries && cfg.maxRetries > 0 ? cfg.maxRetries : 3;
|
|
997
|
+
let req = buildRequest(cfg, messages);
|
|
998
|
+
const dispatcher = cfg.tools ? new ToolDispatcher(cfg.tools, cfg.sandbox) : void 0;
|
|
999
|
+
if (dispatcher && cfg.permissions) {
|
|
1000
|
+
dispatcher.withPermissions(cfg.permissions);
|
|
1001
|
+
}
|
|
1002
|
+
const result = {
|
|
1003
|
+
finalContent: "",
|
|
1004
|
+
providerReasoning: "",
|
|
1005
|
+
totalTurns: 0,
|
|
1006
|
+
totalUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
1007
|
+
messages: req.messages.slice(),
|
|
1008
|
+
reasoningTrace: [],
|
|
1009
|
+
stopReason: "complete"
|
|
1010
|
+
};
|
|
1011
|
+
let traceSeq = 0;
|
|
1012
|
+
const appendTrace = (type, title, content, details) => {
|
|
1013
|
+
result.reasoningTrace.push({
|
|
1014
|
+
id: `trace-${++traceSeq}`,
|
|
1015
|
+
type,
|
|
1016
|
+
title,
|
|
1017
|
+
content,
|
|
1018
|
+
details
|
|
1019
|
+
});
|
|
1020
|
+
};
|
|
1021
|
+
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
1022
|
+
if (signal?.aborted) {
|
|
1023
|
+
result.stopReason = "aborted";
|
|
1024
|
+
return result;
|
|
1025
|
+
}
|
|
1026
|
+
const resp = await callWithRetry(cfg.provider, req, maxRetries, cfg.onError, signal);
|
|
1027
|
+
result.totalTurns = turn;
|
|
1028
|
+
result.totalUsage.promptTokens += resp.usage.promptTokens;
|
|
1029
|
+
result.totalUsage.completionTokens += resp.usage.completionTokens;
|
|
1030
|
+
result.totalUsage.totalTokens += resp.usage.totalTokens;
|
|
1031
|
+
if (resp.reasoning?.trim()) {
|
|
1032
|
+
result.providerReasoning = result.providerReasoning ? result.providerReasoning + "\n\n" + resp.reasoning : resp.reasoning;
|
|
1033
|
+
appendTrace("thinking", `Provider reasoning ${turn}`, previewText(resp.reasoning, 220));
|
|
1034
|
+
}
|
|
1035
|
+
appendTrace("thinking", `Turn ${turn}`, void 0, [
|
|
1036
|
+
`Finish reason: ${resp.finishReason}`,
|
|
1037
|
+
`Tool calls requested: ${resp.toolCalls?.length ?? 0}`,
|
|
1038
|
+
`Tokens this turn: ${resp.usage.totalTokens}`
|
|
1039
|
+
]);
|
|
1040
|
+
if (cfg.onTurn && !cfg.onTurn(turn, resp)) {
|
|
1041
|
+
result.finalContent = resp.content;
|
|
1042
|
+
result.stopReason = "aborted";
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1045
|
+
if (cfg.shouldStop?.(turn, resp)) {
|
|
1046
|
+
result.finalContent = resp.content;
|
|
1047
|
+
result.stopReason = "stopped";
|
|
1048
|
+
result.messages.push({ role: "assistant", content: resp.content });
|
|
1049
|
+
return result;
|
|
1050
|
+
}
|
|
1051
|
+
if (!resp.toolCalls?.length) {
|
|
1052
|
+
result.finalContent = resp.content;
|
|
1053
|
+
result.stopReason = "complete";
|
|
1054
|
+
result.messages.push({ role: "assistant", content: resp.content });
|
|
1055
|
+
return result;
|
|
1056
|
+
}
|
|
1057
|
+
if (!dispatcher) {
|
|
1058
|
+
result.stopReason = "error";
|
|
1059
|
+
throw new Error("LLM requested tool calls but no ToolRegistry is configured");
|
|
1060
|
+
}
|
|
1061
|
+
const callsToRun = resp.toolCalls.filter(
|
|
1062
|
+
(tc) => cfg.onToolCall == null || cfg.onToolCall(tc)
|
|
1063
|
+
);
|
|
1064
|
+
for (const tc of callsToRun) {
|
|
1065
|
+
appendTrace("action", `Tool call: ${tc.name}`, previewJson(tc.arguments, 220));
|
|
1066
|
+
}
|
|
1067
|
+
let toolResults = await dispatcher.dispatchAll(callsToRun, cfg.sandboxId ?? "", signal);
|
|
1068
|
+
if (cfg.onToolResult) {
|
|
1069
|
+
toolResults = toolResults.map(
|
|
1070
|
+
(tr, i) => cfg.onToolResult(callsToRun[i], tr)
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
for (const tr of toolResults) {
|
|
1074
|
+
appendTrace(tr.error ? "thinking" : "result", `Tool result: ${tr.name}`, previewText(tr.content, 220));
|
|
1075
|
+
}
|
|
1076
|
+
const toolMsgs = toolResultsToMessages(resp.toolCalls, toolResults);
|
|
1077
|
+
result.messages.push(...toolMsgs);
|
|
1078
|
+
req = buildRequest(cfg, result.messages);
|
|
1079
|
+
}
|
|
1080
|
+
result.stopReason = "max_turns";
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1083
|
+
async function runAgentLoopWithEngine(engine, modeId, cfg, messages, signal) {
|
|
1084
|
+
const callerSuppliedPrompt = Boolean(cfg.systemPrompt);
|
|
1085
|
+
let resolvedModel = cfg.model ?? "";
|
|
1086
|
+
if (hasModes(engine) && modeId) {
|
|
1087
|
+
const mode = await engine.modes.get(modeId, signal);
|
|
1088
|
+
if (!cfg.model && mode.modelSettings?.model) {
|
|
1089
|
+
cfg.model = mode.modelSettings.model;
|
|
1090
|
+
}
|
|
1091
|
+
if (!callerSuppliedPrompt) {
|
|
1092
|
+
if (hasPrompt(engine)) {
|
|
1093
|
+
engine.prompt.set("mode", mode.promptContent ?? "");
|
|
1094
|
+
cfg.systemPrompt = engine.prompt.build();
|
|
1095
|
+
} else {
|
|
1096
|
+
cfg.systemPrompt = mode.promptContent ?? "";
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
resolvedModel = cfg.model ?? "";
|
|
1100
|
+
} else if (!callerSuppliedPrompt && hasPrompt(engine)) {
|
|
1101
|
+
cfg.systemPrompt = engine.prompt.build();
|
|
1102
|
+
}
|
|
1103
|
+
if (!resolvedModel) resolvedModel = cfg.model ?? "";
|
|
1104
|
+
if (!cfg.provider) {
|
|
1105
|
+
if (!hasLLM(engine)) throw new Error("no LLM provider configured");
|
|
1106
|
+
cfg.provider = resolveProvider(engine, resolvedModel);
|
|
1107
|
+
const [, modelOnly] = parseModelRef(cfg.model ?? "");
|
|
1108
|
+
if (modelOnly) cfg.model = modelOnly;
|
|
1109
|
+
}
|
|
1110
|
+
if (!cfg.tools && hasTools(engine)) cfg.tools = engine.tools;
|
|
1111
|
+
if (!cfg.sandbox && hasSandbox(engine)) cfg.sandbox = engine.sandbox;
|
|
1112
|
+
return runAgentLoop(cfg, messages, signal);
|
|
1113
|
+
}
|
|
1114
|
+
function resolveProvider(engine, model) {
|
|
1115
|
+
if (!hasLLM(engine)) throw new Error("no LLM provider configured");
|
|
1116
|
+
if (model) {
|
|
1117
|
+
const llm = engine.llm;
|
|
1118
|
+
if ("route" in llm && typeof llm.route === "function") {
|
|
1119
|
+
return llm.route(model);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return engine.llm;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// src/runtime.ts
|
|
1126
|
+
var Runtime = class {
|
|
1127
|
+
engine;
|
|
1128
|
+
mode = "";
|
|
1129
|
+
model = "";
|
|
1130
|
+
safety;
|
|
1131
|
+
tokenizer = new HeuristicTokenizer();
|
|
1132
|
+
store;
|
|
1133
|
+
sessionContext;
|
|
1134
|
+
compactor;
|
|
1135
|
+
maxMemoryTokens = 0;
|
|
1136
|
+
memoryRoots = DEFAULT_MEMORY_ROOTS;
|
|
1137
|
+
thinkingBudget = 0;
|
|
1138
|
+
interruptGate;
|
|
1139
|
+
permissions;
|
|
1140
|
+
planController;
|
|
1141
|
+
_emit;
|
|
1142
|
+
constructor(engine) {
|
|
1143
|
+
this.engine = engine;
|
|
1144
|
+
}
|
|
1145
|
+
withMode(modeId) {
|
|
1146
|
+
this.mode = modeId;
|
|
1147
|
+
return this;
|
|
1148
|
+
}
|
|
1149
|
+
withModel(model) {
|
|
1150
|
+
this.model = model;
|
|
1151
|
+
return this;
|
|
1152
|
+
}
|
|
1153
|
+
withSafety(s) {
|
|
1154
|
+
this.safety = s;
|
|
1155
|
+
return this;
|
|
1156
|
+
}
|
|
1157
|
+
withPermissions(e) {
|
|
1158
|
+
this.permissions = e;
|
|
1159
|
+
return this;
|
|
1160
|
+
}
|
|
1161
|
+
withTokenizer(t) {
|
|
1162
|
+
this.tokenizer = t;
|
|
1163
|
+
return this;
|
|
1164
|
+
}
|
|
1165
|
+
withConversationStore(s) {
|
|
1166
|
+
this.store = s;
|
|
1167
|
+
return this;
|
|
1168
|
+
}
|
|
1169
|
+
withSessionContext(p) {
|
|
1170
|
+
this.sessionContext = p;
|
|
1171
|
+
return this;
|
|
1172
|
+
}
|
|
1173
|
+
withCompactor(c) {
|
|
1174
|
+
this.compactor = c;
|
|
1175
|
+
return this;
|
|
1176
|
+
}
|
|
1177
|
+
withMaxMemoryTokens(n) {
|
|
1178
|
+
this.maxMemoryTokens = n;
|
|
1179
|
+
return this;
|
|
1180
|
+
}
|
|
1181
|
+
withMemoryRoots(...roots) {
|
|
1182
|
+
this.memoryRoots = roots;
|
|
1183
|
+
return this;
|
|
1184
|
+
}
|
|
1185
|
+
withThinkingBudget(tokens) {
|
|
1186
|
+
this.thinkingBudget = tokens;
|
|
1187
|
+
return this;
|
|
1188
|
+
}
|
|
1189
|
+
withInterruptGate(gate) {
|
|
1190
|
+
this.interruptGate = gate;
|
|
1191
|
+
return this;
|
|
1192
|
+
}
|
|
1193
|
+
withPlanController(pc) {
|
|
1194
|
+
const prev = pc.onStateChanged;
|
|
1195
|
+
pc.onStateChanged = (ev) => {
|
|
1196
|
+
prev?.(ev);
|
|
1197
|
+
const planMode = { state: ev.state, plan: ev.plan, reason: ev.reason };
|
|
1198
|
+
this._emit?.({ type: "plan_mode_changed", planMode });
|
|
1199
|
+
};
|
|
1200
|
+
this.planController = pc;
|
|
1201
|
+
return this;
|
|
1202
|
+
}
|
|
1203
|
+
async run(conv, userMessage, signal) {
|
|
1204
|
+
if (!hasLLM(this.engine)) throw new Error("runtime: no LLM provider");
|
|
1205
|
+
if (!conv) throw new Error("runtime: conversation is required");
|
|
1206
|
+
const tracer = new Tracer();
|
|
1207
|
+
const rr = {
|
|
1208
|
+
response: "",
|
|
1209
|
+
turns: 0,
|
|
1210
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
1211
|
+
stopReason: "",
|
|
1212
|
+
trace: [],
|
|
1213
|
+
traceId: tracer.traceId(),
|
|
1214
|
+
memoryRead: false,
|
|
1215
|
+
memoryWritten: [],
|
|
1216
|
+
warnings: [],
|
|
1217
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
1218
|
+
};
|
|
1219
|
+
appendUser(conv, userMessage);
|
|
1220
|
+
if (isCold(conv)) {
|
|
1221
|
+
await this.orientation(conv, userMessage, rr, signal);
|
|
1222
|
+
} else {
|
|
1223
|
+
await this.warmRefresh(conv, userMessage, signal);
|
|
1224
|
+
}
|
|
1225
|
+
if (signal?.aborted) return rr;
|
|
1226
|
+
await this.preparation(conv, rr, signal);
|
|
1227
|
+
if (signal?.aborted) return rr;
|
|
1228
|
+
const loopResult = await this.execution(conv, rr, signal);
|
|
1229
|
+
if (loopResult?.finalContent) {
|
|
1230
|
+
appendAssistant(conv, loopResult.finalContent);
|
|
1231
|
+
}
|
|
1232
|
+
if (signal?.aborted) return rr;
|
|
1233
|
+
incrementTurn(conv);
|
|
1234
|
+
if (this.store) {
|
|
1235
|
+
try {
|
|
1236
|
+
await this.store.save(conv, signal);
|
|
1237
|
+
} catch (e) {
|
|
1238
|
+
rr.warnings.push(`save conversation: ${e.message}`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
rr.completedAt = /* @__PURE__ */ new Date();
|
|
1242
|
+
rr.trace = tracer.allSpans();
|
|
1243
|
+
return rr;
|
|
1244
|
+
}
|
|
1245
|
+
async *runStream(conv, userMessage, signal) {
|
|
1246
|
+
const out = [];
|
|
1247
|
+
let resolve = null;
|
|
1248
|
+
let done = false;
|
|
1249
|
+
this._emit = (ev) => {
|
|
1250
|
+
out.push(ev);
|
|
1251
|
+
resolve?.();
|
|
1252
|
+
};
|
|
1253
|
+
const runPromise = this.run(conv, userMessage, signal).then(
|
|
1254
|
+
(result) => {
|
|
1255
|
+
if (result.response) {
|
|
1256
|
+
for (const chunk of chunkBySentence(result.response)) {
|
|
1257
|
+
out.push({ type: "delta", delta: chunk });
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
out.push({ type: "done" });
|
|
1261
|
+
done = true;
|
|
1262
|
+
resolve?.();
|
|
1263
|
+
},
|
|
1264
|
+
(err) => {
|
|
1265
|
+
out.push({ type: "error", error: err });
|
|
1266
|
+
out.push({ type: "done" });
|
|
1267
|
+
done = true;
|
|
1268
|
+
resolve?.();
|
|
1269
|
+
}
|
|
1270
|
+
).finally(() => {
|
|
1271
|
+
this._emit = void 0;
|
|
1272
|
+
});
|
|
1273
|
+
while (!done || out.length > 0) {
|
|
1274
|
+
if (out.length === 0) {
|
|
1275
|
+
await new Promise((r) => {
|
|
1276
|
+
resolve = r;
|
|
1277
|
+
});
|
|
1278
|
+
resolve = null;
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
yield out.shift();
|
|
1282
|
+
}
|
|
1283
|
+
await runPromise;
|
|
1284
|
+
}
|
|
1285
|
+
async orientation(conv, userMessage, rr, signal) {
|
|
1286
|
+
if (hasMemory(this.engine) && hasPrompt(this.engine)) {
|
|
1287
|
+
const scopes = [...new Set(this.memoryRoots.map((r) => r.scope))];
|
|
1288
|
+
const sections = [];
|
|
1289
|
+
for (const scope of scopes) {
|
|
1290
|
+
try {
|
|
1291
|
+
const index = await this.engine.memory.view(scope, "MEMORY.md", signal);
|
|
1292
|
+
if (index.trim()) sections.push(index.trim());
|
|
1293
|
+
} catch {
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (sections.length > 0) {
|
|
1297
|
+
let section = sections.join("\n\n");
|
|
1298
|
+
if (this.maxMemoryTokens > 0 && this.tokenizer.count(section) > this.maxMemoryTokens) {
|
|
1299
|
+
section = evictMemoryToTokenBudget(section, this.maxMemoryTokens, this.tokenizer);
|
|
1300
|
+
}
|
|
1301
|
+
this.engine.prompt.set("memory", section);
|
|
1302
|
+
rr.memoryRead = true;
|
|
1303
|
+
conv.memoryRead = true;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
await this.buildSessionLayer(conv, userMessage, signal);
|
|
1307
|
+
}
|
|
1308
|
+
async warmRefresh(conv, userMessage, signal) {
|
|
1309
|
+
await this.buildSessionLayer(conv, userMessage, signal);
|
|
1310
|
+
}
|
|
1311
|
+
async buildSessionLayer(conv, _userMessage, signal) {
|
|
1312
|
+
if (!hasPrompt(this.engine)) return;
|
|
1313
|
+
if (!this.sessionContext) {
|
|
1314
|
+
this.engine.prompt.clear("session");
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
try {
|
|
1318
|
+
const sc = await this.sessionContext.get(conv, signal);
|
|
1319
|
+
if (!sc) {
|
|
1320
|
+
this.engine.prompt.clear("session");
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
const rendered = formatSessionContext(sc);
|
|
1324
|
+
if (!rendered) {
|
|
1325
|
+
this.engine.prompt.clear("session");
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
this.engine.prompt.set("session", rendered);
|
|
1329
|
+
} catch {
|
|
1330
|
+
this.engine.prompt.clear("session");
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
async preparation(conv, rr, signal) {
|
|
1334
|
+
if (!hasBudget(this.engine) || !hasPrompt(this.engine)) return;
|
|
1335
|
+
const memoryTokens = this.tokenizer.count(
|
|
1336
|
+
this.engine.prompt.get("memory")
|
|
1337
|
+
);
|
|
1338
|
+
const compResult = await enforceWithCompaction(
|
|
1339
|
+
this.engine.budget,
|
|
1340
|
+
this.compactor,
|
|
1341
|
+
conv,
|
|
1342
|
+
memoryTokens,
|
|
1343
|
+
this.tokenizer,
|
|
1344
|
+
signal
|
|
1345
|
+
);
|
|
1346
|
+
const enforce = compResult.enforcementResult;
|
|
1347
|
+
if (enforce.overflowTokens > 0) {
|
|
1348
|
+
if (enforce.truncatedHistory) {
|
|
1349
|
+
rr.warnings.push(`budget: dropped ${enforce.historyDropped} messages`);
|
|
1350
|
+
}
|
|
1351
|
+
if (enforce.stillOverflow) {
|
|
1352
|
+
rr.warnings.push("budget: still over after enforcement");
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (compResult.summary) {
|
|
1356
|
+
this.engine.prompt.append(
|
|
1357
|
+
"memory",
|
|
1358
|
+
`
|
|
1359
|
+
|
|
1360
|
+
Context summary (from earlier turns):
|
|
1361
|
+
${compResult.summary}`
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
if (this._emit && enforce.truncatedHistory) {
|
|
1365
|
+
this._emit({
|
|
1366
|
+
type: "compaction",
|
|
1367
|
+
compaction: {
|
|
1368
|
+
messagesDropped: enforce.historyDropped,
|
|
1369
|
+
overflowTokens: enforce.overflowTokens,
|
|
1370
|
+
summary: compResult.summary
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
async execution(conv, rr, signal) {
|
|
1376
|
+
let systemPrompt = "";
|
|
1377
|
+
if (hasPrompt(this.engine)) {
|
|
1378
|
+
if (this.mode && hasModes(this.engine)) {
|
|
1379
|
+
try {
|
|
1380
|
+
const mode = await this.engine.modes.get(this.mode, signal);
|
|
1381
|
+
this.engine.prompt.set("mode", mode.promptContent ?? "");
|
|
1382
|
+
} catch {
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
systemPrompt = this.engine.prompt.build();
|
|
1386
|
+
}
|
|
1387
|
+
if (hasTools(this.engine)) {
|
|
1388
|
+
const blocks = await this.engine.tools.collectDynamicReminders(signal);
|
|
1389
|
+
if (blocks.length > 0) {
|
|
1390
|
+
const wrapped = blocks.map(systemReminder).filter(Boolean);
|
|
1391
|
+
const joined = joinSystemReminders(...wrapped);
|
|
1392
|
+
if (joined) {
|
|
1393
|
+
systemPrompt = systemPrompt ? systemPrompt + "\n\n" + joined : joined;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
const cfg = {
|
|
1398
|
+
systemPrompt,
|
|
1399
|
+
model: this.model,
|
|
1400
|
+
permissions: this.permissions,
|
|
1401
|
+
maxTurns: 50,
|
|
1402
|
+
onToolCall: (call) => {
|
|
1403
|
+
if (this.safety) {
|
|
1404
|
+
const verdict = this.safety.inspect(call);
|
|
1405
|
+
return verdict.decision !== "block";
|
|
1406
|
+
}
|
|
1407
|
+
return true;
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
const loopResult = await runAgentLoopWithEngine(
|
|
1411
|
+
this.engine,
|
|
1412
|
+
this.mode,
|
|
1413
|
+
cfg,
|
|
1414
|
+
conv.messages,
|
|
1415
|
+
signal
|
|
1416
|
+
);
|
|
1417
|
+
rr.turns += loopResult.totalTurns;
|
|
1418
|
+
rr.usage.promptTokens += loopResult.totalUsage.promptTokens;
|
|
1419
|
+
rr.usage.completionTokens += loopResult.totalUsage.completionTokens;
|
|
1420
|
+
rr.usage.totalTokens += loopResult.totalUsage.totalTokens;
|
|
1421
|
+
rr.stopReason = loopResult.stopReason;
|
|
1422
|
+
rr.response = loopResult.finalContent;
|
|
1423
|
+
conv.messages = loopResult.messages;
|
|
1424
|
+
return loopResult;
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
function evictMemoryToTokenBudget(content, maxTokens, tok) {
|
|
1428
|
+
const paragraphs = content.trim().split(/\n\n+/);
|
|
1429
|
+
const kept = [];
|
|
1430
|
+
let used = 0;
|
|
1431
|
+
for (let i = paragraphs.length - 1; i >= 0; i--) {
|
|
1432
|
+
const p = paragraphs[i].trim();
|
|
1433
|
+
if (!p) continue;
|
|
1434
|
+
const tokens = tok.count(p);
|
|
1435
|
+
if (used + tokens > maxTokens && kept.length > 0) break;
|
|
1436
|
+
kept.unshift(p);
|
|
1437
|
+
used += tokens;
|
|
1438
|
+
}
|
|
1439
|
+
if (kept.length < paragraphs.length) {
|
|
1440
|
+
kept.unshift("[Earlier memory entries omitted \u2014 token budget exceeded]");
|
|
1441
|
+
}
|
|
1442
|
+
return kept.join("\n\n");
|
|
1443
|
+
}
|
|
1444
|
+
function chunkBySentence(text) {
|
|
1445
|
+
if (!text) return [];
|
|
1446
|
+
const chunks = [];
|
|
1447
|
+
let current = "";
|
|
1448
|
+
for (let i = 0; i < text.length; i++) {
|
|
1449
|
+
const ch = text[i];
|
|
1450
|
+
current += ch;
|
|
1451
|
+
if (ch === "." || ch === "!" || ch === "?" || ch === "\n") {
|
|
1452
|
+
const next = text[i + 1];
|
|
1453
|
+
if (!next || next === " " || next === "\n" || next === " ") {
|
|
1454
|
+
const chunk = current.trim();
|
|
1455
|
+
if (chunk) chunks.push(chunk + " ");
|
|
1456
|
+
current = "";
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (current.trim()) chunks.push(current.trim());
|
|
1461
|
+
return chunks;
|
|
1462
|
+
}
|
|
1463
|
+
function newRuntime(engine) {
|
|
1464
|
+
return new Runtime(engine);
|
|
1465
|
+
}
|
|
1466
|
+
export {
|
|
1467
|
+
BulletCompactor,
|
|
1468
|
+
DEFAULT_MEMORY_ROOTS,
|
|
1469
|
+
DefaultSafetyFilter,
|
|
1470
|
+
ErrThreadAccessDenied,
|
|
1471
|
+
HeuristicTokenizer,
|
|
1472
|
+
InMemoryExecutionContext,
|
|
1473
|
+
InMemoryInterruptStore,
|
|
1474
|
+
InterruptApprover,
|
|
1475
|
+
InterruptGate,
|
|
1476
|
+
KeywordMemorySelector,
|
|
1477
|
+
LLMCompactor,
|
|
1478
|
+
PROTOCOL_VERSION,
|
|
1479
|
+
PermissionEngine,
|
|
1480
|
+
RoutedLLMProvider,
|
|
1481
|
+
Runtime,
|
|
1482
|
+
StaticModeProvider,
|
|
1483
|
+
SystemPromptBuilder,
|
|
1484
|
+
ToolDispatcher,
|
|
1485
|
+
ToolRegistry,
|
|
1486
|
+
Tracer,
|
|
1487
|
+
appendAssistant,
|
|
1488
|
+
appendUser,
|
|
1489
|
+
areIndependent,
|
|
1490
|
+
collectStream,
|
|
1491
|
+
defaultContextBudget,
|
|
1492
|
+
emitArtifact,
|
|
1493
|
+
enforceContextBudget,
|
|
1494
|
+
enforceWithCompaction,
|
|
1495
|
+
exactMatcher,
|
|
1496
|
+
fanOutStream,
|
|
1497
|
+
formatSessionContext,
|
|
1498
|
+
hasBudget,
|
|
1499
|
+
hasLLM,
|
|
1500
|
+
hasMemory,
|
|
1501
|
+
hasModes,
|
|
1502
|
+
hasPrompt,
|
|
1503
|
+
hasSandbox,
|
|
1504
|
+
hasThreads,
|
|
1505
|
+
hasTools,
|
|
1506
|
+
incrementTurn,
|
|
1507
|
+
isCold,
|
|
1508
|
+
isToolAllowed,
|
|
1509
|
+
joinSystemReminders,
|
|
1510
|
+
messageCount,
|
|
1511
|
+
newConversation,
|
|
1512
|
+
newEngine,
|
|
1513
|
+
newEngineWithDefaults,
|
|
1514
|
+
newRuntime,
|
|
1515
|
+
parseModelRef,
|
|
1516
|
+
prefixMatcher,
|
|
1517
|
+
ruleMatcherFromFn,
|
|
1518
|
+
runAgentLoop,
|
|
1519
|
+
runAgentLoopWithEngine,
|
|
1520
|
+
systemReminder,
|
|
1521
|
+
toToolDef,
|
|
1522
|
+
toolResultsToMessages,
|
|
1523
|
+
tracerFromContext,
|
|
1524
|
+
truncateToTokens,
|
|
1525
|
+
withArtifactEmitter,
|
|
1526
|
+
withBudget,
|
|
1527
|
+
withLLM,
|
|
1528
|
+
withMemory,
|
|
1529
|
+
withModes,
|
|
1530
|
+
withPrompt,
|
|
1531
|
+
withSandbox,
|
|
1532
|
+
withThreads,
|
|
1533
|
+
withTools,
|
|
1534
|
+
withTracer
|
|
1535
|
+
};
|
|
1536
|
+
//# sourceMappingURL=index.js.map
|