@cuylabs/agent-core 0.3.0 → 0.5.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.
Files changed (66) hide show
  1. package/README.md +216 -41
  2. package/dist/builder-RcTZuYnO.d.ts +34 -0
  3. package/dist/capabilities/index.d.ts +97 -0
  4. package/dist/capabilities/index.js +46 -0
  5. package/dist/chunk-6TDTQJ4P.js +116 -0
  6. package/dist/chunk-7MUFEN4K.js +559 -0
  7. package/dist/chunk-BDBZ3SLK.js +745 -0
  8. package/dist/chunk-DWYX7ASF.js +26 -0
  9. package/dist/chunk-FG4MD5MU.js +54 -0
  10. package/dist/chunk-IMGQOTU2.js +2019 -0
  11. package/dist/chunk-IVUJDISU.js +556 -0
  12. package/dist/chunk-LRHOS4ZN.js +584 -0
  13. package/dist/chunk-OTUGSCED.js +691 -0
  14. package/dist/chunk-P6YF7USR.js +182 -0
  15. package/dist/chunk-QAQADS4X.js +258 -0
  16. package/dist/chunk-QWFMX226.js +879 -0
  17. package/dist/{chunk-6VKLWNRE.js → chunk-SDSBEQXG.js} +1 -132
  18. package/dist/chunk-VBWWUHWI.js +724 -0
  19. package/dist/chunk-VEKUXUVF.js +41 -0
  20. package/dist/chunk-X635CM2F.js +305 -0
  21. package/dist/chunk-YUUJK53A.js +91 -0
  22. package/dist/chunk-ZXAKHMWH.js +283 -0
  23. package/dist/config-D2xeGEHK.d.ts +52 -0
  24. package/dist/context/index.d.ts +259 -0
  25. package/dist/context/index.js +26 -0
  26. package/dist/identifiers-BLUxFqV_.d.ts +12 -0
  27. package/dist/index-p0kOsVsE.d.ts +1067 -0
  28. package/dist/index-tmhaADz5.d.ts +198 -0
  29. package/dist/index.d.ts +185 -4316
  30. package/dist/index.js +1238 -5368
  31. package/dist/mcp/index.d.ts +26 -0
  32. package/dist/mcp/index.js +14 -0
  33. package/dist/messages-BYWGn8TY.d.ts +110 -0
  34. package/dist/middleware/index.d.ts +7 -0
  35. package/dist/middleware/index.js +12 -0
  36. package/dist/models/index.d.ts +33 -0
  37. package/dist/models/index.js +12 -0
  38. package/dist/network-D76DS5ot.d.ts +5 -0
  39. package/dist/prompt/index.d.ts +224 -0
  40. package/dist/prompt/index.js +45 -0
  41. package/dist/reasoning/index.d.ts +71 -0
  42. package/dist/reasoning/index.js +47 -0
  43. package/dist/registry-CuRWWtcT.d.ts +164 -0
  44. package/dist/resolver-DOfZ-xuk.d.ts +254 -0
  45. package/dist/runner-C7aMP_x3.d.ts +596 -0
  46. package/dist/runtime/index.d.ts +357 -0
  47. package/dist/runtime/index.js +64 -0
  48. package/dist/session-manager-Uawm2Le7.d.ts +274 -0
  49. package/dist/skill/index.d.ts +103 -0
  50. package/dist/skill/index.js +39 -0
  51. package/dist/storage/index.d.ts +167 -0
  52. package/dist/storage/index.js +50 -0
  53. package/dist/sub-agent/index.d.ts +14 -0
  54. package/dist/sub-agent/index.js +15 -0
  55. package/dist/tool/index.d.ts +173 -1
  56. package/dist/tool/index.js +12 -3
  57. package/dist/tool-DYp6-cC3.d.ts +239 -0
  58. package/dist/tool-pFAnJc5Y.d.ts +419 -0
  59. package/dist/tracker-DClqYqTj.d.ts +96 -0
  60. package/dist/tracking/index.d.ts +109 -0
  61. package/dist/tracking/index.js +20 -0
  62. package/dist/types-CQaXbRsS.d.ts +47 -0
  63. package/dist/types-MM1JoX5T.d.ts +810 -0
  64. package/dist/types-VQgymC1N.d.ts +156 -0
  65. package/package.json +89 -5
  66. package/dist/index-QR704uRr.d.ts +0 -472
