@agentic-eng/agent 0.1.0-alpha.0 → 0.1.0-beta.2
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 +2 -0
- package/dist/index.cjs +623 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +556 -1
- package/dist/index.d.ts +556 -1
- package/dist/index.js +593 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Core agent primitives and runtime for EASA — Easy Agent System Architecture.
|
|
4
4
|
|
|
5
|
+
[PLEASE NOTE: This package is not yet ready for production use. It is currently in beta and may change significantly before the first stable release.]
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -1,18 +1,640 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
|
|
6
|
+
function _interopNamespace(e) {
|
|
7
|
+
if (e && e.__esModule) return e;
|
|
8
|
+
var n = Object.create(null);
|
|
9
|
+
if (e) {
|
|
10
|
+
Object.keys(e).forEach(function (k) {
|
|
11
|
+
if (k !== 'default') {
|
|
12
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
13
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () { return e[k]; }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
n.default = e;
|
|
21
|
+
return Object.freeze(n);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
25
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
26
|
+
|
|
27
|
+
// src/errors.ts
|
|
28
|
+
var EasaError = class extends Error {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "EasaError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ProviderError = class extends EasaError {
|
|
35
|
+
constructor(message, cause) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.cause = cause;
|
|
38
|
+
this.name = "ProviderError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var AgentConfigError = class extends EasaError {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "AgentConfigError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var MaxIterationsError = class extends EasaError {
|
|
48
|
+
constructor(maxIterations, iterationsCompleted) {
|
|
49
|
+
super(
|
|
50
|
+
`Agent reasoning loop exceeded maximum of ${maxIterations} iterations (completed ${iterationsCompleted}). The goal could not be achieved.`
|
|
51
|
+
);
|
|
52
|
+
this.maxIterations = maxIterations;
|
|
53
|
+
this.iterationsCompleted = iterationsCompleted;
|
|
54
|
+
this.name = "MaxIterationsError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var ReasoningParseError = class extends EasaError {
|
|
58
|
+
constructor(message, rawContent) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.rawContent = rawContent;
|
|
61
|
+
this.name = "ReasoningParseError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var ToolExecutionError = class extends EasaError {
|
|
65
|
+
constructor(toolName, message, cause) {
|
|
66
|
+
super(`Tool "${toolName}" failed: ${message}`);
|
|
67
|
+
this.toolName = toolName;
|
|
68
|
+
this.cause = cause;
|
|
69
|
+
this.name = "ToolExecutionError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/events.ts
|
|
74
|
+
var ConsoleEventEmitter = class {
|
|
75
|
+
prefix;
|
|
76
|
+
constructor(options) {
|
|
77
|
+
this.prefix = options?.prefix ?? "[EASA]";
|
|
78
|
+
}
|
|
79
|
+
emit(event) {
|
|
80
|
+
const ts = event.timestamp.split("T")[1] ?? event.timestamp;
|
|
81
|
+
const tag = this.formatType(event.type);
|
|
82
|
+
const detail = this.formatData(event);
|
|
83
|
+
console.log(`${this.prefix} ${ts} ${tag} ${detail}`);
|
|
84
|
+
}
|
|
85
|
+
formatType(type) {
|
|
86
|
+
const icons = {
|
|
87
|
+
"agent.invoke.start": "\u25B6 INVOKE",
|
|
88
|
+
"agent.invoke.end": "\u25A0 INVOKE",
|
|
89
|
+
"agent.invoke_stream.start": "\u25B6 STREAM",
|
|
90
|
+
"agent.invoke_stream.end": "\u25A0 STREAM",
|
|
91
|
+
"agent.iteration.start": "\u21BB ITER",
|
|
92
|
+
"agent.iteration.end": "\u2713 ITER",
|
|
93
|
+
"llm.call.start": "\u2192 LLM",
|
|
94
|
+
"llm.call.end": "\u2190 LLM",
|
|
95
|
+
"tool.call.start": "\u2699 TOOL",
|
|
96
|
+
"tool.call.end": "\u2699 TOOL\u2713",
|
|
97
|
+
"tool.schema.inject": "\u{1F4CB} SCHEMA",
|
|
98
|
+
"tool.not_found": "\u26A0 TOOL?",
|
|
99
|
+
"memory.store": "\u{1F4BE} MEMORY",
|
|
100
|
+
"agent.error": "\u2717 ERROR"
|
|
101
|
+
};
|
|
102
|
+
return icons[type] ?? type;
|
|
103
|
+
}
|
|
104
|
+
formatData(event) {
|
|
105
|
+
const d = event.data;
|
|
106
|
+
switch (event.type) {
|
|
107
|
+
case "agent.invoke.start":
|
|
108
|
+
return `agent="${event.agentName}" prompt="${this.truncate(d["prompt"])}"`;
|
|
109
|
+
case "agent.invoke.end":
|
|
110
|
+
return `agent="${event.agentName}" iterations=${d["totalIterations"]} completed=${d["completed"]}`;
|
|
111
|
+
case "agent.invoke_stream.start":
|
|
112
|
+
return `agent="${event.agentName}" prompt="${this.truncate(d["prompt"])}"`;
|
|
113
|
+
case "agent.invoke_stream.end":
|
|
114
|
+
return `agent="${event.agentName}" length=${d["contentLength"]}`;
|
|
115
|
+
case "agent.iteration.start":
|
|
116
|
+
return `iteration=${d["iteration"]}/${d["maxIterations"]}`;
|
|
117
|
+
case "agent.iteration.end":
|
|
118
|
+
return `iteration=${d["iteration"]} action="${d["action"]}"`;
|
|
119
|
+
case "llm.call.start":
|
|
120
|
+
return `messages=${d["messageCount"]}`;
|
|
121
|
+
case "llm.call.end":
|
|
122
|
+
return `tokens=${d["totalTokens"] ?? "n/a"}`;
|
|
123
|
+
case "tool.call.start":
|
|
124
|
+
return `tool="${d["toolName"]}"`;
|
|
125
|
+
case "tool.call.end":
|
|
126
|
+
return `tool="${d["toolName"]}" success=${d["success"]}`;
|
|
127
|
+
case "tool.schema.inject":
|
|
128
|
+
return `tool="${d["toolName"]}"`;
|
|
129
|
+
case "tool.not_found":
|
|
130
|
+
return `tool="${d["toolName"]}" available=${d["availableTools"]}`;
|
|
131
|
+
case "memory.store":
|
|
132
|
+
return `label="${d["label"]}"`;
|
|
133
|
+
case "agent.error":
|
|
134
|
+
return `error="${d["message"]}"`;
|
|
135
|
+
default:
|
|
136
|
+
return JSON.stringify(d);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
truncate(str, max = 80) {
|
|
140
|
+
if (!str) return "";
|
|
141
|
+
return str.length > max ? str.substring(0, max) + "\u2026" : str;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var NoopEventEmitter = class {
|
|
145
|
+
emit(_event) {
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
3
149
|
// src/agent.ts
|
|
150
|
+
var DEFAULT_MAX_ITERATIONS = 5;
|
|
151
|
+
function buildReasoningSystemPrompt(toolIndex) {
|
|
152
|
+
const toolSection = toolIndex ? `
|
|
153
|
+
|
|
154
|
+
You have access to the following tools:
|
|
155
|
+
${toolIndex}
|
|
156
|
+
|
|
157
|
+
When you need to use a tool, set action to "tool_call" and provide the tool_call field.
|
|
158
|
+
When you set action to "tool_call", the system will inject the tool's full input schema
|
|
159
|
+
so you can construct the input accurately in the next step.` : "";
|
|
160
|
+
const toolCallFormat = toolIndex ? `,
|
|
161
|
+
"tool_call": null | { "name": "<tool_name>", "input": { ... } }` : "";
|
|
162
|
+
const toolRules = toolIndex ? `
|
|
163
|
+
- If you need to use a tool, set action to "tool_call" and specify the tool name and input in tool_call.
|
|
164
|
+
- After a tool executes, you will receive its result and can continue reasoning.` : "";
|
|
165
|
+
return `You are an AI agent. You MUST respond with valid JSON in the following format and nothing else:
|
|
166
|
+
|
|
167
|
+
{
|
|
168
|
+
"action": "done" | "continue"${toolIndex ? ' | "tool_call"' : ""},
|
|
169
|
+
"reasoning": "<your internal chain-of-thought for this step>",
|
|
170
|
+
"content": "<the output for the user (final answer when done, intermediate when continue)>"${toolCallFormat},
|
|
171
|
+
"memory": null | { "label": "<short title>", "content": "<knowledge to remember>", "tags": ["optional", "tags"] }
|
|
172
|
+
}${toolSection}
|
|
173
|
+
|
|
174
|
+
Rules:
|
|
175
|
+
- If you can answer directly, set action to "done" and provide the final answer in content.
|
|
176
|
+
- If you need more thinking, research, or steps, set action to "continue" and explain in reasoning what you still need.${toolRules}
|
|
177
|
+
- Only set memory when you discover knowledge worth persisting for future conversations.
|
|
178
|
+
- Always respond with valid JSON. No markdown, no code fences, no extra text.`;
|
|
179
|
+
}
|
|
4
180
|
var Agent = class {
|
|
5
181
|
name;
|
|
6
182
|
description;
|
|
183
|
+
provider;
|
|
184
|
+
systemPrompt;
|
|
185
|
+
defaultOptions;
|
|
186
|
+
maxIterations;
|
|
187
|
+
memory;
|
|
188
|
+
tools;
|
|
189
|
+
emitter;
|
|
190
|
+
messages = [];
|
|
7
191
|
constructor(config) {
|
|
8
192
|
if (!config.name || config.name.trim().length === 0) {
|
|
9
|
-
throw new
|
|
193
|
+
throw new AgentConfigError("Agent name is required and cannot be empty.");
|
|
194
|
+
}
|
|
195
|
+
if (!config.provider) {
|
|
196
|
+
throw new AgentConfigError("Agent requires an LLM provider.");
|
|
10
197
|
}
|
|
11
198
|
this.name = config.name;
|
|
12
199
|
this.description = config.description;
|
|
200
|
+
this.provider = config.provider;
|
|
201
|
+
this.systemPrompt = config.systemPrompt;
|
|
202
|
+
this.defaultOptions = config.defaultOptions;
|
|
203
|
+
this.maxIterations = config.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
204
|
+
this.memory = config.memory;
|
|
205
|
+
this.tools = config.tools;
|
|
206
|
+
this.emitter = config.emitter ?? new NoopEventEmitter();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Send a prompt to the agent and run the reasoning loop until the agent
|
|
210
|
+
* produces a final answer or exhausts max iterations.
|
|
211
|
+
*
|
|
212
|
+
* The loop handles three actions:
|
|
213
|
+
* - `done` → return final answer
|
|
214
|
+
* - `continue` → feed intermediate output back, next iteration
|
|
215
|
+
* - `tool_call` → inject full tool schema, LLM constructs input, execute tool, feed result back
|
|
216
|
+
*/
|
|
217
|
+
async invoke(prompt, options) {
|
|
218
|
+
this.emit("agent.invoke.start", { prompt });
|
|
219
|
+
this.messages.push({ role: "user", content: prompt });
|
|
220
|
+
const maxIter = options?.maxIterations ?? this.maxIterations;
|
|
221
|
+
const iterations = [];
|
|
222
|
+
let pendingToolSchema;
|
|
223
|
+
for (let i = 1; i <= maxIter; i++) {
|
|
224
|
+
this.emit("agent.iteration.start", { iteration: i, maxIterations: maxIter });
|
|
225
|
+
const messagesToSend = this.buildMessages(pendingToolSchema);
|
|
226
|
+
const mergedOptions = this.mergeOptions(options);
|
|
227
|
+
pendingToolSchema = void 0;
|
|
228
|
+
this.emit("llm.call.start", { messageCount: messagesToSend.length });
|
|
229
|
+
let rawResponse;
|
|
230
|
+
try {
|
|
231
|
+
rawResponse = await this.provider.chat(messagesToSend, mergedOptions);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this.emit("agent.error", { message: "LLM provider chat call failed.", error: String(error) });
|
|
234
|
+
throw new ProviderError("LLM provider chat call failed.", error);
|
|
235
|
+
}
|
|
236
|
+
this.emit("llm.call.end", { totalTokens: rawResponse.usage?.totalTokens });
|
|
237
|
+
const parsed = this.parseReasoningResponse(rawResponse.message.content);
|
|
238
|
+
const iterationResult = {
|
|
239
|
+
iteration: i,
|
|
240
|
+
response: parsed,
|
|
241
|
+
rawResponse
|
|
242
|
+
};
|
|
243
|
+
iterations.push(iterationResult);
|
|
244
|
+
if (parsed.memory && this.memory) {
|
|
245
|
+
this.emit("memory.store", { label: parsed.memory.label });
|
|
246
|
+
await this.memory.store(this.name, parsed.memory);
|
|
247
|
+
}
|
|
248
|
+
this.emit("agent.iteration.end", { iteration: i, action: parsed.action });
|
|
249
|
+
if (parsed.action === "done") {
|
|
250
|
+
this.messages.push({ role: "assistant", content: parsed.content });
|
|
251
|
+
this.emit("agent.invoke.end", { totalIterations: i, completed: true });
|
|
252
|
+
return {
|
|
253
|
+
content: parsed.content,
|
|
254
|
+
trace: { iterations, completed: true, totalIterations: i }
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (parsed.action === "tool_call" && parsed.tool_call) {
|
|
258
|
+
const toolCall = parsed.tool_call;
|
|
259
|
+
if (!this.tools || !this.tools.has(toolCall.name)) {
|
|
260
|
+
this.emit("tool.not_found", {
|
|
261
|
+
toolName: toolCall.name,
|
|
262
|
+
availableTools: this.tools?.getCompactIndex() ?? "none"
|
|
263
|
+
});
|
|
264
|
+
this.messages.push({ role: "assistant", content: rawResponse.message.content });
|
|
265
|
+
this.messages.push({
|
|
266
|
+
role: "user",
|
|
267
|
+
content: `Tool "${toolCall.name}" is not available. Available tools: ${this.tools?.getCompactIndex() ?? "none"}. Please choose a different approach.`
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (toolCall.input && Object.keys(toolCall.input).length > 0) {
|
|
272
|
+
this.emit("tool.call.start", { toolName: toolCall.name, input: toolCall.input });
|
|
273
|
+
const toolResult = await this.executeTool(toolCall);
|
|
274
|
+
this.emit("tool.call.end", { toolName: toolCall.name, success: toolResult.success });
|
|
275
|
+
this.messages.push({ role: "assistant", content: rawResponse.message.content });
|
|
276
|
+
this.messages.push({
|
|
277
|
+
role: "user",
|
|
278
|
+
content: `Tool "${toolCall.name}" result:
|
|
279
|
+
${JSON.stringify(toolResult, null, 2)}`
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
this.emit("tool.schema.inject", { toolName: toolCall.name });
|
|
284
|
+
const schema = this.tools.getFullSchema(toolCall.name);
|
|
285
|
+
pendingToolSchema = schema;
|
|
286
|
+
this.messages.push({ role: "assistant", content: rawResponse.message.content });
|
|
287
|
+
this.messages.push({
|
|
288
|
+
role: "user",
|
|
289
|
+
content: `Here is the full schema for tool "${toolCall.name}":
|
|
290
|
+
${schema}
|
|
291
|
+
|
|
292
|
+
Please provide the tool_call with the correct input.`
|
|
293
|
+
});
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
this.messages.push({ role: "assistant", content: rawResponse.message.content });
|
|
297
|
+
this.messages.push({
|
|
298
|
+
role: "user",
|
|
299
|
+
content: `Continue. Iteration ${i} of ${maxIter}. Previous reasoning: ${parsed.reasoning}`
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const lastIteration = iterations[iterations.length - 1];
|
|
303
|
+
const lastContent = lastIteration?.response.content ?? "Unable to complete the task.";
|
|
304
|
+
this.messages.push({ role: "assistant", content: lastContent });
|
|
305
|
+
this.emit("agent.invoke.end", { totalIterations: iterations.length, completed: false });
|
|
306
|
+
this.emit("agent.error", { message: `Max iterations (${maxIter}) exceeded.` });
|
|
307
|
+
throw new MaxIterationsError(maxIter, iterations.length);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Send a prompt to the agent and receive a streaming response.
|
|
311
|
+
* This is a single-pass stream (no reasoning loop) — useful for
|
|
312
|
+
* conversational responses where iteration is not needed.
|
|
313
|
+
*
|
|
314
|
+
* The assistant's full message is appended to conversation history
|
|
315
|
+
* once the stream completes.
|
|
316
|
+
*/
|
|
317
|
+
async *invokeStream(prompt, options) {
|
|
318
|
+
this.emit("agent.invoke_stream.start", { prompt });
|
|
319
|
+
this.messages.push({ role: "user", content: prompt });
|
|
320
|
+
const messagesToSend = this.buildMessages();
|
|
321
|
+
const mergedOptions = this.mergeOptions(options);
|
|
322
|
+
let fullContent = "";
|
|
323
|
+
try {
|
|
324
|
+
for await (const chunk of this.provider.chatStream(messagesToSend, mergedOptions)) {
|
|
325
|
+
fullContent += chunk.delta;
|
|
326
|
+
yield chunk;
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.emit("agent.error", { message: "LLM provider stream call failed.", error: String(error) });
|
|
330
|
+
throw new ProviderError("LLM provider stream call failed.", error);
|
|
331
|
+
}
|
|
332
|
+
this.messages.push({ role: "assistant", content: fullContent });
|
|
333
|
+
this.emit("agent.invoke_stream.end", { contentLength: fullContent.length });
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Returns a copy of the current conversation history.
|
|
337
|
+
*/
|
|
338
|
+
getMessages() {
|
|
339
|
+
return [...this.messages];
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Clears the conversation history.
|
|
343
|
+
*/
|
|
344
|
+
clearHistory() {
|
|
345
|
+
this.messages = [];
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Emits a lifecycle event via the configured emitter.
|
|
349
|
+
*/
|
|
350
|
+
emit(type, data) {
|
|
351
|
+
this.emitter.emit({
|
|
352
|
+
type,
|
|
353
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
354
|
+
agentName: this.name,
|
|
355
|
+
data
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Executes a tool and returns the result. Catches errors gracefully
|
|
360
|
+
* so the agent can recover.
|
|
361
|
+
*/
|
|
362
|
+
async executeTool(toolCall) {
|
|
363
|
+
const tool = this.tools.get(toolCall.name);
|
|
364
|
+
try {
|
|
365
|
+
return await tool.execute(toolCall.input);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
368
|
+
return {
|
|
369
|
+
toolName: toolCall.name,
|
|
370
|
+
success: false,
|
|
371
|
+
output: "",
|
|
372
|
+
error: message
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Parses the LLM's raw text response into a structured `LLMReasoningResponse`.
|
|
378
|
+
* Attempts to extract JSON from the response, handling common LLM quirks
|
|
379
|
+
* like wrapping in code fences.
|
|
380
|
+
*/
|
|
381
|
+
parseReasoningResponse(raw) {
|
|
382
|
+
const cleaned = raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
383
|
+
let parsed;
|
|
384
|
+
try {
|
|
385
|
+
parsed = JSON.parse(cleaned);
|
|
386
|
+
} catch {
|
|
387
|
+
throw new ReasoningParseError(
|
|
388
|
+
`Failed to parse LLM response as JSON: ${raw.substring(0, 200)}`,
|
|
389
|
+
raw
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (typeof parsed !== "object" || parsed === null || !("action" in parsed) || !("content" in parsed)) {
|
|
393
|
+
throw new ReasoningParseError(
|
|
394
|
+
"LLM response JSON is missing required fields (action, content).",
|
|
395
|
+
raw
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const obj = parsed;
|
|
399
|
+
if (obj["action"] !== "done" && obj["action"] !== "continue" && obj["action"] !== "tool_call") {
|
|
400
|
+
throw new ReasoningParseError(
|
|
401
|
+
`Invalid action "${String(obj["action"])}". Must be "done", "continue", or "tool_call".`,
|
|
402
|
+
raw
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
action: obj["action"],
|
|
407
|
+
reasoning: typeof obj["reasoning"] === "string" ? obj["reasoning"] : "",
|
|
408
|
+
content: typeof obj["content"] === "string" ? obj["content"] : "",
|
|
409
|
+
tool_call: this.parseToolCall(obj["tool_call"]),
|
|
410
|
+
memory: this.parseMemoryEntry(obj["memory"])
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Parses a tool_call field from the LLM response.
|
|
415
|
+
*/
|
|
416
|
+
parseToolCall(raw) {
|
|
417
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
418
|
+
const obj = raw;
|
|
419
|
+
if (typeof obj["name"] !== "string") return void 0;
|
|
420
|
+
return {
|
|
421
|
+
name: obj["name"],
|
|
422
|
+
input: typeof obj["input"] === "object" && obj["input"] !== null ? obj["input"] : {}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Parses an optional memory entry from the LLM response.
|
|
427
|
+
*/
|
|
428
|
+
parseMemoryEntry(raw) {
|
|
429
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
430
|
+
const obj = raw;
|
|
431
|
+
if (typeof obj["label"] !== "string" || typeof obj["content"] !== "string") {
|
|
432
|
+
return void 0;
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
label: obj["label"],
|
|
436
|
+
content: obj["content"],
|
|
437
|
+
tags: Array.isArray(obj["tags"]) ? obj["tags"].filter((t) => typeof t === "string") : void 0
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Builds the full message array to send to the provider.
|
|
442
|
+
* Includes: reasoning system prompt (with compact tool index) → custom system prompt → conversation.
|
|
443
|
+
* Optionally appends the full schema for a pending tool call.
|
|
444
|
+
*/
|
|
445
|
+
buildMessages(pendingToolSchema) {
|
|
446
|
+
const messages = [];
|
|
447
|
+
const toolIndex = this.tools?.getCompactIndex() ?? "";
|
|
448
|
+
messages.push({ role: "system", content: buildReasoningSystemPrompt(toolIndex) });
|
|
449
|
+
if (this.systemPrompt) {
|
|
450
|
+
messages.push({ role: "system", content: this.systemPrompt });
|
|
451
|
+
}
|
|
452
|
+
messages.push(...this.messages);
|
|
453
|
+
if (pendingToolSchema) {
|
|
454
|
+
messages.push({
|
|
455
|
+
role: "system",
|
|
456
|
+
content: `Full tool schema for your tool_call:
|
|
457
|
+
${pendingToolSchema}`
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return messages;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Merges per-call options with default options. Per-call options take precedence.
|
|
464
|
+
*/
|
|
465
|
+
mergeOptions(options) {
|
|
466
|
+
if (!this.defaultOptions && !options) return void 0;
|
|
467
|
+
return { ...this.defaultOptions, ...options };
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
var FlatFileMemoryProvider = class {
|
|
471
|
+
constructor(directory) {
|
|
472
|
+
this.directory = directory;
|
|
473
|
+
}
|
|
474
|
+
async store(agentName, entry) {
|
|
475
|
+
await fs__namespace.promises.mkdir(this.directory, { recursive: true });
|
|
476
|
+
const filePath = this.getFilePath(agentName);
|
|
477
|
+
const knlBlock = this.toKnlBlock(agentName, entry);
|
|
478
|
+
await fs__namespace.promises.appendFile(filePath, knlBlock + "\n", "utf-8");
|
|
479
|
+
}
|
|
480
|
+
async retrieve(agentName) {
|
|
481
|
+
const filePath = this.getFilePath(agentName);
|
|
482
|
+
let content;
|
|
483
|
+
try {
|
|
484
|
+
content = await fs__namespace.promises.readFile(filePath, "utf-8");
|
|
485
|
+
} catch {
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
return this.parseKnlBlocks(content);
|
|
489
|
+
}
|
|
490
|
+
getFilePath(agentName) {
|
|
491
|
+
const safeName = agentName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
492
|
+
return path__namespace.join(this.directory, `${safeName}.memory.knl`);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Converts a MemoryEntry into a KNL DATA block.
|
|
496
|
+
*
|
|
497
|
+
* Format:
|
|
498
|
+
* ```
|
|
499
|
+
* <<<
|
|
500
|
+
* KNL[DATA][1.0]:::
|
|
501
|
+
* ID[knl:data:easa.memory:{agentName}:{timestamp}:v1.0]:::
|
|
502
|
+
* NS[easa.memory]:::
|
|
503
|
+
* LABEL["{label}"]:::
|
|
504
|
+
* TAGS[{tags}]:::
|
|
505
|
+
* ITEM["{content}"] [DESC:"{label}"]
|
|
506
|
+
* >>>
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
509
|
+
toKnlBlock(agentName, entry) {
|
|
510
|
+
const timestamp = Date.now();
|
|
511
|
+
const safeName = agentName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
512
|
+
const escapedLabel = this.escapeKnl(entry.label);
|
|
513
|
+
const escapedContent = this.escapeKnl(entry.content);
|
|
514
|
+
const tags = entry.tags?.join(",") ?? "";
|
|
515
|
+
const lines = [
|
|
516
|
+
"<<<",
|
|
517
|
+
"KNL[DATA][1.0]:::",
|
|
518
|
+
`ID[knl:data:easa.memory:${safeName}:${timestamp}:v1.0]:::`,
|
|
519
|
+
"NS[easa.memory]:::",
|
|
520
|
+
`LABEL["${escapedLabel}"]:::`
|
|
521
|
+
];
|
|
522
|
+
if (tags) {
|
|
523
|
+
lines.push(`TAGS[${tags}]:::`);
|
|
524
|
+
}
|
|
525
|
+
lines.push(`ITEM["${escapedContent}"] [DESC:"${escapedLabel}"]`);
|
|
526
|
+
lines.push(">>>");
|
|
527
|
+
return lines.join("\n");
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Parses KNL DATA blocks back into MemoryEntry objects.
|
|
531
|
+
*/
|
|
532
|
+
parseKnlBlocks(content) {
|
|
533
|
+
const entries = [];
|
|
534
|
+
const blockPattern = /<<<([\s\S]*?)>>>/g;
|
|
535
|
+
let match;
|
|
536
|
+
while ((match = blockPattern.exec(content)) !== null) {
|
|
537
|
+
const block = match[1] ?? "";
|
|
538
|
+
const labelMatch = /LABEL\["([^"]*)"\]/.exec(block);
|
|
539
|
+
const itemMatch = /ITEM\["([^"]*)"\]/.exec(block);
|
|
540
|
+
const tagsMatch = /TAGS\[([^\]]*)\]/.exec(block);
|
|
541
|
+
if (labelMatch?.[1] && itemMatch?.[1]) {
|
|
542
|
+
const entry = {
|
|
543
|
+
label: this.unescapeKnl(labelMatch[1]),
|
|
544
|
+
content: this.unescapeKnl(itemMatch[1])
|
|
545
|
+
};
|
|
546
|
+
if (tagsMatch?.[1]) {
|
|
547
|
+
entry.tags = tagsMatch[1].split(",").map((t) => t.trim()).filter(Boolean);
|
|
548
|
+
}
|
|
549
|
+
entries.push(entry);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return entries;
|
|
553
|
+
}
|
|
554
|
+
escapeKnl(str) {
|
|
555
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
556
|
+
}
|
|
557
|
+
unescapeKnl(str) {
|
|
558
|
+
return str.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// src/tool.ts
|
|
563
|
+
var ToolRegistry = class {
|
|
564
|
+
tools = /* @__PURE__ */ new Map();
|
|
565
|
+
/**
|
|
566
|
+
* Register one or more tools.
|
|
567
|
+
*/
|
|
568
|
+
register(...tools) {
|
|
569
|
+
for (const tool of tools) {
|
|
570
|
+
this.tools.set(tool.definition.name, tool);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Get a tool by name. Returns undefined if not found.
|
|
575
|
+
*/
|
|
576
|
+
get(name) {
|
|
577
|
+
return this.tools.get(name);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Returns all registered tool definitions.
|
|
581
|
+
*/
|
|
582
|
+
getDefinitions() {
|
|
583
|
+
return Array.from(this.tools.values()).map((t) => t.definition);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Returns a compact index of tools: just names and descriptions.
|
|
587
|
+
* This is what gets sent to the LLM on every call (token-efficient).
|
|
588
|
+
*/
|
|
589
|
+
getCompactIndex() {
|
|
590
|
+
if (this.tools.size === 0) return "";
|
|
591
|
+
const lines = Array.from(this.tools.values()).map(
|
|
592
|
+
(t) => `- ${t.definition.name}: ${t.definition.description}`
|
|
593
|
+
);
|
|
594
|
+
return lines.join("\n");
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Returns the full schema for a specific tool (name, description, inputSchema).
|
|
598
|
+
* Used when the LLM requests a tool call — injected into the next iteration
|
|
599
|
+
* so the LLM can construct the input accurately.
|
|
600
|
+
*/
|
|
601
|
+
getFullSchema(name) {
|
|
602
|
+
const tool = this.tools.get(name);
|
|
603
|
+
if (!tool) return void 0;
|
|
604
|
+
return JSON.stringify(
|
|
605
|
+
{
|
|
606
|
+
name: tool.definition.name,
|
|
607
|
+
description: tool.definition.description,
|
|
608
|
+
inputSchema: tool.definition.inputSchema
|
|
609
|
+
},
|
|
610
|
+
null,
|
|
611
|
+
2
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Returns the number of registered tools.
|
|
616
|
+
*/
|
|
617
|
+
get size() {
|
|
618
|
+
return this.tools.size;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Check if a tool with the given name is registered.
|
|
622
|
+
*/
|
|
623
|
+
has(name) {
|
|
624
|
+
return this.tools.has(name);
|
|
13
625
|
}
|
|
14
626
|
};
|
|
15
627
|
|
|
16
628
|
exports.Agent = Agent;
|
|
629
|
+
exports.AgentConfigError = AgentConfigError;
|
|
630
|
+
exports.ConsoleEventEmitter = ConsoleEventEmitter;
|
|
631
|
+
exports.EasaError = EasaError;
|
|
632
|
+
exports.FlatFileMemoryProvider = FlatFileMemoryProvider;
|
|
633
|
+
exports.MaxIterationsError = MaxIterationsError;
|
|
634
|
+
exports.NoopEventEmitter = NoopEventEmitter;
|
|
635
|
+
exports.ProviderError = ProviderError;
|
|
636
|
+
exports.ReasoningParseError = ReasoningParseError;
|
|
637
|
+
exports.ToolExecutionError = ToolExecutionError;
|
|
638
|
+
exports.ToolRegistry = ToolRegistry;
|
|
17
639
|
//# sourceMappingURL=index.cjs.map
|
|
18
640
|
//# sourceMappingURL=index.cjs.map
|