@opperai/agents 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -0
- package/dist/index.cjs +2656 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1927 -0
- package/dist/index.d.ts +1927 -0
- package/dist/index.js +2616 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2656 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
var zod = require('zod');
|
|
5
|
+
var opperai = require('opperai');
|
|
6
|
+
var zodToJsonSchema = require('zod-to-json-schema');
|
|
7
|
+
var index_js = require('@modelcontextprotocol/sdk/client/index.js');
|
|
8
|
+
var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
|
|
9
|
+
var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
10
|
+
var streamableHttp_js = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
|
|
11
|
+
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __esm = (fn, res) => function __init() {
|
|
15
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
16
|
+
};
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/base/result.ts
|
|
23
|
+
exports.Result = void 0; exports.ok = void 0; exports.err = void 0;
|
|
24
|
+
var init_result = __esm({
|
|
25
|
+
"src/base/result.ts"() {
|
|
26
|
+
exports.Result = {
|
|
27
|
+
ok(value) {
|
|
28
|
+
return { ok: true, value };
|
|
29
|
+
},
|
|
30
|
+
err(error) {
|
|
31
|
+
return { ok: false, error };
|
|
32
|
+
},
|
|
33
|
+
isOk(result) {
|
|
34
|
+
return result.ok;
|
|
35
|
+
},
|
|
36
|
+
isErr(result) {
|
|
37
|
+
return !result.ok;
|
|
38
|
+
},
|
|
39
|
+
map(result, mapper) {
|
|
40
|
+
if (result.ok) {
|
|
41
|
+
return exports.Result.ok(mapper(result.value));
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
},
|
|
45
|
+
mapError(result, mapper) {
|
|
46
|
+
if (!result.ok) {
|
|
47
|
+
return exports.Result.err(mapper(result.error));
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
},
|
|
51
|
+
unwrapOr(result, fallback) {
|
|
52
|
+
return result.ok ? result.value : fallback;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
exports.ok = exports.Result.ok;
|
|
56
|
+
exports.err = exports.Result.err;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
exports.ToolMetadataSchema = void 0; exports.ToolCallRecordSchema = void 0; var toolResultBaseSchema; exports.ToolResultSuccessSchema = void 0; exports.ToolResultFailureSchema = void 0; exports.ToolResultSchema = void 0; exports.ToolResultFactory = void 0; exports.createToolCallRecord = void 0; exports.validateToolInput = void 0; exports.coerceToolDefinition = void 0; exports.isToolProvider = void 0; exports.normalizeToolEntries = void 0;
|
|
60
|
+
var init_tool = __esm({
|
|
61
|
+
"src/base/tool.ts"() {
|
|
62
|
+
init_result();
|
|
63
|
+
exports.ToolMetadataSchema = zod.z.record(zod.z.string(), zod.z.unknown());
|
|
64
|
+
exports.ToolCallRecordSchema = zod.z.object({
|
|
65
|
+
id: zod.z.string().optional().default(() => crypto.randomUUID()),
|
|
66
|
+
toolName: zod.z.string(),
|
|
67
|
+
input: zod.z.unknown(),
|
|
68
|
+
output: zod.z.unknown().optional(),
|
|
69
|
+
success: zod.z.boolean().optional(),
|
|
70
|
+
error: zod.z.union([zod.z.string(), zod.z.object({ message: zod.z.string() }).passthrough()]).optional(),
|
|
71
|
+
startedAt: zod.z.number().default(() => Date.now()),
|
|
72
|
+
finishedAt: zod.z.number().optional(),
|
|
73
|
+
metadata: exports.ToolMetadataSchema.default({})
|
|
74
|
+
});
|
|
75
|
+
toolResultBaseSchema = zod.z.object({
|
|
76
|
+
toolName: zod.z.string(),
|
|
77
|
+
metadata: exports.ToolMetadataSchema.default({}),
|
|
78
|
+
startedAt: zod.z.number().optional(),
|
|
79
|
+
finishedAt: zod.z.number().optional()
|
|
80
|
+
});
|
|
81
|
+
exports.ToolResultSuccessSchema = toolResultBaseSchema.extend({
|
|
82
|
+
success: zod.z.literal(true),
|
|
83
|
+
output: zod.z.any()
|
|
84
|
+
}).refine((data) => data.output !== void 0, {
|
|
85
|
+
message: "output is required"
|
|
86
|
+
});
|
|
87
|
+
exports.ToolResultFailureSchema = toolResultBaseSchema.extend({
|
|
88
|
+
success: zod.z.literal(false),
|
|
89
|
+
error: zod.z.union([zod.z.string(), zod.z.instanceof(Error)])
|
|
90
|
+
});
|
|
91
|
+
exports.ToolResultSchema = zod.z.union([
|
|
92
|
+
exports.ToolResultSuccessSchema,
|
|
93
|
+
exports.ToolResultFailureSchema
|
|
94
|
+
]);
|
|
95
|
+
exports.ToolResultFactory = {
|
|
96
|
+
success(toolName, output, init = {}) {
|
|
97
|
+
const result = {
|
|
98
|
+
success: true,
|
|
99
|
+
toolName,
|
|
100
|
+
output,
|
|
101
|
+
...init.startedAt !== void 0 && { startedAt: init.startedAt },
|
|
102
|
+
finishedAt: init.finishedAt ?? Date.now(),
|
|
103
|
+
metadata: init.metadata ?? {}
|
|
104
|
+
};
|
|
105
|
+
exports.ToolResultSuccessSchema.parse(result);
|
|
106
|
+
return result;
|
|
107
|
+
},
|
|
108
|
+
failure(toolName, error, init = {}) {
|
|
109
|
+
const result = {
|
|
110
|
+
success: false,
|
|
111
|
+
toolName,
|
|
112
|
+
error,
|
|
113
|
+
...init.startedAt !== void 0 && { startedAt: init.startedAt },
|
|
114
|
+
finishedAt: init.finishedAt ?? Date.now(),
|
|
115
|
+
metadata: init.metadata ?? {}
|
|
116
|
+
};
|
|
117
|
+
exports.ToolResultFailureSchema.parse(result);
|
|
118
|
+
return result;
|
|
119
|
+
},
|
|
120
|
+
isSuccess(result) {
|
|
121
|
+
return result.success;
|
|
122
|
+
},
|
|
123
|
+
isFailure(result) {
|
|
124
|
+
return !result.success;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
exports.createToolCallRecord = (input) => exports.ToolCallRecordSchema.parse(input);
|
|
128
|
+
exports.validateToolInput = (schema, payload) => {
|
|
129
|
+
const parsed = schema.safeParse(payload);
|
|
130
|
+
if (!parsed.success) {
|
|
131
|
+
return exports.err(parsed.error);
|
|
132
|
+
}
|
|
133
|
+
return exports.ok(parsed.data);
|
|
134
|
+
};
|
|
135
|
+
exports.coerceToolDefinition = (definition) => {
|
|
136
|
+
if (!definition.name || definition.name.trim().length === 0) {
|
|
137
|
+
throw new Error("Tool definition requires a non-empty name");
|
|
138
|
+
}
|
|
139
|
+
if (!definition.metadata) {
|
|
140
|
+
definition.metadata = {};
|
|
141
|
+
}
|
|
142
|
+
return definition;
|
|
143
|
+
};
|
|
144
|
+
exports.isToolProvider = (value) => {
|
|
145
|
+
if (typeof value !== "object" || value === null) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const candidate = value;
|
|
149
|
+
return typeof candidate.setup === "function" && typeof candidate.teardown === "function";
|
|
150
|
+
};
|
|
151
|
+
exports.normalizeToolEntries = (entries) => {
|
|
152
|
+
const tools = [];
|
|
153
|
+
const providers = [];
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
if (exports.isToolProvider(entry)) {
|
|
156
|
+
providers.push(entry);
|
|
157
|
+
} else {
|
|
158
|
+
tools.push(entry);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { tools, providers };
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/base/context.ts
|
|
167
|
+
var context_exports = {};
|
|
168
|
+
__export(context_exports, {
|
|
169
|
+
AgentContext: () => exports.AgentContext,
|
|
170
|
+
ExecutionCycleSchema: () => exports.ExecutionCycleSchema,
|
|
171
|
+
UsageSchema: () => exports.UsageSchema
|
|
172
|
+
});
|
|
173
|
+
exports.UsageSchema = void 0; exports.ExecutionCycleSchema = void 0; exports.AgentContext = void 0;
|
|
174
|
+
var init_context = __esm({
|
|
175
|
+
"src/base/context.ts"() {
|
|
176
|
+
init_tool();
|
|
177
|
+
exports.UsageSchema = zod.z.object({
|
|
178
|
+
requests: zod.z.number().int().nonnegative().default(0),
|
|
179
|
+
inputTokens: zod.z.number().int().nonnegative().default(0),
|
|
180
|
+
outputTokens: zod.z.number().int().nonnegative().default(0),
|
|
181
|
+
totalTokens: zod.z.number().int().nonnegative().default(0),
|
|
182
|
+
cost: zod.z.object({
|
|
183
|
+
generation: zod.z.number().nonnegative().default(0),
|
|
184
|
+
platform: zod.z.number().nonnegative().default(0),
|
|
185
|
+
total: zod.z.number().nonnegative().default(0)
|
|
186
|
+
}).default({ generation: 0, platform: 0, total: 0 })
|
|
187
|
+
});
|
|
188
|
+
exports.ExecutionCycleSchema = zod.z.object({
|
|
189
|
+
iteration: zod.z.number().int().nonnegative(),
|
|
190
|
+
thought: zod.z.unknown().nullable().optional(),
|
|
191
|
+
toolCalls: zod.z.array(exports.ToolCallRecordSchema).default([]),
|
|
192
|
+
results: zod.z.array(zod.z.unknown()).default([]),
|
|
193
|
+
timestamp: zod.z.number().default(() => Date.now())
|
|
194
|
+
});
|
|
195
|
+
exports.AgentContext = class {
|
|
196
|
+
agentName;
|
|
197
|
+
sessionId;
|
|
198
|
+
parentSpanId;
|
|
199
|
+
iteration = 0;
|
|
200
|
+
goal;
|
|
201
|
+
executionHistory = [];
|
|
202
|
+
toolCalls = [];
|
|
203
|
+
usage;
|
|
204
|
+
metadata;
|
|
205
|
+
startedAt;
|
|
206
|
+
updatedAt;
|
|
207
|
+
constructor(options) {
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
this.agentName = options.agentName;
|
|
210
|
+
this.sessionId = options.sessionId ?? crypto.randomUUID();
|
|
211
|
+
this.parentSpanId = options.parentSpanId ?? null;
|
|
212
|
+
this.goal = options.goal;
|
|
213
|
+
this.metadata = { ...options.metadata ?? {} };
|
|
214
|
+
this.startedAt = now;
|
|
215
|
+
this.updatedAt = now;
|
|
216
|
+
this.usage = exports.UsageSchema.parse({});
|
|
217
|
+
}
|
|
218
|
+
updateUsage(delta) {
|
|
219
|
+
const next = exports.UsageSchema.parse(delta);
|
|
220
|
+
this.usage = {
|
|
221
|
+
requests: this.usage.requests + next.requests,
|
|
222
|
+
inputTokens: this.usage.inputTokens + next.inputTokens,
|
|
223
|
+
outputTokens: this.usage.outputTokens + next.outputTokens,
|
|
224
|
+
totalTokens: this.usage.totalTokens + next.totalTokens,
|
|
225
|
+
cost: {
|
|
226
|
+
generation: this.usage.cost.generation + next.cost.generation,
|
|
227
|
+
platform: this.usage.cost.platform + next.cost.platform,
|
|
228
|
+
total: this.usage.cost.total + next.cost.total
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
this.touch();
|
|
232
|
+
}
|
|
233
|
+
addCycle(cycle) {
|
|
234
|
+
const parsed = exports.ExecutionCycleSchema.parse(cycle);
|
|
235
|
+
this.executionHistory.push(parsed);
|
|
236
|
+
this.touch();
|
|
237
|
+
return parsed;
|
|
238
|
+
}
|
|
239
|
+
recordToolCall(call) {
|
|
240
|
+
const parsed = exports.ToolCallRecordSchema.parse(call);
|
|
241
|
+
this.toolCalls.push(parsed);
|
|
242
|
+
this.touch();
|
|
243
|
+
return parsed;
|
|
244
|
+
}
|
|
245
|
+
getContextSize() {
|
|
246
|
+
return this.usage.totalTokens;
|
|
247
|
+
}
|
|
248
|
+
getLastNCycles(count = 3) {
|
|
249
|
+
if (count <= 0) {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
return this.executionHistory.slice(-count);
|
|
253
|
+
}
|
|
254
|
+
getLastIterationsSummary(count = 2) {
|
|
255
|
+
return this.getLastNCycles(count).map((cycle) => {
|
|
256
|
+
const thought = typeof cycle.thought === "object" && cycle.thought !== null ? { ...cycle.thought } : { text: cycle.thought };
|
|
257
|
+
return {
|
|
258
|
+
iteration: cycle.iteration,
|
|
259
|
+
thought,
|
|
260
|
+
toolCalls: cycle.toolCalls.map((call) => ({
|
|
261
|
+
toolName: call.toolName,
|
|
262
|
+
...call.success !== void 0 && { success: call.success }
|
|
263
|
+
})),
|
|
264
|
+
results: [...cycle.results]
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
setMetadata(key, value) {
|
|
269
|
+
this.metadata[key] = value;
|
|
270
|
+
this.touch();
|
|
271
|
+
}
|
|
272
|
+
clearHistory() {
|
|
273
|
+
this.executionHistory.length = 0;
|
|
274
|
+
this.toolCalls.length = 0;
|
|
275
|
+
this.touch();
|
|
276
|
+
}
|
|
277
|
+
snapshot() {
|
|
278
|
+
return {
|
|
279
|
+
agentName: this.agentName,
|
|
280
|
+
sessionId: this.sessionId,
|
|
281
|
+
parentSpanId: this.parentSpanId,
|
|
282
|
+
iteration: this.iteration,
|
|
283
|
+
goal: this.goal,
|
|
284
|
+
executionHistory: [...this.executionHistory],
|
|
285
|
+
usage: { ...this.usage },
|
|
286
|
+
toolCalls: [...this.toolCalls],
|
|
287
|
+
metadata: { ...this.metadata },
|
|
288
|
+
startedAt: this.startedAt,
|
|
289
|
+
updatedAt: this.updatedAt
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
touch() {
|
|
293
|
+
this.updatedAt = Date.now();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// src/utils/logger.ts
|
|
300
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
301
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
302
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
303
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
304
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
305
|
+
LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
|
|
306
|
+
return LogLevel2;
|
|
307
|
+
})(LogLevel || {});
|
|
308
|
+
var formatMeta = (meta) => {
|
|
309
|
+
if (!meta) {
|
|
310
|
+
return "";
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
return ` ${JSON.stringify(meta)}`;
|
|
314
|
+
} catch {
|
|
315
|
+
return " [unserializable meta]";
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
var writeToStdout = (level, message, meta) => {
|
|
319
|
+
const suffix = formatMeta(meta);
|
|
320
|
+
process.stdout.write(`[${level}] ${message}${suffix}
|
|
321
|
+
`);
|
|
322
|
+
};
|
|
323
|
+
var ConsoleLogger = class {
|
|
324
|
+
level;
|
|
325
|
+
constructor(level = 1 /* INFO */) {
|
|
326
|
+
this.level = level;
|
|
327
|
+
}
|
|
328
|
+
debug(message, meta) {
|
|
329
|
+
if (this.level <= 0 /* DEBUG */) {
|
|
330
|
+
writeToStdout("DEBUG", message, meta);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
info(message, meta) {
|
|
334
|
+
if (this.level <= 1 /* INFO */) {
|
|
335
|
+
writeToStdout("INFO", message, meta);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
warn(message, meta) {
|
|
339
|
+
if (this.level <= 2 /* WARN */) {
|
|
340
|
+
console.warn(`[WARN] ${message}`, meta ? meta : "");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
error(message, error, meta) {
|
|
344
|
+
if (this.level <= 3 /* ERROR */) {
|
|
345
|
+
if (error) {
|
|
346
|
+
console.error(`[ERROR] ${message}`, error, meta ? meta : "");
|
|
347
|
+
} else {
|
|
348
|
+
console.error(`[ERROR] ${message}`, meta ? meta : "");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
getLevel() {
|
|
353
|
+
return this.level;
|
|
354
|
+
}
|
|
355
|
+
setLevel(level) {
|
|
356
|
+
this.level = level;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var SilentLogger = class {
|
|
360
|
+
debug(message, meta) {
|
|
361
|
+
}
|
|
362
|
+
info(message, meta) {
|
|
363
|
+
}
|
|
364
|
+
warn(message, meta) {
|
|
365
|
+
}
|
|
366
|
+
error(message, error, meta) {
|
|
367
|
+
}
|
|
368
|
+
getLevel() {
|
|
369
|
+
return 4 /* SILENT */;
|
|
370
|
+
}
|
|
371
|
+
setLevel(level) {
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
var defaultLogger = new ConsoleLogger(2 /* WARN */);
|
|
375
|
+
function getDefaultLogger() {
|
|
376
|
+
return defaultLogger;
|
|
377
|
+
}
|
|
378
|
+
function setDefaultLogger(logger) {
|
|
379
|
+
defaultLogger = logger;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/base/hooks.ts
|
|
383
|
+
var HookEvents = {
|
|
384
|
+
AgentStart: "agent:start",
|
|
385
|
+
AgentEnd: "agent:end",
|
|
386
|
+
LoopStart: "loop:start",
|
|
387
|
+
LoopEnd: "loop:end",
|
|
388
|
+
LlmCall: "llm:call",
|
|
389
|
+
LlmResponse: "llm:response",
|
|
390
|
+
ThinkEnd: "think:end",
|
|
391
|
+
BeforeTool: "tool:before",
|
|
392
|
+
AfterTool: "tool:after",
|
|
393
|
+
ToolError: "tool:error",
|
|
394
|
+
MemoryRead: "memory:read",
|
|
395
|
+
MemoryWrite: "memory:write",
|
|
396
|
+
MemoryError: "memory:error"
|
|
397
|
+
};
|
|
398
|
+
var HookManager = class {
|
|
399
|
+
registry = /* @__PURE__ */ new Map();
|
|
400
|
+
logger;
|
|
401
|
+
constructor(logger) {
|
|
402
|
+
this.logger = logger ?? getDefaultLogger();
|
|
403
|
+
}
|
|
404
|
+
on(event, handler) {
|
|
405
|
+
const existing = this.registry.get(event) ?? /* @__PURE__ */ new Set();
|
|
406
|
+
existing.add(handler);
|
|
407
|
+
this.registry.set(event, existing);
|
|
408
|
+
return () => existing.delete(handler);
|
|
409
|
+
}
|
|
410
|
+
once(event, handler) {
|
|
411
|
+
const disposable = async (payload) => {
|
|
412
|
+
await handler(payload);
|
|
413
|
+
this.off(event, disposable);
|
|
414
|
+
};
|
|
415
|
+
return this.on(event, disposable);
|
|
416
|
+
}
|
|
417
|
+
off(event, handler) {
|
|
418
|
+
const listeners = this.registry.get(event);
|
|
419
|
+
if (!listeners) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
listeners.delete(handler);
|
|
423
|
+
if (listeners.size === 0) {
|
|
424
|
+
this.registry.delete(event);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
clear() {
|
|
428
|
+
this.registry.clear();
|
|
429
|
+
}
|
|
430
|
+
listenerCount(event) {
|
|
431
|
+
if (event) {
|
|
432
|
+
return this.registry.get(event)?.size ?? 0;
|
|
433
|
+
}
|
|
434
|
+
let total = 0;
|
|
435
|
+
for (const listeners of this.registry.values()) {
|
|
436
|
+
total += listeners.size;
|
|
437
|
+
}
|
|
438
|
+
return total;
|
|
439
|
+
}
|
|
440
|
+
async emit(event, payload) {
|
|
441
|
+
const listeners = Array.from(
|
|
442
|
+
this.registry.get(event) ?? /* @__PURE__ */ new Set()
|
|
443
|
+
);
|
|
444
|
+
if (listeners.length === 0) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
for (const [index, listener] of listeners.entries()) {
|
|
448
|
+
try {
|
|
449
|
+
await Promise.resolve(listener(payload));
|
|
450
|
+
} catch (error) {
|
|
451
|
+
this.logger.warn(`Hook handler failed for event "${event}"`, {
|
|
452
|
+
event,
|
|
453
|
+
handlerIndex: index,
|
|
454
|
+
error: error instanceof Error ? error.message : String(error)
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
registerMany(registrations) {
|
|
460
|
+
const cleanups = registrations.map(
|
|
461
|
+
({ event, handler }) => this.on(event, handler)
|
|
462
|
+
);
|
|
463
|
+
return () => cleanups.forEach((dispose) => dispose());
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
var createHookManager = (logger) => new HookManager(logger);
|
|
467
|
+
|
|
468
|
+
// src/base/agent.ts
|
|
469
|
+
init_tool();
|
|
470
|
+
var MemoryEntryMetadataSchema = zod.z.object({
|
|
471
|
+
createdAt: zod.z.number(),
|
|
472
|
+
updatedAt: zod.z.number(),
|
|
473
|
+
accessCount: zod.z.number().default(0)
|
|
474
|
+
});
|
|
475
|
+
var MemoryEntrySchema = zod.z.object({
|
|
476
|
+
key: zod.z.string(),
|
|
477
|
+
description: zod.z.string(),
|
|
478
|
+
value: zod.z.unknown(),
|
|
479
|
+
metadata: MemoryEntryMetadataSchema
|
|
480
|
+
});
|
|
481
|
+
var InMemoryStore = class {
|
|
482
|
+
store = /* @__PURE__ */ new Map();
|
|
483
|
+
/**
|
|
484
|
+
* Check if memory has any entries
|
|
485
|
+
*/
|
|
486
|
+
async hasEntries() {
|
|
487
|
+
return this.store.size > 0;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* List memory catalog (summaries without values) for LLM consumption
|
|
491
|
+
*/
|
|
492
|
+
async listEntries() {
|
|
493
|
+
const catalog = [];
|
|
494
|
+
for (const entry of this.store.values()) {
|
|
495
|
+
catalog.push({
|
|
496
|
+
key: entry.key,
|
|
497
|
+
description: entry.description,
|
|
498
|
+
metadata: { ...entry.metadata }
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return catalog;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Read values from memory by keys
|
|
505
|
+
*/
|
|
506
|
+
async read(keys) {
|
|
507
|
+
const keysToRead = keys ?? Array.from(this.store.keys());
|
|
508
|
+
const snapshot = {};
|
|
509
|
+
for (const key of keysToRead) {
|
|
510
|
+
const entry = this.store.get(key);
|
|
511
|
+
if (entry) {
|
|
512
|
+
entry.metadata.accessCount += 1;
|
|
513
|
+
entry.metadata.updatedAt = Date.now();
|
|
514
|
+
snapshot[key] = entry.value;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return snapshot;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Write a value to memory with description
|
|
521
|
+
*/
|
|
522
|
+
async write(key, value, description, metadata) {
|
|
523
|
+
const now = Date.now();
|
|
524
|
+
const existing = this.store.get(key);
|
|
525
|
+
const entry = {
|
|
526
|
+
key,
|
|
527
|
+
description: description ?? existing?.description ?? key,
|
|
528
|
+
value,
|
|
529
|
+
metadata: existing ? {
|
|
530
|
+
createdAt: existing.metadata.createdAt,
|
|
531
|
+
updatedAt: now,
|
|
532
|
+
accessCount: existing.metadata.accessCount,
|
|
533
|
+
...metadata ?? {}
|
|
534
|
+
} : {
|
|
535
|
+
createdAt: now,
|
|
536
|
+
updatedAt: now,
|
|
537
|
+
accessCount: 0,
|
|
538
|
+
...metadata ?? {}
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
this.store.set(key, entry);
|
|
542
|
+
return { ...entry, metadata: { ...entry.metadata } };
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Delete a memory entry by key
|
|
546
|
+
*/
|
|
547
|
+
async delete(key) {
|
|
548
|
+
return this.store.delete(key);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Clear all memory entries
|
|
552
|
+
*/
|
|
553
|
+
async clear() {
|
|
554
|
+
const count = this.store.size;
|
|
555
|
+
this.store.clear();
|
|
556
|
+
return count;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Get the current size of the memory store
|
|
560
|
+
*
|
|
561
|
+
* @returns Number of entries in memory
|
|
562
|
+
*/
|
|
563
|
+
get size() {
|
|
564
|
+
return this.store.size;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
var createInMemoryStore = () => new InMemoryStore();
|
|
568
|
+
|
|
569
|
+
// src/base/agent.ts
|
|
570
|
+
var DEFAULT_MODEL = "gcp/gemini-flash-latest";
|
|
571
|
+
var BaseAgent = class {
|
|
572
|
+
/**
|
|
573
|
+
* Agent name
|
|
574
|
+
*/
|
|
575
|
+
name;
|
|
576
|
+
/**
|
|
577
|
+
* Agent description
|
|
578
|
+
*/
|
|
579
|
+
description;
|
|
580
|
+
/**
|
|
581
|
+
* Agent instructions
|
|
582
|
+
*/
|
|
583
|
+
instructions;
|
|
584
|
+
/**
|
|
585
|
+
* Maximum iterations for the agent loop
|
|
586
|
+
*/
|
|
587
|
+
maxIterations;
|
|
588
|
+
/**
|
|
589
|
+
* Model identifier
|
|
590
|
+
*/
|
|
591
|
+
model;
|
|
592
|
+
/**
|
|
593
|
+
* Input validation schema
|
|
594
|
+
*/
|
|
595
|
+
inputSchema;
|
|
596
|
+
/**
|
|
597
|
+
* Output validation schema
|
|
598
|
+
*/
|
|
599
|
+
outputSchema;
|
|
600
|
+
/**
|
|
601
|
+
* Whether memory is enabled
|
|
602
|
+
*/
|
|
603
|
+
enableMemory;
|
|
604
|
+
/**
|
|
605
|
+
* Memory instance for persistent storage (null if disabled or initialization failed)
|
|
606
|
+
*/
|
|
607
|
+
memory;
|
|
608
|
+
/**
|
|
609
|
+
* Agent metadata
|
|
610
|
+
*/
|
|
611
|
+
metadata;
|
|
612
|
+
/**
|
|
613
|
+
* Hook manager for lifecycle events
|
|
614
|
+
*/
|
|
615
|
+
hooks;
|
|
616
|
+
/**
|
|
617
|
+
* Registry of available tools
|
|
618
|
+
*/
|
|
619
|
+
tools;
|
|
620
|
+
/**
|
|
621
|
+
* Baseline tools registered directly on the agent
|
|
622
|
+
*/
|
|
623
|
+
baseTools;
|
|
624
|
+
/**
|
|
625
|
+
* Registered tool providers that can expand into tools at runtime
|
|
626
|
+
*/
|
|
627
|
+
toolProviders;
|
|
628
|
+
/**
|
|
629
|
+
* Active tools supplied by providers for the current execution
|
|
630
|
+
*/
|
|
631
|
+
providerToolRegistry;
|
|
632
|
+
/**
|
|
633
|
+
* Opper client configuration
|
|
634
|
+
*/
|
|
635
|
+
opperConfig;
|
|
636
|
+
constructor(config) {
|
|
637
|
+
this.name = config.name;
|
|
638
|
+
this.description = config.description;
|
|
639
|
+
this.instructions = config.instructions;
|
|
640
|
+
this.maxIterations = config.maxIterations ?? 25;
|
|
641
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
642
|
+
this.inputSchema = config.inputSchema;
|
|
643
|
+
this.outputSchema = config.outputSchema;
|
|
644
|
+
this.enableMemory = config.enableMemory ?? false;
|
|
645
|
+
this.metadata = { ...config.metadata ?? {} };
|
|
646
|
+
this.hooks = new HookManager();
|
|
647
|
+
this.tools = /* @__PURE__ */ new Map();
|
|
648
|
+
this.baseTools = /* @__PURE__ */ new Map();
|
|
649
|
+
this.toolProviders = /* @__PURE__ */ new Set();
|
|
650
|
+
this.providerToolRegistry = /* @__PURE__ */ new Map();
|
|
651
|
+
this.opperConfig = {
|
|
652
|
+
apiKey: config.opperConfig?.apiKey ?? process.env["OPPER_API_KEY"],
|
|
653
|
+
baseUrl: config.opperConfig?.baseUrl,
|
|
654
|
+
...config.opperConfig
|
|
655
|
+
};
|
|
656
|
+
this.memory = this.initializeMemory(config);
|
|
657
|
+
if (config.tools) {
|
|
658
|
+
this.registerTools(config.tools);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Initialize memory subsystem with graceful degradation
|
|
663
|
+
*
|
|
664
|
+
* @param config - Agent configuration
|
|
665
|
+
* @returns Memory instance or null
|
|
666
|
+
*/
|
|
667
|
+
initializeMemory(config) {
|
|
668
|
+
if (!this.enableMemory && !config.memory) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
return config.memory ?? new InMemoryStore();
|
|
673
|
+
} catch (error) {
|
|
674
|
+
console.warn(
|
|
675
|
+
`[${this.name}] Memory initialization failed, continuing without memory:`,
|
|
676
|
+
error
|
|
677
|
+
);
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Main entry point for agent execution.
|
|
683
|
+
* Orchestrates lifecycle: context initialization, hook triggering, loop execution, teardown.
|
|
684
|
+
*
|
|
685
|
+
* @param input - Input to process
|
|
686
|
+
* @param parentSpanId - Optional parent span ID for tracing
|
|
687
|
+
* @returns Processed output
|
|
688
|
+
*/
|
|
689
|
+
async process(input, parentSpanId) {
|
|
690
|
+
const validatedInput = this.inputSchema ? this.inputSchema.parse(input) : input;
|
|
691
|
+
const context = await this.initializeContext(validatedInput, parentSpanId);
|
|
692
|
+
try {
|
|
693
|
+
await this.activateToolProviders();
|
|
694
|
+
await this.triggerHook(HookEvents.AgentStart, { context });
|
|
695
|
+
const result = await this.runLoop(validatedInput, context);
|
|
696
|
+
const validatedOutput = this.outputSchema ? this.outputSchema.parse(result) : result;
|
|
697
|
+
await this.triggerHook(HookEvents.AgentEnd, {
|
|
698
|
+
context,
|
|
699
|
+
result: validatedOutput
|
|
700
|
+
});
|
|
701
|
+
return validatedOutput;
|
|
702
|
+
} catch (error) {
|
|
703
|
+
await this.triggerHook(HookEvents.AgentEnd, {
|
|
704
|
+
context,
|
|
705
|
+
error
|
|
706
|
+
});
|
|
707
|
+
throw error;
|
|
708
|
+
} finally {
|
|
709
|
+
await this.deactivateToolProviders();
|
|
710
|
+
await this.teardownContext(context);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Initialize agent context before execution.
|
|
715
|
+
* Can be overridden for custom initialization logic.
|
|
716
|
+
*
|
|
717
|
+
* @param input - Agent input
|
|
718
|
+
* @param parentSpanId - Optional parent span ID
|
|
719
|
+
* @returns Initialized context
|
|
720
|
+
*/
|
|
721
|
+
async initializeContext(input, parentSpanId) {
|
|
722
|
+
const context = new (await Promise.resolve().then(() => (init_context(), context_exports))).AgentContext({
|
|
723
|
+
agentName: this.name,
|
|
724
|
+
parentSpanId: parentSpanId ?? null,
|
|
725
|
+
goal: input,
|
|
726
|
+
metadata: { ...this.metadata }
|
|
727
|
+
});
|
|
728
|
+
return context;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Teardown agent context after execution.
|
|
732
|
+
* Can be overridden for custom cleanup logic.
|
|
733
|
+
*
|
|
734
|
+
* @param context - Agent context to teardown
|
|
735
|
+
*/
|
|
736
|
+
async teardownContext(context) {
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Add a tool to the agent's tool registry.
|
|
740
|
+
*
|
|
741
|
+
* @param tool - Tool to add
|
|
742
|
+
*/
|
|
743
|
+
addTool(tool2) {
|
|
744
|
+
const coerced = exports.coerceToolDefinition(tool2);
|
|
745
|
+
if (this.tools.has(coerced.name)) {
|
|
746
|
+
console.warn(
|
|
747
|
+
`[${this.name}] Tool "${coerced.name}" already registered. Overwriting existing definition.`
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
const typedTool = coerced;
|
|
751
|
+
this.baseTools.set(coerced.name, typedTool);
|
|
752
|
+
this.tools.set(coerced.name, typedTool);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Remove a tool from the agent's tool registry.
|
|
756
|
+
*
|
|
757
|
+
* @param toolName - Name of tool to remove
|
|
758
|
+
* @returns True if tool was removed, false if not found
|
|
759
|
+
*/
|
|
760
|
+
removeTool(toolName) {
|
|
761
|
+
this.baseTools.delete(toolName);
|
|
762
|
+
return this.tools.delete(toolName);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Get a tool by name.
|
|
766
|
+
*
|
|
767
|
+
* @param toolName - Name of tool to retrieve
|
|
768
|
+
* @returns Tool instance or undefined
|
|
769
|
+
*/
|
|
770
|
+
getTool(toolName) {
|
|
771
|
+
return this.tools.get(toolName);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get all registered tools.
|
|
775
|
+
*
|
|
776
|
+
* @returns Array of all tools
|
|
777
|
+
*/
|
|
778
|
+
getTools() {
|
|
779
|
+
return Array.from(this.tools.values());
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Register a tool provider that expands into tools at runtime.
|
|
783
|
+
*
|
|
784
|
+
* @param provider - Tool provider to add
|
|
785
|
+
*/
|
|
786
|
+
addToolProvider(provider) {
|
|
787
|
+
this.toolProviders.add(provider);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Convert this agent into a tool that can be used by other agents.
|
|
791
|
+
*
|
|
792
|
+
* @param toolName - Optional custom name for the tool (defaults to agent name)
|
|
793
|
+
* @param toolDescription - Optional custom description (defaults to agent description)
|
|
794
|
+
* @returns Tool wrapping this agent
|
|
795
|
+
*/
|
|
796
|
+
asTool(toolName, toolDescription) {
|
|
797
|
+
const tool2 = {
|
|
798
|
+
name: toolName ?? this.name,
|
|
799
|
+
description: toolDescription ?? this.description ?? `Agent: ${this.name}`,
|
|
800
|
+
...this.inputSchema && { schema: this.inputSchema },
|
|
801
|
+
metadata: {
|
|
802
|
+
isAgent: true,
|
|
803
|
+
agentName: this.name
|
|
804
|
+
},
|
|
805
|
+
execute: async (input, executionContext) => {
|
|
806
|
+
try {
|
|
807
|
+
const parentSpanId = executionContext.spanId ?? executionContext.agentContext.parentSpanId ?? void 0;
|
|
808
|
+
const result = await this.process(input, parentSpanId);
|
|
809
|
+
return exports.ToolResultFactory.success(tool2.name, result);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
return exports.ToolResultFactory.failure(
|
|
812
|
+
tool2.name,
|
|
813
|
+
error instanceof Error ? error : new Error(String(error))
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
return tool2;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Register a hook handler for a specific event.
|
|
822
|
+
*
|
|
823
|
+
* @param event - Hook event name
|
|
824
|
+
* @param handler - Hook handler function
|
|
825
|
+
* @returns Cleanup function to unregister the hook
|
|
826
|
+
*/
|
|
827
|
+
registerHook(event, handler) {
|
|
828
|
+
return this.hooks.on(event, handler);
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Trigger a hook event with a payload.
|
|
832
|
+
* Swallows errors to prevent hook failures from breaking agent execution.
|
|
833
|
+
*
|
|
834
|
+
* @param event - Hook event name
|
|
835
|
+
* @param payload - Event payload
|
|
836
|
+
*/
|
|
837
|
+
async triggerHook(event, payload) {
|
|
838
|
+
try {
|
|
839
|
+
await this.hooks.emit(event, payload);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
console.warn(`Hook error for event ${event}:`, error);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Execute a tool with proper context, hooks, and error handling.
|
|
846
|
+
*
|
|
847
|
+
* @param toolName - Name of tool to execute
|
|
848
|
+
* @param input - Tool input
|
|
849
|
+
* @param context - Agent context
|
|
850
|
+
* @param options - Optional execution options (signal, span ID)
|
|
851
|
+
* @returns Tool execution result
|
|
852
|
+
*/
|
|
853
|
+
async executeTool(toolName, input, context, options) {
|
|
854
|
+
const tool2 = this.tools.get(toolName);
|
|
855
|
+
if (!tool2) {
|
|
856
|
+
const failure = exports.ToolResultFactory.failure(
|
|
857
|
+
toolName,
|
|
858
|
+
new Error(`Tool "${toolName}" not found in agent registry`)
|
|
859
|
+
);
|
|
860
|
+
const timestamp = Date.now();
|
|
861
|
+
context.recordToolCall({
|
|
862
|
+
toolName,
|
|
863
|
+
input,
|
|
864
|
+
success: false,
|
|
865
|
+
error: failure.error instanceof Error ? failure.error.message : String(failure.error),
|
|
866
|
+
startedAt: timestamp,
|
|
867
|
+
finishedAt: timestamp,
|
|
868
|
+
metadata: {}
|
|
869
|
+
});
|
|
870
|
+
await this.triggerHook(HookEvents.ToolError, {
|
|
871
|
+
context,
|
|
872
|
+
toolName,
|
|
873
|
+
error: failure.error
|
|
874
|
+
});
|
|
875
|
+
return failure;
|
|
876
|
+
}
|
|
877
|
+
const executionContext = {
|
|
878
|
+
agentContext: context,
|
|
879
|
+
...options?.signal && { signal: options.signal },
|
|
880
|
+
...options?.spanId && { spanId: options.spanId },
|
|
881
|
+
metadata: {}
|
|
882
|
+
};
|
|
883
|
+
const startedAt = Date.now();
|
|
884
|
+
try {
|
|
885
|
+
await this.triggerHook(HookEvents.BeforeTool, {
|
|
886
|
+
context,
|
|
887
|
+
tool: tool2,
|
|
888
|
+
input
|
|
889
|
+
});
|
|
890
|
+
const result = await tool2.execute(input, executionContext);
|
|
891
|
+
const finishedAt = Date.now();
|
|
892
|
+
const record = context.recordToolCall({
|
|
893
|
+
toolName: tool2.name,
|
|
894
|
+
input,
|
|
895
|
+
...result.success && { output: result.output },
|
|
896
|
+
success: result.success,
|
|
897
|
+
...!result.success && {
|
|
898
|
+
error: result.error instanceof Error ? result.error.message : String(result.error)
|
|
899
|
+
},
|
|
900
|
+
startedAt,
|
|
901
|
+
finishedAt,
|
|
902
|
+
metadata: {}
|
|
903
|
+
});
|
|
904
|
+
await this.triggerHook(HookEvents.AfterTool, {
|
|
905
|
+
context,
|
|
906
|
+
tool: tool2,
|
|
907
|
+
result,
|
|
908
|
+
record
|
|
909
|
+
});
|
|
910
|
+
return result;
|
|
911
|
+
} catch (error) {
|
|
912
|
+
const failure = exports.ToolResultFactory.failure(
|
|
913
|
+
toolName,
|
|
914
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
915
|
+
{
|
|
916
|
+
startedAt,
|
|
917
|
+
finishedAt: Date.now()
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
const record = context.recordToolCall({
|
|
921
|
+
toolName: tool2.name,
|
|
922
|
+
input,
|
|
923
|
+
success: false,
|
|
924
|
+
error: failure.error instanceof Error ? failure.error.message : String(failure.error),
|
|
925
|
+
startedAt,
|
|
926
|
+
finishedAt: failure.finishedAt,
|
|
927
|
+
metadata: {}
|
|
928
|
+
});
|
|
929
|
+
await this.triggerHook(HookEvents.ToolError, {
|
|
930
|
+
context,
|
|
931
|
+
tool: tool2,
|
|
932
|
+
toolName: tool2.name,
|
|
933
|
+
error
|
|
934
|
+
});
|
|
935
|
+
await this.triggerHook(HookEvents.AfterTool, {
|
|
936
|
+
context,
|
|
937
|
+
tool: tool2,
|
|
938
|
+
result: failure,
|
|
939
|
+
record
|
|
940
|
+
});
|
|
941
|
+
return failure;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
registerTools(entries) {
|
|
945
|
+
const { tools, providers } = exports.normalizeToolEntries(entries);
|
|
946
|
+
for (const tool2 of tools) {
|
|
947
|
+
this.addTool(tool2);
|
|
948
|
+
}
|
|
949
|
+
for (const provider of providers) {
|
|
950
|
+
this.toolProviders.add(provider);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
async activateToolProviders() {
|
|
954
|
+
const activationPromises = Array.from(this.toolProviders).filter((provider) => !this.providerToolRegistry.has(provider)).map(async (provider) => {
|
|
955
|
+
try {
|
|
956
|
+
const baseAgent = this;
|
|
957
|
+
const providedTools = await provider.setup(baseAgent) ?? [];
|
|
958
|
+
const normalizedTools = providedTools.map(
|
|
959
|
+
(tool2) => exports.coerceToolDefinition(tool2)
|
|
960
|
+
);
|
|
961
|
+
this.providerToolRegistry.set(provider, normalizedTools);
|
|
962
|
+
for (const tool2 of normalizedTools) {
|
|
963
|
+
if (this.tools.has(tool2.name)) {
|
|
964
|
+
console.warn(
|
|
965
|
+
`[${this.name}] Tool "${tool2.name}" from provider overwrites existing tool.`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
this.tools.set(tool2.name, tool2);
|
|
969
|
+
}
|
|
970
|
+
} catch (error) {
|
|
971
|
+
console.warn(
|
|
972
|
+
`[${this.name}] Failed to activate tool provider:`,
|
|
973
|
+
error
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
await Promise.allSettled(activationPromises);
|
|
978
|
+
}
|
|
979
|
+
async deactivateToolProviders() {
|
|
980
|
+
const teardownEntries = Array.from(this.providerToolRegistry.entries());
|
|
981
|
+
this.providerToolRegistry.clear();
|
|
982
|
+
const teardownPromises = teardownEntries.map(async ([provider, tools]) => {
|
|
983
|
+
for (const tool2 of tools) {
|
|
984
|
+
this.tools.delete(tool2.name);
|
|
985
|
+
const baseTool = this.baseTools.get(tool2.name);
|
|
986
|
+
if (baseTool) {
|
|
987
|
+
this.tools.set(tool2.name, baseTool);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
try {
|
|
991
|
+
await provider.teardown();
|
|
992
|
+
} catch (error) {
|
|
993
|
+
console.warn(
|
|
994
|
+
`[${this.name}] Error while tearing down tool provider:`,
|
|
995
|
+
error
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
await Promise.allSettled(teardownPromises);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
// src/index.ts
|
|
1004
|
+
init_context();
|
|
1005
|
+
init_result();
|
|
1006
|
+
init_tool();
|
|
1007
|
+
var ThoughtSchema = zod.z.object({
|
|
1008
|
+
/**
|
|
1009
|
+
* The reasoning or internal monologue of the agent
|
|
1010
|
+
*/
|
|
1011
|
+
reasoning: zod.z.string(),
|
|
1012
|
+
/**
|
|
1013
|
+
* Planned next action(s)
|
|
1014
|
+
*/
|
|
1015
|
+
plan: zod.z.string().optional(),
|
|
1016
|
+
/**
|
|
1017
|
+
* Confidence level (0-1)
|
|
1018
|
+
*/
|
|
1019
|
+
confidence: zod.z.number().min(0).max(1).optional(),
|
|
1020
|
+
/**
|
|
1021
|
+
* Additional metadata
|
|
1022
|
+
*/
|
|
1023
|
+
metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
1024
|
+
});
|
|
1025
|
+
var ToolCallSchema = zod.z.object({
|
|
1026
|
+
/**
|
|
1027
|
+
* Unique identifier for this tool call
|
|
1028
|
+
*/
|
|
1029
|
+
id: zod.z.string(),
|
|
1030
|
+
/**
|
|
1031
|
+
* Name of the tool to invoke
|
|
1032
|
+
*/
|
|
1033
|
+
toolName: zod.z.string(),
|
|
1034
|
+
/**
|
|
1035
|
+
* Arguments to pass to the tool
|
|
1036
|
+
*/
|
|
1037
|
+
arguments: zod.z.unknown()
|
|
1038
|
+
});
|
|
1039
|
+
var MemoryUpdateSchema = zod.z.object({
|
|
1040
|
+
/**
|
|
1041
|
+
* Value to store for this key
|
|
1042
|
+
*/
|
|
1043
|
+
value: zod.z.unknown(),
|
|
1044
|
+
/**
|
|
1045
|
+
* Optional description of the memory entry
|
|
1046
|
+
*/
|
|
1047
|
+
description: zod.z.string().optional(),
|
|
1048
|
+
/**
|
|
1049
|
+
* Optional metadata for the memory entry
|
|
1050
|
+
*/
|
|
1051
|
+
metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
1052
|
+
});
|
|
1053
|
+
var AgentDecisionSchema = zod.z.object({
|
|
1054
|
+
/**
|
|
1055
|
+
* Agent's internal reasoning
|
|
1056
|
+
*/
|
|
1057
|
+
reasoning: zod.z.string(),
|
|
1058
|
+
/**
|
|
1059
|
+
* Tool calls to execute (if any)
|
|
1060
|
+
* Empty array signals task completion
|
|
1061
|
+
*/
|
|
1062
|
+
toolCalls: zod.z.array(ToolCallSchema).default([]),
|
|
1063
|
+
/**
|
|
1064
|
+
* Memory keys to read during this iteration
|
|
1065
|
+
*/
|
|
1066
|
+
memoryReads: zod.z.array(zod.z.string()).default([]),
|
|
1067
|
+
/**
|
|
1068
|
+
* Memory entries to write/update (key -> payload)
|
|
1069
|
+
*/
|
|
1070
|
+
memoryUpdates: zod.z.record(MemoryUpdateSchema).default({})
|
|
1071
|
+
});
|
|
1072
|
+
var ToolExecutionSummarySchema = zod.z.object({
|
|
1073
|
+
/**
|
|
1074
|
+
* Tool name
|
|
1075
|
+
*/
|
|
1076
|
+
toolName: zod.z.string(),
|
|
1077
|
+
/**
|
|
1078
|
+
* Whether execution succeeded
|
|
1079
|
+
*/
|
|
1080
|
+
success: zod.z.boolean(),
|
|
1081
|
+
/**
|
|
1082
|
+
* Output if successful
|
|
1083
|
+
*/
|
|
1084
|
+
output: zod.z.unknown().optional(),
|
|
1085
|
+
/**
|
|
1086
|
+
* Error message if failed
|
|
1087
|
+
*/
|
|
1088
|
+
error: zod.z.string().optional()
|
|
1089
|
+
});
|
|
1090
|
+
var isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1091
|
+
var isZodSchema = (value) => typeof value === "object" && value !== null && "_def" in value && typeof value.parse === "function";
|
|
1092
|
+
var readTokenCount = (usage, key) => {
|
|
1093
|
+
if (!usage) {
|
|
1094
|
+
return 0;
|
|
1095
|
+
}
|
|
1096
|
+
const value = usage[key];
|
|
1097
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1098
|
+
};
|
|
1099
|
+
var extractUsage = (response) => {
|
|
1100
|
+
const usageRecord = isPlainRecord(response.usage) ? response.usage : void 0;
|
|
1101
|
+
return {
|
|
1102
|
+
inputTokens: readTokenCount(usageRecord, "input_tokens"),
|
|
1103
|
+
outputTokens: readTokenCount(usageRecord, "output_tokens")
|
|
1104
|
+
};
|
|
1105
|
+
};
|
|
1106
|
+
var extractCost = (response) => {
|
|
1107
|
+
const costRecord = isPlainRecord(response.cost) ? response.cost : void 0;
|
|
1108
|
+
return {
|
|
1109
|
+
generation: readTokenCount(costRecord, "generation"),
|
|
1110
|
+
platform: readTokenCount(costRecord, "platform"),
|
|
1111
|
+
total: readTokenCount(costRecord, "total")
|
|
1112
|
+
};
|
|
1113
|
+
};
|
|
1114
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
1115
|
+
maxRetries: 3,
|
|
1116
|
+
initialDelayMs: 1e3,
|
|
1117
|
+
backoffMultiplier: 2,
|
|
1118
|
+
maxDelayMs: 1e4
|
|
1119
|
+
};
|
|
1120
|
+
var OpperClient = class {
|
|
1121
|
+
client;
|
|
1122
|
+
logger;
|
|
1123
|
+
retryConfig;
|
|
1124
|
+
constructor(apiKey, options = {}) {
|
|
1125
|
+
this.client = new opperai.Opper({
|
|
1126
|
+
httpBearer: apiKey ?? process.env["OPPER_HTTP_BEARER"] ?? ""
|
|
1127
|
+
});
|
|
1128
|
+
this.logger = options.logger ?? getDefaultLogger();
|
|
1129
|
+
this.retryConfig = {
|
|
1130
|
+
...DEFAULT_RETRY_CONFIG,
|
|
1131
|
+
...options.retryConfig ?? {}
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Make a call to Opper with retry logic
|
|
1136
|
+
*/
|
|
1137
|
+
async call(options) {
|
|
1138
|
+
return this.withRetry(async () => {
|
|
1139
|
+
const inputSchema = this.toJsonSchema(options.inputSchema);
|
|
1140
|
+
const outputSchema = this.toJsonSchema(options.outputSchema);
|
|
1141
|
+
const response = await this.client.call({
|
|
1142
|
+
name: options.name,
|
|
1143
|
+
instructions: options.instructions,
|
|
1144
|
+
input: options.input,
|
|
1145
|
+
...inputSchema && { inputSchema },
|
|
1146
|
+
...outputSchema && { outputSchema },
|
|
1147
|
+
...options.model && { model: options.model },
|
|
1148
|
+
...options.parentSpanId && { parentSpanId: options.parentSpanId }
|
|
1149
|
+
});
|
|
1150
|
+
const usagePayload = extractUsage(response);
|
|
1151
|
+
const costPayload = extractCost(response);
|
|
1152
|
+
const usage = {
|
|
1153
|
+
inputTokens: usagePayload.inputTokens,
|
|
1154
|
+
outputTokens: usagePayload.outputTokens,
|
|
1155
|
+
totalTokens: usagePayload.inputTokens + usagePayload.outputTokens,
|
|
1156
|
+
cost: costPayload
|
|
1157
|
+
};
|
|
1158
|
+
const result = {
|
|
1159
|
+
...response.jsonPayload !== void 0 && response.jsonPayload !== null ? { jsonPayload: response.jsonPayload } : {},
|
|
1160
|
+
...response.message !== void 0 && response.message !== null ? { message: response.message } : {},
|
|
1161
|
+
spanId: response.spanId,
|
|
1162
|
+
usage
|
|
1163
|
+
};
|
|
1164
|
+
return result;
|
|
1165
|
+
}, options.name);
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Create a span for tracing
|
|
1169
|
+
*/
|
|
1170
|
+
async createSpan(options) {
|
|
1171
|
+
return this.withRetry(async () => {
|
|
1172
|
+
const span = await this.client.spans.create({
|
|
1173
|
+
name: options.name,
|
|
1174
|
+
...options.input !== void 0 && { input: options.input },
|
|
1175
|
+
...options.parentSpanId && { parentId: options.parentSpanId }
|
|
1176
|
+
});
|
|
1177
|
+
return {
|
|
1178
|
+
id: span.id,
|
|
1179
|
+
name: options.name,
|
|
1180
|
+
input: options.input
|
|
1181
|
+
};
|
|
1182
|
+
}, `create-span:${options.name}`);
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Update a span with output or error
|
|
1186
|
+
*/
|
|
1187
|
+
async updateSpan(spanId, output, options) {
|
|
1188
|
+
return this.withRetry(async () => {
|
|
1189
|
+
const serializedOutput = output !== void 0 && output !== null ? typeof output === "object" ? JSON.stringify(output) : String(output) : void 0;
|
|
1190
|
+
await this.client.spans.update(spanId, {
|
|
1191
|
+
...serializedOutput !== void 0 && { output: serializedOutput },
|
|
1192
|
+
...options?.error && { error: options.error }
|
|
1193
|
+
});
|
|
1194
|
+
}, `update-span:${spanId}`);
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Get the underlying Opper client
|
|
1198
|
+
*/
|
|
1199
|
+
getClient() {
|
|
1200
|
+
return this.client;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Execute a function with retry logic and exponential backoff
|
|
1204
|
+
*/
|
|
1205
|
+
async withRetry(fn, operationName) {
|
|
1206
|
+
let lastError;
|
|
1207
|
+
let delay = this.retryConfig.initialDelayMs;
|
|
1208
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
1209
|
+
try {
|
|
1210
|
+
return await fn();
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1213
|
+
if (attempt === this.retryConfig.maxRetries) {
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
if (!this.isRetryableError(error)) {
|
|
1217
|
+
throw lastError;
|
|
1218
|
+
}
|
|
1219
|
+
this.logger.warn(
|
|
1220
|
+
`Opper operation "${operationName}" failed (attempt ${attempt + 1}/${this.retryConfig.maxRetries + 1}), retrying in ${delay}ms`,
|
|
1221
|
+
{
|
|
1222
|
+
error: lastError.message,
|
|
1223
|
+
attempt: attempt + 1,
|
|
1224
|
+
delayMs: delay
|
|
1225
|
+
}
|
|
1226
|
+
);
|
|
1227
|
+
await this.sleep(delay);
|
|
1228
|
+
delay = Math.min(
|
|
1229
|
+
delay * this.retryConfig.backoffMultiplier,
|
|
1230
|
+
this.retryConfig.maxDelayMs
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
this.logger.error(
|
|
1235
|
+
`Opper operation "${operationName}" failed after ${this.retryConfig.maxRetries + 1} attempts`,
|
|
1236
|
+
lastError
|
|
1237
|
+
);
|
|
1238
|
+
throw lastError;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Check if an error is retryable
|
|
1242
|
+
*/
|
|
1243
|
+
isRetryableError(error) {
|
|
1244
|
+
if (!(error instanceof Error)) {
|
|
1245
|
+
return false;
|
|
1246
|
+
}
|
|
1247
|
+
const errorMessage = error.message.toLowerCase();
|
|
1248
|
+
const retryablePatterns = [
|
|
1249
|
+
"network",
|
|
1250
|
+
"timeout",
|
|
1251
|
+
"econnreset",
|
|
1252
|
+
"enotfound",
|
|
1253
|
+
"econnrefused",
|
|
1254
|
+
"etimedout",
|
|
1255
|
+
"rate limit",
|
|
1256
|
+
"429",
|
|
1257
|
+
"500",
|
|
1258
|
+
"502",
|
|
1259
|
+
"503",
|
|
1260
|
+
"504"
|
|
1261
|
+
];
|
|
1262
|
+
return retryablePatterns.some((pattern) => errorMessage.includes(pattern));
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Convert Zod schema to JSON Schema
|
|
1266
|
+
*/
|
|
1267
|
+
toJsonSchema(schema) {
|
|
1268
|
+
if (!schema) {
|
|
1269
|
+
return void 0;
|
|
1270
|
+
}
|
|
1271
|
+
if (isZodSchema(schema)) {
|
|
1272
|
+
try {
|
|
1273
|
+
return zodToJsonSchema.zodToJsonSchema(schema);
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
this.logger.warn("Failed to convert Zod schema to JSON Schema", {
|
|
1276
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1277
|
+
});
|
|
1278
|
+
return void 0;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
if (isPlainRecord(schema)) {
|
|
1282
|
+
return schema;
|
|
1283
|
+
}
|
|
1284
|
+
this.logger.warn("Unsupported schema type provided to OpperClient", {
|
|
1285
|
+
schemaType: typeof schema
|
|
1286
|
+
});
|
|
1287
|
+
return void 0;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Sleep for specified milliseconds
|
|
1291
|
+
*/
|
|
1292
|
+
sleep(ms) {
|
|
1293
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
function createOpperClient(apiKey, options) {
|
|
1297
|
+
return new OpperClient(apiKey, options);
|
|
1298
|
+
}
|
|
1299
|
+
var SchemaValidationError = class extends Error {
|
|
1300
|
+
issues;
|
|
1301
|
+
constructor(message, issues) {
|
|
1302
|
+
super(message);
|
|
1303
|
+
this.name = "SchemaValidationError";
|
|
1304
|
+
this.issues = issues;
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
var validateSchema = (schema, value, options = {}) => {
|
|
1308
|
+
const result = schema.safeParse(value);
|
|
1309
|
+
if (!result.success) {
|
|
1310
|
+
throw new SchemaValidationError(
|
|
1311
|
+
options.message ?? "Schema validation failed",
|
|
1312
|
+
result.error.issues
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
return result.data;
|
|
1316
|
+
};
|
|
1317
|
+
var isSchemaValid = (schema, value) => {
|
|
1318
|
+
return schema.safeParse(value).success;
|
|
1319
|
+
};
|
|
1320
|
+
var schemaToJson = (schema, options = {}) => {
|
|
1321
|
+
return zodToJsonSchema.zodToJsonSchema(schema, options);
|
|
1322
|
+
};
|
|
1323
|
+
var getSchemaDefault = (schema) => {
|
|
1324
|
+
const result = schema.safeParse(void 0);
|
|
1325
|
+
return result.success ? result.data : void 0;
|
|
1326
|
+
};
|
|
1327
|
+
var mergeSchemaDefaults = (schema, value) => {
|
|
1328
|
+
const defaults = getSchemaDefault(schema);
|
|
1329
|
+
if (defaults && typeof defaults === "object") {
|
|
1330
|
+
return validateSchema(schema, { ...defaults, ...value });
|
|
1331
|
+
}
|
|
1332
|
+
return validateSchema(schema, value);
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
// src/core/agent.ts
|
|
1336
|
+
var isToolSuccessResult = (value) => {
|
|
1337
|
+
if (typeof value !== "object" || value === null) {
|
|
1338
|
+
return false;
|
|
1339
|
+
}
|
|
1340
|
+
const candidate = value;
|
|
1341
|
+
return candidate.success === true && typeof candidate.toolName === "string" && "output" in candidate;
|
|
1342
|
+
};
|
|
1343
|
+
var Agent = class extends BaseAgent {
|
|
1344
|
+
opperClient;
|
|
1345
|
+
logger;
|
|
1346
|
+
verbose;
|
|
1347
|
+
constructor(config) {
|
|
1348
|
+
super(config);
|
|
1349
|
+
this.logger = config.logger ?? getDefaultLogger();
|
|
1350
|
+
this.verbose = config.verbose ?? false;
|
|
1351
|
+
if (this.verbose) {
|
|
1352
|
+
try {
|
|
1353
|
+
this.logger.setLevel?.(1 /* INFO */);
|
|
1354
|
+
} catch {
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
this.opperClient = config.opperClient ?? new OpperClient(this.opperConfig.apiKey, {
|
|
1358
|
+
logger: this.logger
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Serialize input for passing to LLM or spans
|
|
1363
|
+
*/
|
|
1364
|
+
serializeInput(input) {
|
|
1365
|
+
if (typeof input === "string") {
|
|
1366
|
+
return input;
|
|
1367
|
+
}
|
|
1368
|
+
if (typeof input === "object" && input !== null) {
|
|
1369
|
+
return JSON.stringify(input);
|
|
1370
|
+
}
|
|
1371
|
+
return String(input);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Main agent loop: think → tool execution → memory handling → repeat until complete
|
|
1375
|
+
*/
|
|
1376
|
+
async runLoop(input, context) {
|
|
1377
|
+
this.log("Starting agent loop", {
|
|
1378
|
+
goal: input,
|
|
1379
|
+
maxIterations: this.maxIterations,
|
|
1380
|
+
tools: Array.from(this.tools.keys())
|
|
1381
|
+
});
|
|
1382
|
+
const parentSpan = await this.opperClient.createSpan({
|
|
1383
|
+
name: `${this.name}_execution`,
|
|
1384
|
+
input: this.serializeInput(input),
|
|
1385
|
+
...context.parentSpanId ? { parentSpanId: context.parentSpanId } : {}
|
|
1386
|
+
});
|
|
1387
|
+
context.parentSpanId = parentSpan.id;
|
|
1388
|
+
try {
|
|
1389
|
+
while (context.iteration < this.maxIterations) {
|
|
1390
|
+
const currentIteration = context.iteration + 1;
|
|
1391
|
+
this.log(`Iteration ${currentIteration}/${this.maxIterations}`, {
|
|
1392
|
+
toolsAvailable: this.tools.size
|
|
1393
|
+
});
|
|
1394
|
+
await this.triggerHook(HookEvents.LoopStart, { context });
|
|
1395
|
+
let loopComplete = false;
|
|
1396
|
+
try {
|
|
1397
|
+
const { decision, spanId: thinkSpanId } = await this.think(
|
|
1398
|
+
input,
|
|
1399
|
+
context
|
|
1400
|
+
);
|
|
1401
|
+
const memoryResults = await this.handleMemoryActions(
|
|
1402
|
+
decision,
|
|
1403
|
+
context,
|
|
1404
|
+
thinkSpanId
|
|
1405
|
+
);
|
|
1406
|
+
const toolCallStartIndex = context.toolCalls.length;
|
|
1407
|
+
const toolResults = await this.executeToolCalls(
|
|
1408
|
+
decision,
|
|
1409
|
+
context,
|
|
1410
|
+
thinkSpanId
|
|
1411
|
+
);
|
|
1412
|
+
const combinedResults = [...memoryResults, ...toolResults];
|
|
1413
|
+
const newToolCalls = context.toolCalls.slice(toolCallStartIndex);
|
|
1414
|
+
context.addCycle({
|
|
1415
|
+
iteration: currentIteration,
|
|
1416
|
+
thought: {
|
|
1417
|
+
reasoning: decision.reasoning,
|
|
1418
|
+
memoryReads: decision.memoryReads,
|
|
1419
|
+
memoryUpdates: decision.memoryUpdates
|
|
1420
|
+
},
|
|
1421
|
+
toolCalls: newToolCalls,
|
|
1422
|
+
results: combinedResults,
|
|
1423
|
+
timestamp: Date.now()
|
|
1424
|
+
});
|
|
1425
|
+
context.iteration = currentIteration;
|
|
1426
|
+
const hasToolCalls = decision.toolCalls.length > 0;
|
|
1427
|
+
const hasMemoryReads = this.enableMemory && (decision.memoryReads?.length ?? 0) > 0;
|
|
1428
|
+
loopComplete = !hasToolCalls && !hasMemoryReads;
|
|
1429
|
+
} finally {
|
|
1430
|
+
await this.triggerHook(HookEvents.LoopEnd, { context });
|
|
1431
|
+
}
|
|
1432
|
+
if (loopComplete) {
|
|
1433
|
+
this.log("Loop complete, generating final result", {
|
|
1434
|
+
iteration: context.iteration + 1
|
|
1435
|
+
});
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (context.iteration >= this.maxIterations) {
|
|
1440
|
+
throw new Error(
|
|
1441
|
+
`Agent exceeded maximum iterations (${this.maxIterations}) without completing the task`
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
const result = await this.generateFinalResult(input, context);
|
|
1445
|
+
await this.opperClient.updateSpan(parentSpan.id, result);
|
|
1446
|
+
return result;
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
await this.opperClient.updateSpan(parentSpan.id, void 0, {
|
|
1449
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1450
|
+
});
|
|
1451
|
+
throw error;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Think step: Call LLM to decide next action
|
|
1456
|
+
*/
|
|
1457
|
+
async think(input, context) {
|
|
1458
|
+
const spanName = "think";
|
|
1459
|
+
this.log("Think step", { iteration: context.iteration });
|
|
1460
|
+
await this.triggerHook(HookEvents.LlmCall, { context, callType: "think" });
|
|
1461
|
+
try {
|
|
1462
|
+
const instructions = this.buildThinkInstructions();
|
|
1463
|
+
const thinkContext = await this.buildThinkContext(input, context);
|
|
1464
|
+
const response = await this.opperClient.call({
|
|
1465
|
+
name: spanName,
|
|
1466
|
+
instructions,
|
|
1467
|
+
input: thinkContext,
|
|
1468
|
+
outputSchema: AgentDecisionSchema,
|
|
1469
|
+
model: this.model,
|
|
1470
|
+
...context.parentSpanId && { parentSpanId: context.parentSpanId }
|
|
1471
|
+
});
|
|
1472
|
+
context.updateUsage({
|
|
1473
|
+
requests: 1,
|
|
1474
|
+
inputTokens: response.usage.inputTokens,
|
|
1475
|
+
outputTokens: response.usage.outputTokens,
|
|
1476
|
+
totalTokens: response.usage.totalTokens,
|
|
1477
|
+
cost: response.usage.cost
|
|
1478
|
+
});
|
|
1479
|
+
const decision = AgentDecisionSchema.parse(response.jsonPayload);
|
|
1480
|
+
await this.triggerHook(HookEvents.LlmResponse, {
|
|
1481
|
+
context,
|
|
1482
|
+
callType: "think",
|
|
1483
|
+
response
|
|
1484
|
+
});
|
|
1485
|
+
await this.triggerHook(HookEvents.ThinkEnd, {
|
|
1486
|
+
context,
|
|
1487
|
+
thought: { reasoning: decision.reasoning }
|
|
1488
|
+
});
|
|
1489
|
+
this.log("Think result", {
|
|
1490
|
+
reasoning: decision.reasoning,
|
|
1491
|
+
toolCalls: decision.toolCalls.length,
|
|
1492
|
+
memoryReads: decision.memoryReads?.length ?? 0,
|
|
1493
|
+
memoryWrites: Object.keys(decision.memoryUpdates ?? {}).length
|
|
1494
|
+
});
|
|
1495
|
+
return { decision, spanId: response.spanId };
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
this.logger.error("Think step failed", error);
|
|
1498
|
+
throw new Error(
|
|
1499
|
+
`Think step failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Build static instructions for the think step
|
|
1505
|
+
*/
|
|
1506
|
+
buildThinkInstructions() {
|
|
1507
|
+
let instructions = `You are in a Think-Act reasoning loop.
|
|
1508
|
+
|
|
1509
|
+
YOUR TASK:
|
|
1510
|
+
1. Analyze the current situation
|
|
1511
|
+
2. Decide if the goal is complete or more actions are needed
|
|
1512
|
+
3. If more actions needed: specify tools to call
|
|
1513
|
+
4. If goal complete: return empty tool_calls list
|
|
1514
|
+
|
|
1515
|
+
IMPORTANT:
|
|
1516
|
+
- Return empty toolCalls array when task is COMPLETE
|
|
1517
|
+
- Only use available tools
|
|
1518
|
+
- Provide clear reasoning for each decision`;
|
|
1519
|
+
if (this.enableMemory) {
|
|
1520
|
+
instructions += `
|
|
1521
|
+
|
|
1522
|
+
MEMORY SYSTEM:
|
|
1523
|
+
You have access to a persistent memory system that works across iterations.
|
|
1524
|
+
|
|
1525
|
+
Memory Operations:
|
|
1526
|
+
1. READ: Add keys to memoryReads when you need to load existing entries
|
|
1527
|
+
2. WRITE: Populate memoryUpdates with key-value pairs (with optional description/metadata)
|
|
1528
|
+
Example: memoryUpdates = { "favorite_color": { "value": "blue", "description": "User likes blue" } }
|
|
1529
|
+
|
|
1530
|
+
When to use memory:
|
|
1531
|
+
- Save important calculations, decisions, or user preferences
|
|
1532
|
+
- Load memory when you need information from earlier in the conversation
|
|
1533
|
+
- Use descriptive keys like "budget_total", "user_favorite_city", etc.
|
|
1534
|
+
- When a key appears in memory_catalog and you need its value, add it to memoryReads before continuing
|
|
1535
|
+
|
|
1536
|
+
The memory you write persists across all process() calls on this agent.`;
|
|
1537
|
+
}
|
|
1538
|
+
return instructions;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Build dynamic context for the think step
|
|
1542
|
+
*/
|
|
1543
|
+
async buildThinkContext(input, context) {
|
|
1544
|
+
const availableTools = Array.from(this.tools.values()).map((tool2) => ({
|
|
1545
|
+
name: tool2.name,
|
|
1546
|
+
description: tool2.description || "",
|
|
1547
|
+
// Convert Zod schema to JSON Schema for LLM consumption
|
|
1548
|
+
parameters: tool2.schema ? schemaToJson(tool2.schema) : {}
|
|
1549
|
+
}));
|
|
1550
|
+
const executionHistory = context.getLastNCycles(3).map((cycle) => {
|
|
1551
|
+
const thought = typeof cycle.thought === "object" && cycle.thought !== null ? cycle.thought["reasoning"] || "" : String(cycle.thought || "");
|
|
1552
|
+
const results = Array.isArray(cycle.results) ? cycle.results.map((r) => {
|
|
1553
|
+
const result = r;
|
|
1554
|
+
let actualOutput = result.output;
|
|
1555
|
+
if (result.success && isToolSuccessResult(actualOutput)) {
|
|
1556
|
+
actualOutput = actualOutput.output;
|
|
1557
|
+
}
|
|
1558
|
+
return {
|
|
1559
|
+
tool: result.toolName,
|
|
1560
|
+
success: result.success,
|
|
1561
|
+
// Serialize result properly - use JSON for objects, String for primitives
|
|
1562
|
+
result: result.success ? typeof actualOutput === "object" ? JSON.stringify(actualOutput) : String(actualOutput) : void 0,
|
|
1563
|
+
error: !result.success ? result.error : void 0
|
|
1564
|
+
};
|
|
1565
|
+
}) : [];
|
|
1566
|
+
return {
|
|
1567
|
+
iteration: cycle.iteration,
|
|
1568
|
+
thought,
|
|
1569
|
+
results
|
|
1570
|
+
};
|
|
1571
|
+
});
|
|
1572
|
+
let memoryCatalog = null;
|
|
1573
|
+
if (this.enableMemory && this.memory && await this.memory.hasEntries()) {
|
|
1574
|
+
memoryCatalog = await this.memory.listEntries();
|
|
1575
|
+
}
|
|
1576
|
+
const loadedMemory = context.metadata["current_memory"] ?? null;
|
|
1577
|
+
return {
|
|
1578
|
+
goal: this.serializeInput(input),
|
|
1579
|
+
agent_description: this.description || "",
|
|
1580
|
+
instructions: this.instructions || "No specific instructions.",
|
|
1581
|
+
available_tools: availableTools,
|
|
1582
|
+
execution_history: executionHistory,
|
|
1583
|
+
current_iteration: context.iteration + 1,
|
|
1584
|
+
max_iterations: this.maxIterations,
|
|
1585
|
+
memory_catalog: memoryCatalog,
|
|
1586
|
+
loaded_memory: loadedMemory
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Execute all tool calls from a decision
|
|
1591
|
+
*/
|
|
1592
|
+
async executeToolCalls(decision, context, parentSpanId) {
|
|
1593
|
+
if (decision.toolCalls.length === 0) {
|
|
1594
|
+
return [];
|
|
1595
|
+
}
|
|
1596
|
+
this.log(`Executing ${decision.toolCalls.length} tool call(s)`);
|
|
1597
|
+
const results = [];
|
|
1598
|
+
for (const toolCall of decision.toolCalls) {
|
|
1599
|
+
this.log(`Action: ${toolCall.toolName}`, {
|
|
1600
|
+
parameters: toolCall.arguments
|
|
1601
|
+
});
|
|
1602
|
+
const toolSpan = await this.opperClient.createSpan({
|
|
1603
|
+
name: `tool_${toolCall.toolName}`,
|
|
1604
|
+
input: toolCall.arguments,
|
|
1605
|
+
...parentSpanId ? { parentSpanId } : context.parentSpanId ? { parentSpanId: context.parentSpanId } : {}
|
|
1606
|
+
});
|
|
1607
|
+
try {
|
|
1608
|
+
const result = await this.executeTool(
|
|
1609
|
+
toolCall.toolName,
|
|
1610
|
+
toolCall.arguments,
|
|
1611
|
+
context,
|
|
1612
|
+
{ spanId: toolSpan.id }
|
|
1613
|
+
);
|
|
1614
|
+
if (result.success) {
|
|
1615
|
+
await this.opperClient.updateSpan(toolSpan.id, result.output);
|
|
1616
|
+
} else {
|
|
1617
|
+
await this.opperClient.updateSpan(toolSpan.id, void 0, {
|
|
1618
|
+
error: result.error instanceof Error ? result.error.message : String(result.error)
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
const summary = {
|
|
1622
|
+
toolName: toolCall.toolName,
|
|
1623
|
+
success: result.success,
|
|
1624
|
+
...result.success && { output: result.output },
|
|
1625
|
+
...!result.success && {
|
|
1626
|
+
error: result.error instanceof Error ? result.error.message : String(result.error)
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
results.push(ToolExecutionSummarySchema.parse(summary));
|
|
1630
|
+
this.log(
|
|
1631
|
+
`Tool ${toolCall.toolName} ${result.success ? "succeeded" : "failed"}`,
|
|
1632
|
+
{
|
|
1633
|
+
success: result.success
|
|
1634
|
+
}
|
|
1635
|
+
);
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
await this.opperClient.updateSpan(toolSpan.id, void 0, {
|
|
1638
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1639
|
+
});
|
|
1640
|
+
const summary = {
|
|
1641
|
+
toolName: toolCall.toolName,
|
|
1642
|
+
success: false,
|
|
1643
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1644
|
+
};
|
|
1645
|
+
results.push(ToolExecutionSummarySchema.parse(summary));
|
|
1646
|
+
this.logger.warn(`Tool ${toolCall.toolName} threw error`, {
|
|
1647
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return results;
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Handle memory operations from a decision
|
|
1655
|
+
* Supports read and write operations with graceful degradation
|
|
1656
|
+
*/
|
|
1657
|
+
async handleMemoryActions(decision, context, parentSpanId) {
|
|
1658
|
+
if (!this.enableMemory || !this.memory) {
|
|
1659
|
+
return [];
|
|
1660
|
+
}
|
|
1661
|
+
const hasReads = Array.isArray(decision.memoryReads) && decision.memoryReads.length > 0;
|
|
1662
|
+
const updateEntries = Object.entries(decision.memoryUpdates ?? {}).filter(
|
|
1663
|
+
([key, update]) => typeof key === "string" && key.length > 0 && typeof update === "object" && update !== null && "value" in update
|
|
1664
|
+
);
|
|
1665
|
+
const hasWrites = updateEntries.length > 0;
|
|
1666
|
+
if (!hasReads && !hasWrites) {
|
|
1667
|
+
return [];
|
|
1668
|
+
}
|
|
1669
|
+
this.log("Handling memory operations", {
|
|
1670
|
+
reads: decision.memoryReads?.length ?? 0,
|
|
1671
|
+
writes: updateEntries.length
|
|
1672
|
+
});
|
|
1673
|
+
const spanParentId = parentSpanId ?? context.parentSpanId ?? void 0;
|
|
1674
|
+
const summaries = [];
|
|
1675
|
+
if (hasReads) {
|
|
1676
|
+
try {
|
|
1677
|
+
const keySet = new Set(
|
|
1678
|
+
decision.memoryReads.filter(
|
|
1679
|
+
(key) => typeof key === "string" && key.length > 0
|
|
1680
|
+
)
|
|
1681
|
+
);
|
|
1682
|
+
const keys = Array.from(keySet);
|
|
1683
|
+
if (keys.length > 0) {
|
|
1684
|
+
const memoryReadSpan = await this.opperClient.createSpan({
|
|
1685
|
+
name: "memory_read",
|
|
1686
|
+
input: keys,
|
|
1687
|
+
...spanParentId && { parentSpanId: spanParentId }
|
|
1688
|
+
});
|
|
1689
|
+
const memoryData = await this.memory.read(keys);
|
|
1690
|
+
await this.opperClient.updateSpan(memoryReadSpan.id, memoryData);
|
|
1691
|
+
context.setMetadata("current_memory", memoryData);
|
|
1692
|
+
this.log(`Loaded ${Object.keys(memoryData).length} memory entries`, {
|
|
1693
|
+
keys
|
|
1694
|
+
});
|
|
1695
|
+
summaries.push(
|
|
1696
|
+
ToolExecutionSummarySchema.parse({
|
|
1697
|
+
toolName: "memory_read",
|
|
1698
|
+
success: true,
|
|
1699
|
+
output: { keys, data: memoryData }
|
|
1700
|
+
})
|
|
1701
|
+
);
|
|
1702
|
+
for (const key of keys) {
|
|
1703
|
+
await this.triggerHook(HookEvents.MemoryRead, {
|
|
1704
|
+
context,
|
|
1705
|
+
key,
|
|
1706
|
+
value: memoryData[key]
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
} catch (error) {
|
|
1711
|
+
await this.triggerHook(HookEvents.MemoryError, {
|
|
1712
|
+
context,
|
|
1713
|
+
operation: "read",
|
|
1714
|
+
error
|
|
1715
|
+
});
|
|
1716
|
+
this.logger.warn("Memory read failed", {
|
|
1717
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1718
|
+
});
|
|
1719
|
+
summaries.push(
|
|
1720
|
+
ToolExecutionSummarySchema.parse({
|
|
1721
|
+
toolName: "memory_read",
|
|
1722
|
+
success: false,
|
|
1723
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1724
|
+
})
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (hasWrites) {
|
|
1729
|
+
try {
|
|
1730
|
+
const memoryWriteSpan = await this.opperClient.createSpan({
|
|
1731
|
+
name: "memory_write",
|
|
1732
|
+
input: updateEntries.map(([key]) => key),
|
|
1733
|
+
...spanParentId && { parentSpanId: spanParentId }
|
|
1734
|
+
});
|
|
1735
|
+
for (const [key, update] of updateEntries) {
|
|
1736
|
+
const castUpdate = update;
|
|
1737
|
+
await this.memory.write(
|
|
1738
|
+
key,
|
|
1739
|
+
castUpdate.value,
|
|
1740
|
+
castUpdate.description ?? key,
|
|
1741
|
+
castUpdate.metadata
|
|
1742
|
+
);
|
|
1743
|
+
await this.triggerHook(HookEvents.MemoryWrite, {
|
|
1744
|
+
context,
|
|
1745
|
+
key,
|
|
1746
|
+
value: castUpdate.value
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
await this.opperClient.updateSpan(
|
|
1750
|
+
memoryWriteSpan.id,
|
|
1751
|
+
`Successfully wrote ${updateEntries.length} keys`
|
|
1752
|
+
);
|
|
1753
|
+
this.log(`Wrote ${updateEntries.length} memory entries`);
|
|
1754
|
+
summaries.push(
|
|
1755
|
+
ToolExecutionSummarySchema.parse({
|
|
1756
|
+
toolName: "memory_write",
|
|
1757
|
+
success: true,
|
|
1758
|
+
output: {
|
|
1759
|
+
keys: updateEntries.map(([key]) => key)
|
|
1760
|
+
}
|
|
1761
|
+
})
|
|
1762
|
+
);
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
await this.triggerHook(HookEvents.MemoryError, {
|
|
1765
|
+
context,
|
|
1766
|
+
operation: "write",
|
|
1767
|
+
error
|
|
1768
|
+
});
|
|
1769
|
+
this.logger.warn("Memory write failed", {
|
|
1770
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1771
|
+
});
|
|
1772
|
+
summaries.push(
|
|
1773
|
+
ToolExecutionSummarySchema.parse({
|
|
1774
|
+
toolName: "memory_write",
|
|
1775
|
+
success: false,
|
|
1776
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1777
|
+
})
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
return summaries;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Generate final result based on execution history
|
|
1785
|
+
*/
|
|
1786
|
+
async generateFinalResult(input, context) {
|
|
1787
|
+
this.log("Generating final result", { totalIterations: context.iteration });
|
|
1788
|
+
const finalContext = {
|
|
1789
|
+
goal: this.serializeInput(input),
|
|
1790
|
+
instructions: this.instructions || "No specific instructions.",
|
|
1791
|
+
execution_history: context.executionHistory.map((cycle) => {
|
|
1792
|
+
const results = Array.isArray(cycle.results) ? cycle.results : [];
|
|
1793
|
+
return {
|
|
1794
|
+
iteration: cycle.iteration,
|
|
1795
|
+
actions_taken: results.map(
|
|
1796
|
+
(r) => r.toolName
|
|
1797
|
+
),
|
|
1798
|
+
results: results.filter((r) => r.success).map((r) => {
|
|
1799
|
+
const result = r;
|
|
1800
|
+
let actualOutput = result.output;
|
|
1801
|
+
if (isToolSuccessResult(actualOutput)) {
|
|
1802
|
+
actualOutput = actualOutput.output;
|
|
1803
|
+
}
|
|
1804
|
+
return {
|
|
1805
|
+
tool: result.toolName,
|
|
1806
|
+
// Serialize result properly - use JSON for objects, String for primitives
|
|
1807
|
+
result: typeof actualOutput === "object" ? JSON.stringify(actualOutput) : String(actualOutput)
|
|
1808
|
+
};
|
|
1809
|
+
})
|
|
1810
|
+
};
|
|
1811
|
+
}),
|
|
1812
|
+
total_iterations: context.iteration
|
|
1813
|
+
};
|
|
1814
|
+
const instructions = `Generate the final result based on the execution history.
|
|
1815
|
+
Follow any instructions provided for formatting and style.`;
|
|
1816
|
+
try {
|
|
1817
|
+
const callOptions = {
|
|
1818
|
+
name: "generate_final_result",
|
|
1819
|
+
instructions,
|
|
1820
|
+
input: finalContext,
|
|
1821
|
+
model: this.model
|
|
1822
|
+
};
|
|
1823
|
+
if (context.parentSpanId) {
|
|
1824
|
+
callOptions.parentSpanId = context.parentSpanId;
|
|
1825
|
+
}
|
|
1826
|
+
if (this.outputSchema) {
|
|
1827
|
+
callOptions.outputSchema = this.outputSchema;
|
|
1828
|
+
}
|
|
1829
|
+
const response = await this.opperClient.call(callOptions);
|
|
1830
|
+
context.updateUsage({
|
|
1831
|
+
requests: 1,
|
|
1832
|
+
inputTokens: response.usage.inputTokens,
|
|
1833
|
+
outputTokens: response.usage.outputTokens,
|
|
1834
|
+
totalTokens: response.usage.totalTokens,
|
|
1835
|
+
cost: response.usage.cost
|
|
1836
|
+
});
|
|
1837
|
+
if (this.outputSchema) {
|
|
1838
|
+
const parsed = this.outputSchema.parse(response.jsonPayload);
|
|
1839
|
+
this.log("Final result generated (schema-validated)");
|
|
1840
|
+
return parsed;
|
|
1841
|
+
}
|
|
1842
|
+
this.log("Final result generated");
|
|
1843
|
+
return response.message;
|
|
1844
|
+
} catch (error) {
|
|
1845
|
+
this.logger.error("Failed to generate final result", error);
|
|
1846
|
+
throw new Error(
|
|
1847
|
+
`Failed to generate final result: ${error instanceof Error ? error.message : String(error)}`
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Log helper
|
|
1853
|
+
*/
|
|
1854
|
+
log(message, data) {
|
|
1855
|
+
if (this.verbose) {
|
|
1856
|
+
this.logger.info(`[${this.name}] ${message}`, data);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
};
|
|
1860
|
+
var DEFAULT_CLIENT_INFO = {
|
|
1861
|
+
name: "opper-agent-ts-mcp-client",
|
|
1862
|
+
version: "0.0.0"
|
|
1863
|
+
};
|
|
1864
|
+
var MCPClient = class _MCPClient {
|
|
1865
|
+
config;
|
|
1866
|
+
client;
|
|
1867
|
+
transport;
|
|
1868
|
+
connected;
|
|
1869
|
+
toolCache;
|
|
1870
|
+
constructor(config, options = {}) {
|
|
1871
|
+
this.config = config;
|
|
1872
|
+
this.client = new index_js.Client(options.clientInfo ?? DEFAULT_CLIENT_INFO, {
|
|
1873
|
+
enforceStrictCapabilities: false
|
|
1874
|
+
});
|
|
1875
|
+
this.transport = null;
|
|
1876
|
+
this.connected = false;
|
|
1877
|
+
this.toolCache = null;
|
|
1878
|
+
}
|
|
1879
|
+
static fromConfig(config, options) {
|
|
1880
|
+
return new _MCPClient(config, options);
|
|
1881
|
+
}
|
|
1882
|
+
async connect() {
|
|
1883
|
+
if (this.connected) {
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const transport = this.createTransport();
|
|
1887
|
+
this.transport = transport;
|
|
1888
|
+
try {
|
|
1889
|
+
await this.client.connect(transport);
|
|
1890
|
+
this.connected = true;
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
await transport.close().catch(() => {
|
|
1893
|
+
});
|
|
1894
|
+
this.transport = null;
|
|
1895
|
+
throw error;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
async disconnect() {
|
|
1899
|
+
if (!this.connected) {
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
await this.client.close().catch(() => {
|
|
1903
|
+
});
|
|
1904
|
+
await this.transport?.close().catch(() => {
|
|
1905
|
+
});
|
|
1906
|
+
this.transport = null;
|
|
1907
|
+
this.connected = false;
|
|
1908
|
+
this.toolCache = null;
|
|
1909
|
+
}
|
|
1910
|
+
async listTools() {
|
|
1911
|
+
const session = this.ensureConnected();
|
|
1912
|
+
if (this.toolCache) {
|
|
1913
|
+
return this.toolCache;
|
|
1914
|
+
}
|
|
1915
|
+
const response = await session.listTools({});
|
|
1916
|
+
const tools = response.tools?.map((tool2) => {
|
|
1917
|
+
const parameters = tool2.inputSchema ?? {};
|
|
1918
|
+
const outputSchema = tool2["outputSchema"];
|
|
1919
|
+
const normalized = {
|
|
1920
|
+
name: tool2.name,
|
|
1921
|
+
description: tool2.description ?? "",
|
|
1922
|
+
parameters
|
|
1923
|
+
};
|
|
1924
|
+
if (outputSchema) {
|
|
1925
|
+
normalized.outputSchema = outputSchema;
|
|
1926
|
+
}
|
|
1927
|
+
return normalized;
|
|
1928
|
+
}) ?? [];
|
|
1929
|
+
this.toolCache = tools;
|
|
1930
|
+
return tools;
|
|
1931
|
+
}
|
|
1932
|
+
async callTool(toolName, args) {
|
|
1933
|
+
const session = this.ensureConnected();
|
|
1934
|
+
const response = await session.callTool({
|
|
1935
|
+
name: toolName,
|
|
1936
|
+
arguments: args
|
|
1937
|
+
});
|
|
1938
|
+
return response;
|
|
1939
|
+
}
|
|
1940
|
+
get isConnected() {
|
|
1941
|
+
return this.connected;
|
|
1942
|
+
}
|
|
1943
|
+
ensureConnected() {
|
|
1944
|
+
if (!this.connected) {
|
|
1945
|
+
throw new Error(`MCP server "${this.config.name}" is not connected`);
|
|
1946
|
+
}
|
|
1947
|
+
return this.client;
|
|
1948
|
+
}
|
|
1949
|
+
createTransport() {
|
|
1950
|
+
if (this.config.transport === "stdio") {
|
|
1951
|
+
const stdioOptions = {
|
|
1952
|
+
command: this.config.command
|
|
1953
|
+
};
|
|
1954
|
+
if (this.config.args.length > 0) {
|
|
1955
|
+
stdioOptions.args = this.config.args;
|
|
1956
|
+
}
|
|
1957
|
+
if (Object.keys(this.config.env).length > 0) {
|
|
1958
|
+
stdioOptions.env = this.config.env;
|
|
1959
|
+
}
|
|
1960
|
+
if (this.config.cwd) {
|
|
1961
|
+
stdioOptions.cwd = this.config.cwd;
|
|
1962
|
+
}
|
|
1963
|
+
if (this.config.stderr) {
|
|
1964
|
+
stdioOptions.stderr = this.config.stderr;
|
|
1965
|
+
}
|
|
1966
|
+
return new stdio_js.StdioClientTransport(stdioOptions);
|
|
1967
|
+
}
|
|
1968
|
+
if (this.config.transport === "streamable-http") {
|
|
1969
|
+
const options2 = {};
|
|
1970
|
+
const headers2 = this.config.headers;
|
|
1971
|
+
if (headers2 && Object.keys(headers2).length > 0) {
|
|
1972
|
+
options2.requestInit = { headers: headers2 };
|
|
1973
|
+
}
|
|
1974
|
+
if (this.config.sessionId) {
|
|
1975
|
+
options2.sessionId = this.config.sessionId;
|
|
1976
|
+
}
|
|
1977
|
+
return new streamableHttp_js.StreamableHTTPClientTransport(
|
|
1978
|
+
new URL(this.config.url),
|
|
1979
|
+
options2
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
const headers = this.config.headers ?? {};
|
|
1983
|
+
const headerEntries = Object.entries(headers);
|
|
1984
|
+
const options = {};
|
|
1985
|
+
if (headerEntries.length > 0) {
|
|
1986
|
+
options.eventSourceInit = {
|
|
1987
|
+
fetch: async (url, init) => {
|
|
1988
|
+
const mergedHeaders = new Headers(init?.headers ?? {});
|
|
1989
|
+
for (const [key, value] of headerEntries) {
|
|
1990
|
+
mergedHeaders.set(key, value);
|
|
1991
|
+
}
|
|
1992
|
+
if (!mergedHeaders.has("Accept")) {
|
|
1993
|
+
mergedHeaders.set("Accept", "text/event-stream");
|
|
1994
|
+
}
|
|
1995
|
+
return fetch(url, {
|
|
1996
|
+
...init,
|
|
1997
|
+
headers: mergedHeaders
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
if (headerEntries.length > 0 || this.config.method === "POST") {
|
|
2003
|
+
options.requestInit = {
|
|
2004
|
+
...this.config.method ? { method: this.config.method } : {},
|
|
2005
|
+
...headerEntries.length > 0 ? { headers } : {}
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
return new sse_js.SSEClientTransport(new URL(this.config.url), options);
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
2011
|
+
var MCPTransportSchema = zod.z.enum(["stdio", "http-sse", "streamable-http"]);
|
|
2012
|
+
var MCPBaseConfigSchema = zod.z.object({
|
|
2013
|
+
name: zod.z.string().min(1, "name is required"),
|
|
2014
|
+
transport: MCPTransportSchema,
|
|
2015
|
+
timeout: zod.z.number().positive("timeout must be positive").default(30),
|
|
2016
|
+
metadata: zod.z.record(zod.z.string(), zod.z.unknown()).default({})
|
|
2017
|
+
});
|
|
2018
|
+
var MCPStdIoConfigSchema = MCPBaseConfigSchema.extend({
|
|
2019
|
+
transport: zod.z.literal("stdio"),
|
|
2020
|
+
command: zod.z.string().min(1, "command is required for stdio transport"),
|
|
2021
|
+
args: zod.z.array(zod.z.string()).default([]),
|
|
2022
|
+
env: zod.z.record(zod.z.string(), zod.z.string()).default({}),
|
|
2023
|
+
cwd: zod.z.string().optional(),
|
|
2024
|
+
stderr: zod.z.union([zod.z.literal("inherit"), zod.z.literal("pipe"), zod.z.literal("ignore")]).optional()
|
|
2025
|
+
});
|
|
2026
|
+
var MCPHttpSseConfigSchema = MCPBaseConfigSchema.extend({
|
|
2027
|
+
transport: zod.z.literal("http-sse"),
|
|
2028
|
+
url: zod.z.string().url("url must be a valid HTTP(S) URL"),
|
|
2029
|
+
headers: zod.z.record(zod.z.string(), zod.z.string()).default({}),
|
|
2030
|
+
method: zod.z.enum(["GET", "POST"]).default("GET")
|
|
2031
|
+
});
|
|
2032
|
+
var MCPStreamableHttpConfigSchema = MCPBaseConfigSchema.extend({
|
|
2033
|
+
transport: zod.z.literal("streamable-http"),
|
|
2034
|
+
url: zod.z.string().url("url must be a valid HTTP(S) URL"),
|
|
2035
|
+
headers: zod.z.record(zod.z.string(), zod.z.string()).default({}),
|
|
2036
|
+
sessionId: zod.z.string().optional()
|
|
2037
|
+
});
|
|
2038
|
+
var MCPConfigVariants = zod.z.discriminatedUnion("transport", [
|
|
2039
|
+
MCPStdIoConfigSchema,
|
|
2040
|
+
MCPHttpSseConfigSchema,
|
|
2041
|
+
MCPStreamableHttpConfigSchema
|
|
2042
|
+
]);
|
|
2043
|
+
var MCPServerConfigSchema = MCPConfigVariants.superRefine(
|
|
2044
|
+
(value, ctx) => {
|
|
2045
|
+
if (value.transport === "http-sse" || value.transport === "streamable-http") {
|
|
2046
|
+
if (!value.url) {
|
|
2047
|
+
ctx.addIssue({
|
|
2048
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2049
|
+
message: `url is required for ${value.transport} transport`,
|
|
2050
|
+
path: ["url"]
|
|
2051
|
+
});
|
|
2052
|
+
} else if (!value.url.startsWith("http://") && !value.url.startsWith("https://")) {
|
|
2053
|
+
ctx.addIssue({
|
|
2054
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2055
|
+
message: "url must start with http:// or https://",
|
|
2056
|
+
path: ["url"]
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
);
|
|
2062
|
+
var MCPconfig = (config) => MCPServerConfigSchema.parse(config);
|
|
2063
|
+
var createMCPServerConfig = MCPconfig;
|
|
2064
|
+
|
|
2065
|
+
// src/mcp/provider.ts
|
|
2066
|
+
init_tool();
|
|
2067
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
2068
|
+
var MCPToolProvider = class {
|
|
2069
|
+
configs;
|
|
2070
|
+
namePrefix;
|
|
2071
|
+
clientOptions;
|
|
2072
|
+
clientFactory;
|
|
2073
|
+
logger;
|
|
2074
|
+
clients;
|
|
2075
|
+
constructor(configs, options = {}) {
|
|
2076
|
+
if (configs.length === 0) {
|
|
2077
|
+
throw new Error(
|
|
2078
|
+
"MCPToolProvider requires at least one server configuration"
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
this.configs = configs;
|
|
2082
|
+
this.namePrefix = options.namePrefix ?? void 0;
|
|
2083
|
+
this.clientOptions = options.clientOptions ?? void 0;
|
|
2084
|
+
this.clientFactory = options.clientFactory ?? ((config, factoryOptions) => MCPClient.fromConfig(config, factoryOptions));
|
|
2085
|
+
this.logger = options.logger ?? {
|
|
2086
|
+
warn: (message, context) => console.warn(`[MCPToolProvider] ${message}`, context),
|
|
2087
|
+
error: (message, context) => console.error(`[MCPToolProvider] ${message}`, context),
|
|
2088
|
+
debug: () => {
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
this.clients = /* @__PURE__ */ new Map();
|
|
2092
|
+
}
|
|
2093
|
+
async setup(agent) {
|
|
2094
|
+
const tools = [];
|
|
2095
|
+
for (const config of this.configs) {
|
|
2096
|
+
let client = null;
|
|
2097
|
+
try {
|
|
2098
|
+
client = this.clientFactory(config, this.clientOptions);
|
|
2099
|
+
await client.connect();
|
|
2100
|
+
this.clients.set(config.name, client);
|
|
2101
|
+
const mcpTools = await client.listTools();
|
|
2102
|
+
for (const mcpTool of mcpTools) {
|
|
2103
|
+
tools.push(this.wrapTool(config, client, mcpTool));
|
|
2104
|
+
}
|
|
2105
|
+
this.logger?.debug?.("Registered MCP server tools", {
|
|
2106
|
+
server: config.name,
|
|
2107
|
+
toolCount: mcpTools.length
|
|
2108
|
+
});
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
this.logger?.warn?.("Failed to initialize MCP server", {
|
|
2111
|
+
server: config.name,
|
|
2112
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2113
|
+
});
|
|
2114
|
+
if (client) {
|
|
2115
|
+
await client.disconnect().catch(() => {
|
|
2116
|
+
});
|
|
2117
|
+
this.clients.delete(config.name);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
return tools;
|
|
2122
|
+
}
|
|
2123
|
+
async teardown() {
|
|
2124
|
+
const disconnects = Array.from(this.clients.entries()).map(
|
|
2125
|
+
async ([serverName, client]) => {
|
|
2126
|
+
try {
|
|
2127
|
+
await client.disconnect();
|
|
2128
|
+
this.logger?.debug?.("Disconnected MCP server", {
|
|
2129
|
+
server: serverName
|
|
2130
|
+
});
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
this.logger?.warn?.("Error disconnecting MCP server", {
|
|
2133
|
+
server: serverName,
|
|
2134
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2135
|
+
});
|
|
2136
|
+
} finally {
|
|
2137
|
+
this.clients.delete(serverName);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
);
|
|
2141
|
+
await Promise.allSettled(disconnects);
|
|
2142
|
+
}
|
|
2143
|
+
wrapTool(config, client, mcpTool) {
|
|
2144
|
+
const prefix = this.namePrefix ?? config.name;
|
|
2145
|
+
const toolName = `${prefix}:${mcpTool.name}`;
|
|
2146
|
+
return {
|
|
2147
|
+
name: toolName,
|
|
2148
|
+
description: mcpTool.description,
|
|
2149
|
+
metadata: {
|
|
2150
|
+
provider: "mcp",
|
|
2151
|
+
server: config.name,
|
|
2152
|
+
originalToolName: mcpTool.name,
|
|
2153
|
+
parameters: mcpTool.parameters,
|
|
2154
|
+
outputSchema: mcpTool.outputSchema
|
|
2155
|
+
},
|
|
2156
|
+
execute: async (input, context) => {
|
|
2157
|
+
const startedAt = Date.now();
|
|
2158
|
+
const args = isRecord(input) ? input : input === void 0 ? {} : { value: input };
|
|
2159
|
+
try {
|
|
2160
|
+
const result = await client.callTool(mcpTool.name, args);
|
|
2161
|
+
return exports.ToolResultFactory.success(toolName, result, {
|
|
2162
|
+
metadata: {
|
|
2163
|
+
provider: "mcp",
|
|
2164
|
+
server: config.name,
|
|
2165
|
+
tool: mcpTool.name
|
|
2166
|
+
},
|
|
2167
|
+
startedAt,
|
|
2168
|
+
finishedAt: Date.now()
|
|
2169
|
+
});
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
2172
|
+
return exports.ToolResultFactory.failure(toolName, failure, {
|
|
2173
|
+
metadata: {
|
|
2174
|
+
provider: "mcp",
|
|
2175
|
+
server: config.name,
|
|
2176
|
+
tool: mcpTool.name
|
|
2177
|
+
},
|
|
2178
|
+
startedAt,
|
|
2179
|
+
finishedAt: Date.now()
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
var mcp = (...configs) => {
|
|
2187
|
+
if (configs.length === 0) {
|
|
2188
|
+
throw new Error("At least one MCP server configuration is required");
|
|
2189
|
+
}
|
|
2190
|
+
const parsedConfigs = configs.map(
|
|
2191
|
+
(config) => MCPServerConfigSchema.parse(config)
|
|
2192
|
+
);
|
|
2193
|
+
return new MCPToolProvider(parsedConfigs);
|
|
2194
|
+
};
|
|
2195
|
+
|
|
2196
|
+
// src/utils/tool-decorators.ts
|
|
2197
|
+
init_tool();
|
|
2198
|
+
var reflectWithMetadata = Reflect;
|
|
2199
|
+
var ReflectMetadata = {
|
|
2200
|
+
define: (metadataKey, metadataValue, target, propertyKey) => {
|
|
2201
|
+
if (typeof reflectWithMetadata.defineMetadata === "function") {
|
|
2202
|
+
reflectWithMetadata.defineMetadata(
|
|
2203
|
+
metadataKey,
|
|
2204
|
+
metadataValue,
|
|
2205
|
+
target,
|
|
2206
|
+
propertyKey
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
},
|
|
2210
|
+
get: (metadataKey, target, propertyKey) => {
|
|
2211
|
+
if (typeof reflectWithMetadata.getMetadata === "function") {
|
|
2212
|
+
return reflectWithMetadata.getMetadata(metadataKey, target, propertyKey);
|
|
2213
|
+
}
|
|
2214
|
+
return void 0;
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
var TOOL_METADATA_KEY = "opper:tool";
|
|
2218
|
+
var toolMetadataStore = /* @__PURE__ */ new WeakMap();
|
|
2219
|
+
function setToolMetadata(target, propertyKey, metadata) {
|
|
2220
|
+
ReflectMetadata.define(TOOL_METADATA_KEY, metadata, target, propertyKey);
|
|
2221
|
+
let metadataForTarget = toolMetadataStore.get(target);
|
|
2222
|
+
if (!metadataForTarget) {
|
|
2223
|
+
metadataForTarget = /* @__PURE__ */ new Map();
|
|
2224
|
+
toolMetadataStore.set(target, metadataForTarget);
|
|
2225
|
+
}
|
|
2226
|
+
metadataForTarget.set(
|
|
2227
|
+
propertyKey,
|
|
2228
|
+
metadata
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
function getToolMetadata(target, propertyKey) {
|
|
2232
|
+
const metadata = ReflectMetadata.get(
|
|
2233
|
+
TOOL_METADATA_KEY,
|
|
2234
|
+
target,
|
|
2235
|
+
propertyKey
|
|
2236
|
+
);
|
|
2237
|
+
if (metadata) {
|
|
2238
|
+
return metadata;
|
|
2239
|
+
}
|
|
2240
|
+
const metadataForTarget = toolMetadataStore.get(target);
|
|
2241
|
+
return metadataForTarget?.get(propertyKey);
|
|
2242
|
+
}
|
|
2243
|
+
function isStage3DecoratorContext(value) {
|
|
2244
|
+
return typeof value === "object" && value !== null && "kind" in value;
|
|
2245
|
+
}
|
|
2246
|
+
function createExecuteWrapper(toolName, method, options, callTargetRef) {
|
|
2247
|
+
return async (input, context) => {
|
|
2248
|
+
const startedAt = Date.now();
|
|
2249
|
+
try {
|
|
2250
|
+
if (options.schema) {
|
|
2251
|
+
options.schema.parse(input);
|
|
2252
|
+
}
|
|
2253
|
+
const invocationTarget = callTargetRef.current;
|
|
2254
|
+
const result = await Promise.resolve(
|
|
2255
|
+
method.call(invocationTarget, input, context)
|
|
2256
|
+
);
|
|
2257
|
+
return exports.ToolResultFactory.success(toolName, result, {
|
|
2258
|
+
startedAt,
|
|
2259
|
+
finishedAt: Date.now(),
|
|
2260
|
+
...options.metadata && { metadata: options.metadata }
|
|
2261
|
+
});
|
|
2262
|
+
} catch (error) {
|
|
2263
|
+
return exports.ToolResultFactory.failure(
|
|
2264
|
+
toolName,
|
|
2265
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
2266
|
+
{
|
|
2267
|
+
startedAt,
|
|
2268
|
+
finishedAt: Date.now(),
|
|
2269
|
+
...options.metadata && { metadata: options.metadata }
|
|
2270
|
+
}
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
function normalizePropertyKey(propertyKey) {
|
|
2276
|
+
return typeof propertyKey === "symbol" ? propertyKey.description ?? propertyKey.toString() : propertyKey;
|
|
2277
|
+
}
|
|
2278
|
+
function createToolDefinition(options, methodName, propertyKey, method, callTargetRef) {
|
|
2279
|
+
const name = options.name ?? methodName;
|
|
2280
|
+
const description = options.description ?? extractJSDocDescription(method) ?? `Tool: ${normalizePropertyKey(propertyKey)}`;
|
|
2281
|
+
return {
|
|
2282
|
+
name,
|
|
2283
|
+
description,
|
|
2284
|
+
...options.schema && { schema: options.schema },
|
|
2285
|
+
...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
2286
|
+
metadata: {
|
|
2287
|
+
...options.metadata,
|
|
2288
|
+
isDecorated: true,
|
|
2289
|
+
propertyKey: normalizePropertyKey(propertyKey)
|
|
2290
|
+
},
|
|
2291
|
+
execute: createExecuteWrapper(name, method, options, callTargetRef)
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
function createFunctionTool(fn, options = {}) {
|
|
2295
|
+
const name = options.name ?? (fn.name || "anonymous_tool");
|
|
2296
|
+
const description = options.description ?? extractJSDocDescription(fn) ?? `Tool: ${name}`;
|
|
2297
|
+
const tool2 = {
|
|
2298
|
+
name,
|
|
2299
|
+
description,
|
|
2300
|
+
...options.schema && { schema: options.schema },
|
|
2301
|
+
...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
2302
|
+
metadata: {
|
|
2303
|
+
...options.metadata,
|
|
2304
|
+
isFunction: true,
|
|
2305
|
+
functionName: fn.name
|
|
2306
|
+
},
|
|
2307
|
+
execute: async (input, context) => {
|
|
2308
|
+
const startedAt = Date.now();
|
|
2309
|
+
try {
|
|
2310
|
+
if (options.schema) {
|
|
2311
|
+
options.schema.parse(input);
|
|
2312
|
+
}
|
|
2313
|
+
let result;
|
|
2314
|
+
if (options.timeoutMs !== void 0) {
|
|
2315
|
+
result = await executeWithTimeout(
|
|
2316
|
+
fn(input, context),
|
|
2317
|
+
options.timeoutMs,
|
|
2318
|
+
name
|
|
2319
|
+
);
|
|
2320
|
+
} else {
|
|
2321
|
+
result = await Promise.resolve(fn(input, context));
|
|
2322
|
+
}
|
|
2323
|
+
return exports.ToolResultFactory.success(name, result, {
|
|
2324
|
+
startedAt,
|
|
2325
|
+
finishedAt: Date.now(),
|
|
2326
|
+
...options.metadata && { metadata: options.metadata }
|
|
2327
|
+
});
|
|
2328
|
+
} catch (error) {
|
|
2329
|
+
return exports.ToolResultFactory.failure(
|
|
2330
|
+
name,
|
|
2331
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
2332
|
+
{
|
|
2333
|
+
startedAt,
|
|
2334
|
+
finishedAt: Date.now(),
|
|
2335
|
+
...options.metadata && { metadata: options.metadata }
|
|
2336
|
+
}
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
return tool2;
|
|
2342
|
+
}
|
|
2343
|
+
function tool(options) {
|
|
2344
|
+
const decoratorOptions = options ?? {};
|
|
2345
|
+
function decorator(...decoratorArgs) {
|
|
2346
|
+
if (decoratorArgs.length === 2 && isStage3DecoratorContext(decoratorArgs[1])) {
|
|
2347
|
+
const [method, context] = decoratorArgs;
|
|
2348
|
+
if (context.kind !== "method") {
|
|
2349
|
+
throw new Error("@tool can only be applied to methods.");
|
|
2350
|
+
}
|
|
2351
|
+
if (context.name === void 0) {
|
|
2352
|
+
throw new Error("@tool requires a named method to attach metadata.");
|
|
2353
|
+
}
|
|
2354
|
+
const propertyKey2 = context.name;
|
|
2355
|
+
const inferredName = typeof propertyKey2 === "string" && propertyKey2.length > 0 ? propertyKey2 : method.name || "anonymous_tool";
|
|
2356
|
+
const callTargetRef2 = {};
|
|
2357
|
+
const toolMetadata2 = createToolDefinition(
|
|
2358
|
+
decoratorOptions,
|
|
2359
|
+
inferredName,
|
|
2360
|
+
propertyKey2,
|
|
2361
|
+
method,
|
|
2362
|
+
callTargetRef2
|
|
2363
|
+
);
|
|
2364
|
+
const registerMetadata = (target2) => {
|
|
2365
|
+
callTargetRef2.current = target2;
|
|
2366
|
+
setToolMetadata(target2, propertyKey2, toolMetadata2);
|
|
2367
|
+
};
|
|
2368
|
+
if (typeof context.addInitializer === "function") {
|
|
2369
|
+
context.addInitializer(function() {
|
|
2370
|
+
const target2 = context.static ? this : Object.getPrototypeOf(this);
|
|
2371
|
+
if (target2) {
|
|
2372
|
+
registerMetadata(target2);
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
const [target, propertyKey, descriptor] = decoratorArgs;
|
|
2379
|
+
const resolvedDescriptor = descriptor ?? Object.getOwnPropertyDescriptor(target, propertyKey) ?? (() => {
|
|
2380
|
+
const value = target[propertyKey];
|
|
2381
|
+
if (typeof value === "function") {
|
|
2382
|
+
return { value };
|
|
2383
|
+
}
|
|
2384
|
+
return void 0;
|
|
2385
|
+
})();
|
|
2386
|
+
if (!resolvedDescriptor || typeof resolvedDescriptor.value !== "function") {
|
|
2387
|
+
throw new Error(
|
|
2388
|
+
`@tool can only be applied to methods, not ${typeof resolvedDescriptor?.value}`
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
const originalMethod = resolvedDescriptor.value;
|
|
2392
|
+
const callTargetRef = { current: target };
|
|
2393
|
+
const toolMetadata = createToolDefinition(
|
|
2394
|
+
decoratorOptions,
|
|
2395
|
+
normalizePropertyKey(propertyKey),
|
|
2396
|
+
propertyKey,
|
|
2397
|
+
originalMethod,
|
|
2398
|
+
callTargetRef
|
|
2399
|
+
);
|
|
2400
|
+
setToolMetadata(target, propertyKey, toolMetadata);
|
|
2401
|
+
return resolvedDescriptor;
|
|
2402
|
+
}
|
|
2403
|
+
return decorator;
|
|
2404
|
+
}
|
|
2405
|
+
function extractTools(instance) {
|
|
2406
|
+
const tools = [];
|
|
2407
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
2408
|
+
const propertyKeys = [
|
|
2409
|
+
...Object.getOwnPropertyNames(prototype),
|
|
2410
|
+
...Object.getOwnPropertySymbols(prototype)
|
|
2411
|
+
];
|
|
2412
|
+
for (const propertyKey of propertyKeys) {
|
|
2413
|
+
if (propertyKey === "constructor") continue;
|
|
2414
|
+
const toolMetadata = getToolMetadata(prototype, propertyKey);
|
|
2415
|
+
if (toolMetadata) {
|
|
2416
|
+
const instanceMethod = instance[propertyKey];
|
|
2417
|
+
if (typeof instanceMethod !== "function") {
|
|
2418
|
+
continue;
|
|
2419
|
+
}
|
|
2420
|
+
const tool2 = {
|
|
2421
|
+
...toolMetadata,
|
|
2422
|
+
execute: async (input, context) => {
|
|
2423
|
+
const startedAt = Date.now();
|
|
2424
|
+
try {
|
|
2425
|
+
if (toolMetadata.schema) {
|
|
2426
|
+
toolMetadata.schema.parse(input);
|
|
2427
|
+
}
|
|
2428
|
+
const result = await Promise.resolve(
|
|
2429
|
+
instanceMethod.call(instance, input, context)
|
|
2430
|
+
);
|
|
2431
|
+
return exports.ToolResultFactory.success(toolMetadata.name, result, {
|
|
2432
|
+
startedAt,
|
|
2433
|
+
finishedAt: Date.now(),
|
|
2434
|
+
...toolMetadata.metadata && { metadata: toolMetadata.metadata }
|
|
2435
|
+
});
|
|
2436
|
+
} catch (error) {
|
|
2437
|
+
return exports.ToolResultFactory.failure(
|
|
2438
|
+
toolMetadata.name,
|
|
2439
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
2440
|
+
{
|
|
2441
|
+
startedAt,
|
|
2442
|
+
finishedAt: Date.now(),
|
|
2443
|
+
...toolMetadata.metadata && {
|
|
2444
|
+
metadata: toolMetadata.metadata
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
tools.push(tool2);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
return tools;
|
|
2455
|
+
}
|
|
2456
|
+
async function executeWithTimeout(promise, timeoutMs, toolName) {
|
|
2457
|
+
return Promise.race([
|
|
2458
|
+
Promise.resolve(promise),
|
|
2459
|
+
new Promise((_, reject) => {
|
|
2460
|
+
setTimeout(() => {
|
|
2461
|
+
reject(new Error(`Tool "${toolName}" timed out after ${timeoutMs}ms`));
|
|
2462
|
+
}, timeoutMs);
|
|
2463
|
+
})
|
|
2464
|
+
]);
|
|
2465
|
+
}
|
|
2466
|
+
function extractJSDocDescription(fn) {
|
|
2467
|
+
const source = fn.toString();
|
|
2468
|
+
const match = /\/\*\*\s*\n\s*\*\s*(.+?)\s*\n/.exec(source);
|
|
2469
|
+
return match?.[1];
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/utils/tool-runner.ts
|
|
2473
|
+
init_tool();
|
|
2474
|
+
var ToolRunner = class {
|
|
2475
|
+
/**
|
|
2476
|
+
* Execute a tool with the given input and context
|
|
2477
|
+
*
|
|
2478
|
+
* @param tool - Tool to execute
|
|
2479
|
+
* @param input - Input data
|
|
2480
|
+
* @param context - Agent execution context
|
|
2481
|
+
* @param options - Execution options
|
|
2482
|
+
* @returns Tool execution result
|
|
2483
|
+
*/
|
|
2484
|
+
static async execute(tool2, input, context, options = {}) {
|
|
2485
|
+
if (options.signal?.aborted) {
|
|
2486
|
+
return exports.ToolResultFactory.failure(
|
|
2487
|
+
tool2.name,
|
|
2488
|
+
new Error(`Tool "${tool2.name}" execution was aborted`)
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
if (tool2.schema) {
|
|
2492
|
+
const validation = tool2.schema.safeParse(input);
|
|
2493
|
+
if (!validation.success) {
|
|
2494
|
+
return exports.ToolResultFactory.failure(
|
|
2495
|
+
tool2.name,
|
|
2496
|
+
new Error(
|
|
2497
|
+
`Invalid input for tool "${tool2.name}": ${validation.error.message}`
|
|
2498
|
+
)
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
const executionContext = {
|
|
2503
|
+
agentContext: context,
|
|
2504
|
+
...options.signal && { signal: options.signal },
|
|
2505
|
+
metadata: options.metadata ?? {}
|
|
2506
|
+
};
|
|
2507
|
+
try {
|
|
2508
|
+
const timeoutMs = options.timeoutMs ?? tool2.timeoutMs;
|
|
2509
|
+
let result;
|
|
2510
|
+
if (timeoutMs !== void 0) {
|
|
2511
|
+
result = await this.executeWithTimeout(
|
|
2512
|
+
Promise.resolve(tool2.execute(input, executionContext)),
|
|
2513
|
+
timeoutMs,
|
|
2514
|
+
tool2.name
|
|
2515
|
+
);
|
|
2516
|
+
} else {
|
|
2517
|
+
result = await Promise.resolve(tool2.execute(input, executionContext));
|
|
2518
|
+
}
|
|
2519
|
+
return result;
|
|
2520
|
+
} catch (error) {
|
|
2521
|
+
return exports.ToolResultFactory.failure(
|
|
2522
|
+
tool2.name,
|
|
2523
|
+
error instanceof Error ? error : new Error(String(error))
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Execute multiple tools in parallel
|
|
2529
|
+
*
|
|
2530
|
+
* @param executions - Array of tool execution tuples [tool, input, context, options?]
|
|
2531
|
+
* @returns Array of results in same order as input
|
|
2532
|
+
*/
|
|
2533
|
+
static async executeParallel(executions) {
|
|
2534
|
+
return Promise.all(
|
|
2535
|
+
executions.map(
|
|
2536
|
+
([tool2, input, context, options]) => this.execute(tool2, input, context, options)
|
|
2537
|
+
)
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Execute multiple tools sequentially, stopping on first failure
|
|
2542
|
+
*
|
|
2543
|
+
* @param executions - Array of tool execution tuples
|
|
2544
|
+
* @returns Array of results up to first failure (inclusive)
|
|
2545
|
+
*/
|
|
2546
|
+
static async executeSequential(executions) {
|
|
2547
|
+
const results = [];
|
|
2548
|
+
for (const [tool2, input, context, options] of executions) {
|
|
2549
|
+
const result = await this.execute(tool2, input, context, options);
|
|
2550
|
+
results.push(result);
|
|
2551
|
+
if (!result.success) {
|
|
2552
|
+
break;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
return results;
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* Execute a tool with timeout
|
|
2559
|
+
*
|
|
2560
|
+
* @param promise - Tool execution promise
|
|
2561
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
2562
|
+
* @param toolName - Tool name for error messages
|
|
2563
|
+
* @returns Result or rejects with timeout error
|
|
2564
|
+
*/
|
|
2565
|
+
static async executeWithTimeout(promise, timeoutMs, toolName) {
|
|
2566
|
+
return Promise.race([
|
|
2567
|
+
promise,
|
|
2568
|
+
new Promise((_, reject) => {
|
|
2569
|
+
setTimeout(() => {
|
|
2570
|
+
reject(
|
|
2571
|
+
new Error(`Tool "${toolName}" timed out after ${timeoutMs}ms`)
|
|
2572
|
+
);
|
|
2573
|
+
}, timeoutMs);
|
|
2574
|
+
})
|
|
2575
|
+
]);
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Validate tool input without executing
|
|
2579
|
+
*
|
|
2580
|
+
* @param tool - Tool to validate input for
|
|
2581
|
+
* @param input - Input to validate
|
|
2582
|
+
* @returns true if valid, Error if invalid
|
|
2583
|
+
*/
|
|
2584
|
+
static validate(tool2, input) {
|
|
2585
|
+
if (!tool2.schema) {
|
|
2586
|
+
return true;
|
|
2587
|
+
}
|
|
2588
|
+
const validation = tool2.schema.safeParse(input);
|
|
2589
|
+
if (!validation.success) {
|
|
2590
|
+
return new Error(
|
|
2591
|
+
`Invalid input for tool "${tool2.name}": ${validation.error.message}`
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
return true;
|
|
2595
|
+
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Check if a result indicates success
|
|
2598
|
+
*
|
|
2599
|
+
* @param result - Tool result to check
|
|
2600
|
+
* @returns true if success, false if failure
|
|
2601
|
+
*/
|
|
2602
|
+
static isSuccess(result) {
|
|
2603
|
+
return result.success === true;
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* Check if a result indicates failure
|
|
2607
|
+
*
|
|
2608
|
+
* @param result - Tool result to check
|
|
2609
|
+
* @returns true if failure, false if success
|
|
2610
|
+
*/
|
|
2611
|
+
static isFailure(result) {
|
|
2612
|
+
return result.success === false;
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2616
|
+
exports.Agent = Agent;
|
|
2617
|
+
exports.AgentDecisionSchema = AgentDecisionSchema;
|
|
2618
|
+
exports.BaseAgent = BaseAgent;
|
|
2619
|
+
exports.ConsoleLogger = ConsoleLogger;
|
|
2620
|
+
exports.DEFAULT_MODEL = DEFAULT_MODEL;
|
|
2621
|
+
exports.DEFAULT_RETRY_CONFIG = DEFAULT_RETRY_CONFIG;
|
|
2622
|
+
exports.HookEvents = HookEvents;
|
|
2623
|
+
exports.HookManager = HookManager;
|
|
2624
|
+
exports.InMemoryStore = InMemoryStore;
|
|
2625
|
+
exports.LogLevel = LogLevel;
|
|
2626
|
+
exports.MCPClient = MCPClient;
|
|
2627
|
+
exports.MCPServerConfigSchema = MCPServerConfigSchema;
|
|
2628
|
+
exports.MCPToolProvider = MCPToolProvider;
|
|
2629
|
+
exports.MCPconfig = MCPconfig;
|
|
2630
|
+
exports.MemoryEntryMetadataSchema = MemoryEntryMetadataSchema;
|
|
2631
|
+
exports.MemoryEntrySchema = MemoryEntrySchema;
|
|
2632
|
+
exports.MemoryUpdateSchema = MemoryUpdateSchema;
|
|
2633
|
+
exports.OpperClient = OpperClient;
|
|
2634
|
+
exports.SchemaValidationError = SchemaValidationError;
|
|
2635
|
+
exports.SilentLogger = SilentLogger;
|
|
2636
|
+
exports.ThoughtSchema = ThoughtSchema;
|
|
2637
|
+
exports.ToolCallSchema = ToolCallSchema;
|
|
2638
|
+
exports.ToolExecutionSummarySchema = ToolExecutionSummarySchema;
|
|
2639
|
+
exports.ToolRunner = ToolRunner;
|
|
2640
|
+
exports.createFunctionTool = createFunctionTool;
|
|
2641
|
+
exports.createHookManager = createHookManager;
|
|
2642
|
+
exports.createInMemoryStore = createInMemoryStore;
|
|
2643
|
+
exports.createMCPServerConfig = createMCPServerConfig;
|
|
2644
|
+
exports.createOpperClient = createOpperClient;
|
|
2645
|
+
exports.extractTools = extractTools;
|
|
2646
|
+
exports.getDefaultLogger = getDefaultLogger;
|
|
2647
|
+
exports.getSchemaDefault = getSchemaDefault;
|
|
2648
|
+
exports.isSchemaValid = isSchemaValid;
|
|
2649
|
+
exports.mcp = mcp;
|
|
2650
|
+
exports.mergeSchemaDefaults = mergeSchemaDefaults;
|
|
2651
|
+
exports.schemaToJson = schemaToJson;
|
|
2652
|
+
exports.setDefaultLogger = setDefaultLogger;
|
|
2653
|
+
exports.tool = tool;
|
|
2654
|
+
exports.validateSchema = validateSchema;
|
|
2655
|
+
//# sourceMappingURL=index.cjs.map
|
|
2656
|
+
//# sourceMappingURL=index.cjs.map
|