@@ -0,0 +1,691 @@
1
+ // src/middleware/runner.ts
2
+ var MiddlewareRunner = class {
3
+ stack;
4
+ constructor(middleware = []) {
5
+ this.stack = Object.freeze([...middleware]);
6
+ }
7
+ /** Number of registered middleware */
8
+ get count() {
9
+ return this.stack.length;
10
+ }
11
+ /** Whether any middleware is registered */
12
+ get hasMiddleware() {
13
+ return this.stack.length > 0;
14
+ }
15
+ /** Get the middleware list (for fork inheritance) */
16
+ getMiddleware() {
17
+ return this.stack;
18
+ }
19
+ // --------------------------------------------------------------------------
20
+ // beforeToolCall — array order, first "deny" wins
21
+ // --------------------------------------------------------------------------
22
+ /**
23
+ * Run all `beforeToolCall` hooks in order.
24
+ *
25
+ * Returns `{ action: "allow" }` if all middleware allow (or have no hook).
26
+ * Returns `{ action: "deny", reason }` on first denial — remaining
27
+ * middleware are skipped.
28
+ */
29
+ async runBeforeToolCall(tool, args, ctx) {
30
+ for (const mw of this.stack) {
31
+ if (!mw.beforeToolCall) continue;
32
+ try {
33
+ const decision = await mw.beforeToolCall(tool, args, ctx);
34
+ if (decision.action === "deny") {
35
+ return decision;
36
+ }
37
+ } catch (err) {
38
+ return {
39
+ action: "deny",
40
+ reason: `Middleware "${mw.name}" error: ${err instanceof Error ? err.message : String(err)}`
41
+ };
42
+ }
43
+ }
44
+ return { action: "allow" };
45
+ }
46
+ // --------------------------------------------------------------------------
47
+ // afterToolCall — reverse order (innermost first)
48
+ // --------------------------------------------------------------------------
49
+ /**
50
+ * Run all `afterToolCall` hooks in reverse order.
51
+ *
52
+ * Each hook receives the result from the previous hook (or the
53
+ * original tool result for the first hook). Errors are caught
54
+ * and logged — the original result passes through.
55
+ */
56
+ async runAfterToolCall(tool, args, result, ctx) {
57
+ let current = result;
58
+ for (let i = this.stack.length - 1; i >= 0; i--) {
59
+ const mw = this.stack[i];
60
+ if (!mw.afterToolCall) continue;
61
+ try {
62
+ current = await mw.afterToolCall(tool, args, current, ctx);
63
+ } catch (err) {
64
+ console.warn(
65
+ `[middleware] "${mw.name}" afterToolCall error:`,
66
+ err instanceof Error ? err.message : String(err)
67
+ );
68
+ }
69
+ }
70
+ return current;
71
+ }
72
+ // --------------------------------------------------------------------------
73
+ // promptSections — all run, results merged
74
+ // --------------------------------------------------------------------------
75
+ /**
76
+ * Collect prompt sections from all middleware.
77
+ *
78
+ * Returns a flat array of sections. Each middleware can return a single
79
+ * section, an array of sections, or undefined/empty.
80
+ */
81
+ collectPromptSections(ctx) {
82
+ const sections = [];
83
+ for (const mw of this.stack) {
84
+ if (!mw.promptSections) continue;
85
+ try {
86
+ const result = mw.promptSections(ctx);
87
+ if (!result) continue;
88
+ if (Array.isArray(result)) {
89
+ sections.push(...result);
90
+ } else {
91
+ sections.push(result);
92
+ }
93
+ } catch (err) {
94
+ console.warn(
95
+ `[middleware] "${mw.name}" promptSections error:`,
96
+ err instanceof Error ? err.message : String(err)
97
+ );
98
+ }
99
+ }
100
+ return sections;
101
+ }
102
+ // --------------------------------------------------------------------------
103
+ // onEvent — fire-and-forget, all run
104
+ // --------------------------------------------------------------------------
105
+ /**
106
+ * Broadcast an event to all middleware observers.
107
+ *
108
+ * Non-blocking — errors are caught and logged. This never
109
+ * slows down the streaming pipeline.
110
+ */
111
+ emitEvent(event) {
112
+ for (const mw of this.stack) {
113
+ if (!mw.onEvent) continue;
114
+ try {
115
+ mw.onEvent(event);
116
+ } catch {
117
+ }
118
+ }
119
+ }
120
+ /**
121
+ * Get the OTel context for a session from the telemetry middleware.
122
+ * Returns undefined if no telemetry middleware is registered.
123
+ */
124
+ getOtelContext(sessionId) {
125
+ for (const mw of this.stack) {
126
+ if (mw.getOtelContext) {
127
+ const ctx = mw.getOtelContext(sessionId);
128
+ if (ctx) return ctx;
129
+ }
130
+ }
131
+ return void 0;
132
+ }
133
+ // --------------------------------------------------------------------------
134
+ // onChatStart — array order, sequential
135
+ // --------------------------------------------------------------------------
136
+ /**
137
+ * Run all `onChatStart` hooks in order.
138
+ *
139
+ * Errors are caught and logged — a broken logger should not
140
+ * prevent the chat from starting.
141
+ */
142
+ async runChatStart(sessionId, message) {
143
+ for (const mw of this.stack) {
144
+ if (!mw.onChatStart) continue;
145
+ try {
146
+ await mw.onChatStart(sessionId, message);
147
+ } catch (err) {
148
+ console.warn(
149
+ `[middleware] "${mw.name}" onChatStart error:`,
150
+ err instanceof Error ? err.message : String(err)
151
+ );
152
+ }
153
+ }
154
+ }
155
+ // --------------------------------------------------------------------------
156
+ // onChatEnd — array order, sequential
157
+ // --------------------------------------------------------------------------
158
+ /**
159
+ * Run all `onChatEnd` hooks in order.
160
+ *
161
+ * Always called, even when the stream errored. Errors in handlers
162
+ * are caught and logged.
163
+ */
164
+ async runChatEnd(sessionId, result) {
165
+ for (const mw of this.stack) {
166
+ if (!mw.onChatEnd) continue;
167
+ try {
168
+ await mw.onChatEnd(sessionId, result);
169
+ } catch (err) {
170
+ console.warn(
171
+ `[middleware] "${mw.name}" onChatEnd error:`,
172
+ err instanceof Error ? err.message : String(err)
173
+ );
174
+ }
175
+ }
176
+ }
177
+ };
178
+
179
+ // src/safety/approval/risk.ts
180
+ var DEFAULT_TOOL_RISKS = {
181
+ read: "safe",
182
+ read_file: "safe",
183
+ grep: "safe",
184
+ glob: "safe",
185
+ list_dir: "safe",
186
+ invoke_agent: "safe",
187
+ wait_agent: "safe",
188
+ close_agent: "safe",
189
+ skill: "safe",
190
+ skill_resource: "safe",
191
+ write: "moderate",
192
+ write_file: "moderate",
193
+ edit: "moderate",
194
+ edit_file: "moderate",
195
+ create_file: "moderate",
196
+ bash: "dangerous",
197
+ shell: "dangerous",
198
+ delete_file: "dangerous",
199
+ remove: "dangerous"
200
+ };
201
+ function getToolRisk(tool, customRisks) {
202
+ if (customRisks?.[tool]) {
203
+ return customRisks[tool];
204
+ }
205
+ if (DEFAULT_TOOL_RISKS[tool]) {
206
+ return DEFAULT_TOOL_RISKS[tool];
207
+ }
208
+ return "moderate";
209
+ }
210
+
211
+ // src/safety/approval/errors.ts
212
+ var ApprovalDeniedError = class extends Error {
213
+ constructor(tool, args, message) {
214
+ super(message || `Operation denied: ${tool}`);
215
+ this.tool = tool;
216
+ this.args = args;
217
+ this.name = "ApprovalDeniedError";
218
+ }
219
+ };
220
+ var ApprovalTimeoutError = class extends Error {
221
+ constructor(tool, timeoutMs) {
222
+ super(`Approval timeout after ${timeoutMs}ms for: ${tool}`);
223
+ this.tool = tool;
224
+ this.timeoutMs = timeoutMs;
225
+ this.name = "ApprovalTimeoutError";
226
+ }
227
+ };
228
+
229
+ // src/safety/approval/patterns.ts
230
+ function matchApprovalPattern(pattern, value) {
231
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
232
+ return new RegExp(`^${regex}$`, "i").test(value);
233
+ }
234
+ function extractApprovalPatterns(tool, args) {
235
+ if (!args || typeof args !== "object") {
236
+ return [tool];
237
+ }
238
+ const record = args;
239
+ if ("path" in record && typeof record.path === "string" || "filePath" in record && typeof record.filePath === "string") {
240
+ const path = record.path ?? record.filePath;
241
+ const dir = path.substring(0, path.lastIndexOf("/") + 1);
242
+ return [dir ? `${dir}*` : path];
243
+ }
244
+ if ("command" in record && typeof record.command === "string") {
245
+ const command = record.command.split(/\s+/)[0];
246
+ return [command];
247
+ }
248
+ if ("pattern" in record && typeof record.pattern === "string") {
249
+ return [record.pattern];
250
+ }
251
+ return [tool];
252
+ }
253
+ function describeApprovalOperation(tool, args) {
254
+ if (!args || typeof args !== "object") {
255
+ return `Execute ${tool}`;
256
+ }
257
+ const record = args;
258
+ switch (tool) {
259
+ case "read":
260
+ case "read_file":
261
+ return `Read file: ${record.path ?? record.filePath}`;
262
+ case "write":
263
+ case "write_file":
264
+ case "create_file":
265
+ return `Write file: ${record.path ?? record.filePath}`;
266
+ case "edit":
267
+ case "edit_file":
268
+ return `Edit file: ${record.path ?? record.filePath}`;
269
+ case "delete_file":
270
+ case "remove":
271
+ return `Delete: ${record.path}`;
272
+ case "bash":
273
+ case "shell": {
274
+ const command = String(record.command);
275
+ return `Run command: ${command.slice(0, 100)}${command.length > 100 ? "..." : ""}`;
276
+ }
277
+ case "grep":
278
+ return `Search for: ${record.pattern}`;
279
+ case "glob":
280
+ return `Find files: ${record.pattern}`;
281
+ default:
282
+ return `${tool}(${JSON.stringify(args).slice(0, 50)}...)`;
283
+ }
284
+ }
285
+
286
+ // src/safety/approval/handler.ts
287
+ var requestCounter = 0;
288
+ function findMatchingRule(rules, tool, patterns) {
289
+ for (let index = rules.length - 1; index >= 0; index--) {
290
+ const rule = rules[index];
291
+ const toolMatches = rule.tool === "*" || matchApprovalPattern(rule.tool, tool);
292
+ const patternMatches = patterns.some(
293
+ (pattern) => matchApprovalPattern(rule.pattern, pattern)
294
+ );
295
+ if (toolMatches && patternMatches) {
296
+ return rule;
297
+ }
298
+ }
299
+ return void 0;
300
+ }
301
+ function createApprovalHandler(config = {}) {
302
+ const {
303
+ defaultAction = "ask",
304
+ timeout = 5 * 60 * 1e3,
305
+ onRequest
306
+ } = config;
307
+ const rules = [...config.rules ?? []];
308
+ const pending = /* @__PURE__ */ new Map();
309
+ async function request(sessionId, tool, args, customRisks) {
310
+ const risk = getToolRisk(tool, customRisks);
311
+ const patterns = extractApprovalPatterns(tool, args);
312
+ const matchingRule = findMatchingRule(rules, tool, patterns);
313
+ if (matchingRule) {
314
+ if (matchingRule.action === "allow") {
315
+ return;
316
+ }
317
+ throw new ApprovalDeniedError(
318
+ tool,
319
+ args,
320
+ `Denied by rule: ${matchingRule.pattern}`
321
+ );
322
+ }
323
+ if (risk === "safe" && defaultAction !== "deny") {
324
+ return;
325
+ }
326
+ if (!onRequest) {
327
+ if (defaultAction === "allow") {
328
+ return;
329
+ }
330
+ if (defaultAction === "deny") {
331
+ throw new ApprovalDeniedError(tool, args);
332
+ }
333
+ throw new ApprovalDeniedError(tool, args, "No approval handler configured");
334
+ }
335
+ const id = `approval-${++requestCounter}-${Date.now()}`;
336
+ const requestData = {
337
+ id,
338
+ sessionId,
339
+ tool,
340
+ args,
341
+ description: describeApprovalOperation(tool, args),
342
+ risk,
343
+ patterns,
344
+ timestamp: Date.now()
345
+ };
346
+ const action = await Promise.race([
347
+ new Promise((resolve, reject) => {
348
+ pending.set(id, { resolve, reject });
349
+ onRequest(requestData).then(resolve).catch(reject).finally(() => pending.delete(id));
350
+ }),
351
+ new Promise((_, reject) => {
352
+ setTimeout(() => {
353
+ pending.delete(id);
354
+ reject(new ApprovalTimeoutError(tool, timeout));
355
+ }, timeout);
356
+ })
357
+ ]);
358
+ switch (action) {
359
+ case "allow":
360
+ return;
361
+ case "deny":
362
+ throw new ApprovalDeniedError(tool, args);
363
+ case "remember":
364
+ for (const pattern of patterns) {
365
+ rules.push({ pattern, tool, action: "allow" });
366
+ }
367
+ return;
368
+ }
369
+ }
370
+ function cancelAll(reason) {
371
+ for (const [id, { reject }] of pending) {
372
+ reject(new Error(reason ?? "Cancelled"));
373
+ pending.delete(id);
374
+ }
375
+ }
376
+ function addRule(rule) {
377
+ rules.push(rule);
378
+ }
379
+ function getRules() {
380
+ return rules;
381
+ }
382
+ function clearSessionRules() {
383
+ rules.length = config.rules?.length ?? 0;
384
+ }
385
+ return {
386
+ request,
387
+ cancelAll,
388
+ addRule,
389
+ getRules,
390
+ clearSessionRules
391
+ };
392
+ }
393
+
394
+ // src/middleware/approval.ts
395
+ function approvalMiddleware(config = {}) {
396
+ const handler = createApprovalHandler(config);
397
+ return {
398
+ name: "approval",
399
+ async beforeToolCall(tool, args, ctx) {
400
+ try {
401
+ await handler.request(ctx.sessionID, tool, args, config.customRisks);
402
+ return { action: "allow" };
403
+ } catch (err) {
404
+ const reason = err instanceof Error ? err.message : `Approval denied: ${tool}`;
405
+ return { action: "deny", reason };
406
+ }
407
+ }
408
+ };
409
+ }
410
+
411
+ // src/middleware/telemetry/otel.ts
412
+ var otelModulePromise;
413
+ function getInputMimeType(value) {
414
+ const trimmed = value.trimStart();
415
+ return trimmed.startsWith("{") || trimmed.startsWith("[") ? "application/json" : "text/plain";
416
+ }
417
+ async function getOtelModule() {
418
+ if (!otelModulePromise) {
419
+ otelModulePromise = import("@opentelemetry/api").then((module) => module).catch(() => null);
420
+ }
421
+ return await otelModulePromise;
422
+ }
423
+ function scheduleSpanTimeout(options) {
424
+ const { spans, key, otel, timeoutMs } = options;
425
+ return setTimeout(() => {
426
+ const entry = spans.get(key);
427
+ if (!entry) {
428
+ return;
429
+ }
430
+ entry.span.setStatus({
431
+ code: otel.SpanStatusCode.ERROR,
432
+ message: "Span timed out (possible leak \u2014 onChatEnd never called)"
433
+ });
434
+ entry.span.end();
435
+ spans.delete(key);
436
+ }, timeoutMs);
437
+ }
438
+ function otelMiddleware(config = {}) {
439
+ const recordInputs = config.recordInputs ?? true;
440
+ const recordOutputs = config.recordOutputs ?? true;
441
+ const agentName = config.agentName ?? "agent";
442
+ const emitToolSpans = config.emitToolSpans ?? true;
443
+ const spanTimeoutMs = config.spanTimeoutMs ?? 5 * 60 * 1e3;
444
+ const chatSpans = /* @__PURE__ */ new Map();
445
+ const toolSpans = /* @__PURE__ */ new Map();
446
+ let otel = null;
447
+ let tracer = null;
448
+ async function ensureTracer() {
449
+ if (tracer) {
450
+ return tracer;
451
+ }
452
+ otel = await getOtelModule();
453
+ if (!otel) {
454
+ return null;
455
+ }
456
+ tracer = otel.trace.getTracer("@cuylabs/agent-core");
457
+ return tracer;
458
+ }
459
+ return {
460
+ name: "opentelemetry",
461
+ getOtelContext(sessionId) {
462
+ return chatSpans.get(sessionId)?.ctx;
463
+ },
464
+ async onChatStart(sessionId, message) {
465
+ const activeTracer = await ensureTracer();
466
+ if (!activeTracer || !otel) {
467
+ return;
468
+ }
469
+ const attributes = {
470
+ "openinference.span.kind": "AGENT",
471
+ "gen_ai.operation.name": "invoke_agent",
472
+ "gen_ai.agent.name": agentName,
473
+ "gen_ai.agent.session_id": sessionId
474
+ };
475
+ if (config.agentDescription) {
476
+ attributes["gen_ai.agent.description"] = config.agentDescription;
477
+ }
478
+ if (recordInputs) {
479
+ const inputValue = message.slice(0, 4096);
480
+ attributes["input.value"] = inputValue;
481
+ attributes["input.mime_type"] = getInputMimeType(inputValue);
482
+ }
483
+ const span = activeTracer.startSpan(`invoke_agent ${agentName}`, {
484
+ attributes
485
+ });
486
+ const ctx = otel.trace.setSpan(otel.context.active(), span);
487
+ chatSpans.set(sessionId, {
488
+ span,
489
+ ctx,
490
+ timer: scheduleSpanTimeout({
491
+ spans: chatSpans,
492
+ key: sessionId,
493
+ otel,
494
+ timeoutMs: spanTimeoutMs
495
+ })
496
+ });
497
+ },
498
+ async beforeToolCall(tool, args, ctx) {
499
+ if (!emitToolSpans) {
500
+ return { action: "allow" };
501
+ }
502
+ const activeTracer = await ensureTracer();
503
+ if (!activeTracer || !otel) {
504
+ return { action: "allow" };
505
+ }
506
+ const sessionId = ctx?.sessionID;
507
+ const parent = sessionId ? chatSpans.get(sessionId) : void 0;
508
+ const parentCtx = parent?.ctx ?? otel.context.active();
509
+ const span = activeTracer.startSpan(
510
+ `execute_tool ${tool}`,
511
+ {
512
+ attributes: {
513
+ "openinference.span.kind": "TOOL",
514
+ "gen_ai.operation.name": "execute_tool",
515
+ "gen_ai.tool.name": tool,
516
+ ...recordInputs && args != null ? (() => {
517
+ const argsString = JSON.stringify(args).slice(0, 4096);
518
+ return {
519
+ "gen_ai.tool.call.arguments": argsString,
520
+ "input.value": argsString,
521
+ "input.mime_type": getInputMimeType(argsString),
522
+ "tool.parameters": argsString
523
+ };
524
+ })() : {}
525
+ }
526
+ },
527
+ parentCtx
528
+ );
529
+ const callId = ctx?.messageID ?? JSON.stringify(args);
530
+ const key = `${tool}:${callId}`;
531
+ const toolCtx = otel.trace.setSpan(parentCtx, span);
532
+ toolSpans.set(key, {
533
+ span,
534
+ ctx: toolCtx,
535
+ timer: scheduleSpanTimeout({
536
+ spans: toolSpans,
537
+ key,
538
+ otel,
539
+ timeoutMs: spanTimeoutMs
540
+ })
541
+ });
542
+ return { action: "allow" };
543
+ },
544
+ async afterToolCall(tool, args, result, ctx) {
545
+ const callId = ctx?.messageID ?? JSON.stringify(args);
546
+ const keyPrefix = `${tool}:`;
547
+ const key = `${tool}:${callId}`;
548
+ const entry = toolSpans.get(key) ?? Array.from(toolSpans.entries()).find(
549
+ ([candidate]) => candidate.startsWith(keyPrefix)
550
+ )?.[1];
551
+ if (entry) {
552
+ if (entry.timer) {
553
+ clearTimeout(entry.timer);
554
+ }
555
+ if (recordOutputs && result.output) {
556
+ const outputValue = (typeof result.output === "string" ? result.output : JSON.stringify(result.output)).slice(0, 4096);
557
+ entry.span.setAttribute("gen_ai.tool.call.result", outputValue);
558
+ entry.span.setAttribute("output.value", outputValue);
559
+ entry.span.setAttribute("output.mime_type", getInputMimeType(outputValue));
560
+ }
561
+ entry.span.setStatus({ code: otel?.SpanStatusCode.OK ?? 1 });
562
+ entry.span.end();
563
+ for (const [candidateKey, candidateEntry] of toolSpans) {
564
+ if (candidateEntry === entry) {
565
+ toolSpans.delete(candidateKey);
566
+ break;
567
+ }
568
+ }
569
+ }
570
+ return result;
571
+ },
572
+ onEvent(event) {
573
+ if (event.type !== "tool-error") {
574
+ return;
575
+ }
576
+ for (const [key, entry] of toolSpans) {
577
+ if (!key.startsWith(`${event.toolName}:`)) {
578
+ continue;
579
+ }
580
+ if (entry.timer) {
581
+ clearTimeout(entry.timer);
582
+ }
583
+ entry.span.setStatus({
584
+ code: otel?.SpanStatusCode.ERROR ?? 2,
585
+ message: event.error
586
+ });
587
+ entry.span.recordException({
588
+ name: "ToolError",
589
+ message: event.error
590
+ });
591
+ entry.span.end();
592
+ toolSpans.delete(key);
593
+ break;
594
+ }
595
+ },
596
+ async onChatEnd(sessionId, result) {
597
+ const entry = chatSpans.get(sessionId);
598
+ if (!entry) {
599
+ return;
600
+ }
601
+ if (entry.timer) {
602
+ clearTimeout(entry.timer);
603
+ }
604
+ if (result.usage) {
605
+ entry.span.setAttributes({
606
+ "gen_ai.usage.input_tokens": result.usage.inputTokens ?? 0,
607
+ "gen_ai.usage.output_tokens": result.usage.outputTokens ?? 0
608
+ });
609
+ }
610
+ if (recordOutputs && result.output) {
611
+ const outputValue = result.output.slice(0, 4096);
612
+ entry.span.setAttribute("gen_ai.output.text", outputValue);
613
+ entry.span.setAttribute("output.value", outputValue);
614
+ entry.span.setAttribute("output.mime_type", getInputMimeType(outputValue));
615
+ }
616
+ if (result.error) {
617
+ entry.span.setStatus({
618
+ code: otel?.SpanStatusCode.ERROR ?? 2,
619
+ message: result.error.message
620
+ });
621
+ entry.span.recordException(result.error);
622
+ } else {
623
+ entry.span.setStatus({ code: otel?.SpanStatusCode.OK ?? 1 });
624
+ }
625
+ entry.span.end();
626
+ chatSpans.delete(sessionId);
627
+ }
628
+ };
629
+ }
630
+
631
+ // src/middleware/telemetry/provider.ts
632
+ function createTelemetryConfig(config) {
633
+ const middleware = otelMiddleware({
634
+ agentName: config.agentName,
635
+ agentDescription: config.agentDescription,
636
+ recordInputs: config.recordInputs,
637
+ recordOutputs: config.recordOutputs,
638
+ emitToolSpans: config.emitToolSpans,
639
+ spanTimeoutMs: config.spanTimeoutMs
640
+ });
641
+ const telemetry = {
642
+ isEnabled: true,
643
+ functionId: config.agentName,
644
+ recordInputs: config.recordInputs,
645
+ recordOutputs: config.recordOutputs
646
+ };
647
+ let providerPromise;
648
+ if (config.spanProcessor) {
649
+ providerPromise = createAndRegisterProvider(
650
+ config.spanProcessor,
651
+ config.serviceName ?? config.agentName
652
+ );
653
+ }
654
+ return {
655
+ middleware,
656
+ telemetry,
657
+ shutdown: async () => {
658
+ if (!providerPromise) {
659
+ return;
660
+ }
661
+ const provider = await providerPromise;
662
+ await provider.forceFlush();
663
+ await provider.shutdown();
664
+ }
665
+ };
666
+ }
667
+ async function createAndRegisterProvider(spanProcessor, serviceName) {
668
+ const [{ NodeTracerProvider }, { resourceFromAttributes }, { ATTR_SERVICE_NAME }] = await Promise.all([
669
+ import("@opentelemetry/sdk-trace-node"),
670
+ import("@opentelemetry/resources"),
671
+ import("@opentelemetry/semantic-conventions")
672
+ ]);
673
+ const provider = new NodeTracerProvider({
674
+ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: serviceName }),
675
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
676
+ spanProcessors: [spanProcessor]
677
+ });
678
+ provider.register();
679
+ return provider;
680
+ }
681
+
682
+ export {
683
+ MiddlewareRunner,
684
+ otelMiddleware,
685
+ createTelemetryConfig,
686
+ getToolRisk,
687
+ ApprovalDeniedError,
688
+ ApprovalTimeoutError,
689
+ createApprovalHandler,
690
+ approvalMiddleware
691
+ };