zidane 5.4.1 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-DHQAsdj6.d.ts → agent-DxBoKDba.d.ts} +140 -1
- package/dist/agent-DxBoKDba.d.ts.map +1 -0
- package/dist/chat.d.ts +4 -4
- package/dist/chat.js +1 -1
- package/dist/{index-CrqFoaQA.d.ts → index-B2VOOijU.d.ts} +461 -8
- package/dist/index-B2VOOijU.d.ts.map +1 -0
- package/dist/{index-CHSaLab5.d.ts → index-BOtXdQkW.d.ts} +2 -2
- package/dist/{index-CHSaLab5.d.ts.map → index-BOtXdQkW.d.ts.map} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1522 -47
- package/dist/index.js.map +1 -1
- package/dist/{login-8c5C0FYq.js → login-CJbeAadS.js} +2 -2
- package/dist/{login-8c5C0FYq.js.map → login-CJbeAadS.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/{presets-Ck4VusTo.js → presets-MCcvxiNT.js} +2 -2
- package/dist/{presets-Ck4VusTo.js.map → presets-MCcvxiNT.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/{tools-PQH1Ge4M.js → tools-BNfyY14s.js} +98 -13
- package/dist/tools-BNfyY14s.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-ByB2MSCB.d.ts → transcript-anchors-DonKvoh4.d.ts} +3 -3
- package/dist/{transcript-anchors-ByB2MSCB.d.ts.map → transcript-anchors-DonKvoh4.d.ts.map} +1 -1
- package/dist/tui.d.ts +2 -2
- package/dist/tui.js +3 -3
- package/dist/{turn-operations-Bqs4YbbH.js → turn-operations-TKvy0q29.js} +3 -3
- package/dist/{turn-operations-Bqs4YbbH.js.map → turn-operations-TKvy0q29.js.map} +1 -1
- package/dist/types-IcokUOyC.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/agent-DHQAsdj6.d.ts.map +0 -1
- package/dist/index-CrqFoaQA.d.ts.map +0 -1
- package/dist/tools-PQH1Ge4M.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,1037 @@
|
|
|
1
|
-
import { A as readStateKey, C as PERSISTENCE_PREVIEW_BYTES, D as resolvePersistDir, E as maybePersistToolResult, O as getReadState, S as PERSISTED_STUB_PREFIX, T as cleanupPersistedSession, _ as createSkillsReadTool, a as multiEdit, b as TOOL_USE_SKIPPED_MESSAGE, c as grep, g as createSkillsRunScriptTool, h as createSkillsUseTool, j as resolveReadStateMap, l as glob, n as createSpawnTool, p as createAgent, s as createInteractionTool, u as edit, v as INTERRUPT_MESSAGE_FOR_TOOL_USE, w as buildPersistedStub, x as validateToolArgs, y as SHELL_CASCADE_CANCEL_MESSAGE } from "./tools-
|
|
1
|
+
import { A as readStateKey, C as PERSISTENCE_PREVIEW_BYTES, D as resolvePersistDir, E as maybePersistToolResult, O as getReadState, S as PERSISTED_STUB_PREFIX, T as cleanupPersistedSession, _ as createSkillsReadTool, a as multiEdit, b as TOOL_USE_SKIPPED_MESSAGE, c as grep, g as createSkillsRunScriptTool, h as createSkillsUseTool, j as resolveReadStateMap, l as glob, n as createSpawnTool, p as createAgent, s as createInteractionTool, u as edit, v as INTERRUPT_MESSAGE_FOR_TOOL_USE, w as buildPersistedStub, x as validateToolArgs, y as SHELL_CASCADE_CANCEL_MESSAGE } from "./tools-BNfyY14s.js";
|
|
2
2
|
import { n as createProcessContext, t as createSandboxContext } from "./contexts-BwiHIr2w.js";
|
|
3
3
|
import { a as AgentToolPairingError, c as matchesContextExceeded, i as AgentToolNotAllowedError, l as toTypedError, n as AgentContextExceededError, o as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, r as AgentProviderError, s as errorMessage, t as AgentAbortedError } from "./errors-Byb0F8B9.js";
|
|
4
4
|
import { n as toolResultToText, t as toolOutputByteLength } from "./types-IcokUOyC.js";
|
|
5
5
|
import { a as detectTurnInterruption, b as sanitizeToolSpecs, c as fromAnthropic, d as toOpenAI, f as OpenAICompatHttpError, g as openaiCompat, h as mapOAIFinishReason, i as autoDetectAndConvert, l as fromOpenAI, m as classifyOpenAICompatError, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, o as ensureToolResultPairing, r as TOOL_USE_INTERRUPTED_MARKER, s as filterUnresolvedToolUses, t as ORPHANED_TOOL_RESULT_MARKER, u as toAnthropic, y as sanitizeToolSchema } from "./messages-D0xT979U.js";
|
|
6
6
|
import { c as createMemoryMcpCredentialStore, i as resultToString, l as hasAuthorizationHeader, n as normalizeMcpBlocks, r as normalizeMcpServers, s as McpOAuthProvider, t as connectMcpServers } from "./mcp-DhmmJfxK.js";
|
|
7
7
|
import { _ as validateResourcePath, a as discoverSkills, b as createSkillActivationState, f as IMPLICITLY_ALLOWED_SKILL_TOOLS, g as parseAllowedToolPattern, h as matchesAllowedTool, i as writeSkillsToDisk, l as parseSkillFile, m as isToolAllowedByUnion, n as resolveSkills, p as installAllowedToolsGate, r as writeSkillToDisk, t as interpolateShellCommands, u as buildCatalog, v as validateSkillForWrite, y as validateSkillName } from "./interpolate-ERgZUxgg.js";
|
|
8
|
-
import { C as summaryToTurn, E as CompactPromptTooLongError, S as stripImagesFromTurns, T as CompactInvalidInputError, _ as buildTailCompactPrompt, a as selectFilesFromSession, b as anchorPreviewFor, c as BYTES_PER_TOKEN, d as BASE_INSTRUCTIONS, f as NO_TOOLS_PREAMBLE, g as buildFullCompactPrompt, h as buildFromCompactPrompt, i as selectFilesFromReadState, l as estimateTokens, m as buildCompactPrompt, n as startOAuthCallback, o as selectRecentFiles, p as TRAILER, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer, u as utf8ByteLength, v as buildUpToCompactPrompt, w as truncateHeadForPtlRetry, x as sliceForCompaction, y as ANCHOR_PREVIEW_MAX_CHARS } from "./login-
|
|
8
|
+
import { C as summaryToTurn, E as CompactPromptTooLongError, S as stripImagesFromTurns, T as CompactInvalidInputError, _ as buildTailCompactPrompt, a as selectFilesFromSession, b as anchorPreviewFor, c as BYTES_PER_TOKEN, d as BASE_INSTRUCTIONS, f as NO_TOOLS_PREAMBLE, g as buildFullCompactPrompt, h as buildFromCompactPrompt, i as selectFilesFromReadState, l as estimateTokens, m as buildCompactPrompt, n as startOAuthCallback, o as selectRecentFiles, p as TRAILER, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer, u as utf8ByteLength, v as buildUpToCompactPrompt, w as truncateHeadForPtlRetry, x as sliceForCompaction, y as ANCHOR_PREVIEW_MAX_CHARS } from "./login-CJbeAadS.js";
|
|
9
9
|
import { r as statsByModel, t as flattenTurns } from "./stats-DgOvY7wd.js";
|
|
10
|
-
import { i as basic_default, n as definePreset, r as basicTools } from "./presets-
|
|
10
|
+
import { i as basic_default, n as definePreset, r as basicTools } from "./presets-MCcvxiNT.js";
|
|
11
11
|
import { i as anthropic, n as openai, r as cerebras, t as openrouter } from "./providers-x3LZByR5.js";
|
|
12
12
|
import { a as createFileMapStore, i as createMemoryStore, n as loadSession, r as createRemoteStore, t as createSession } from "./session-BHZwxmfr.js";
|
|
13
13
|
import { defineSkill } from "./skills.js";
|
|
14
|
+
//#region src/logger.ts
|
|
15
|
+
/**
|
|
16
|
+
* Build a Logger from a sink. Stateless and cheap; create one per agent
|
|
17
|
+
* (or per app) and use `.with()` to attach correlation ids per-call.
|
|
18
|
+
*/
|
|
19
|
+
function createLogger(sink, baseAttributes = {}) {
|
|
20
|
+
function emit(level, message, attrs) {
|
|
21
|
+
try {
|
|
22
|
+
sink.emit({
|
|
23
|
+
level,
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
message,
|
|
26
|
+
attrs: attrs ? {
|
|
27
|
+
...baseAttributes,
|
|
28
|
+
...attrs
|
|
29
|
+
} : { ...baseAttributes }
|
|
30
|
+
});
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
debug: (m, a) => emit("debug", m, a),
|
|
35
|
+
info: (m, a) => emit("info", m, a),
|
|
36
|
+
warn: (m, a) => emit("warn", m, a),
|
|
37
|
+
error: (m, a) => emit("error", m, a),
|
|
38
|
+
with: (extra) => createLogger(sink, {
|
|
39
|
+
...baseAttributes,
|
|
40
|
+
...extra
|
|
41
|
+
}),
|
|
42
|
+
baseAttributes
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const LEVEL_ORDER = {
|
|
46
|
+
debug: 0,
|
|
47
|
+
info: 1,
|
|
48
|
+
warn: 2,
|
|
49
|
+
error: 3
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Human-readable terminal sink. Renders each record as
|
|
53
|
+
* `<ISO timestamp> <LEVEL> <message> <attrs as kv pairs>`.
|
|
54
|
+
*
|
|
55
|
+
* Honors `process.stderr` by default so log lines don't interleave with
|
|
56
|
+
* the agent's stdout-bound output (chat responses, JSON results).
|
|
57
|
+
*/
|
|
58
|
+
function consoleSink(options = {}) {
|
|
59
|
+
const min = LEVEL_ORDER[options.minLevel ?? "info"];
|
|
60
|
+
const stream = options.stream ?? process.stderr;
|
|
61
|
+
return { emit(record) {
|
|
62
|
+
if (LEVEL_ORDER[record.level] < min) return;
|
|
63
|
+
const ts = new Date(record.timestamp).toISOString();
|
|
64
|
+
const kv = Object.entries(record.attrs).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(" ");
|
|
65
|
+
stream.write(`${ts} ${record.level.toUpperCase().padEnd(5)} ${record.message}${kv ? ` ${kv}` : ""}\n`);
|
|
66
|
+
} };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* One-JSON-object-per-line sink. Suitable for piping into log aggregators
|
|
70
|
+
* (Datadog Agent, Fluent Bit, Loki, Vector) that expect JSONL.
|
|
71
|
+
*/
|
|
72
|
+
function jsonSink(options = {}) {
|
|
73
|
+
const min = LEVEL_ORDER[options.minLevel ?? "info"];
|
|
74
|
+
const stream = options.stream ?? process.stderr;
|
|
75
|
+
return { emit(record) {
|
|
76
|
+
if (LEVEL_ORDER[record.level] < min) return;
|
|
77
|
+
try {
|
|
78
|
+
stream.write(`${JSON.stringify(record)}\n`);
|
|
79
|
+
} catch {}
|
|
80
|
+
} };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Install a bundle of hook handlers that emit a structured line per
|
|
84
|
+
* relevant lifecycle event, automatically attaching correlation ids
|
|
85
|
+
* (`runId`, `turnId`, `callId`, `childId`, `depth`, `agentName`).
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const logger = createLogger(consoleSink({ minLevel: 'debug' }), { service: 'tui' })
|
|
90
|
+
* const lh = createLoggingHooks({ logger })
|
|
91
|
+
* const uninstall = lh.install(agent.hooks)
|
|
92
|
+
* try { await agent.run({ prompt }) }
|
|
93
|
+
* finally { uninstall() }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
function createLoggingHooks(options) {
|
|
97
|
+
const root = options.logger;
|
|
98
|
+
const includeLifecycle = options.includeLifecycle ?? true;
|
|
99
|
+
const minLevel = LEVEL_ORDER[options.level ?? "info"];
|
|
100
|
+
/**
|
|
101
|
+
* Wrap a Logger so emissions below `minLevel` are dropped before they
|
|
102
|
+
* hit the sink. Lets us gate harness chatter without forcing every
|
|
103
|
+
* `LogSink` to re-implement level filtering. Preserves `.with()`
|
|
104
|
+
* composition (children inherit the filter).
|
|
105
|
+
*/
|
|
106
|
+
function gateLevel(logger) {
|
|
107
|
+
const skip = (level) => LEVEL_ORDER[level] < minLevel;
|
|
108
|
+
const wrap = (l) => ({
|
|
109
|
+
debug: (m, a) => {
|
|
110
|
+
if (!skip("debug")) l.debug(m, a);
|
|
111
|
+
},
|
|
112
|
+
info: (m, a) => {
|
|
113
|
+
if (!skip("info")) l.info(m, a);
|
|
114
|
+
},
|
|
115
|
+
warn: (m, a) => {
|
|
116
|
+
if (!skip("warn")) l.warn(m, a);
|
|
117
|
+
},
|
|
118
|
+
error: (m, a) => {
|
|
119
|
+
if (!skip("error")) l.error(m, a);
|
|
120
|
+
},
|
|
121
|
+
with: (extra) => wrap(l.with(extra)),
|
|
122
|
+
baseAttributes: l.baseAttributes
|
|
123
|
+
});
|
|
124
|
+
return wrap(logger);
|
|
125
|
+
}
|
|
126
|
+
return { install(hooks) {
|
|
127
|
+
const unregisters = [];
|
|
128
|
+
const gatedRoot = gateLevel(root);
|
|
129
|
+
let runLogger = gatedRoot;
|
|
130
|
+
let turnLogger = gatedRoot;
|
|
131
|
+
unregisters.push(hooks.hook("agent:start", (ctx) => {
|
|
132
|
+
runLogger = gatedRoot.with({
|
|
133
|
+
runId: ctx.runId,
|
|
134
|
+
...ctx.parentRunId ? { parentRunId: ctx.parentRunId } : {},
|
|
135
|
+
depth: ctx.depth,
|
|
136
|
+
...ctx.agentName ? { agentName: ctx.agentName } : {}
|
|
137
|
+
});
|
|
138
|
+
turnLogger = runLogger;
|
|
139
|
+
if (includeLifecycle) runLogger.debug("agent run started");
|
|
140
|
+
}));
|
|
141
|
+
unregisters.push(hooks.hook("agent:done", (stats) => {
|
|
142
|
+
runLogger.info("agent run completed", {
|
|
143
|
+
turns: stats.turns,
|
|
144
|
+
totalIn: stats.totalIn,
|
|
145
|
+
totalOut: stats.totalOut,
|
|
146
|
+
...typeof stats.cost === "number" ? { cost: stats.cost } : {},
|
|
147
|
+
elapsedMs: stats.elapsed,
|
|
148
|
+
...typeof stats.timeTillFirstTokenMs === "number" ? { ttftMs: stats.timeTillFirstTokenMs } : {}
|
|
149
|
+
});
|
|
150
|
+
}));
|
|
151
|
+
unregisters.push(hooks.hook("agent:abort", () => {
|
|
152
|
+
runLogger.warn("agent run aborted");
|
|
153
|
+
}));
|
|
154
|
+
unregisters.push(hooks.hook("turn:before", (ctx) => {
|
|
155
|
+
turnLogger = runLogger.with({
|
|
156
|
+
turnId: ctx.turnId,
|
|
157
|
+
turn: ctx.turn
|
|
158
|
+
});
|
|
159
|
+
if (includeLifecycle) turnLogger.debug("turn started");
|
|
160
|
+
}));
|
|
161
|
+
unregisters.push(hooks.hook("turn:after", (ctx) => {
|
|
162
|
+
turnLogger.debug("turn ended", {
|
|
163
|
+
inputTokens: ctx.usage.input,
|
|
164
|
+
outputTokens: ctx.usage.output,
|
|
165
|
+
...ctx.usage.finishReason ? { finishReason: ctx.usage.finishReason } : {},
|
|
166
|
+
...ctx.usage.modelId ? { modelId: ctx.usage.modelId } : {},
|
|
167
|
+
...typeof ctx.usage.timeToFirstTokenMs === "number" ? { ttftMs: ctx.usage.timeToFirstTokenMs } : {}
|
|
168
|
+
});
|
|
169
|
+
}));
|
|
170
|
+
unregisters.push(hooks.hook("stream:error", (ctx) => {
|
|
171
|
+
turnLogger.error("stream error", {
|
|
172
|
+
message: ctx.err instanceof Error ? ctx.err.message : String(ctx.err),
|
|
173
|
+
...ctx.statusCode !== void 0 ? { statusCode: ctx.statusCode } : {},
|
|
174
|
+
...ctx.requestId !== void 0 ? { requestId: ctx.requestId } : {}
|
|
175
|
+
});
|
|
176
|
+
}));
|
|
177
|
+
unregisters.push(hooks.hook("tool:before", (ctx) => {
|
|
178
|
+
if (!includeLifecycle) return;
|
|
179
|
+
turnLogger.debug("tool started", {
|
|
180
|
+
toolName: ctx.name,
|
|
181
|
+
displayName: ctx.displayName,
|
|
182
|
+
callId: ctx.callId
|
|
183
|
+
});
|
|
184
|
+
}));
|
|
185
|
+
unregisters.push(hooks.hook("tool:after", (ctx) => {
|
|
186
|
+
if (!includeLifecycle) return;
|
|
187
|
+
turnLogger.debug("tool ended", {
|
|
188
|
+
toolName: ctx.name,
|
|
189
|
+
callId: ctx.callId,
|
|
190
|
+
outputBytes: ctx.outputBytes
|
|
191
|
+
});
|
|
192
|
+
}));
|
|
193
|
+
unregisters.push(hooks.hook("tool:error", (ctx) => {
|
|
194
|
+
turnLogger.error("tool error", {
|
|
195
|
+
toolName: ctx.name,
|
|
196
|
+
callId: ctx.callId,
|
|
197
|
+
message: ctx.error.message
|
|
198
|
+
});
|
|
199
|
+
}));
|
|
200
|
+
unregisters.push(hooks.hook("tool:dispatched", (ctx) => {
|
|
201
|
+
const isAnomaly = ctx.outcome === "gate-block" || ctx.outcome === "unknown" || ctx.outcome === "invalid-input";
|
|
202
|
+
if (!isAnomaly && !includeLifecycle) return;
|
|
203
|
+
turnLogger[isAnomaly ? "warn" : "debug"]("tool dispatched", {
|
|
204
|
+
toolName: ctx.name,
|
|
205
|
+
callId: ctx.callId,
|
|
206
|
+
outcome: ctx.outcome,
|
|
207
|
+
...ctx.reason ? { reason: ctx.reason } : {}
|
|
208
|
+
});
|
|
209
|
+
}));
|
|
210
|
+
unregisters.push(hooks.hook("validation:reject", (ctx) => {
|
|
211
|
+
turnLogger.warn("tool input rejected", {
|
|
212
|
+
toolName: ctx.name,
|
|
213
|
+
callId: ctx.callId,
|
|
214
|
+
reason: ctx.reason
|
|
215
|
+
});
|
|
216
|
+
}));
|
|
217
|
+
unregisters.push(hooks.hook("budget:exceeded", (ctx) => {
|
|
218
|
+
turnLogger.warn("byte budget exceeded", {
|
|
219
|
+
bytes: ctx.bytes,
|
|
220
|
+
budget: ctx.budget
|
|
221
|
+
});
|
|
222
|
+
}));
|
|
223
|
+
unregisters.push(hooks.hook("tool-budget:exceeded", (ctx) => {
|
|
224
|
+
turnLogger.warn("tool budget exceeded", {
|
|
225
|
+
toolName: ctx.tool,
|
|
226
|
+
count: ctx.count,
|
|
227
|
+
max: ctx.max,
|
|
228
|
+
mode: ctx.mode
|
|
229
|
+
});
|
|
230
|
+
}));
|
|
231
|
+
unregisters.push(hooks.hook("mcp:bootstrap:end", (ctx) => {
|
|
232
|
+
if (ctx.ok) {
|
|
233
|
+
if (includeLifecycle) runLogger.debug("mcp bootstrap ok", {
|
|
234
|
+
server: ctx.name,
|
|
235
|
+
transport: ctx.transport,
|
|
236
|
+
durationMs: ctx.durationMs,
|
|
237
|
+
toolCount: ctx.toolCount,
|
|
238
|
+
...ctx.lazy ? { lazy: true } : {},
|
|
239
|
+
...ctx.cached ? { cached: true } : {}
|
|
240
|
+
});
|
|
241
|
+
} else runLogger.warn("mcp bootstrap failed", {
|
|
242
|
+
server: ctx.name,
|
|
243
|
+
transport: ctx.transport,
|
|
244
|
+
durationMs: ctx.durationMs,
|
|
245
|
+
message: ctx.error.message
|
|
246
|
+
});
|
|
247
|
+
}));
|
|
248
|
+
unregisters.push(hooks.hook("mcp:error", (ctx) => {
|
|
249
|
+
runLogger.error("mcp error", {
|
|
250
|
+
server: ctx.name,
|
|
251
|
+
message: ctx.error.message
|
|
252
|
+
});
|
|
253
|
+
}));
|
|
254
|
+
unregisters.push(hooks.hook("mcp:auth:required", (ctx) => {
|
|
255
|
+
runLogger.warn("mcp auth required", {
|
|
256
|
+
server: ctx.name,
|
|
257
|
+
transport: ctx.transport,
|
|
258
|
+
reason: ctx.reason
|
|
259
|
+
});
|
|
260
|
+
}));
|
|
261
|
+
unregisters.push(hooks.hook("mcp:tool:error", (ctx) => {
|
|
262
|
+
turnLogger.error("mcp tool error", {
|
|
263
|
+
server: ctx.server,
|
|
264
|
+
tool: ctx.displayName,
|
|
265
|
+
callId: ctx.callId,
|
|
266
|
+
message: ctx.error.message
|
|
267
|
+
});
|
|
268
|
+
}));
|
|
269
|
+
unregisters.push(hooks.hook("spawn:before", (ctx) => {
|
|
270
|
+
if (!includeLifecycle) return;
|
|
271
|
+
runLogger.debug("spawn started", {
|
|
272
|
+
childId: ctx.id,
|
|
273
|
+
depth: ctx.depth
|
|
274
|
+
});
|
|
275
|
+
}));
|
|
276
|
+
unregisters.push(hooks.hook("spawn:complete", (ctx) => {
|
|
277
|
+
runLogger.info("spawn completed", {
|
|
278
|
+
childId: ctx.id,
|
|
279
|
+
...ctx.depth ? { depth: ctx.depth } : {},
|
|
280
|
+
status: ctx.status ?? "completed",
|
|
281
|
+
turns: ctx.stats.turns,
|
|
282
|
+
totalIn: ctx.stats.totalIn,
|
|
283
|
+
totalOut: ctx.stats.totalOut,
|
|
284
|
+
...typeof ctx.stats.cost === "number" ? { cost: ctx.stats.cost } : {}
|
|
285
|
+
});
|
|
286
|
+
}));
|
|
287
|
+
unregisters.push(hooks.hook("spawn:error", (ctx) => {
|
|
288
|
+
runLogger.error("spawn error", {
|
|
289
|
+
childId: ctx.id,
|
|
290
|
+
...ctx.depth ? { depth: ctx.depth } : {},
|
|
291
|
+
message: ctx.error.message
|
|
292
|
+
});
|
|
293
|
+
}));
|
|
294
|
+
let disposed = false;
|
|
295
|
+
return function uninstall() {
|
|
296
|
+
if (disposed) return;
|
|
297
|
+
disposed = true;
|
|
298
|
+
for (const un of unregisters) try {
|
|
299
|
+
un();
|
|
300
|
+
} catch {}
|
|
301
|
+
};
|
|
302
|
+
} };
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/metrics.ts
|
|
306
|
+
function prefixed$1(prefix, name) {
|
|
307
|
+
return prefix ? `${prefix}${name}` : name;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Drop `undefined` entries before handing attributes to a metrics
|
|
311
|
+
* backend. OTel's `@opentelemetry/api` rejects `undefined` attribute
|
|
312
|
+
* values (and Prometheus / StatsD adapters silently mis-label them); the
|
|
313
|
+
* helper's own API surface allows `undefined` for ergonomic call sites
|
|
314
|
+
* (`{...(cond ? { foo } : {})}`-style spreads), so we strip here at the
|
|
315
|
+
* boundary.
|
|
316
|
+
*/
|
|
317
|
+
function mergeAttrs(base, extra) {
|
|
318
|
+
const out = {};
|
|
319
|
+
if (base) {
|
|
320
|
+
for (const [k, v] of Object.entries(base)) if (v !== void 0) out[k] = v;
|
|
321
|
+
}
|
|
322
|
+
for (const [k, v] of Object.entries(extra)) if (v !== void 0) out[k] = v;
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Build a set of metrics hook handlers that can be installed on an agent.
|
|
327
|
+
*
|
|
328
|
+
* @example OpenTelemetry
|
|
329
|
+
* ```ts
|
|
330
|
+
* import { metrics } from '@opentelemetry/api'
|
|
331
|
+
* const meter = metrics.getMeter('zidane')
|
|
332
|
+
* const m = createMetricsHooks({ meter, baseAttributes: { service: 'tui' } })
|
|
333
|
+
* const uninstall = m.install(agent.hooks)
|
|
334
|
+
* try { await agent.run({ prompt }) }
|
|
335
|
+
* finally { uninstall() }
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
function createMetricsHooks(options) {
|
|
339
|
+
const ns = options.namespace;
|
|
340
|
+
const base = options.baseAttributes;
|
|
341
|
+
const onError = options.onError ?? (() => {});
|
|
342
|
+
const make = {
|
|
343
|
+
counter: (name, opts) => safeFactory(() => options.meter.createCounter(prefixed$1(ns, name), opts), name, onError),
|
|
344
|
+
histogram: (name, opts) => safeFactory(() => options.meter.createHistogram(prefixed$1(ns, name), opts), name, onError),
|
|
345
|
+
upDown: (name, opts) => safeFactory(() => options.meter.createUpDownCounter(prefixed$1(ns, name), opts), name, onError)
|
|
346
|
+
};
|
|
347
|
+
const turnDuration = make.histogram("gen_ai.client.operation.duration", {
|
|
348
|
+
unit: "ms",
|
|
349
|
+
description: "Per-turn LLM operation duration."
|
|
350
|
+
});
|
|
351
|
+
const tokenUsage = make.histogram("gen_ai.client.token.usage", {
|
|
352
|
+
unit: "tokens",
|
|
353
|
+
description: "Per-turn token usage (tagged by gen_ai.token.type)."
|
|
354
|
+
});
|
|
355
|
+
const ttft = make.histogram("gen_ai.client.time_to_first_token", {
|
|
356
|
+
unit: "ms",
|
|
357
|
+
description: "Per-turn time to first token."
|
|
358
|
+
});
|
|
359
|
+
const toolDuration = make.histogram("gen_ai.tool.duration", {
|
|
360
|
+
unit: "ms",
|
|
361
|
+
description: "Native tool execution wall clock."
|
|
362
|
+
});
|
|
363
|
+
const toolOutput = make.histogram("gen_ai.tool.output_bytes", {
|
|
364
|
+
unit: "By",
|
|
365
|
+
description: "Native tool output bytes."
|
|
366
|
+
});
|
|
367
|
+
const mcpToolDuration = make.histogram("gen_ai.mcp.tool.duration", {
|
|
368
|
+
unit: "ms",
|
|
369
|
+
description: "MCP tool execution wall clock."
|
|
370
|
+
});
|
|
371
|
+
const mcpBootstrapDuration = make.histogram("gen_ai.mcp.bootstrap.duration", {
|
|
372
|
+
unit: "ms",
|
|
373
|
+
description: "MCP server bootstrap duration."
|
|
374
|
+
});
|
|
375
|
+
const runsStarted = make.counter("gen_ai.agent.runs", { description: "Runs started." });
|
|
376
|
+
const runsCompleted = make.counter("gen_ai.agent.runs.completed", { description: "Runs completed." });
|
|
377
|
+
const aborts = make.counter("gen_ai.agent.aborts", { description: "Run abort events." });
|
|
378
|
+
const toolCalls = make.counter("gen_ai.tool.calls", { description: "Tool dispatches (tagged by outcome)." });
|
|
379
|
+
const toolErrors = make.counter("gen_ai.tool.errors", { description: "Native tool errors." });
|
|
380
|
+
const mcpToolErrors = make.counter("gen_ai.mcp.tool.errors", { description: "MCP tool errors." });
|
|
381
|
+
const streamErrors = make.counter("gen_ai.stream.errors", { description: "Provider stream errors." });
|
|
382
|
+
const validationRejects = make.counter("gen_ai.validation.rejects", { description: "Tool input validation rejects." });
|
|
383
|
+
const validationCoercions = make.counter("gen_ai.validation.coercions", { description: "Tool input auto-coercions." });
|
|
384
|
+
const gateBlocks = make.counter("gen_ai.gate.blocks", { description: "Tool-gate refusals." });
|
|
385
|
+
const budgetExceeded = make.counter("gen_ai.budget.exceeded", { description: "Per-turn byte budget exceedances." });
|
|
386
|
+
const toolBudgetExceeded = make.counter("gen_ai.tool_budget.exceeded", { description: "Per-tool dispatch budget exceedances." });
|
|
387
|
+
const pairingRepairs = make.counter("gen_ai.pairing.repairs", { description: "Tool-pairing repair counts." });
|
|
388
|
+
const oauthRefreshes = make.counter("gen_ai.oauth.refreshes", { description: "Provider OAuth credential refreshes." });
|
|
389
|
+
const mcpErrors = make.counter("gen_ai.mcp.errors", { description: "MCP server errors." });
|
|
390
|
+
const mcpAuthRequired = make.counter("gen_ai.mcp.auth.required", { description: "MCP bootstrap blocked on missing auth." });
|
|
391
|
+
const costMeter = make.counter("gen_ai.cost_usd", {
|
|
392
|
+
unit: "USD",
|
|
393
|
+
description: "Cumulative cost across turns."
|
|
394
|
+
});
|
|
395
|
+
const runsActive = make.upDown("gen_ai.agent.runs.active", { description: "In-flight agent runs." });
|
|
396
|
+
return { install(hooks) {
|
|
397
|
+
const turnStart = /* @__PURE__ */ new Map();
|
|
398
|
+
const toolStart = /* @__PURE__ */ new Map();
|
|
399
|
+
const mcpStart = /* @__PURE__ */ new Map();
|
|
400
|
+
const unregisters = [];
|
|
401
|
+
const record = (instrument, value, attrs, kind) => {
|
|
402
|
+
if (!instrument) return;
|
|
403
|
+
try {
|
|
404
|
+
instrument.record(value, mergeAttrs(base, attrs));
|
|
405
|
+
} catch (err) {
|
|
406
|
+
try {
|
|
407
|
+
onError(kind, err);
|
|
408
|
+
} catch {}
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const add = (instrument, value, attrs, kind) => {
|
|
412
|
+
if (!instrument) return;
|
|
413
|
+
try {
|
|
414
|
+
instrument.add(value, mergeAttrs(base, attrs));
|
|
415
|
+
} catch (err) {
|
|
416
|
+
try {
|
|
417
|
+
onError(kind, err);
|
|
418
|
+
} catch {}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
unregisters.push(hooks.hook("agent:start", (ctx) => {
|
|
422
|
+
const attrs = {
|
|
423
|
+
"gen_ai.agent.depth": ctx.depth,
|
|
424
|
+
...ctx.agentName ? { "gen_ai.agent.name": ctx.agentName } : {}
|
|
425
|
+
};
|
|
426
|
+
add(runsStarted, 1, attrs, "runs");
|
|
427
|
+
add(runsActive, 1, attrs, "runs.active");
|
|
428
|
+
}));
|
|
429
|
+
unregisters.push(hooks.hook("agent:done", () => {
|
|
430
|
+
add(runsCompleted, 1, {}, "runs.completed");
|
|
431
|
+
add(runsActive, -1, {}, "runs.active");
|
|
432
|
+
}));
|
|
433
|
+
unregisters.push(hooks.hook("agent:abort", () => {
|
|
434
|
+
add(aborts, 1, {}, "aborts");
|
|
435
|
+
}));
|
|
436
|
+
unregisters.push(hooks.hook("turn:before", (ctx) => {
|
|
437
|
+
turnStart.set(ctx.turnId, Date.now());
|
|
438
|
+
}));
|
|
439
|
+
unregisters.push(hooks.hook("turn:after", (ctx) => {
|
|
440
|
+
const started = turnStart.get(ctx.turnId);
|
|
441
|
+
turnStart.delete(ctx.turnId);
|
|
442
|
+
const tagsBase = {
|
|
443
|
+
"gen_ai.operation.name": "chat",
|
|
444
|
+
...ctx.usage.modelId ? { "gen_ai.response.model": ctx.usage.modelId } : {},
|
|
445
|
+
...ctx.usage.finishReason ? { "gen_ai.response.finish_reason": ctx.usage.finishReason } : {}
|
|
446
|
+
};
|
|
447
|
+
if (typeof started === "number") record(turnDuration, Date.now() - started, tagsBase, "turn.duration");
|
|
448
|
+
if (typeof ctx.usage.timeToFirstTokenMs === "number") record(ttft, ctx.usage.timeToFirstTokenMs, tagsBase, "ttft");
|
|
449
|
+
record(tokenUsage, ctx.usage.input, {
|
|
450
|
+
...tagsBase,
|
|
451
|
+
"gen_ai.token.type": "input"
|
|
452
|
+
}, "token.input");
|
|
453
|
+
record(tokenUsage, ctx.usage.output, {
|
|
454
|
+
...tagsBase,
|
|
455
|
+
"gen_ai.token.type": "output"
|
|
456
|
+
}, "token.output");
|
|
457
|
+
if (typeof ctx.usage.cacheRead === "number" && ctx.usage.cacheRead > 0) record(tokenUsage, ctx.usage.cacheRead, {
|
|
458
|
+
...tagsBase,
|
|
459
|
+
"gen_ai.token.type": "cache_read"
|
|
460
|
+
}, "token.cache_read");
|
|
461
|
+
if (typeof ctx.usage.cacheCreation === "number" && ctx.usage.cacheCreation > 0) record(tokenUsage, ctx.usage.cacheCreation, {
|
|
462
|
+
...tagsBase,
|
|
463
|
+
"gen_ai.token.type": "cache_creation"
|
|
464
|
+
}, "token.cache_creation");
|
|
465
|
+
if (typeof ctx.usage.cost === "number" && ctx.usage.cost > 0) add(costMeter, ctx.usage.cost, tagsBase, "cost");
|
|
466
|
+
}));
|
|
467
|
+
unregisters.push(hooks.hook("stream:error", (ctx) => {
|
|
468
|
+
add(streamErrors, 1, {
|
|
469
|
+
"http.response.status_code": ctx.statusCode,
|
|
470
|
+
"error.type": ctx.err instanceof Error ? ctx.err.name : "unknown"
|
|
471
|
+
}, "stream.error");
|
|
472
|
+
}));
|
|
473
|
+
unregisters.push(hooks.hook("tool:before", (ctx) => {
|
|
474
|
+
toolStart.set(ctx.callId, Date.now());
|
|
475
|
+
}));
|
|
476
|
+
unregisters.push(hooks.hook("tool:after", (ctx) => {
|
|
477
|
+
const started = toolStart.get(ctx.callId);
|
|
478
|
+
toolStart.delete(ctx.callId);
|
|
479
|
+
const tags = { "gen_ai.tool.name": ctx.name };
|
|
480
|
+
if (typeof started === "number") record(toolDuration, Date.now() - started, tags, "tool.duration");
|
|
481
|
+
record(toolOutput, ctx.outputBytes, tags, "tool.output_bytes");
|
|
482
|
+
}));
|
|
483
|
+
unregisters.push(hooks.hook("tool:error", (ctx) => {
|
|
484
|
+
toolStart.delete(ctx.callId);
|
|
485
|
+
add(toolErrors, 1, {
|
|
486
|
+
"gen_ai.tool.name": ctx.name,
|
|
487
|
+
"error.type": ctx.error.name
|
|
488
|
+
}, "tool.errors");
|
|
489
|
+
}));
|
|
490
|
+
unregisters.push(hooks.hook("tool:dispatched", (ctx) => {
|
|
491
|
+
add(toolCalls, 1, {
|
|
492
|
+
"gen_ai.tool.name": ctx.name,
|
|
493
|
+
"outcome": ctx.outcome
|
|
494
|
+
}, "tool.calls");
|
|
495
|
+
if (ctx.outcome === "gate-block") add(gateBlocks, 1, {
|
|
496
|
+
"gen_ai.tool.name": ctx.name,
|
|
497
|
+
...ctx.reason ? { reason: ctx.reason } : {}
|
|
498
|
+
}, "gate.blocks");
|
|
499
|
+
}));
|
|
500
|
+
unregisters.push(hooks.hook("validation:reject", (ctx) => {
|
|
501
|
+
add(validationRejects, 1, { "gen_ai.tool.name": ctx.name }, "validation.rejects");
|
|
502
|
+
}));
|
|
503
|
+
unregisters.push(hooks.hook("validation:coerce", (ctx) => {
|
|
504
|
+
add(validationCoercions, 1, {
|
|
505
|
+
"gen_ai.tool.name": ctx.name,
|
|
506
|
+
"coercions": ctx.coercions.length
|
|
507
|
+
}, "validation.coercions");
|
|
508
|
+
}));
|
|
509
|
+
unregisters.push(hooks.hook("budget:exceeded", (ctx) => {
|
|
510
|
+
add(budgetExceeded, 1, {
|
|
511
|
+
bytes: ctx.bytes,
|
|
512
|
+
budget: ctx.budget
|
|
513
|
+
}, "budget.exceeded");
|
|
514
|
+
}));
|
|
515
|
+
unregisters.push(hooks.hook("tool-budget:exceeded", (ctx) => {
|
|
516
|
+
add(toolBudgetExceeded, 1, {
|
|
517
|
+
"gen_ai.tool.name": ctx.tool,
|
|
518
|
+
"mode": ctx.mode,
|
|
519
|
+
"count": ctx.count,
|
|
520
|
+
"max": ctx.max
|
|
521
|
+
}, "tool_budget.exceeded");
|
|
522
|
+
}));
|
|
523
|
+
unregisters.push(hooks.hook("pairing:repair", (ctx) => {
|
|
524
|
+
add(pairingRepairs, 1, { mode: ctx.mode }, "pairing.repairs");
|
|
525
|
+
}));
|
|
526
|
+
unregisters.push(hooks.hook("oauth:refresh", (ctx) => {
|
|
527
|
+
add(oauthRefreshes, 1, {
|
|
528
|
+
provider: ctx.provider,
|
|
529
|
+
source: ctx.source
|
|
530
|
+
}, "oauth.refreshes");
|
|
531
|
+
}));
|
|
532
|
+
unregisters.push(hooks.hook("mcp:tool:before", (ctx) => {
|
|
533
|
+
mcpStart.set(ctx.callId, Date.now());
|
|
534
|
+
}));
|
|
535
|
+
unregisters.push(hooks.hook("mcp:tool:after", (ctx) => {
|
|
536
|
+
const started = mcpStart.get(ctx.callId);
|
|
537
|
+
mcpStart.delete(ctx.callId);
|
|
538
|
+
const tags = {
|
|
539
|
+
"gen_ai.mcp.server": ctx.server,
|
|
540
|
+
"gen_ai.tool.name": ctx.displayName
|
|
541
|
+
};
|
|
542
|
+
if (typeof started === "number") record(mcpToolDuration, Date.now() - started, tags, "mcp.tool.duration");
|
|
543
|
+
}));
|
|
544
|
+
unregisters.push(hooks.hook("mcp:tool:error", (ctx) => {
|
|
545
|
+
mcpStart.delete(ctx.callId);
|
|
546
|
+
add(mcpToolErrors, 1, {
|
|
547
|
+
"gen_ai.mcp.server": ctx.server,
|
|
548
|
+
"gen_ai.tool.name": ctx.displayName,
|
|
549
|
+
"error.type": ctx.error.name
|
|
550
|
+
}, "mcp.tool.errors");
|
|
551
|
+
}));
|
|
552
|
+
unregisters.push(hooks.hook("mcp:bootstrap:end", (ctx) => {
|
|
553
|
+
record(mcpBootstrapDuration, ctx.durationMs, {
|
|
554
|
+
"gen_ai.mcp.server": ctx.name,
|
|
555
|
+
"ok": ctx.ok
|
|
556
|
+
}, "mcp.bootstrap.duration");
|
|
557
|
+
}));
|
|
558
|
+
unregisters.push(hooks.hook("mcp:error", (ctx) => {
|
|
559
|
+
add(mcpErrors, 1, {
|
|
560
|
+
"gen_ai.mcp.server": ctx.name,
|
|
561
|
+
"error.type": ctx.error.name
|
|
562
|
+
}, "mcp.errors");
|
|
563
|
+
}));
|
|
564
|
+
unregisters.push(hooks.hook("mcp:auth:required", (ctx) => {
|
|
565
|
+
add(mcpAuthRequired, 1, {
|
|
566
|
+
"gen_ai.mcp.server": ctx.name,
|
|
567
|
+
"transport": ctx.transport,
|
|
568
|
+
"reason": ctx.reason
|
|
569
|
+
}, "mcp.auth.required");
|
|
570
|
+
}));
|
|
571
|
+
let disposed = false;
|
|
572
|
+
return function uninstall() {
|
|
573
|
+
if (disposed) return;
|
|
574
|
+
disposed = true;
|
|
575
|
+
for (const un of unregisters) try {
|
|
576
|
+
un();
|
|
577
|
+
} catch {}
|
|
578
|
+
turnStart.clear();
|
|
579
|
+
toolStart.clear();
|
|
580
|
+
mcpStart.clear();
|
|
581
|
+
};
|
|
582
|
+
} };
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Defensive factory wrapper — returns undefined when meter creation
|
|
586
|
+
* throws. Lets a half-broken meter (one unsupported instrument) still
|
|
587
|
+
* give partial metrics rather than collapsing the whole install.
|
|
588
|
+
*/
|
|
589
|
+
function safeFactory(factory, name, onError) {
|
|
590
|
+
try {
|
|
591
|
+
return factory();
|
|
592
|
+
} catch (err) {
|
|
593
|
+
try {
|
|
594
|
+
onError(`createInstrument:${name}`, err);
|
|
595
|
+
} catch {}
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/run-summary.ts
|
|
601
|
+
/**
|
|
602
|
+
* Build a run-summary collector. State is created fresh inside each
|
|
603
|
+
* `install()` call, so a single collector instance can be installed
|
|
604
|
+
* across multiple agents without attribution cross-talk. `latest()`
|
|
605
|
+
* returns the most-recent summary across **any** install — install
|
|
606
|
+
* per-agent collectors if you need separate post-run snapshots.
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* const collector = createRunSummaryCollector({
|
|
611
|
+
* onSummary: s => console.log(JSON.stringify(s)),
|
|
612
|
+
* })
|
|
613
|
+
* const uninstall = collector.install(agent.hooks)
|
|
614
|
+
* try { await agent.run({ prompt }) }
|
|
615
|
+
* finally { uninstall() }
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
618
|
+
function createRunSummaryCollector(options = {}) {
|
|
619
|
+
let last;
|
|
620
|
+
return {
|
|
621
|
+
latest: () => last,
|
|
622
|
+
install(hooks) {
|
|
623
|
+
let runId;
|
|
624
|
+
let parentRunId;
|
|
625
|
+
let depth = 0;
|
|
626
|
+
let agentName;
|
|
627
|
+
let startedAt = Date.now();
|
|
628
|
+
let aborted = false;
|
|
629
|
+
const errors = [];
|
|
630
|
+
const blocks = [];
|
|
631
|
+
const validationRejects = [];
|
|
632
|
+
const budgetEvents = [];
|
|
633
|
+
const pairingRepairs = {};
|
|
634
|
+
const children = [];
|
|
635
|
+
function resetForNewRun() {
|
|
636
|
+
aborted = false;
|
|
637
|
+
errors.length = 0;
|
|
638
|
+
blocks.length = 0;
|
|
639
|
+
validationRejects.length = 0;
|
|
640
|
+
budgetEvents.length = 0;
|
|
641
|
+
for (const k of Object.keys(pairingRepairs)) delete pairingRepairs[k];
|
|
642
|
+
children.length = 0;
|
|
643
|
+
}
|
|
644
|
+
const unregisters = [];
|
|
645
|
+
unregisters.push(hooks.hook("agent:start", (ctx) => {
|
|
646
|
+
resetForNewRun();
|
|
647
|
+
runId = ctx.runId;
|
|
648
|
+
parentRunId = ctx.parentRunId;
|
|
649
|
+
depth = ctx.depth;
|
|
650
|
+
agentName = ctx.agentName;
|
|
651
|
+
startedAt = ctx.startedAt;
|
|
652
|
+
}));
|
|
653
|
+
unregisters.push(hooks.hook("agent:abort", () => {
|
|
654
|
+
aborted = true;
|
|
655
|
+
}));
|
|
656
|
+
unregisters.push(hooks.hook("stream:error", (ctx) => {
|
|
657
|
+
const msg = ctx.err instanceof Error ? ctx.err.message : String(ctx.err);
|
|
658
|
+
const errorType = ctx.err instanceof Error ? ctx.err.name : "unknown";
|
|
659
|
+
errors.push({
|
|
660
|
+
kind: "stream",
|
|
661
|
+
message: msg,
|
|
662
|
+
errorType,
|
|
663
|
+
turnId: ctx.turnId,
|
|
664
|
+
...ctx.statusCode !== void 0 ? { statusCode: ctx.statusCode } : {},
|
|
665
|
+
...ctx.requestId !== void 0 ? { requestId: ctx.requestId } : {}
|
|
666
|
+
});
|
|
667
|
+
}));
|
|
668
|
+
unregisters.push(hooks.hook("tool:error", (ctx) => {
|
|
669
|
+
errors.push({
|
|
670
|
+
kind: "tool",
|
|
671
|
+
message: ctx.error.message,
|
|
672
|
+
errorType: ctx.error.name,
|
|
673
|
+
turnId: ctx.turnId,
|
|
674
|
+
callId: ctx.callId,
|
|
675
|
+
toolName: ctx.name
|
|
676
|
+
});
|
|
677
|
+
}));
|
|
678
|
+
unregisters.push(hooks.hook("mcp:tool:error", (ctx) => {
|
|
679
|
+
errors.push({
|
|
680
|
+
kind: "mcp-tool",
|
|
681
|
+
message: ctx.error.message,
|
|
682
|
+
errorType: ctx.error.name,
|
|
683
|
+
turnId: ctx.turnId,
|
|
684
|
+
callId: ctx.callId,
|
|
685
|
+
server: ctx.server,
|
|
686
|
+
toolName: ctx.displayName
|
|
687
|
+
});
|
|
688
|
+
}));
|
|
689
|
+
unregisters.push(hooks.hook("mcp:error", (ctx) => {
|
|
690
|
+
errors.push({
|
|
691
|
+
kind: "mcp",
|
|
692
|
+
message: ctx.error.message,
|
|
693
|
+
errorType: ctx.error.name,
|
|
694
|
+
server: ctx.name
|
|
695
|
+
});
|
|
696
|
+
}));
|
|
697
|
+
unregisters.push(hooks.hook("spawn:error", (ctx) => {
|
|
698
|
+
errors.push({
|
|
699
|
+
kind: "spawn",
|
|
700
|
+
message: ctx.error.message,
|
|
701
|
+
errorType: ctx.error.name,
|
|
702
|
+
childId: ctx.id
|
|
703
|
+
});
|
|
704
|
+
}));
|
|
705
|
+
unregisters.push(hooks.hook("tool:dispatched", (ctx) => {
|
|
706
|
+
if (ctx.outcome === "gate-block" || ctx.outcome === "unknown" || ctx.outcome === "invalid-input") blocks.push({
|
|
707
|
+
callId: ctx.callId,
|
|
708
|
+
toolName: ctx.name,
|
|
709
|
+
outcome: ctx.outcome,
|
|
710
|
+
...ctx.reason ? { reason: ctx.reason } : {}
|
|
711
|
+
});
|
|
712
|
+
}));
|
|
713
|
+
unregisters.push(hooks.hook("validation:reject", (ctx) => {
|
|
714
|
+
validationRejects.push({
|
|
715
|
+
callId: ctx.callId,
|
|
716
|
+
toolName: ctx.name,
|
|
717
|
+
reason: ctx.reason
|
|
718
|
+
});
|
|
719
|
+
}));
|
|
720
|
+
unregisters.push(hooks.hook("budget:exceeded", (ctx) => {
|
|
721
|
+
budgetEvents.push({
|
|
722
|
+
kind: "bytes",
|
|
723
|
+
observed: ctx.bytes,
|
|
724
|
+
limit: ctx.budget,
|
|
725
|
+
turnId: ctx.turnId
|
|
726
|
+
});
|
|
727
|
+
}));
|
|
728
|
+
unregisters.push(hooks.hook("tool-budget:exceeded", (ctx) => {
|
|
729
|
+
budgetEvents.push({
|
|
730
|
+
kind: "tool-count",
|
|
731
|
+
toolName: ctx.tool,
|
|
732
|
+
mode: ctx.mode,
|
|
733
|
+
observed: ctx.count,
|
|
734
|
+
limit: ctx.max,
|
|
735
|
+
turnId: ctx.turnId
|
|
736
|
+
});
|
|
737
|
+
}));
|
|
738
|
+
unregisters.push(hooks.hook("pairing:repair", (ctx) => {
|
|
739
|
+
pairingRepairs[ctx.mode] = (pairingRepairs[ctx.mode] ?? 0) + 1;
|
|
740
|
+
}));
|
|
741
|
+
unregisters.push(hooks.hook("agent:done", (stats) => {
|
|
742
|
+
const endedAt = Date.now();
|
|
743
|
+
const byModel = [];
|
|
744
|
+
for (const [modelId, usage] of statsByModel(stats)) byModel.push({
|
|
745
|
+
modelId,
|
|
746
|
+
input: usage.input,
|
|
747
|
+
output: usage.output,
|
|
748
|
+
cacheRead: usage.cacheRead,
|
|
749
|
+
cacheCreation: usage.cacheCreation,
|
|
750
|
+
cost: usage.cost,
|
|
751
|
+
turns: usage.turns
|
|
752
|
+
});
|
|
753
|
+
for (const c of stats.children ?? []) children.push(minimalSummaryFromStats(c.stats, {
|
|
754
|
+
depth: c.depth ?? depth + 1,
|
|
755
|
+
status: c.status === "aborted" ? "aborted" : "completed"
|
|
756
|
+
}));
|
|
757
|
+
const summary = {
|
|
758
|
+
...runId ? { runId } : {},
|
|
759
|
+
...parentRunId ? { parentRunId } : {},
|
|
760
|
+
depth,
|
|
761
|
+
...agentName ? { agentName } : {},
|
|
762
|
+
startedAt,
|
|
763
|
+
endedAt,
|
|
764
|
+
durationMs: endedAt - startedAt,
|
|
765
|
+
status: aborted ? "aborted" : "completed",
|
|
766
|
+
turns: stats.turns,
|
|
767
|
+
totals: buildTotals(stats),
|
|
768
|
+
byModel,
|
|
769
|
+
errors: errors.slice(),
|
|
770
|
+
blocks: blocks.slice(),
|
|
771
|
+
validationRejects: validationRejects.slice(),
|
|
772
|
+
budgetEvents: budgetEvents.slice(),
|
|
773
|
+
pairingRepairs: { ...pairingRepairs },
|
|
774
|
+
...children.length > 0 ? { children: children.slice() } : {}
|
|
775
|
+
};
|
|
776
|
+
last = summary;
|
|
777
|
+
try {
|
|
778
|
+
options.onSummary?.(summary);
|
|
779
|
+
} catch {}
|
|
780
|
+
}));
|
|
781
|
+
let disposed = false;
|
|
782
|
+
return function uninstall() {
|
|
783
|
+
if (disposed) return;
|
|
784
|
+
disposed = true;
|
|
785
|
+
for (const un of unregisters) try {
|
|
786
|
+
un();
|
|
787
|
+
} catch {}
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function buildTotals(stats) {
|
|
793
|
+
return {
|
|
794
|
+
input: stats.totalIn,
|
|
795
|
+
output: stats.totalOut,
|
|
796
|
+
cacheRead: stats.totalCacheRead,
|
|
797
|
+
cacheCreation: stats.totalCacheCreation,
|
|
798
|
+
...typeof stats.cost === "number" ? { cost: stats.cost } : {},
|
|
799
|
+
...typeof stats.timeTillFirstTokenMs === "number" ? { ttftMs: stats.timeTillFirstTokenMs } : {}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function minimalSummaryFromStats(stats, meta) {
|
|
803
|
+
const byModel = [];
|
|
804
|
+
for (const [modelId, usage] of statsByModel(stats)) byModel.push({
|
|
805
|
+
modelId,
|
|
806
|
+
input: usage.input,
|
|
807
|
+
output: usage.output,
|
|
808
|
+
cacheRead: usage.cacheRead,
|
|
809
|
+
cacheCreation: usage.cacheCreation,
|
|
810
|
+
cost: usage.cost,
|
|
811
|
+
turns: usage.turns
|
|
812
|
+
});
|
|
813
|
+
const children = [];
|
|
814
|
+
for (const c of stats.children ?? []) children.push(minimalSummaryFromStats(c.stats, {
|
|
815
|
+
depth: c.depth ?? meta.depth + 1,
|
|
816
|
+
status: c.status === "aborted" ? "aborted" : "completed"
|
|
817
|
+
}));
|
|
818
|
+
return {
|
|
819
|
+
depth: meta.depth,
|
|
820
|
+
startedAt: 0,
|
|
821
|
+
endedAt: 0,
|
|
822
|
+
durationMs: stats.elapsed,
|
|
823
|
+
status: meta.status,
|
|
824
|
+
turns: stats.turns,
|
|
825
|
+
totals: buildTotals(stats),
|
|
826
|
+
byModel,
|
|
827
|
+
errors: [],
|
|
828
|
+
blocks: [],
|
|
829
|
+
validationRejects: [],
|
|
830
|
+
budgetEvents: [],
|
|
831
|
+
pairingRepairs: {},
|
|
832
|
+
...children.length > 0 ? { children } : {}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
//#endregion
|
|
14
836
|
//#region src/tracing.ts
|
|
15
837
|
/**
|
|
838
|
+
* Stable keys we emit unconditionally — present in BOTH Sentry and OTel
|
|
839
|
+
* conventions with identical semantics, so no `conventions` branching is
|
|
840
|
+
* needed at the call site.
|
|
841
|
+
*/
|
|
842
|
+
const GEN_AI = {
|
|
843
|
+
system: "gen_ai.system",
|
|
844
|
+
operationName: "gen_ai.operation.name",
|
|
845
|
+
requestModel: "gen_ai.request.model",
|
|
846
|
+
responseModel: "gen_ai.response.model",
|
|
847
|
+
responseFinishReasons: "gen_ai.response.finish_reasons",
|
|
848
|
+
responseId: "gen_ai.response.id",
|
|
849
|
+
responseStreaming: "gen_ai.response.streaming",
|
|
850
|
+
responseTokensPerSecond: "gen_ai.response.tokens_per_second",
|
|
851
|
+
responseTimeToFirstTokenSeconds: "gen_ai.response.time_to_first_token",
|
|
852
|
+
responseTimeToFirstTokenMs: "gen_ai.client.time_to_first_token",
|
|
853
|
+
usageInputTokens: "gen_ai.usage.input_tokens",
|
|
854
|
+
usageOutputTokens: "gen_ai.usage.output_tokens",
|
|
855
|
+
usageTotalTokens: "gen_ai.usage.total_tokens",
|
|
856
|
+
usageInputTokensCached: "gen_ai.usage.input_tokens.cached",
|
|
857
|
+
usageInputTokensCacheWrite: "gen_ai.usage.input_tokens.cache_write",
|
|
858
|
+
usageOutputTokensReasoning: "gen_ai.usage.output_tokens.reasoning",
|
|
859
|
+
usageCacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
|
|
860
|
+
usageCacheCreationInputTokens: "gen_ai.usage.cache_creation_input_tokens",
|
|
861
|
+
usageReasoningTokens: "gen_ai.usage.reasoning_tokens",
|
|
862
|
+
costTotalTokens: "gen_ai.cost.total_tokens",
|
|
863
|
+
costInputTokens: "gen_ai.cost.input_tokens",
|
|
864
|
+
costOutputTokens: "gen_ai.cost.output_tokens",
|
|
865
|
+
usageCostUsd: "gen_ai.usage.cost_usd",
|
|
866
|
+
toolName: "gen_ai.tool.name",
|
|
867
|
+
toolDescription: "gen_ai.tool.description",
|
|
868
|
+
toolType: "gen_ai.tool.type",
|
|
869
|
+
toolCallId: "gen_ai.tool.call.id",
|
|
870
|
+
toolCallArguments: "gen_ai.tool.call.arguments",
|
|
871
|
+
toolCallResult: "gen_ai.tool.call.result",
|
|
872
|
+
toolMessage: "gen_ai.tool.message",
|
|
873
|
+
toolInputDeprecated: "gen_ai.tool.input",
|
|
874
|
+
toolOutputDeprecated: "gen_ai.tool.output",
|
|
875
|
+
requestMaxTokens: "gen_ai.request.max_tokens",
|
|
876
|
+
requestTemperature: "gen_ai.request.temperature",
|
|
877
|
+
requestTopP: "gen_ai.request.top_p",
|
|
878
|
+
requestTopK: "gen_ai.request.top_k",
|
|
879
|
+
requestSeed: "gen_ai.request.seed",
|
|
880
|
+
requestFrequencyPenalty: "gen_ai.request.frequency_penalty",
|
|
881
|
+
requestPresencePenalty: "gen_ai.request.presence_penalty",
|
|
882
|
+
inputMessages: "gen_ai.input.messages",
|
|
883
|
+
outputMessages: "gen_ai.output.messages",
|
|
884
|
+
systemInstructions: "gen_ai.system_instructions",
|
|
885
|
+
toolDefinitions: "gen_ai.tool.definitions",
|
|
886
|
+
agentName: "gen_ai.agent.name",
|
|
887
|
+
agentRunId: "gen_ai.agent.run.id",
|
|
888
|
+
agentParentRunId: "gen_ai.agent.parent_run.id",
|
|
889
|
+
agentDepth: "gen_ai.agent.depth",
|
|
890
|
+
pipelineName: "gen_ai.pipeline.name",
|
|
891
|
+
turn: "gen_ai.agent.turn",
|
|
892
|
+
mcpServer: "gen_ai.mcp.server",
|
|
893
|
+
mcpToolCount: "gen_ai.mcp.tool_count"
|
|
894
|
+
};
|
|
895
|
+
/** Compose namespace prefix once. */
|
|
896
|
+
function prefixed(namespace, name) {
|
|
897
|
+
return namespace ? `${namespace}/${name}` : name;
|
|
898
|
+
}
|
|
899
|
+
/** Drop undefined entries — tracers vary on how they render `undefined`. */
|
|
900
|
+
function compact(attrs) {
|
|
901
|
+
const out = {};
|
|
902
|
+
for (const [k, v] of Object.entries(attrs)) if (v !== void 0) out[k] = v;
|
|
903
|
+
return out;
|
|
904
|
+
}
|
|
905
|
+
function blockToParts(block) {
|
|
906
|
+
switch (block.type) {
|
|
907
|
+
case "text": return [{
|
|
908
|
+
type: "text",
|
|
909
|
+
content: typeof block.text === "string" ? block.text : ""
|
|
910
|
+
}];
|
|
911
|
+
case "image": return [{
|
|
912
|
+
type: "text",
|
|
913
|
+
content: "[Blob substitute]"
|
|
914
|
+
}];
|
|
915
|
+
case "tool_call": return [{
|
|
916
|
+
type: "tool_call",
|
|
917
|
+
...typeof block.name === "string" ? { name: block.name } : {},
|
|
918
|
+
...typeof block.id === "string" ? { call_id: block.id } : {},
|
|
919
|
+
arguments: safeJson(block.input)
|
|
920
|
+
}];
|
|
921
|
+
case "tool_result": {
|
|
922
|
+
const output = block.output;
|
|
923
|
+
const text = typeof output === "string" ? output : Array.isArray(output) ? output.map((b) => b.type === "image" ? "[Blob substitute]" : b.text ?? "").join("\n") : "";
|
|
924
|
+
return [{
|
|
925
|
+
type: "tool_result",
|
|
926
|
+
...typeof block.callId === "string" ? { call_id: block.callId } : {},
|
|
927
|
+
content: text
|
|
928
|
+
}];
|
|
929
|
+
}
|
|
930
|
+
case "thinking": return [{
|
|
931
|
+
type: "reasoning",
|
|
932
|
+
content: typeof block.text === "string" ? block.text : ""
|
|
933
|
+
}];
|
|
934
|
+
default: return [];
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function messageToSentry(msg) {
|
|
938
|
+
const parts = [];
|
|
939
|
+
for (const block of msg.content) parts.push(...blockToParts(block));
|
|
940
|
+
return {
|
|
941
|
+
role: msg.role,
|
|
942
|
+
parts
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function safeJson(value) {
|
|
946
|
+
try {
|
|
947
|
+
return JSON.stringify(value ?? null);
|
|
948
|
+
} catch {
|
|
949
|
+
return "\"[unserializable]\"";
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Stringify an array of `SessionMessage`s into Sentry's `gen_ai.input.messages`
|
|
954
|
+
* format. Each entry's role + content is mapped via `messageToSentry`.
|
|
955
|
+
* Returns `undefined` when the input is empty or unserializable.
|
|
956
|
+
*/
|
|
957
|
+
function stringifyMessages(messages) {
|
|
958
|
+
if (messages.length === 0) return void 0;
|
|
959
|
+
try {
|
|
960
|
+
const sentry = messages.map((m) => messageToSentry({
|
|
961
|
+
role: m.role,
|
|
962
|
+
content: m.content
|
|
963
|
+
}));
|
|
964
|
+
return JSON.stringify(sentry);
|
|
965
|
+
} catch {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Stringify a `StreamOptions.tools` array into Sentry's
|
|
971
|
+
* `gen_ai.tool.definitions` format: `[{name, description?, parameters?}, …]`.
|
|
972
|
+
*
|
|
973
|
+
* The input is `unknown[]` because `StreamOptions.tools` carries the
|
|
974
|
+
* provider-formatted shape (Anthropic / OpenAI / OpenAI-compat all
|
|
975
|
+
* differ). We probe the three names that overlap (`name`, `description`,
|
|
976
|
+
* `inputSchema` / `parameters`) and degrade gracefully otherwise.
|
|
977
|
+
*/
|
|
978
|
+
function stringifyToolDefs(tools) {
|
|
979
|
+
if (!tools || tools.length === 0) return void 0;
|
|
980
|
+
try {
|
|
981
|
+
return JSON.stringify(tools.map((raw) => {
|
|
982
|
+
if (!raw || typeof raw !== "object") return { name: "anonymous" };
|
|
983
|
+
const t = raw;
|
|
984
|
+
const name = typeof t.name === "string" ? t.name : "anonymous";
|
|
985
|
+
const description = typeof t.description === "string" ? t.description : void 0;
|
|
986
|
+
const parameters = t.parameters ?? t.input_schema ?? t.inputSchema;
|
|
987
|
+
return {
|
|
988
|
+
name,
|
|
989
|
+
...description ? { description } : {},
|
|
990
|
+
...parameters !== void 0 ? { parameters } : {}
|
|
991
|
+
};
|
|
992
|
+
}));
|
|
993
|
+
} catch {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Build the usage attribute set per the configured conventions. Sentry
|
|
999
|
+
* expects `input_tokens` to include cached, with `input_tokens.cached`
|
|
1000
|
+
* + `input_tokens.cache_write` subkeys; OTel splits cache into
|
|
1001
|
+
* separate top-level keys. `'both'` emits everything.
|
|
1002
|
+
*/
|
|
1003
|
+
function buildUsageAttrs(usage, conventions) {
|
|
1004
|
+
const cacheRead = usage.cacheRead ?? 0;
|
|
1005
|
+
const cacheWrite = usage.cacheCreation ?? 0;
|
|
1006
|
+
const inputInclCache = usage.input + cacheRead + cacheWrite;
|
|
1007
|
+
const totalTokens = inputInclCache + usage.output;
|
|
1008
|
+
const wantSentry = conventions === "sentry" || conventions === "both";
|
|
1009
|
+
const wantOtel = conventions === "otel" || conventions === "both";
|
|
1010
|
+
const out = {
|
|
1011
|
+
[GEN_AI.responseModel]: usage.modelId,
|
|
1012
|
+
[GEN_AI.responseFinishReasons]: usage.finishReason ? [usage.finishReason] : void 0,
|
|
1013
|
+
[GEN_AI.usageOutputTokens]: usage.output,
|
|
1014
|
+
[GEN_AI.usageTotalTokens]: totalTokens
|
|
1015
|
+
};
|
|
1016
|
+
if (wantSentry) {
|
|
1017
|
+
out[GEN_AI.usageInputTokens] = inputInclCache;
|
|
1018
|
+
if (cacheRead > 0) out[GEN_AI.usageInputTokensCached] = cacheRead;
|
|
1019
|
+
if (cacheWrite > 0) out[GEN_AI.usageInputTokensCacheWrite] = cacheWrite;
|
|
1020
|
+
if (typeof usage.thinking === "number" && usage.thinking > 0) out[GEN_AI.usageOutputTokensReasoning] = usage.thinking;
|
|
1021
|
+
if (typeof usage.cost === "number") out[GEN_AI.costTotalTokens] = usage.cost;
|
|
1022
|
+
if (typeof usage.timeToFirstTokenMs === "number") out[GEN_AI.responseTimeToFirstTokenSeconds] = usage.timeToFirstTokenMs / 1e3;
|
|
1023
|
+
}
|
|
1024
|
+
if (wantOtel) {
|
|
1025
|
+
if (!wantSentry) out[GEN_AI.usageInputTokens] = usage.input;
|
|
1026
|
+
if (cacheRead > 0) out[GEN_AI.usageCacheReadInputTokens] = cacheRead;
|
|
1027
|
+
if (cacheWrite > 0) out[GEN_AI.usageCacheCreationInputTokens] = cacheWrite;
|
|
1028
|
+
if (typeof usage.thinking === "number" && usage.thinking > 0) out[GEN_AI.usageReasoningTokens] = usage.thinking;
|
|
1029
|
+
if (typeof usage.cost === "number") out[GEN_AI.usageCostUsd] = usage.cost;
|
|
1030
|
+
if (typeof usage.timeToFirstTokenMs === "number") out[GEN_AI.responseTimeToFirstTokenMs] = usage.timeToFirstTokenMs;
|
|
1031
|
+
}
|
|
1032
|
+
return out;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
16
1035
|
* Build a set of tracing hook handlers that can be installed on an agent.
|
|
17
1036
|
*
|
|
18
1037
|
* @example Sentry
|
|
@@ -24,85 +1043,530 @@ import { defineSkill } from "./skills.js";
|
|
|
24
1043
|
* try { await agent.run({ prompt }) }
|
|
25
1044
|
* finally { uninstall() }
|
|
26
1045
|
* ```
|
|
1046
|
+
*
|
|
1047
|
+
* @example OpenTelemetry with parent-context propagation
|
|
1048
|
+
* ```ts
|
|
1049
|
+
* import { trace, context, propagation } from '@opentelemetry/api'
|
|
1050
|
+
*
|
|
1051
|
+
* const tracer = trace.getTracer('zidane')
|
|
1052
|
+
* const tracing = createTracingHooks({
|
|
1053
|
+
* startSpan: (name, attrs, parentCtx) => {
|
|
1054
|
+
* const ctx = parentCtx ? propagation.extract(context.active(), parentCtx) : context.active()
|
|
1055
|
+
* return tracer.startSpan(name, { attributes: attrs }, ctx)
|
|
1056
|
+
* },
|
|
1057
|
+
* getActiveTraceContext: () => {
|
|
1058
|
+
* const carrier: Record<string, string> = {}
|
|
1059
|
+
* propagation.inject(context.active(), carrier)
|
|
1060
|
+
* return carrier
|
|
1061
|
+
* },
|
|
1062
|
+
* })
|
|
1063
|
+
* ```
|
|
27
1064
|
*/
|
|
28
1065
|
function createTracingHooks(options) {
|
|
29
|
-
const
|
|
1066
|
+
const namespace = options.namespace;
|
|
1067
|
+
const legacy = options.legacyAttributes ?? true;
|
|
1068
|
+
const conventions = options.conventions ?? "sentry";
|
|
1069
|
+
const wantSentry = conventions === "sentry" || conventions === "both";
|
|
1070
|
+
const captureContent = options.captureMessageContent ?? true;
|
|
1071
|
+
const onError = options.onError ?? (() => {});
|
|
30
1072
|
return { install(hooks) {
|
|
1073
|
+
const runSpans = /* @__PURE__ */ new Map();
|
|
31
1074
|
const turnSpans = /* @__PURE__ */ new Map();
|
|
32
1075
|
const toolSpans = /* @__PURE__ */ new Map();
|
|
33
1076
|
const mcpSpans = /* @__PURE__ */ new Map();
|
|
34
|
-
|
|
1077
|
+
const spawnSpans = /* @__PURE__ */ new Map();
|
|
1078
|
+
const bootstrapSpans = /* @__PURE__ */ new Map();
|
|
1079
|
+
let activeSystem;
|
|
1080
|
+
const turnTrack = /* @__PURE__ */ new Map();
|
|
1081
|
+
let activeRunSpan;
|
|
1082
|
+
function safeStartSpan(name, attrs, parentContext) {
|
|
1083
|
+
try {
|
|
1084
|
+
return options.startSpan(prefixed(namespace, name), compact(attrs), parentContext);
|
|
1085
|
+
} catch (err) {
|
|
1086
|
+
try {
|
|
1087
|
+
onError("startSpan", err);
|
|
1088
|
+
} catch {}
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function safeSetAttrs(span, attrs) {
|
|
1093
|
+
if (!span?.setAttributes) return;
|
|
1094
|
+
try {
|
|
1095
|
+
span.setAttributes(compact(attrs));
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
try {
|
|
1098
|
+
onError("setAttributes", err);
|
|
1099
|
+
} catch {}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
function safeEnd(map, key) {
|
|
35
1103
|
const span = map.get(key);
|
|
36
|
-
if (span)
|
|
1104
|
+
if (!span) return;
|
|
1105
|
+
try {
|
|
1106
|
+
span.end();
|
|
1107
|
+
} catch (err) {
|
|
37
1108
|
try {
|
|
38
|
-
|
|
1109
|
+
onError("end", err);
|
|
39
1110
|
} catch {}
|
|
40
|
-
map.delete(key);
|
|
41
1111
|
}
|
|
1112
|
+
map.delete(key);
|
|
42
1113
|
}
|
|
43
1114
|
function endAll(map) {
|
|
44
1115
|
for (const [, span] of map) try {
|
|
45
1116
|
span.end();
|
|
46
|
-
} catch {
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
try {
|
|
1119
|
+
onError("end", err);
|
|
1120
|
+
} catch {}
|
|
1121
|
+
}
|
|
47
1122
|
map.clear();
|
|
48
1123
|
}
|
|
49
|
-
|
|
50
|
-
unregisters.push(hooks.hook("turn:before", (ctx) => {
|
|
51
|
-
const span = options.startSpan(`${prefix}turn:${ctx.turn}`, { turnId: ctx.turnId });
|
|
52
|
-
turnSpans.set(ctx.turnId, span);
|
|
53
|
-
}));
|
|
54
|
-
function safeSetAttrs(span, attrs) {
|
|
1124
|
+
function safeAddEvent(span, name, attrs) {
|
|
55
1125
|
if (!span) return;
|
|
1126
|
+
if (typeof span.addEvent === "function") {
|
|
1127
|
+
try {
|
|
1128
|
+
span.addEvent(name, compact(attrs));
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
try {
|
|
1131
|
+
onError("addEvent", err);
|
|
1132
|
+
} catch {}
|
|
1133
|
+
}
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
safeSetAttrs(span, Object.fromEntries(Object.entries(compact(attrs)).map(([k, v]) => [`event.${name}.${k}`, v])));
|
|
1137
|
+
}
|
|
1138
|
+
function redact(kind, value, meta) {
|
|
1139
|
+
if (typeof options.redact !== "function") return value;
|
|
56
1140
|
try {
|
|
57
|
-
|
|
58
|
-
} catch {
|
|
1141
|
+
return options.redact(kind, value, meta);
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
try {
|
|
1144
|
+
onError("redact", err);
|
|
1145
|
+
} catch {}
|
|
1146
|
+
return value;
|
|
1147
|
+
}
|
|
59
1148
|
}
|
|
1149
|
+
const unregisters = [];
|
|
1150
|
+
unregisters.push(hooks.hook("agent:start", (ctx) => {
|
|
1151
|
+
activeSystem = ctx.providerName;
|
|
1152
|
+
const span = safeStartSpan(`invoke_agent ${ctx.agentName && ctx.agentName.length > 0 ? ctx.agentName : "agent"}`, {
|
|
1153
|
+
[GEN_AI.operationName]: "invoke_agent",
|
|
1154
|
+
[GEN_AI.system]: activeSystem,
|
|
1155
|
+
[GEN_AI.agentName]: ctx.agentName,
|
|
1156
|
+
[GEN_AI.agentRunId]: ctx.runId,
|
|
1157
|
+
[GEN_AI.agentParentRunId]: ctx.parentRunId,
|
|
1158
|
+
[GEN_AI.agentDepth]: ctx.depth,
|
|
1159
|
+
"sentry.op": "gen_ai.invoke_agent",
|
|
1160
|
+
...legacy ? {
|
|
1161
|
+
runId: ctx.runId,
|
|
1162
|
+
parentRunId: ctx.parentRunId,
|
|
1163
|
+
agentName: ctx.agentName,
|
|
1164
|
+
depth: ctx.depth
|
|
1165
|
+
} : {}
|
|
1166
|
+
}, ctx.tracingContext);
|
|
1167
|
+
if (span) {
|
|
1168
|
+
runSpans.set(ctx.runId, span);
|
|
1169
|
+
activeRunSpan = span;
|
|
1170
|
+
}
|
|
1171
|
+
}));
|
|
1172
|
+
unregisters.push(hooks.hook("agent:done", (stats) => {
|
|
1173
|
+
const usageAttrs = buildUsageAttrs({
|
|
1174
|
+
input: stats.totalIn,
|
|
1175
|
+
output: stats.totalOut,
|
|
1176
|
+
cacheRead: stats.totalCacheRead,
|
|
1177
|
+
cacheCreation: stats.totalCacheCreation,
|
|
1178
|
+
cost: stats.cost,
|
|
1179
|
+
timeToFirstTokenMs: stats.timeTillFirstTokenMs
|
|
1180
|
+
}, conventions);
|
|
1181
|
+
for (const [, span] of runSpans) safeSetAttrs(span, {
|
|
1182
|
+
...usageAttrs,
|
|
1183
|
+
[GEN_AI.turn]: stats.turns,
|
|
1184
|
+
"gen_ai.agent.elapsed_ms": stats.elapsed,
|
|
1185
|
+
...legacy ? {
|
|
1186
|
+
totalIn: stats.totalIn,
|
|
1187
|
+
totalOut: stats.totalOut,
|
|
1188
|
+
totalCacheRead: stats.totalCacheRead,
|
|
1189
|
+
totalCacheCreation: stats.totalCacheCreation,
|
|
1190
|
+
cost: stats.cost,
|
|
1191
|
+
turns: stats.turns,
|
|
1192
|
+
elapsed: stats.elapsed
|
|
1193
|
+
} : {}
|
|
1194
|
+
});
|
|
1195
|
+
endAll(runSpans);
|
|
1196
|
+
endAll(turnSpans);
|
|
1197
|
+
endAll(toolSpans);
|
|
1198
|
+
endAll(mcpSpans);
|
|
1199
|
+
endAll(spawnSpans);
|
|
1200
|
+
endAll(bootstrapSpans);
|
|
1201
|
+
activeRunSpan = void 0;
|
|
1202
|
+
activeSystem = void 0;
|
|
1203
|
+
turnTrack.clear();
|
|
1204
|
+
}));
|
|
1205
|
+
unregisters.push(hooks.hook("agent:abort", () => {
|
|
1206
|
+
for (const [, span] of runSpans) safeSetAttrs(span, { "gen_ai.agent.status": "aborted" });
|
|
1207
|
+
}));
|
|
1208
|
+
unregisters.push(hooks.hook("turn:before", (ctx) => {
|
|
1209
|
+
const requestedModel = ctx.options.model;
|
|
1210
|
+
const baseAttrs = {
|
|
1211
|
+
[GEN_AI.operationName]: "chat",
|
|
1212
|
+
[GEN_AI.system]: activeSystem,
|
|
1213
|
+
[GEN_AI.requestModel]: requestedModel,
|
|
1214
|
+
[GEN_AI.turn]: ctx.turn,
|
|
1215
|
+
[GEN_AI.responseStreaming]: true,
|
|
1216
|
+
[GEN_AI.requestMaxTokens]: ctx.options.maxTokens,
|
|
1217
|
+
"sentry.op": "gen_ai.chat",
|
|
1218
|
+
"gen_ai.agent.turn_id": ctx.turnId,
|
|
1219
|
+
...legacy ? {
|
|
1220
|
+
turnId: ctx.turnId,
|
|
1221
|
+
turn: ctx.turn
|
|
1222
|
+
} : {}
|
|
1223
|
+
};
|
|
1224
|
+
if (captureContent) {
|
|
1225
|
+
const systemText = ctx.options.system;
|
|
1226
|
+
if (systemText) baseAttrs[GEN_AI.systemInstructions] = redact("system", systemText, {
|
|
1227
|
+
turnId: ctx.turnId,
|
|
1228
|
+
turn: ctx.turn
|
|
1229
|
+
});
|
|
1230
|
+
const inputMsgs = stringifyMessages(ctx.options.messages);
|
|
1231
|
+
if (inputMsgs) baseAttrs[GEN_AI.inputMessages] = redact("prompt", inputMsgs, {
|
|
1232
|
+
turnId: ctx.turnId,
|
|
1233
|
+
turn: ctx.turn
|
|
1234
|
+
});
|
|
1235
|
+
const toolDefs = stringifyToolDefs(ctx.options.tools);
|
|
1236
|
+
if (toolDefs) baseAttrs[GEN_AI.toolDefinitions] = toolDefs;
|
|
1237
|
+
}
|
|
1238
|
+
const span = safeStartSpan(`chat${requestedModel ? ` ${requestedModel}` : ""}`, baseAttrs);
|
|
1239
|
+
if (span) {
|
|
1240
|
+
turnSpans.set(ctx.turnId, span);
|
|
1241
|
+
turnTrack.set(ctx.turnId, { startedAt: Date.now() });
|
|
1242
|
+
}
|
|
1243
|
+
}));
|
|
1244
|
+
unregisters.push(hooks.hook("stream:start", (ctx) => {
|
|
1245
|
+
safeAddEvent(turnSpans.get(ctx.turnId), "gen_ai.stream.start", { startedAt: ctx.startedAt });
|
|
1246
|
+
}));
|
|
60
1247
|
unregisters.push(hooks.hook("turn:after", (ctx) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1248
|
+
const span = turnSpans.get(ctx.turnId);
|
|
1249
|
+
const tracked = turnTrack.get(ctx.turnId);
|
|
1250
|
+
turnTrack.delete(ctx.turnId);
|
|
1251
|
+
let tokensPerSecond;
|
|
1252
|
+
if (tracked && ctx.usage.output > 0) {
|
|
1253
|
+
const elapsedMs = Date.now() - tracked.startedAt;
|
|
1254
|
+
if (elapsedMs > 0) tokensPerSecond = ctx.usage.output / elapsedMs * 1e3;
|
|
1255
|
+
}
|
|
1256
|
+
const usageAttrs = buildUsageAttrs(ctx.usage, conventions);
|
|
1257
|
+
const cumInputTokens = wantSentry ? ctx.cumulativeUsage.input + ctx.cumulativeUsage.cacheRead + ctx.cumulativeUsage.cacheCreation : ctx.cumulativeUsage.input;
|
|
1258
|
+
const attrs = {
|
|
1259
|
+
...usageAttrs,
|
|
1260
|
+
[GEN_AI.responseTokensPerSecond]: tokensPerSecond,
|
|
1261
|
+
"gen_ai.agent.cumulative.input_tokens": cumInputTokens,
|
|
1262
|
+
"gen_ai.agent.cumulative.output_tokens": ctx.cumulativeUsage.output,
|
|
1263
|
+
"gen_ai.agent.cumulative.total_tokens": cumInputTokens + ctx.cumulativeUsage.output,
|
|
1264
|
+
"gen_ai.agent.cumulative.cache_read_input_tokens": ctx.cumulativeUsage.cacheRead,
|
|
1265
|
+
"gen_ai.agent.cumulative.cache_creation_input_tokens": ctx.cumulativeUsage.cacheCreation,
|
|
1266
|
+
"gen_ai.agent.cumulative.cost_usd": ctx.cumulativeUsage.cost,
|
|
1267
|
+
"gen_ai.agent.cumulative.turns": ctx.cumulativeUsage.turns,
|
|
1268
|
+
...legacy ? {
|
|
1269
|
+
inputTokens: ctx.usage.input,
|
|
1270
|
+
outputTokens: ctx.usage.output,
|
|
1271
|
+
finishReason: ctx.usage.finishReason,
|
|
1272
|
+
modelId: ctx.usage.modelId
|
|
1273
|
+
} : {}
|
|
1274
|
+
};
|
|
1275
|
+
if (captureContent && ctx.message && ctx.message.role !== "system") {
|
|
1276
|
+
const outputMsgs = stringifyMessages([{
|
|
1277
|
+
role: ctx.message.role,
|
|
1278
|
+
content: ctx.message.content
|
|
1279
|
+
}]);
|
|
1280
|
+
if (outputMsgs) attrs[GEN_AI.outputMessages] = redact("stream-text", outputMsgs, {
|
|
1281
|
+
turnId: ctx.turnId,
|
|
1282
|
+
turn: ctx.turn
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
safeSetAttrs(span, attrs);
|
|
1286
|
+
safeEnd(turnSpans, ctx.turnId);
|
|
1287
|
+
}));
|
|
1288
|
+
unregisters.push(hooks.hook("stream:error", (ctx) => {
|
|
1289
|
+
const span = turnSpans.get(ctx.turnId);
|
|
1290
|
+
const message = ctx.err instanceof Error ? ctx.err.message : String(ctx.err);
|
|
1291
|
+
safeSetAttrs(span, {
|
|
1292
|
+
"error.type": ctx.err instanceof Error ? ctx.err.name : "unknown",
|
|
1293
|
+
"error.message": message,
|
|
1294
|
+
"http.response.status_code": ctx.statusCode,
|
|
1295
|
+
"gen_ai.request.id": ctx.requestId
|
|
1296
|
+
});
|
|
1297
|
+
safeAddEvent(span, "gen_ai.stream.error", {
|
|
1298
|
+
"error.type": ctx.err instanceof Error ? ctx.err.name : "unknown",
|
|
1299
|
+
"error.message": message,
|
|
1300
|
+
"http.response.status_code": ctx.statusCode,
|
|
1301
|
+
"gen_ai.request.id": ctx.requestId
|
|
66
1302
|
});
|
|
67
|
-
endSpan(turnSpans, ctx.turnId);
|
|
68
1303
|
}));
|
|
69
1304
|
unregisters.push(hooks.hook("tool:before", (ctx) => {
|
|
70
|
-
const span =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
1305
|
+
const span = safeStartSpan(`execute_tool ${ctx.displayName}`, {
|
|
1306
|
+
[GEN_AI.operationName]: "execute_tool",
|
|
1307
|
+
[GEN_AI.system]: activeSystem,
|
|
1308
|
+
[GEN_AI.toolName]: ctx.name,
|
|
1309
|
+
[GEN_AI.toolType]: "function",
|
|
1310
|
+
[GEN_AI.toolCallId]: ctx.callId,
|
|
1311
|
+
"sentry.op": "gen_ai.execute_tool",
|
|
1312
|
+
"gen_ai.tool.display_name": ctx.displayName,
|
|
1313
|
+
"gen_ai.agent.turn_id": ctx.turnId,
|
|
1314
|
+
"gen_ai.agent.depth": ctx.depth,
|
|
1315
|
+
"gen_ai.agent.run.id": ctx.runId,
|
|
1316
|
+
"gen_ai.agent.parent_run.id": ctx.parentRunId,
|
|
1317
|
+
...legacy ? {
|
|
1318
|
+
toolName: ctx.name,
|
|
1319
|
+
displayName: ctx.displayName,
|
|
1320
|
+
turnId: ctx.turnId,
|
|
1321
|
+
callId: ctx.callId
|
|
1322
|
+
} : {}
|
|
75
1323
|
});
|
|
76
|
-
|
|
1324
|
+
if (span) {
|
|
1325
|
+
toolSpans.set(ctx.callId, span);
|
|
1326
|
+
if (captureContent) try {
|
|
1327
|
+
const redacted = redact("tool-input", JSON.stringify(ctx.input), {
|
|
1328
|
+
toolName: ctx.name,
|
|
1329
|
+
displayName: ctx.displayName,
|
|
1330
|
+
callId: ctx.callId,
|
|
1331
|
+
turnId: ctx.turnId
|
|
1332
|
+
});
|
|
1333
|
+
const attrs = {};
|
|
1334
|
+
if (wantSentry) attrs[GEN_AI.toolCallArguments] = redacted;
|
|
1335
|
+
if (conventions === "otel" || conventions === "both") attrs[GEN_AI.toolInputDeprecated] = redacted;
|
|
1336
|
+
safeSetAttrs(span, attrs);
|
|
1337
|
+
} catch {}
|
|
1338
|
+
}
|
|
77
1339
|
}));
|
|
78
1340
|
unregisters.push(hooks.hook("tool:after", (ctx) => {
|
|
79
|
-
|
|
1341
|
+
const span = toolSpans.get(ctx.callId);
|
|
1342
|
+
safeSetAttrs(span, {
|
|
1343
|
+
"gen_ai.tool.output_bytes": ctx.outputBytes,
|
|
1344
|
+
"gen_ai.tool.coercions": ctx.coercions?.length ?? 0
|
|
1345
|
+
});
|
|
1346
|
+
if (captureContent && typeof ctx.result === "string") {
|
|
1347
|
+
const redacted = redact("tool-result", ctx.result, {
|
|
1348
|
+
toolName: ctx.name,
|
|
1349
|
+
callId: ctx.callId
|
|
1350
|
+
});
|
|
1351
|
+
const attrs = {};
|
|
1352
|
+
if (wantSentry) attrs[GEN_AI.toolCallResult] = redacted;
|
|
1353
|
+
if (conventions === "otel" || conventions === "both") attrs[GEN_AI.toolOutputDeprecated] = redacted;
|
|
1354
|
+
safeSetAttrs(span, attrs);
|
|
1355
|
+
}
|
|
1356
|
+
safeEnd(toolSpans, ctx.callId);
|
|
80
1357
|
}));
|
|
81
1358
|
unregisters.push(hooks.hook("tool:error", (ctx) => {
|
|
82
|
-
safeSetAttrs(toolSpans.get(ctx.callId), {
|
|
83
|
-
|
|
1359
|
+
safeSetAttrs(toolSpans.get(ctx.callId), {
|
|
1360
|
+
"error.type": ctx.error.name,
|
|
1361
|
+
"error.message": ctx.error.message
|
|
1362
|
+
});
|
|
1363
|
+
safeEnd(toolSpans, ctx.callId);
|
|
84
1364
|
}));
|
|
85
|
-
unregisters.push(hooks.hook("
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1365
|
+
unregisters.push(hooks.hook("tool:dispatched", (ctx) => {
|
|
1366
|
+
if (ctx.outcome === "gate-block") safeAddEvent(activeRunSpan, "gen_ai.gate.block", {
|
|
1367
|
+
[GEN_AI.toolName]: ctx.name,
|
|
1368
|
+
[GEN_AI.toolCallId]: ctx.callId,
|
|
1369
|
+
reason: ctx.reason
|
|
1370
|
+
});
|
|
1371
|
+
else if (ctx.outcome === "gate-substitute") safeAddEvent(activeRunSpan, "gen_ai.gate.substitute", {
|
|
1372
|
+
[GEN_AI.toolName]: ctx.name,
|
|
1373
|
+
[GEN_AI.toolCallId]: ctx.callId
|
|
1374
|
+
});
|
|
1375
|
+
else if (ctx.outcome === "unknown") safeAddEvent(activeRunSpan, "gen_ai.tool.unknown", {
|
|
1376
|
+
[GEN_AI.toolName]: ctx.name,
|
|
1377
|
+
[GEN_AI.toolCallId]: ctx.callId
|
|
1378
|
+
});
|
|
1379
|
+
else if (ctx.outcome === "invalid-input") safeAddEvent(activeRunSpan, "gen_ai.tool.invalid_input", {
|
|
1380
|
+
[GEN_AI.toolName]: ctx.name,
|
|
1381
|
+
[GEN_AI.toolCallId]: ctx.callId
|
|
1382
|
+
});
|
|
1383
|
+
}));
|
|
1384
|
+
unregisters.push(hooks.hook("validation:reject", (ctx) => {
|
|
1385
|
+
safeAddEvent(activeRunSpan, "gen_ai.validation.reject", {
|
|
1386
|
+
[GEN_AI.toolName]: ctx.name,
|
|
1387
|
+
[GEN_AI.toolCallId]: ctx.callId,
|
|
1388
|
+
reason: ctx.reason
|
|
1389
|
+
});
|
|
1390
|
+
}));
|
|
1391
|
+
unregisters.push(hooks.hook("validation:coerce", (ctx) => {
|
|
1392
|
+
safeAddEvent(activeRunSpan, "gen_ai.validation.coerce", {
|
|
1393
|
+
[GEN_AI.toolName]: ctx.name,
|
|
1394
|
+
[GEN_AI.toolCallId]: ctx.callId,
|
|
1395
|
+
coercions: ctx.coercions
|
|
1396
|
+
});
|
|
1397
|
+
}));
|
|
1398
|
+
unregisters.push(hooks.hook("budget:exceeded", (ctx) => {
|
|
1399
|
+
safeAddEvent(activeRunSpan, "gen_ai.budget.exceeded", {
|
|
90
1400
|
turnId: ctx.turnId,
|
|
91
|
-
|
|
1401
|
+
bytes: ctx.bytes,
|
|
1402
|
+
budget: ctx.budget
|
|
1403
|
+
});
|
|
1404
|
+
}));
|
|
1405
|
+
unregisters.push(hooks.hook("tool-budget:exceeded", (ctx) => {
|
|
1406
|
+
safeAddEvent(activeRunSpan, "gen_ai.tool_budget.exceeded", {
|
|
1407
|
+
[GEN_AI.toolName]: ctx.tool,
|
|
1408
|
+
turnId: ctx.turnId,
|
|
1409
|
+
count: ctx.count,
|
|
1410
|
+
max: ctx.max,
|
|
1411
|
+
mode: ctx.mode
|
|
1412
|
+
});
|
|
1413
|
+
}));
|
|
1414
|
+
unregisters.push(hooks.hook("pairing:repair", (ctx) => {
|
|
1415
|
+
safeAddEvent(activeRunSpan, "gen_ai.pairing.repair", {
|
|
1416
|
+
mode: ctx.mode,
|
|
1417
|
+
callId: ctx.callId,
|
|
1418
|
+
turnId: ctx.turnId
|
|
1419
|
+
});
|
|
1420
|
+
}));
|
|
1421
|
+
unregisters.push(hooks.hook("oauth:refresh", (ctx) => {
|
|
1422
|
+
safeAddEvent(activeRunSpan, "gen_ai.oauth.refresh", {
|
|
1423
|
+
provider: ctx.provider,
|
|
1424
|
+
providerId: ctx.providerId,
|
|
1425
|
+
source: ctx.source
|
|
92
1426
|
});
|
|
93
|
-
|
|
1427
|
+
}));
|
|
1428
|
+
unregisters.push(hooks.hook("mcp:tool:before", (ctx) => {
|
|
1429
|
+
const span = safeStartSpan(`execute_tool ${ctx.server}.${ctx.tool}`, {
|
|
1430
|
+
[GEN_AI.operationName]: "execute_tool",
|
|
1431
|
+
[GEN_AI.system]: activeSystem,
|
|
1432
|
+
[GEN_AI.toolName]: ctx.displayName,
|
|
1433
|
+
[GEN_AI.toolType]: "extension",
|
|
1434
|
+
[GEN_AI.toolCallId]: ctx.callId,
|
|
1435
|
+
[GEN_AI.mcpServer]: ctx.server,
|
|
1436
|
+
"sentry.op": "gen_ai.execute_tool",
|
|
1437
|
+
"gen_ai.mcp.tool": ctx.tool,
|
|
1438
|
+
"gen_ai.agent.turn_id": ctx.turnId,
|
|
1439
|
+
"gen_ai.agent.run.id": ctx.runId,
|
|
1440
|
+
...legacy ? {
|
|
1441
|
+
server: ctx.server,
|
|
1442
|
+
tool: ctx.tool,
|
|
1443
|
+
displayName: ctx.displayName,
|
|
1444
|
+
turnId: ctx.turnId,
|
|
1445
|
+
callId: ctx.callId
|
|
1446
|
+
} : {}
|
|
1447
|
+
});
|
|
1448
|
+
if (span) {
|
|
1449
|
+
mcpSpans.set(ctx.callId, span);
|
|
1450
|
+
if (captureContent) try {
|
|
1451
|
+
const redacted = redact("mcp-tool-input", JSON.stringify(ctx.input), {
|
|
1452
|
+
server: ctx.server,
|
|
1453
|
+
tool: ctx.tool,
|
|
1454
|
+
callId: ctx.callId
|
|
1455
|
+
});
|
|
1456
|
+
const attrs = {};
|
|
1457
|
+
if (wantSentry) attrs[GEN_AI.toolCallArguments] = redacted;
|
|
1458
|
+
if (conventions === "otel" || conventions === "both") attrs[GEN_AI.toolInputDeprecated] = redacted;
|
|
1459
|
+
safeSetAttrs(span, attrs);
|
|
1460
|
+
} catch {}
|
|
1461
|
+
}
|
|
94
1462
|
}));
|
|
95
1463
|
unregisters.push(hooks.hook("mcp:tool:after", (ctx) => {
|
|
96
|
-
|
|
1464
|
+
const span = mcpSpans.get(ctx.callId);
|
|
1465
|
+
safeSetAttrs(span, { "gen_ai.tool.output_bytes": ctx.outputBytes });
|
|
1466
|
+
if (captureContent && typeof ctx.result === "string") {
|
|
1467
|
+
const redacted = redact("mcp-tool-result", ctx.result, {
|
|
1468
|
+
server: ctx.server,
|
|
1469
|
+
tool: ctx.tool,
|
|
1470
|
+
callId: ctx.callId
|
|
1471
|
+
});
|
|
1472
|
+
const attrs = {};
|
|
1473
|
+
if (wantSentry) attrs[GEN_AI.toolCallResult] = redacted;
|
|
1474
|
+
if (conventions === "otel" || conventions === "both") attrs[GEN_AI.toolOutputDeprecated] = redacted;
|
|
1475
|
+
safeSetAttrs(span, attrs);
|
|
1476
|
+
}
|
|
1477
|
+
safeEnd(mcpSpans, ctx.callId);
|
|
97
1478
|
}));
|
|
98
1479
|
unregisters.push(hooks.hook("mcp:tool:error", (ctx) => {
|
|
99
|
-
safeSetAttrs(mcpSpans.get(ctx.callId), {
|
|
100
|
-
|
|
1480
|
+
safeSetAttrs(mcpSpans.get(ctx.callId), {
|
|
1481
|
+
"error.type": ctx.error.name,
|
|
1482
|
+
"error.message": ctx.error.message
|
|
1483
|
+
});
|
|
1484
|
+
safeEnd(mcpSpans, ctx.callId);
|
|
101
1485
|
}));
|
|
102
|
-
unregisters.push(hooks.hook("
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
1486
|
+
unregisters.push(hooks.hook("mcp:bootstrap:start", (ctx) => {
|
|
1487
|
+
const span = safeStartSpan(`mcp.bootstrap ${ctx.name}`, {
|
|
1488
|
+
[GEN_AI.operationName]: "mcp.bootstrap",
|
|
1489
|
+
[GEN_AI.system]: activeSystem,
|
|
1490
|
+
[GEN_AI.mcpServer]: ctx.name,
|
|
1491
|
+
"gen_ai.mcp.transport": ctx.transport
|
|
1492
|
+
});
|
|
1493
|
+
if (span) bootstrapSpans.set(ctx.name, span);
|
|
1494
|
+
}));
|
|
1495
|
+
unregisters.push(hooks.hook("mcp:bootstrap:end", (ctx) => {
|
|
1496
|
+
const span = bootstrapSpans.get(ctx.name);
|
|
1497
|
+
const ok = ctx.ok;
|
|
1498
|
+
safeSetAttrs(span, {
|
|
1499
|
+
"gen_ai.mcp.bootstrap.duration_ms": ctx.durationMs,
|
|
1500
|
+
"gen_ai.mcp.bootstrap.ok": ok,
|
|
1501
|
+
...ok ? {
|
|
1502
|
+
[GEN_AI.mcpToolCount]: ctx.toolCount,
|
|
1503
|
+
"gen_ai.mcp.lazy": ctx.lazy,
|
|
1504
|
+
"gen_ai.mcp.cached": ctx.cached
|
|
1505
|
+
} : {
|
|
1506
|
+
"error.type": ctx.error.name,
|
|
1507
|
+
"error.message": ctx.error.message
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
safeEnd(bootstrapSpans, ctx.name);
|
|
1511
|
+
}));
|
|
1512
|
+
unregisters.push(hooks.hook("mcp:auth:required", (ctx) => {
|
|
1513
|
+
safeAddEvent(bootstrapSpans.get(ctx.name) ?? activeRunSpan, "gen_ai.mcp.auth.required", {
|
|
1514
|
+
server: ctx.name,
|
|
1515
|
+
transport: ctx.transport,
|
|
1516
|
+
reason: ctx.reason
|
|
1517
|
+
});
|
|
1518
|
+
}));
|
|
1519
|
+
unregisters.push(hooks.hook("mcp:error", (ctx) => {
|
|
1520
|
+
safeAddEvent(activeRunSpan, "gen_ai.mcp.error", {
|
|
1521
|
+
"server": ctx.name,
|
|
1522
|
+
"error.type": ctx.error.name,
|
|
1523
|
+
"error.message": ctx.error.message
|
|
1524
|
+
});
|
|
1525
|
+
}));
|
|
1526
|
+
unregisters.push(hooks.hook("spawn:before", (ctx) => {
|
|
1527
|
+
const span = safeStartSpan(`handoff to ${ctx.task.length > 60 ? `${ctx.task.slice(0, 57)}…` : ctx.task}`, {
|
|
1528
|
+
[GEN_AI.operationName]: "handoff",
|
|
1529
|
+
[GEN_AI.system]: activeSystem,
|
|
1530
|
+
"sentry.op": "gen_ai.handoff",
|
|
1531
|
+
"gen_ai.agent.spawn.id": ctx.id,
|
|
1532
|
+
"gen_ai.agent.spawn.task": ctx.task,
|
|
1533
|
+
"gen_ai.agent.depth": ctx.depth
|
|
1534
|
+
});
|
|
1535
|
+
if (span) spawnSpans.set(ctx.id, span);
|
|
1536
|
+
if (typeof options.getActiveTraceContext === "function" && ctx.tracingContext) {
|
|
1537
|
+
let carrier;
|
|
1538
|
+
try {
|
|
1539
|
+
carrier = options.getActiveTraceContext();
|
|
1540
|
+
} catch (err) {
|
|
1541
|
+
try {
|
|
1542
|
+
onError("getActiveTraceContext", err);
|
|
1543
|
+
} catch {}
|
|
1544
|
+
}
|
|
1545
|
+
if (carrier) for (const [k, v] of Object.entries(carrier)) ctx.tracingContext[k] = v;
|
|
1546
|
+
}
|
|
1547
|
+
}));
|
|
1548
|
+
unregisters.push(hooks.hook("spawn:complete", (ctx) => {
|
|
1549
|
+
const span = spawnSpans.get(ctx.id);
|
|
1550
|
+
const childUsage = buildUsageAttrs({
|
|
1551
|
+
input: ctx.stats.totalIn,
|
|
1552
|
+
output: ctx.stats.totalOut,
|
|
1553
|
+
cacheRead: ctx.stats.totalCacheRead,
|
|
1554
|
+
cacheCreation: ctx.stats.totalCacheCreation,
|
|
1555
|
+
cost: ctx.stats.cost
|
|
1556
|
+
}, conventions);
|
|
1557
|
+
safeSetAttrs(span, {
|
|
1558
|
+
"gen_ai.agent.spawn.status": ctx.status ?? "completed",
|
|
1559
|
+
"gen_ai.agent.spawn.depth": ctx.depth,
|
|
1560
|
+
...childUsage
|
|
1561
|
+
});
|
|
1562
|
+
safeEnd(spawnSpans, ctx.id);
|
|
1563
|
+
}));
|
|
1564
|
+
unregisters.push(hooks.hook("spawn:error", (ctx) => {
|
|
1565
|
+
safeSetAttrs(spawnSpans.get(ctx.id), {
|
|
1566
|
+
"error.type": ctx.error.name,
|
|
1567
|
+
"error.message": ctx.error.message
|
|
1568
|
+
});
|
|
1569
|
+
safeEnd(spawnSpans, ctx.id);
|
|
106
1570
|
}));
|
|
107
1571
|
let disposed = false;
|
|
108
1572
|
return function uninstall() {
|
|
@@ -111,12 +1575,23 @@ function createTracingHooks(options) {
|
|
|
111
1575
|
for (const un of unregisters) try {
|
|
112
1576
|
un();
|
|
113
1577
|
} catch {}
|
|
1578
|
+
endAll(runSpans);
|
|
114
1579
|
endAll(turnSpans);
|
|
115
1580
|
endAll(toolSpans);
|
|
116
1581
|
endAll(mcpSpans);
|
|
1582
|
+
endAll(spawnSpans);
|
|
1583
|
+
endAll(bootstrapSpans);
|
|
1584
|
+
activeRunSpan = void 0;
|
|
117
1585
|
};
|
|
118
1586
|
} };
|
|
119
1587
|
}
|
|
1588
|
+
/**
|
|
1589
|
+
* OpenTelemetry Gen AI semantic-convention attribute keys used by the
|
|
1590
|
+
* built-in tracer. Exported so consumers integrating with raw OTel SDKs
|
|
1591
|
+
* (instead of going through `createTracingHooks`) can stay aligned with
|
|
1592
|
+
* the names the harness itself emits.
|
|
1593
|
+
*/
|
|
1594
|
+
const GEN_AI_ATTRIBUTES = GEN_AI;
|
|
120
1595
|
//#endregion
|
|
121
1596
|
//#region src/zod.ts
|
|
122
1597
|
/**
|
|
@@ -141,6 +1616,6 @@ function zodToJsonSchema(jsonSchema) {
|
|
|
141
1616
|
return rest;
|
|
142
1617
|
}
|
|
143
1618
|
//#endregion
|
|
144
|
-
export { ANCHOR_PREVIEW_MAX_CHARS, AgentAbortedError, AgentContextExceededError, AgentProviderError, AgentToolNotAllowedError, AgentToolPairingError, BASE_INSTRUCTIONS, BYTES_PER_TOKEN, CONTEXT_EXCEEDED_MESSAGE_PATTERNS, CompactInvalidInputError, CompactPromptTooLongError, IMPLICITLY_ALLOWED_SKILL_TOOLS, INTERRUPT_MESSAGE_FOR_TOOL_USE, McpOAuthProvider, NO_TOOLS_PREAMBLE, ORPHANED_TOOL_RESULT_MARKER, OpenAICompatHttpError, PERSISTED_STUB_PREFIX, PERSISTENCE_PREVIEW_BYTES, SHELL_CASCADE_CANCEL_MESSAGE, SYNTHETIC_TOOL_RESULT_PLACEHOLDER, TOOL_USE_INTERRUPTED_MARKER, TOOL_USE_SKIPPED_MESSAGE, TRAILER, anchorPreviewFor, anthropic, autoDetectAndConvert, basic_default as basic, basicTools, buildCatalog, buildCompactPrompt, buildFromCompactPrompt, buildFullCompactPrompt, buildPersistedStub, buildPostCompactAttachments, buildTailCompactPrompt, buildUpToCompactPrompt, cerebras, classifyOpenAICompatError, cleanupPersistedSession, compactConversation, connectMcpServers, createAgent, createFileMapStore, createInteractionTool, createMemoryMcpCredentialStore, createMemoryStore, createProcessContext, createRemoteStore, createSandboxContext, createSession, createSkillActivationState, createSkillsReadTool, createSkillsRunScriptTool, createSkillsUseTool, createSpawnTool, createTracingHooks, definePreset, defineSkill, detectTurnInterruption, discoverSkills, edit, ensureToolResultPairing, errorMessage, estimateTokens, filterUnresolvedToolUses, flattenTurns, fromAnthropic, fromOpenAI, getReadState, glob, grep, hasAuthorizationHeader, installAllowedToolsGate, interpolateShellCommands, isToolAllowedByUnion, loadSession, loginMcpServer, mapOAIFinishReason, matchesAllowedTool, matchesContextExceeded, maybePersistToolResult, multiEdit, normalizeMcpBlocks, normalizeMcpServers, openai, openaiCompat, openrouter, parseAllowedToolPattern, parseSkillFile, readStateKey, resolvePersistDir, resolveReadStateMap, resolveSkills, resultToString, sanitizeToolSchema, sanitizeToolSpecs, selectFilesFromReadState, selectFilesFromSession, selectRecentFiles, sliceForCompaction, startOAuthCallback, statsByModel, stripImagesFromTurns, summaryToTurn, toAnthropic, toOpenAI, toTypedError, toolOutputByteLength, toolResultToText, truncateHeadForPtlRetry, utf8ByteLength, validateResourcePath, validateSkillForWrite, validateSkillName, validateToolArgs, writeSkillToDisk, writeSkillsToDisk, zodToJsonSchema };
|
|
1619
|
+
export { ANCHOR_PREVIEW_MAX_CHARS, AgentAbortedError, AgentContextExceededError, AgentProviderError, AgentToolNotAllowedError, AgentToolPairingError, BASE_INSTRUCTIONS, BYTES_PER_TOKEN, CONTEXT_EXCEEDED_MESSAGE_PATTERNS, CompactInvalidInputError, CompactPromptTooLongError, GEN_AI_ATTRIBUTES, IMPLICITLY_ALLOWED_SKILL_TOOLS, INTERRUPT_MESSAGE_FOR_TOOL_USE, McpOAuthProvider, NO_TOOLS_PREAMBLE, ORPHANED_TOOL_RESULT_MARKER, OpenAICompatHttpError, PERSISTED_STUB_PREFIX, PERSISTENCE_PREVIEW_BYTES, SHELL_CASCADE_CANCEL_MESSAGE, SYNTHETIC_TOOL_RESULT_PLACEHOLDER, TOOL_USE_INTERRUPTED_MARKER, TOOL_USE_SKIPPED_MESSAGE, TRAILER, anchorPreviewFor, anthropic, autoDetectAndConvert, basic_default as basic, basicTools, buildCatalog, buildCompactPrompt, buildFromCompactPrompt, buildFullCompactPrompt, buildPersistedStub, buildPostCompactAttachments, buildTailCompactPrompt, buildUpToCompactPrompt, cerebras, classifyOpenAICompatError, cleanupPersistedSession, compactConversation, connectMcpServers, consoleSink, createAgent, createFileMapStore, createInteractionTool, createLogger, createLoggingHooks, createMemoryMcpCredentialStore, createMemoryStore, createMetricsHooks, createProcessContext, createRemoteStore, createRunSummaryCollector, createSandboxContext, createSession, createSkillActivationState, createSkillsReadTool, createSkillsRunScriptTool, createSkillsUseTool, createSpawnTool, createTracingHooks, definePreset, defineSkill, detectTurnInterruption, discoverSkills, edit, ensureToolResultPairing, errorMessage, estimateTokens, filterUnresolvedToolUses, flattenTurns, fromAnthropic, fromOpenAI, getReadState, glob, grep, hasAuthorizationHeader, installAllowedToolsGate, interpolateShellCommands, isToolAllowedByUnion, jsonSink, loadSession, loginMcpServer, mapOAIFinishReason, matchesAllowedTool, matchesContextExceeded, maybePersistToolResult, multiEdit, normalizeMcpBlocks, normalizeMcpServers, openai, openaiCompat, openrouter, parseAllowedToolPattern, parseSkillFile, readStateKey, resolvePersistDir, resolveReadStateMap, resolveSkills, resultToString, sanitizeToolSchema, sanitizeToolSpecs, selectFilesFromReadState, selectFilesFromSession, selectRecentFiles, sliceForCompaction, startOAuthCallback, statsByModel, stripImagesFromTurns, summaryToTurn, toAnthropic, toOpenAI, toTypedError, toolOutputByteLength, toolResultToText, truncateHeadForPtlRetry, utf8ByteLength, validateResourcePath, validateSkillForWrite, validateSkillName, validateToolArgs, writeSkillToDisk, writeSkillsToDisk, zodToJsonSchema };
|
|
145
1620
|
|
|
146
1621
|
//# sourceMappingURL=index.js.map
|