zidane 5.4.1 → 5.4.3

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 (68) hide show
  1. package/README.md +15 -0
  2. package/dist/{agent-DHQAsdj6.d.ts → agent-Yu8uhpy-.d.ts} +213 -3
  3. package/dist/agent-Yu8uhpy-.d.ts.map +1 -0
  4. package/dist/chat.d.ts +49 -6
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +2 -2
  7. package/dist/{errors-Byb0F8B9.js → errors-CDwtPIMX.js} +4 -2
  8. package/dist/{errors-Byb0F8B9.js.map → errors-CDwtPIMX.js.map} +1 -1
  9. package/dist/{index-CHSaLab5.d.ts → index-DklfxeYy.d.ts} +8 -2
  10. package/dist/index-DklfxeYy.d.ts.map +1 -0
  11. package/dist/{index-CrqFoaQA.d.ts → index-j9tY28ah.d.ts} +474 -8
  12. package/dist/index-j9tY28ah.d.ts.map +1 -0
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +1528 -53
  15. package/dist/index.js.map +1 -1
  16. package/dist/{interpolate-ERgZUxgg.js → interpolate-CmtjEyRJ.js} +155 -18
  17. package/dist/interpolate-CmtjEyRJ.js.map +1 -0
  18. package/dist/{login-8c5C0FYq.js → login-DxyAERe1.js} +3 -3
  19. package/dist/{login-8c5C0FYq.js.map → login-DxyAERe1.js.map} +1 -1
  20. package/dist/{mcp-DhmmJfxK.js → mcp-CNUbvbsy.js} +2 -2
  21. package/dist/{mcp-DhmmJfxK.js.map → mcp-CNUbvbsy.js.map} +1 -1
  22. package/dist/mcp.d.ts +1 -1
  23. package/dist/mcp.js +1 -1
  24. package/dist/{messages-D0xT979U.js → messages-fTR19Ga6.js} +2 -2
  25. package/dist/{messages-D0xT979U.js.map → messages-fTR19Ga6.js.map} +1 -1
  26. package/dist/{presets-Ck4VusTo.js → presets-D9IbaI40.js} +2 -2
  27. package/dist/{presets-Ck4VusTo.js.map → presets-D9IbaI40.js.map} +1 -1
  28. package/dist/presets.d.ts +2 -2
  29. package/dist/presets.js +1 -1
  30. package/dist/{providers-x3LZByR5.js → providers-CEzRFYtS.js} +3 -3
  31. package/dist/{providers-x3LZByR5.js.map → providers-CEzRFYtS.js.map} +1 -1
  32. package/dist/providers.d.ts +1 -1
  33. package/dist/providers.js +2 -2
  34. package/dist/session/sqlite.d.ts +1 -1
  35. package/dist/session/sqlite.js +1 -1
  36. package/dist/{session-BHZwxmfr.js → session-kwsNnOmt.js} +2 -2
  37. package/dist/{session-BHZwxmfr.js.map → session-kwsNnOmt.js.map} +1 -1
  38. package/dist/session.d.ts +1 -1
  39. package/dist/session.js +2 -2
  40. package/dist/skills.d.ts +2 -2
  41. package/dist/skills.js +1 -1
  42. package/dist/{tools-PQH1Ge4M.js → tools-BK2vG9UX.js} +246 -44
  43. package/dist/tools-BK2vG9UX.js.map +1 -0
  44. package/dist/tools.d.ts +2 -2
  45. package/dist/tools.js +1 -1
  46. package/dist/{transcript-anchors-ByB2MSCB.d.ts → transcript-anchors-DnaBcJej.d.ts} +52 -8
  47. package/dist/transcript-anchors-DnaBcJej.d.ts.map +1 -0
  48. package/dist/tui.d.ts +4 -2
  49. package/dist/tui.d.ts.map +1 -1
  50. package/dist/tui.js +651 -42
  51. package/dist/tui.js.map +1 -1
  52. package/dist/{turn-operations-Bqs4YbbH.js → turn-operations-OzKEOXul.js} +240 -52
  53. package/dist/turn-operations-OzKEOXul.js.map +1 -0
  54. package/dist/types-IcokUOyC.js.map +1 -1
  55. package/dist/types.d.ts +2 -2
  56. package/dist/types.js +1 -1
  57. package/docs/ARCHITECTURE.md +16 -3
  58. package/docs/CHAT.md +1 -1
  59. package/docs/SKILL.md +24 -14
  60. package/docs/TUI.md +24 -0
  61. package/package.json +3 -3
  62. package/dist/agent-DHQAsdj6.d.ts.map +0 -1
  63. package/dist/index-CHSaLab5.d.ts.map +0 -1
  64. package/dist/index-CrqFoaQA.d.ts.map +0 -1
  65. package/dist/interpolate-ERgZUxgg.js.map +0 -1
  66. package/dist/tools-PQH1Ge4M.js.map +0 -1
  67. package/dist/transcript-anchors-ByB2MSCB.d.ts.map +0 -1
  68. package/dist/turn-operations-Bqs4YbbH.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/tracing.ts","../src/zod.ts"],"sourcesContent":["/**\n * Tracing helper — wraps agent lifecycle hooks in caller-provided spans.\n *\n * Zidane is hooks-native; rather than introducing a plugin abstraction just for\n * observability, we ship a small helper that turns an arbitrary `startSpan`\n * function into a bundle of hook handlers. Works with Sentry, OpenTelemetry,\n * Datadog — any tracer that can hand back an object with an `end()` method.\n *\n * The helper:\n * - Starts a `turn:${n}` span on `turn:before`, ends it on `turn:after`.\n * - Starts a `tool:${name}` span on `tool:before`, ends it on `tool:after` / `tool:error`.\n * - Starts a `mcp:${server}:${tool}` span on `mcp:tool:before`, ends it on `mcp:tool:after` / `mcp:tool:error`.\n * - On `agent:done` closes any spans still open (defensive — normally all tool/turn\n * spans have matching end hooks, but an abort mid-execution can leave dangling ones).\n *\n * Returns an `install(hooks)` helper that registers every handler at once and\n * yields an `uninstall()` function for cleanup. Safe across multiple runs.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\n\n/** Minimal span shape — any tracer that provides these two methods is compatible. */\nexport interface Span {\n /** Close the span. Called exactly once per span. */\n end: () => void\n /** Optional: attach additional attributes after the span is started (ignored if unsupported). */\n setAttributes?: (attrs: Record<string, unknown>) => void\n}\n\n/** Function that opens a span. Caller-provided so we stay tracer-agnostic. */\nexport type StartSpan = (name: string, attrs?: Record<string, unknown>) => Span\n\nexport interface TracingHooksOptions {\n /** Tracer seam. Receives a span name + attributes; must return a `Span`. */\n startSpan: StartSpan\n /**\n * Optional attribute namespace. Prepended to every span name (e.g. `\"agent\"` →\n * span names like `agent/turn:0`, `agent/tool:Bash`).\n */\n namespace?: string\n}\n\n/** Value returned by {@link createTracingHooks} — install entrypoint. */\nexport interface TracingHookSet {\n /**\n * Attach every hook handler to the given agent hooks instance.\n *\n * @returns an `uninstall` function that detaches every handler and closes any\n * still-open spans. Safe to call multiple times.\n */\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n/**\n * Build a set of tracing hook handlers that can be installed on an agent.\n *\n * @example Sentry\n * ```ts\n * const tracing = createTracingHooks({\n * startSpan: (name, attrs) => Sentry.startInactiveSpan({ name, attributes: attrs }),\n * })\n * const uninstall = tracing.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createTracingHooks(options: TracingHooksOptions): TracingHookSet {\n const prefix = options.namespace ? `${options.namespace}/` : ''\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n const turnSpans = new Map<string, Span>()\n const toolSpans = new Map<string, Span>()\n const mcpSpans = new Map<string, Span>()\n\n function endSpan(map: Map<string, Span>, key: string): void {\n const span = map.get(key)\n if (span) {\n try {\n span.end()\n }\n catch {\n // Tracer errors should not crash the run.\n }\n map.delete(key)\n }\n }\n\n function endAll(map: Map<string, Span>): void {\n for (const [, span] of map) {\n try {\n span.end()\n }\n catch {\n // ignore\n }\n }\n map.clear()\n }\n\n const unregisters: Array<() => void> = []\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n const span = options.startSpan(`${prefix}turn:${ctx.turn}`, { turnId: ctx.turnId })\n turnSpans.set(ctx.turnId, span)\n }))\n\n function safeSetAttrs(span: Span | undefined, attrs: Record<string, unknown>): void {\n if (!span)\n return\n try {\n span.setAttributes?.(attrs)\n }\n catch {\n // Tracer errors should not crash the run.\n }\n }\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n const span = turnSpans.get(ctx.turnId)\n safeSetAttrs(span, {\n inputTokens: ctx.usage.input,\n outputTokens: ctx.usage.output,\n ...(ctx.usage.finishReason ? { finishReason: ctx.usage.finishReason } : {}),\n ...(ctx.usage.modelId ? { modelId: ctx.usage.modelId } : {}),\n })\n endSpan(turnSpans, ctx.turnId)\n }))\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n const span = options.startSpan(`${prefix}tool:${ctx.displayName}`, {\n toolName: ctx.name,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n })\n toolSpans.set(ctx.callId, span)\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n endSpan(toolSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n safeSetAttrs(toolSpans.get(ctx.callId), { error: ctx.error.message })\n endSpan(toolSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('mcp:tool:before', (ctx) => {\n const span = options.startSpan(`${prefix}mcp:${ctx.server}:${ctx.tool}`, {\n server: ctx.server,\n tool: ctx.tool,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n })\n mcpSpans.set(ctx.callId, span)\n }))\n\n unregisters.push(hooks.hook('mcp:tool:after', (ctx) => {\n endSpan(mcpSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n safeSetAttrs(mcpSpans.get(ctx.callId), { error: ctx.error.message })\n endSpan(mcpSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('agent:done', () => {\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n }))\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch {\n // ignore\n }\n }\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n }\n },\n }\n}\n","/**\n * Zod v4 integration helper.\n *\n * Normalizes the output of z.toJsonSchema() for use as ToolSpec.inputSchema.\n * Zod is an optional peer dependency — consumers call z.toJsonSchema() themselves.\n *\n * Usage:\n * import { z } from 'zod'\n * import { zodToJsonSchema } from 'zidane'\n * const schema = zodToJsonSchema(z.toJsonSchema(z.object({ name: z.string() })))\n */\n\n/**\n * Normalize a JSON Schema (e.g. from zod v4's z.toJsonSchema()) for use\n * as a ToolSpec.inputSchema.\n *\n * Strips the $schema key that some providers reject.\n */\nexport function zodToJsonSchema(jsonSchema: Record<string, unknown>): Record<string, unknown> {\n const { $schema, ...rest } = jsonSchema\n return rest\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["prefixed"],"sources":["../src/logger.ts","../src/metrics.ts","../src/run-summary.ts","../src/tracing.ts","../src/zod.ts"],"sourcesContent":["/**\n * Structured logging primitive with auto-correlation.\n *\n * Every consumer of the harness used to roll its own logger; the TUI has\n * one, downstream apps have theirs, observability backends each want a\n * slightly different shape. This module ships the smallest reasonable\n * shared surface:\n *\n * - {@link Logger} — minimal level-tagged interface (`debug` / `info` /\n * `warn` / `error`) plus a `with(...)` method that returns a child\n * logger carrying baseline attributes (`runId`, `turnId`, `callId`,\n * `childId`, `depth`, anything you want).\n * - {@link createLogger} — builds a Logger from a {@link LogSink}.\n * - {@link consoleSink} / {@link jsonSink} — built-in sinks that cover\n * the common cases (human-readable to a terminal vs. one JSON object\n * per line to a log aggregator).\n * - {@link createLoggingHooks} — installs Logger-attached hook handlers\n * on an agent so every lifecycle event lands on the sink with the\n * right correlation ids already stamped in.\n *\n * Hosts that already own a logger (pino, winston, structlog binding,\n * platform-native) plug it in via a custom {@link LogSink} — the helper\n * itself does no I/O.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error'\n\nexport interface LogRecord {\n level: LogLevel\n /** Unix ms — set by `Logger` at emit time. */\n timestamp: number\n /** Free-form message. Sinks render this as the human-facing line. */\n message: string\n /** Structured fields. Correlation ids land here automatically. */\n attrs: Record<string, unknown>\n}\n\nexport interface LogSink {\n emit: (record: LogRecord) => void\n}\n\nexport interface Logger {\n debug: (message: string, attrs?: Record<string, unknown>) => void\n info: (message: string, attrs?: Record<string, unknown>) => void\n warn: (message: string, attrs?: Record<string, unknown>) => void\n error: (message: string, attrs?: Record<string, unknown>) => void\n /**\n * Returns a child logger that prepends the given attributes onto every\n * subsequent emit. Equivalent to `pino.child` / `winston.child`. The\n * parent and child share the same sink — children are zero-cost.\n */\n with: (extra: Record<string, unknown>) => Logger\n /**\n * Inspectable baseline attributes — handy for tests and for hook\n * handlers that want to clone-with-extra without recursing.\n */\n readonly baseAttributes: Readonly<Record<string, unknown>>\n}\n\n// ---------------------------------------------------------------------------\n// createLogger\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Logger from a sink. Stateless and cheap; create one per agent\n * (or per app) and use `.with()` to attach correlation ids per-call.\n */\nexport function createLogger(\n sink: LogSink,\n baseAttributes: Readonly<Record<string, unknown>> = {},\n): Logger {\n function emit(level: LogLevel, message: string, attrs?: Record<string, unknown>): void {\n try {\n sink.emit({\n level,\n timestamp: Date.now(),\n message,\n attrs: attrs ? { ...baseAttributes, ...attrs } : { ...baseAttributes },\n })\n }\n catch {\n // Sinks should never crash the run.\n }\n }\n\n return {\n debug: (m, a) => emit('debug', m, a),\n info: (m, a) => emit('info', m, a),\n warn: (m, a) => emit('warn', m, a),\n error: (m, a) => emit('error', m, a),\n with: extra => createLogger(sink, { ...baseAttributes, ...extra }),\n baseAttributes,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in sinks\n// ---------------------------------------------------------------------------\n\nexport interface ConsoleSinkOptions {\n /**\n * Minimum level to emit. Defaults to `'info'` — `debug` is dropped so\n * the harness's lifecycle logging is not noisy by default. Set to\n * `'debug'` to see every event.\n */\n minLevel?: LogLevel\n /** Custom output stream. Defaults to `process.stderr` so logs don't pollute stdout. */\n stream?: { write: (chunk: string) => void }\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n}\n\n/**\n * Human-readable terminal sink. Renders each record as\n * `<ISO timestamp> <LEVEL> <message> <attrs as kv pairs>`.\n *\n * Honors `process.stderr` by default so log lines don't interleave with\n * the agent's stdout-bound output (chat responses, JSON results).\n */\nexport function consoleSink(options: ConsoleSinkOptions = {}): LogSink {\n const min = LEVEL_ORDER[options.minLevel ?? 'info']\n const stream = options.stream ?? process.stderr\n return {\n emit(record) {\n if (LEVEL_ORDER[record.level] < min)\n return\n const ts = new Date(record.timestamp).toISOString()\n const kv = Object.entries(record.attrs)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`)\n .join(' ')\n stream.write(`${ts} ${record.level.toUpperCase().padEnd(5)} ${record.message}${kv ? ` ${kv}` : ''}\\n`)\n },\n }\n}\n\n/**\n * One-JSON-object-per-line sink. Suitable for piping into log aggregators\n * (Datadog Agent, Fluent Bit, Loki, Vector) that expect JSONL.\n */\nexport function jsonSink(options: ConsoleSinkOptions = {}): LogSink {\n const min = LEVEL_ORDER[options.minLevel ?? 'info']\n const stream = options.stream ?? process.stderr\n return {\n emit(record) {\n if (LEVEL_ORDER[record.level] < min)\n return\n try {\n stream.write(`${JSON.stringify(record)}\\n`)\n }\n catch {\n // Non-serializable attr (cycle, BigInt, etc.). Skip rather than throw.\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// createLoggingHooks\n// ---------------------------------------------------------------------------\n\nexport interface LoggingHooksOptions {\n logger: Logger\n /**\n * Minimum interesting level for harness-emitted lines. Default `'info'`.\n * Set to `'debug'` to see every tool dispatch / stream event. Set to\n * `'warn'` to mute the chatty ones and only see failures + budgets.\n */\n level?: LogLevel\n /**\n * When true (default), lifecycle events (`agent:start`, `turn:before`,\n * `tool:before`, `mcp:bootstrap:start`) emit at `debug` level so they\n * stay quiet by default. Set false to mute them entirely regardless of\n * the configured minimum level — useful when piping into a tracer\n * that already captures lifecycle.\n */\n includeLifecycle?: boolean\n}\n\nexport interface LoggingHookSet {\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n/**\n * Install a bundle of hook handlers that emit a structured line per\n * relevant lifecycle event, automatically attaching correlation ids\n * (`runId`, `turnId`, `callId`, `childId`, `depth`, `agentName`).\n *\n * @example\n * ```ts\n * const logger = createLogger(consoleSink({ minLevel: 'debug' }), { service: 'tui' })\n * const lh = createLoggingHooks({ logger })\n * const uninstall = lh.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createLoggingHooks(options: LoggingHooksOptions): LoggingHookSet {\n const root = options.logger\n const includeLifecycle = options.includeLifecycle ?? true\n const minLevel = LEVEL_ORDER[options.level ?? 'info']\n\n /**\n * Wrap a Logger so emissions below `minLevel` are dropped before they\n * hit the sink. Lets us gate harness chatter without forcing every\n * `LogSink` to re-implement level filtering. Preserves `.with()`\n * composition (children inherit the filter).\n */\n function gateLevel(logger: Logger): Logger {\n const skip = (level: LogLevel): boolean => LEVEL_ORDER[level] < minLevel\n const wrap = (l: Logger): Logger => ({\n debug: (m, a) => {\n if (!skip('debug'))\n l.debug(m, a)\n },\n info: (m, a) => {\n if (!skip('info'))\n l.info(m, a)\n },\n warn: (m, a) => {\n if (!skip('warn'))\n l.warn(m, a)\n },\n error: (m, a) => {\n if (!skip('error'))\n l.error(m, a)\n },\n with: extra => wrap(l.with(extra)),\n baseAttributes: l.baseAttributes,\n })\n return wrap(logger)\n }\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n const unregisters: Array<() => void> = []\n // Per-install loggers, refreshed on `agent:start` / `turn:before`.\n // Scoped INSIDE install() so concurrent installs across different\n // agents don't share state — each call to `install()` gets its own\n // attribution state.\n const gatedRoot = gateLevel(root)\n let runLogger = gatedRoot\n let turnLogger = gatedRoot\n\n // ---- Agent lifecycle --------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n runLogger = gatedRoot.with({\n runId: ctx.runId,\n ...(ctx.parentRunId ? { parentRunId: ctx.parentRunId } : {}),\n depth: ctx.depth,\n ...(ctx.agentName ? { agentName: ctx.agentName } : {}),\n })\n turnLogger = runLogger\n if (includeLifecycle)\n runLogger.debug('agent run started')\n }))\n\n unregisters.push(hooks.hook('agent:done', (stats) => {\n runLogger.info('agent run completed', {\n turns: stats.turns,\n totalIn: stats.totalIn,\n totalOut: stats.totalOut,\n ...(typeof stats.cost === 'number' ? { cost: stats.cost } : {}),\n elapsedMs: stats.elapsed,\n ...(typeof stats.timeTillFirstTokenMs === 'number' ? { ttftMs: stats.timeTillFirstTokenMs } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n runLogger.warn('agent run aborted')\n }))\n\n // ---- Turn / stream ---------------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n turnLogger = runLogger.with({ turnId: ctx.turnId, turn: ctx.turn })\n if (includeLifecycle)\n turnLogger.debug('turn started')\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n turnLogger.debug('turn ended', {\n inputTokens: ctx.usage.input,\n outputTokens: ctx.usage.output,\n ...(ctx.usage.finishReason ? { finishReason: ctx.usage.finishReason } : {}),\n ...(ctx.usage.modelId ? { modelId: ctx.usage.modelId } : {}),\n ...(typeof ctx.usage.timeToFirstTokenMs === 'number' ? { ttftMs: ctx.usage.timeToFirstTokenMs } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n turnLogger.error('stream error', {\n message: ctx.err instanceof Error ? ctx.err.message : String(ctx.err),\n ...(ctx.statusCode !== undefined ? { statusCode: ctx.statusCode } : {}),\n ...(ctx.requestId !== undefined ? { requestId: ctx.requestId } : {}),\n })\n }))\n\n // ---- Tool calls -------------------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n if (!includeLifecycle)\n return\n turnLogger.debug('tool started', {\n toolName: ctx.name,\n displayName: ctx.displayName,\n callId: ctx.callId,\n })\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n if (!includeLifecycle)\n return\n turnLogger.debug('tool ended', {\n toolName: ctx.name,\n callId: ctx.callId,\n outputBytes: ctx.outputBytes,\n })\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n turnLogger.error('tool error', {\n toolName: ctx.name,\n callId: ctx.callId,\n message: ctx.error.message,\n })\n }))\n\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n // Successful + gate-substitute paths are routine — only log at\n // debug level. The three \"something refused / went wrong\" paths\n // (gate-block, unknown tool, invalid input) land at warn so they\n // surface in default-config dashboards without forcing debug.\n const isAnomaly = ctx.outcome === 'gate-block'\n || ctx.outcome === 'unknown'\n || ctx.outcome === 'invalid-input'\n if (!isAnomaly && !includeLifecycle)\n return\n const lvl: LogLevel = isAnomaly ? 'warn' : 'debug'\n turnLogger[lvl]('tool dispatched', {\n toolName: ctx.name,\n callId: ctx.callId,\n outcome: ctx.outcome,\n ...(ctx.reason ? { reason: ctx.reason } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n turnLogger.warn('tool input rejected', {\n toolName: ctx.name,\n callId: ctx.callId,\n reason: ctx.reason,\n })\n }))\n\n // ---- Budgets ----------------------------------------------------\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n turnLogger.warn('byte budget exceeded', {\n bytes: ctx.bytes,\n budget: ctx.budget,\n })\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n turnLogger.warn('tool budget exceeded', {\n toolName: ctx.tool,\n count: ctx.count,\n max: ctx.max,\n mode: ctx.mode,\n })\n }))\n\n // ---- MCP --------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n if (ctx.ok) {\n if (includeLifecycle) {\n runLogger.debug('mcp bootstrap ok', {\n server: ctx.name,\n transport: ctx.transport,\n durationMs: ctx.durationMs,\n toolCount: ctx.toolCount,\n ...(ctx.lazy ? { lazy: true } : {}),\n ...(ctx.cached ? { cached: true } : {}),\n })\n }\n }\n else {\n runLogger.warn('mcp bootstrap failed', {\n server: ctx.name,\n transport: ctx.transport,\n durationMs: ctx.durationMs,\n message: ctx.error.message,\n })\n }\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n runLogger.error('mcp error', {\n server: ctx.name,\n message: ctx.error.message,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n runLogger.warn('mcp auth required', {\n server: ctx.name,\n transport: ctx.transport,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n turnLogger.error('mcp tool error', {\n server: ctx.server,\n tool: ctx.displayName,\n callId: ctx.callId,\n message: ctx.error.message,\n })\n }))\n\n // ---- Spawn ------------------------------------------------------\n\n unregisters.push(hooks.hook('spawn:before', (ctx) => {\n if (!includeLifecycle)\n return\n runLogger.debug('spawn started', {\n childId: ctx.id,\n depth: ctx.depth,\n })\n }))\n\n unregisters.push(hooks.hook('spawn:complete', (ctx) => {\n runLogger.info('spawn completed', {\n childId: ctx.id,\n ...(ctx.depth ? { depth: ctx.depth } : {}),\n status: ctx.status ?? 'completed',\n turns: ctx.stats.turns,\n totalIn: ctx.stats.totalIn,\n totalOut: ctx.stats.totalOut,\n ...(typeof ctx.stats.cost === 'number' ? { cost: ctx.stats.cost } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('spawn:error', (ctx) => {\n runLogger.error('spawn error', {\n childId: ctx.id,\n ...(ctx.depth ? { depth: ctx.depth } : {}),\n message: ctx.error.message,\n })\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n }\n },\n }\n}\n","/**\n * Metrics helper — wraps agent lifecycle hooks in caller-provided meters.\n *\n * Symmetric with {@link createTracingHooks} from `./tracing`. Where the\n * tracer answers \"what happened in this run?\" by producing spans, the\n * metrics helper answers \"how is the fleet behaving?\" by emitting\n * counters / histograms / up-down counters. Both are tracer-agnostic —\n * the caller supplies a `Meter` (OTel-API-shaped) and the helper plugs\n * the right hook on each metric.\n *\n * Instrument families:\n *\n * ### Histograms (distribution metrics)\n * - `gen_ai.client.operation.duration` (ms) — wall-clock per turn (`turn:before` → `turn:after`).\n * - `gen_ai.client.token.usage` — `input` / `output` token counts per turn (tagged by `gen_ai.token.type`).\n * - `gen_ai.client.time_to_first_token` (ms) — per-turn TTFT from `TurnUsage.timeToFirstTokenMs`.\n * - `gen_ai.tool.duration` (ms) — native tool execution time (`tool:before` → `tool:after`).\n * - `gen_ai.tool.output_bytes` — `tool:after.outputBytes`.\n * - `gen_ai.mcp.tool.duration` (ms) — MCP tool execution time.\n * - `gen_ai.mcp.bootstrap.duration` (ms) — MCP server bootstrap (already measured by harness).\n *\n * ### Counters (monotonic)\n * - `gen_ai.agent.runs` — `agent:start`.\n * - `gen_ai.agent.runs.completed` — `agent:done`.\n * - `gen_ai.agent.aborts` — `agent:abort`.\n * - `gen_ai.tool.calls` — `tool:dispatched` (tagged by `outcome`).\n * - `gen_ai.tool.errors` — `tool:error`.\n * - `gen_ai.mcp.tool.errors` — `mcp:tool:error`.\n * - `gen_ai.stream.errors` — `stream:error` (tagged by `status_code`).\n * - `gen_ai.validation.rejects` — `validation:reject`.\n * - `gen_ai.validation.coercions` — `validation:coerce`.\n * - `gen_ai.gate.blocks` — `tool:dispatched` outcome `gate-block`.\n * - `gen_ai.budget.exceeded` — `budget:exceeded`.\n * - `gen_ai.tool_budget.exceeded` — `tool-budget:exceeded` (tagged by `mode`).\n * - `gen_ai.pairing.repairs` — `pairing:repair` (tagged by `mode`).\n * - `gen_ai.oauth.refreshes` — `oauth:refresh`.\n * - `gen_ai.mcp.errors` — `mcp:error`.\n * - `gen_ai.mcp.auth.required` — `mcp:auth:required`.\n * - `gen_ai.cost_usd` — accumulated cost (added on `turn:after`).\n *\n * ### Up-down counter (gauge-shaped)\n * - `gen_ai.agent.runs.active` — incremented on `agent:start`, decremented on `agent:done`.\n *\n * The `Meter` shape mirrors `@opentelemetry/api`'s `Meter` exactly — passing\n * `metrics.getMeter('zidane')` works directly. Hosts using a different\n * metrics backend (StatsD, Prometheus client, custom in-memory aggregator)\n * implement the same three factory methods and the helper plugs in\n * unchanged.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\n\n// ---------------------------------------------------------------------------\n// OTel-shaped Meter interface\n// ---------------------------------------------------------------------------\n\nexport type MetricAttributes = Record<string, string | number | boolean | undefined>\n\nexport interface Counter {\n add: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface Histogram {\n record: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface UpDownCounter {\n add: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface InstrumentOptions {\n description?: string\n unit?: string\n}\n\n/**\n * Minimal Meter interface — structurally identical to OTel's `Meter`.\n * Hosts passing `metrics.getMeter(name)` (from `@opentelemetry/api`)\n * satisfy this without adaptation.\n */\nexport interface Meter {\n createCounter: (name: string, options?: InstrumentOptions) => Counter\n createHistogram: (name: string, options?: InstrumentOptions) => Histogram\n createUpDownCounter: (name: string, options?: InstrumentOptions) => UpDownCounter\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface MetricsHooksOptions {\n meter: Meter\n /**\n * Optional prefix prepended to every instrument name. Default: no prefix\n * (instrument names follow OTel Gen AI semantic conventions verbatim,\n * which is the most-portable shape). Set to e.g. `'zidane.'` to\n * namespace inside a shared meter registry.\n */\n namespace?: string\n /**\n * Optional baseline attributes applied to every measurement. Typical\n * use: `{ service: 'tui', env: 'prod' }`. Per-event attributes win on\n * key collision.\n */\n baseAttributes?: MetricAttributes\n /**\n * Error sink for meter failures. The helper still swallows the throw\n * so a broken backend can't crash a run; this callback surfaces the\n * failure for ops dashboards.\n */\n onError?: (kind: string, err: unknown) => void\n}\n\nexport interface MetricsHookSet {\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction prefixed(prefix: string | undefined, name: string): string {\n return prefix ? `${prefix}${name}` : name\n}\n\n/**\n * Drop `undefined` entries before handing attributes to a metrics\n * backend. OTel's `@opentelemetry/api` rejects `undefined` attribute\n * values (and Prometheus / StatsD adapters silently mis-label them); the\n * helper's own API surface allows `undefined` for ergonomic call sites\n * (`{...(cond ? { foo } : {})}`-style spreads), so we strip here at the\n * boundary.\n */\nfunction mergeAttrs(\n base: MetricAttributes | undefined,\n extra: MetricAttributes,\n): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {}\n if (base) {\n for (const [k, v] of Object.entries(base)) {\n if (v !== undefined)\n out[k] = v\n }\n }\n for (const [k, v] of Object.entries(extra)) {\n if (v !== undefined)\n out[k] = v\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// createMetricsHooks\n// ---------------------------------------------------------------------------\n\n/**\n * Build a set of metrics hook handlers that can be installed on an agent.\n *\n * @example OpenTelemetry\n * ```ts\n * import { metrics } from '@opentelemetry/api'\n * const meter = metrics.getMeter('zidane')\n * const m = createMetricsHooks({ meter, baseAttributes: { service: 'tui' } })\n * const uninstall = m.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createMetricsHooks(options: MetricsHooksOptions): MetricsHookSet {\n const ns = options.namespace\n const base = options.baseAttributes\n const onError = options.onError ?? (() => {})\n\n const make = {\n counter: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createCounter(prefixed(ns, name), opts), name, onError),\n histogram: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createHistogram(prefixed(ns, name), opts), name, onError),\n upDown: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createUpDownCounter(prefixed(ns, name), opts), name, onError),\n }\n\n // Instruments — created once at install time.\n const turnDuration = make.histogram('gen_ai.client.operation.duration', { unit: 'ms', description: 'Per-turn LLM operation duration.' })\n const tokenUsage = make.histogram('gen_ai.client.token.usage', { unit: 'tokens', description: 'Per-turn token usage (tagged by gen_ai.token.type).' })\n const ttft = make.histogram('gen_ai.client.time_to_first_token', { unit: 'ms', description: 'Per-turn time to first token.' })\n const toolDuration = make.histogram('gen_ai.tool.duration', { unit: 'ms', description: 'Native tool execution wall clock.' })\n const toolOutput = make.histogram('gen_ai.tool.output_bytes', { unit: 'By', description: 'Native tool output bytes.' })\n const mcpToolDuration = make.histogram('gen_ai.mcp.tool.duration', { unit: 'ms', description: 'MCP tool execution wall clock.' })\n const mcpBootstrapDuration = make.histogram('gen_ai.mcp.bootstrap.duration', { unit: 'ms', description: 'MCP server bootstrap duration.' })\n\n const runsStarted = make.counter('gen_ai.agent.runs', { description: 'Runs started.' })\n const runsCompleted = make.counter('gen_ai.agent.runs.completed', { description: 'Runs completed.' })\n const aborts = make.counter('gen_ai.agent.aborts', { description: 'Run abort events.' })\n const toolCalls = make.counter('gen_ai.tool.calls', { description: 'Tool dispatches (tagged by outcome).' })\n const toolErrors = make.counter('gen_ai.tool.errors', { description: 'Native tool errors.' })\n const mcpToolErrors = make.counter('gen_ai.mcp.tool.errors', { description: 'MCP tool errors.' })\n const streamErrors = make.counter('gen_ai.stream.errors', { description: 'Provider stream errors.' })\n const validationRejects = make.counter('gen_ai.validation.rejects', { description: 'Tool input validation rejects.' })\n const validationCoercions = make.counter('gen_ai.validation.coercions', { description: 'Tool input auto-coercions.' })\n const gateBlocks = make.counter('gen_ai.gate.blocks', { description: 'Tool-gate refusals.' })\n const budgetExceeded = make.counter('gen_ai.budget.exceeded', { description: 'Per-turn byte budget exceedances.' })\n const toolBudgetExceeded = make.counter('gen_ai.tool_budget.exceeded', { description: 'Per-tool dispatch budget exceedances.' })\n const pairingRepairs = make.counter('gen_ai.pairing.repairs', { description: 'Tool-pairing repair counts.' })\n const oauthRefreshes = make.counter('gen_ai.oauth.refreshes', { description: 'Provider OAuth credential refreshes.' })\n const mcpErrors = make.counter('gen_ai.mcp.errors', { description: 'MCP server errors.' })\n const mcpAuthRequired = make.counter('gen_ai.mcp.auth.required', { description: 'MCP bootstrap blocked on missing auth.' })\n const costMeter = make.counter('gen_ai.cost_usd', { unit: 'USD', description: 'Cumulative cost across turns.' })\n\n const runsActive = make.upDown('gen_ai.agent.runs.active', { description: 'In-flight agent runs.' })\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n // Per-instrument book-keeping. Each map tracks instrument start times\n // keyed by the identifier that distinguishes concurrent in-flight\n // instances of that kind.\n const turnStart = new Map<string, number>() // turnId -> startedAt\n const toolStart = new Map<string, number>() // callId -> startedAt\n const mcpStart = new Map<string, number>() // callId -> startedAt\n\n const unregisters: Array<() => void> = []\n\n const record = <T extends { record: (v: number, a?: MetricAttributes) => void }>(\n instrument: T | undefined,\n value: number,\n attrs: MetricAttributes,\n kind: string,\n ): void => {\n if (!instrument)\n return\n try {\n instrument.record(value, mergeAttrs(base, attrs))\n }\n catch (err) {\n try {\n onError(kind, err)\n }\n catch { /* ignore */ }\n }\n }\n\n const add = <T extends { add: (v: number, a?: MetricAttributes) => void }>(\n instrument: T | undefined,\n value: number,\n attrs: MetricAttributes,\n kind: string,\n ): void => {\n if (!instrument)\n return\n try {\n instrument.add(value, mergeAttrs(base, attrs))\n }\n catch (err) {\n try {\n onError(kind, err)\n }\n catch { /* ignore */ }\n }\n }\n\n // ---- Agent lifecycle --------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n const attrs: MetricAttributes = {\n 'gen_ai.agent.depth': ctx.depth,\n ...(ctx.agentName ? { 'gen_ai.agent.name': ctx.agentName } : {}),\n }\n add(runsStarted, 1, attrs, 'runs')\n add(runsActive, 1, attrs, 'runs.active')\n }))\n\n unregisters.push(hooks.hook('agent:done', () => {\n add(runsCompleted, 1, {}, 'runs.completed')\n add(runsActive, -1, {}, 'runs.active')\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n add(aborts, 1, {}, 'aborts')\n }))\n\n // ---- Turn / stream metrics -------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n turnStart.set(ctx.turnId, Date.now())\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n const started = turnStart.get(ctx.turnId)\n turnStart.delete(ctx.turnId)\n const tagsBase: MetricAttributes = {\n 'gen_ai.operation.name': 'chat',\n ...(ctx.usage.modelId ? { 'gen_ai.response.model': ctx.usage.modelId } : {}),\n ...(ctx.usage.finishReason ? { 'gen_ai.response.finish_reason': ctx.usage.finishReason } : {}),\n }\n if (typeof started === 'number')\n record(turnDuration, Date.now() - started, tagsBase, 'turn.duration')\n if (typeof ctx.usage.timeToFirstTokenMs === 'number')\n record(ttft, ctx.usage.timeToFirstTokenMs, tagsBase, 'ttft')\n\n record(tokenUsage, ctx.usage.input, { ...tagsBase, 'gen_ai.token.type': 'input' }, 'token.input')\n record(tokenUsage, ctx.usage.output, { ...tagsBase, 'gen_ai.token.type': 'output' }, 'token.output')\n if (typeof ctx.usage.cacheRead === 'number' && ctx.usage.cacheRead > 0)\n record(tokenUsage, ctx.usage.cacheRead, { ...tagsBase, 'gen_ai.token.type': 'cache_read' }, 'token.cache_read')\n if (typeof ctx.usage.cacheCreation === 'number' && ctx.usage.cacheCreation > 0)\n record(tokenUsage, ctx.usage.cacheCreation, { ...tagsBase, 'gen_ai.token.type': 'cache_creation' }, 'token.cache_creation')\n\n if (typeof ctx.usage.cost === 'number' && ctx.usage.cost > 0)\n add(costMeter, ctx.usage.cost, tagsBase, 'cost')\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n add(streamErrors, 1, {\n 'http.response.status_code': ctx.statusCode,\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n }, 'stream.error')\n }))\n\n // ---- Native tool metrics ---------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n toolStart.set(ctx.callId, Date.now())\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n const started = toolStart.get(ctx.callId)\n toolStart.delete(ctx.callId)\n const tags: MetricAttributes = { 'gen_ai.tool.name': ctx.name }\n if (typeof started === 'number')\n record(toolDuration, Date.now() - started, tags, 'tool.duration')\n record(toolOutput, ctx.outputBytes, tags, 'tool.output_bytes')\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n toolStart.delete(ctx.callId)\n add(toolErrors, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'error.type': ctx.error.name,\n }, 'tool.errors')\n }))\n\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n add(toolCalls, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'outcome': ctx.outcome,\n }, 'tool.calls')\n if (ctx.outcome === 'gate-block') {\n add(gateBlocks, 1, {\n 'gen_ai.tool.name': ctx.name,\n ...(ctx.reason ? { reason: ctx.reason } : {}),\n }, 'gate.blocks')\n }\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n add(validationRejects, 1, { 'gen_ai.tool.name': ctx.name }, 'validation.rejects')\n }))\n\n unregisters.push(hooks.hook('validation:coerce', (ctx) => {\n add(validationCoercions, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'coercions': ctx.coercions.length,\n }, 'validation.coercions')\n }))\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n add(budgetExceeded, 1, {\n bytes: ctx.bytes,\n budget: ctx.budget,\n }, 'budget.exceeded')\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n add(toolBudgetExceeded, 1, {\n 'gen_ai.tool.name': ctx.tool,\n 'mode': ctx.mode,\n 'count': ctx.count,\n 'max': ctx.max,\n }, 'tool_budget.exceeded')\n }))\n\n unregisters.push(hooks.hook('pairing:repair', (ctx) => {\n add(pairingRepairs, 1, { mode: ctx.mode }, 'pairing.repairs')\n }))\n\n unregisters.push(hooks.hook('oauth:refresh', (ctx) => {\n add(oauthRefreshes, 1, { provider: ctx.provider, source: ctx.source }, 'oauth.refreshes')\n }))\n\n // ---- MCP metrics -----------------------------------------------\n\n unregisters.push(hooks.hook('mcp:tool:before', (ctx) => {\n mcpStart.set(ctx.callId, Date.now())\n }))\n\n unregisters.push(hooks.hook('mcp:tool:after', (ctx) => {\n const started = mcpStart.get(ctx.callId)\n mcpStart.delete(ctx.callId)\n const tags: MetricAttributes = {\n 'gen_ai.mcp.server': ctx.server,\n 'gen_ai.tool.name': ctx.displayName,\n }\n if (typeof started === 'number')\n record(mcpToolDuration, Date.now() - started, tags, 'mcp.tool.duration')\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n mcpStart.delete(ctx.callId)\n add(mcpToolErrors, 1, {\n 'gen_ai.mcp.server': ctx.server,\n 'gen_ai.tool.name': ctx.displayName,\n 'error.type': ctx.error.name,\n }, 'mcp.tool.errors')\n }))\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n record(mcpBootstrapDuration, ctx.durationMs, {\n 'gen_ai.mcp.server': ctx.name,\n 'ok': ctx.ok,\n }, 'mcp.bootstrap.duration')\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n add(mcpErrors, 1, {\n 'gen_ai.mcp.server': ctx.name,\n 'error.type': ctx.error.name,\n }, 'mcp.errors')\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n add(mcpAuthRequired, 1, {\n 'gen_ai.mcp.server': ctx.name,\n 'transport': ctx.transport,\n 'reason': ctx.reason,\n }, 'mcp.auth.required')\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n turnStart.clear()\n toolStart.clear()\n mcpStart.clear()\n }\n },\n }\n}\n\n/**\n * Defensive factory wrapper — returns undefined when meter creation\n * throws. Lets a half-broken meter (one unsupported instrument) still\n * give partial metrics rather than collapsing the whole install.\n */\nfunction safeFactory<T>(\n factory: () => T,\n name: string,\n onError: (kind: string, err: unknown) => void,\n): T | undefined {\n try {\n return factory()\n }\n catch (err) {\n try {\n onError(`createInstrument:${name}`, err)\n }\n catch { /* ignore */ }\n return undefined\n }\n}\n","/**\n * Run summary collector — one JSON postmortem per `agent.run()`.\n *\n * `AgentStats` is great for billing and per-turn accounting but doesn't\n * carry a record of the *interesting* events that happened during a run\n * (errors, gate blocks, validation rejects, budget hits, MCP failures).\n * Reconstructing that from a hook stream after the fact is exactly the\n * kind of plumbing every consumer ends up re-implementing.\n *\n * This module ships:\n *\n * - {@link RunSummary} — a serializable shape that bundles totals,\n * per-model breakdown, and all the per-run incident lists.\n * - {@link createRunSummaryCollector} — installs hook listeners that\n * build up a {@link RunSummary} during the run and surfaces it on\n * `agent:done`. Calls an optional `onSummary` callback so the host can\n * forward to a log aggregator / DB / postmortem dashboard without\n * touching `agent.run()`'s return value.\n *\n * The collector is purely additive — it doesn't touch the agent loop,\n * doesn't change return shapes, and doesn't suppress any other hook\n * handlers. Drop it in alongside tracing / metrics / logging or use it\n * standalone when you only need the summary.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\nimport type { AgentStats } from './types'\nimport { statsByModel } from './stats'\n\n// ---------------------------------------------------------------------------\n// Public shape\n// ---------------------------------------------------------------------------\n\nexport interface RunSummaryTokens {\n input: number\n output: number\n cacheRead: number\n cacheCreation: number\n cost?: number\n /** First observable byte from the provider, ms from run start. */\n ttftMs?: number\n}\n\nexport interface RunSummaryByModel {\n modelId: string\n input: number\n output: number\n cacheRead: number\n cacheCreation: number\n cost: number\n turns: number\n}\n\nexport interface RunSummaryError {\n kind: 'stream' | 'tool' | 'mcp-tool' | 'mcp' | 'spawn'\n message: string\n errorType?: string\n turnId?: string\n callId?: string\n server?: string\n toolName?: string\n childId?: string\n statusCode?: number\n requestId?: string\n}\n\nexport interface RunSummaryBlock {\n callId: string\n toolName: string\n outcome: 'gate-block' | 'unknown' | 'invalid-input'\n reason?: string\n}\n\nexport interface RunSummaryValidation {\n callId: string\n toolName: string\n reason: string\n}\n\nexport interface RunSummaryBudget {\n kind: 'bytes' | 'tool-count'\n /** Tool name (for `'tool-count'`); absent for byte budgets. */\n toolName?: string\n /** `mode` for `'tool-count'`; absent for byte budgets. */\n mode?: 'steer' | 'block'\n observed: number\n limit: number\n turnId?: string\n}\n\n/**\n * Postmortem snapshot of one `agent.run()`. Strictly serializable — every\n * field round-trips through `JSON.stringify` / `JSON.parse` without loss\n * so a log aggregator can ingest it as-is.\n */\nexport interface RunSummary {\n runId?: string\n parentRunId?: string\n depth: number\n agentName?: string\n startedAt: number\n endedAt: number\n durationMs: number\n status: 'completed' | 'aborted'\n turns: number\n totals: RunSummaryTokens\n byModel: RunSummaryByModel[]\n errors: RunSummaryError[]\n blocks: RunSummaryBlock[]\n validationRejects: RunSummaryValidation[]\n budgetEvents: RunSummaryBudget[]\n /** Counts of pairing repairs, keyed by repair mode. */\n pairingRepairs: Record<string, number>\n /**\n * Postmortem snapshots of child runs that bubbled their stats up via\n * `spawn:complete`. Only present when the run actually spawned.\n */\n children?: RunSummary[]\n}\n\n// ---------------------------------------------------------------------------\n// Collector\n// ---------------------------------------------------------------------------\n\nexport interface RunSummaryCollectorOptions {\n /**\n * Called with the assembled {@link RunSummary} on every `agent:done`.\n * Synchronous — heavy I/O should be deferred (e.g. via `setImmediate`).\n */\n onSummary?: (summary: RunSummary) => void\n}\n\nexport interface RunSummaryCollector {\n /** Install the collector's hook handlers. Returns an uninstall fn. */\n install: (hooks: Hookable<AgentHooks>) => () => void\n /** Most-recent summary; `undefined` until the first `agent:done` fires. */\n latest: () => RunSummary | undefined\n}\n\n/**\n * Build a run-summary collector. State is created fresh inside each\n * `install()` call, so a single collector instance can be installed\n * across multiple agents without attribution cross-talk. `latest()`\n * returns the most-recent summary across **any** install — install\n * per-agent collectors if you need separate post-run snapshots.\n *\n * @example\n * ```ts\n * const collector = createRunSummaryCollector({\n * onSummary: s => console.log(JSON.stringify(s)),\n * })\n * const uninstall = collector.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createRunSummaryCollector(\n options: RunSummaryCollectorOptions = {},\n): RunSummaryCollector {\n let last: RunSummary | undefined\n\n return {\n latest: () => last,\n install(hooks: Hookable<AgentHooks>): () => void {\n // Per-run in-progress accumulators. The hookable bus serializes\n // agent.run lifecycles per agent — we reset between runs on\n // `agent:start` and consume on `agent:done`.\n let runId: string | undefined\n let parentRunId: string | undefined\n let depth = 0\n let agentName: string | undefined\n let startedAt = Date.now()\n let aborted = false\n const errors: RunSummaryError[] = []\n const blocks: RunSummaryBlock[] = []\n const validationRejects: RunSummaryValidation[] = []\n const budgetEvents: RunSummaryBudget[] = []\n const pairingRepairs: Record<string, number> = {}\n const children: RunSummary[] = []\n\n function resetForNewRun(): void {\n aborted = false\n errors.length = 0\n blocks.length = 0\n validationRejects.length = 0\n budgetEvents.length = 0\n for (const k of Object.keys(pairingRepairs))\n delete pairingRepairs[k]\n children.length = 0\n }\n\n const unregisters: Array<() => void> = []\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n resetForNewRun()\n runId = ctx.runId\n parentRunId = ctx.parentRunId\n depth = ctx.depth\n agentName = ctx.agentName\n startedAt = ctx.startedAt\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n aborted = true\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n const msg = ctx.err instanceof Error ? ctx.err.message : String(ctx.err)\n const errorType = ctx.err instanceof Error ? ctx.err.name : 'unknown'\n errors.push({\n kind: 'stream',\n message: msg,\n errorType,\n turnId: ctx.turnId,\n ...(ctx.statusCode !== undefined ? { statusCode: ctx.statusCode } : {}),\n ...(ctx.requestId !== undefined ? { requestId: ctx.requestId } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n errors.push({\n kind: 'tool',\n message: ctx.error.message,\n errorType: ctx.error.name,\n turnId: ctx.turnId,\n callId: ctx.callId,\n toolName: ctx.name,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n errors.push({\n kind: 'mcp-tool',\n message: ctx.error.message,\n errorType: ctx.error.name,\n turnId: ctx.turnId,\n callId: ctx.callId,\n server: ctx.server,\n toolName: ctx.displayName,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n errors.push({\n kind: 'mcp',\n message: ctx.error.message,\n errorType: ctx.error.name,\n server: ctx.name,\n })\n }))\n\n unregisters.push(hooks.hook('spawn:error', (ctx) => {\n errors.push({\n kind: 'spawn',\n message: ctx.error.message,\n errorType: ctx.error.name,\n childId: ctx.id,\n })\n }))\n\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n if (ctx.outcome === 'gate-block' || ctx.outcome === 'unknown' || ctx.outcome === 'invalid-input') {\n blocks.push({\n callId: ctx.callId,\n toolName: ctx.name,\n outcome: ctx.outcome,\n ...(ctx.reason ? { reason: ctx.reason } : {}),\n })\n }\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n validationRejects.push({\n callId: ctx.callId,\n toolName: ctx.name,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n budgetEvents.push({\n kind: 'bytes',\n observed: ctx.bytes,\n limit: ctx.budget,\n turnId: ctx.turnId,\n })\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n budgetEvents.push({\n kind: 'tool-count',\n toolName: ctx.tool,\n mode: ctx.mode,\n observed: ctx.count,\n limit: ctx.max,\n turnId: ctx.turnId,\n })\n }))\n\n unregisters.push(hooks.hook('pairing:repair', (ctx) => {\n pairingRepairs[ctx.mode] = (pairingRepairs[ctx.mode] ?? 0) + 1\n }))\n\n unregisters.push(hooks.hook('agent:done', (stats) => {\n const endedAt = Date.now()\n\n // Build per-model rollup via the existing `statsByModel` helper\n // so we stay in lockstep with how the rest of the harness\n // attributes cost and tokens by model id.\n const byModel: RunSummaryByModel[] = []\n for (const [modelId, usage] of statsByModel(stats)) {\n byModel.push({\n modelId,\n input: usage.input,\n output: usage.output,\n cacheRead: usage.cacheRead,\n cacheCreation: usage.cacheCreation,\n cost: usage.cost,\n turns: usage.turns,\n })\n }\n\n // Flatten child stats into nested summaries. We don't have the\n // child's full event lists here (those landed on the child's\n // own hooks), so the nested entry is a minimal totals-only\n // record — enough for a flat per-run audit trail; consumers\n // wanting full per-child event lists install a collector on\n // each subagent.\n for (const c of stats.children ?? []) {\n children.push(minimalSummaryFromStats(c.stats, {\n depth: c.depth ?? depth + 1,\n status: c.status === 'aborted' ? 'aborted' : 'completed',\n }))\n }\n\n const summary: RunSummary = {\n ...(runId ? { runId } : {}),\n ...(parentRunId ? { parentRunId } : {}),\n depth,\n ...(agentName ? { agentName } : {}),\n startedAt,\n endedAt,\n durationMs: endedAt - startedAt,\n status: aborted ? 'aborted' : 'completed',\n turns: stats.turns,\n totals: buildTotals(stats),\n byModel,\n errors: errors.slice(),\n blocks: blocks.slice(),\n validationRejects: validationRejects.slice(),\n budgetEvents: budgetEvents.slice(),\n pairingRepairs: { ...pairingRepairs },\n ...(children.length > 0 ? { children: children.slice() } : {}),\n }\n\n last = summary\n try {\n options.onSummary?.(summary)\n }\n catch {\n // Sink errors are not the collector's concern.\n }\n }))\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildTotals(stats: AgentStats): RunSummaryTokens {\n return {\n input: stats.totalIn,\n output: stats.totalOut,\n cacheRead: stats.totalCacheRead,\n cacheCreation: stats.totalCacheCreation,\n ...(typeof stats.cost === 'number' ? { cost: stats.cost } : {}),\n ...(typeof stats.timeTillFirstTokenMs === 'number' ? { ttftMs: stats.timeTillFirstTokenMs } : {}),\n }\n}\n\nfunction minimalSummaryFromStats(\n stats: AgentStats,\n meta: { depth: number, status: 'completed' | 'aborted' },\n): RunSummary {\n const byModel: RunSummaryByModel[] = []\n for (const [modelId, usage] of statsByModel(stats)) {\n byModel.push({\n modelId,\n input: usage.input,\n output: usage.output,\n cacheRead: usage.cacheRead,\n cacheCreation: usage.cacheCreation,\n cost: usage.cost,\n turns: usage.turns,\n })\n }\n const children: RunSummary[] = []\n for (const c of stats.children ?? []) {\n children.push(minimalSummaryFromStats(c.stats, {\n depth: c.depth ?? meta.depth + 1,\n status: c.status === 'aborted' ? 'aborted' : 'completed',\n }))\n }\n return {\n depth: meta.depth,\n startedAt: 0,\n endedAt: 0,\n durationMs: stats.elapsed,\n status: meta.status,\n turns: stats.turns,\n totals: buildTotals(stats),\n byModel,\n errors: [],\n blocks: [],\n validationRejects: [],\n budgetEvents: [],\n pairingRepairs: {},\n ...(children.length > 0 ? { children } : {}),\n }\n}\n","/**\n * Tracing helper — wraps agent lifecycle hooks in caller-provided spans.\n *\n * Zidane is hooks-native; rather than introducing a plugin abstraction just for\n * observability, we ship a small helper that turns an arbitrary `startSpan`\n * function into a bundle of hook handlers. Works with Sentry, OpenTelemetry,\n * Datadog — any tracer that can hand back an object with an `end()` method.\n *\n * Attribute conventions follow the Sentry AI Agents spec (which itself\n * tracks OpenTelemetry Gen AI semconv v1.36, with a few divergences). The\n * `conventions` option lets callers pick `'sentry'` (default — Sentry's\n * exact keys), `'otel'` (vendor-neutral OTel 1.36), or `'both'` (emit\n * every variant for maximum portability).\n *\n * Span coverage:\n *\n * - `invoke_agent <name?>` — root span, opens on `agent:start`, closes on `agent:done`. Sentry op `gen_ai.invoke_agent`.\n * - `chat <model?>` — per-turn LLM call, opens on `turn:before`, closes on `turn:after`. Sentry op `gen_ai.chat`.\n * - `execute_tool <name>` — native tool execution, opens on `tool:before`, closes on `tool:after` / `tool:error`. Sentry op `gen_ai.execute_tool`.\n * - `execute_tool <server>.<name>` — MCP tool variant, same op. Tagged with `gen_ai.tool.type: 'extension'`.\n * - `handoff to <task>` — subagent handoff (parent→child), opens on `spawn:before`, closes on `spawn:complete` / `spawn:error`. Sentry op `gen_ai.handoff`.\n * Also injects parent trace context into the spawn carrier when `propagator` is configured.\n * - `mcp.bootstrap <server>` — server bootstrap, opens on `mcp:bootstrap:start`, closes on `mcp:bootstrap:end`. Zidane-specific (no Sentry op equivalent).\n *\n * Span events (not spans — recorded on the active span via `addEvent`):\n *\n * - `gen_ai.gate.block` — a `tool:gate` listener refused a call.\n * - `gen_ai.validation.reject` — tool input failed schema validation.\n * - `gen_ai.validation.coerce` — tool input was auto-coerced to satisfy the schema.\n * - `gen_ai.budget.exceeded` — byte budget exceeded (`budget:exceeded`).\n * - `gen_ai.tool_budget.exceeded` — per-tool dispatch cap exceeded.\n * - `gen_ai.pairing.repair` — pre-send pairing repair fired.\n * - `gen_ai.stream.error` — provider stream errored (carries `statusCode` / `requestId`).\n * - `gen_ai.mcp.auth.required` — MCP server bootstrap blocked on missing OAuth tokens.\n * - `gen_ai.oauth.refresh` — provider OAuth credentials refreshed.\n *\n * Attribute naming follows the Sentry AI Agents conventions\n * (https://develop.sentry.dev/sdk/telemetry/traces/modules/ai-agents/) by\n * default — which closely track OTel Gen AI semconv v1.36 with a few\n * intentional differences (`usage.input_tokens.cached` instead of\n * `usage.cache_read_input_tokens`, `cost.total_tokens` instead of\n * `usage.cost_usd`, seconds instead of ms for `time_to_first_token`).\n * Set `conventions: 'otel'` to emit only the vendor-neutral OTel keys, or\n * `'both'` to emit everything for maximum portability. Legacy attribute\n * names (`inputTokens`, `toolName`, `displayName`, …) are also emitted by\n * default for back-compat; set `legacyAttributes: false` to drop them.\n *\n * On `agent:done` or `uninstall()` it closes any spans still open (defensive —\n * normally all tool/turn spans have matching end hooks, but an abort\n * mid-execution can leave dangling ones).\n *\n * Returns an `install(hooks)` helper that registers every handler at once and\n * yields an `uninstall()` function for cleanup. Safe across multiple runs.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\nimport type { SessionMessage } from './types'\n\n/** Minimal span shape — any tracer that provides these methods is compatible. */\nexport interface Span {\n /** Close the span. Called exactly once per span. */\n end: () => void\n /** Optional: attach additional attributes after the span is started (ignored if unsupported). */\n setAttributes?: (attrs: Record<string, unknown>) => void\n /**\n * Optional: record a structured event on the span. Used for non-span\n * occurrences (gate blocks, validation rejects, budget hits, pairing\n * repairs). Maps to OTel's `Span.addEvent` and Sentry's `addBreadcrumb`.\n * Tracers without an event surface should treat this as a no-op (the\n * helper falls back to `setAttributes` with an `event.*` key prefix).\n */\n addEvent?: (name: string, attrs?: Record<string, unknown>) => void\n}\n\n/**\n * Function that opens a span. Caller-provided so we stay tracer-agnostic.\n *\n * `parentContext` carries opaque trace metadata propagated from another\n * agent (typically a W3C `{ traceparent, tracestate }` carrier received on\n * `agent:start.tracingContext` after a parent agent's tracer injected it on\n * `spawn:before`). Implementations that integrate with OTel should extract\n * the carrier into a `Context` and use it as the parent when opening the\n * span; implementations that don't care about cross-agent linkage can\n * ignore the third argument entirely.\n */\nexport type StartSpan = (\n name: string,\n attrs?: Record<string, unknown>,\n parentContext?: Readonly<Record<string, string>>,\n) => Span\n\nexport type TracingConventions = 'sentry' | 'otel' | 'both'\n\nexport interface TracingHooksOptions {\n /** Tracer seam. Receives a span name + attributes; must return a `Span`. */\n startSpan: StartSpan\n /**\n * Optional attribute namespace. Prepended to every span name with a `/`\n * separator (e.g. `\"agent\"` → `agent/chat <model>`, `agent/execute_tool Bash`).\n *\n * Empty / undefined = no prefix.\n */\n namespace?: string\n /**\n * Which Gen-AI attribute conventions to emit.\n *\n * - `'sentry'` (default) — Sentry AI Agents conventions. Most common\n * choice — Sentry, Datadog AI, Honeycomb derived dashboards and\n * Langfuse all consume these.\n * - `'otel'` — vendor-neutral OpenTelemetry Gen AI semconv v1.36.\n * Diverges from Sentry on cache-token keys, cost keys, and TTFT unit.\n * - `'both'` — emit every variant. Doubles the per-span attribute\n * payload; pick when shipping to multiple backends with different\n * ingestion conventions.\n */\n conventions?: TracingConventions\n /**\n * Capture the actual prompt / completion text on spans\n * (`gen_ai.input.messages`, `gen_ai.output.messages`,\n * `gen_ai.system_instructions`, `gen_ai.tool.definitions`,\n * `gen_ai.tool.call.arguments`, `gen_ai.tool.call.result`).\n *\n * Defaults to `true` — the Sentry AI Agents dashboard requires these\n * fields to render the conversation viewer. Set `false` to drop them\n * for privacy / cost reasons; everything still goes through `redact`\n * when enabled, so most consumers want it on with a redactor wired up\n * rather than off entirely.\n */\n captureMessageContent?: boolean\n /**\n * Emit legacy attribute keys (`inputTokens`, `outputTokens`, `toolName`,\n * `displayName`, `finishReason`, `modelId`) alongside the new\n * `gen_ai.*` semconv keys. Defaults to `true` so existing dashboards\n * keep working. Set `false` once you've migrated all consumers off the\n * legacy names.\n */\n legacyAttributes?: boolean\n /**\n * Error sink for tracer failures. Replaces the historic silent-swallow.\n * Called when `Span.end()`, `setAttributes()`, `addEvent()`, or\n * `startSpan()` throws — so degraded tracers stay visible. The helper\n * still does not let tracer errors propagate into the agent loop.\n *\n * `kind` identifies which step failed (`'startSpan'`, `'end'`,\n * `'setAttributes'`, `'addEvent'`, `'redact'`, `'getActiveTraceContext'`).\n * Default: no-op (silent).\n */\n onError?: (kind: string, err: unknown) => void\n /**\n * Optional callback the helper invokes on `spawn:before` (after opening\n * the spawn span) to read the active trace context. Whatever it returns\n * is written into `ctx.tracingContext` and forwarded to the child's\n * `agent.run({ tracingContext })`. Typical use:\n *\n * ```ts\n * getActiveTraceContext: () => {\n * const carrier: Record<string, string> = {}\n * propagation.inject(context.active(), carrier)\n * return carrier\n * }\n * ```\n *\n * Returning `undefined` or an empty object leaves the carrier empty\n * and the child runs with no parent context. The helper's own\n * `propagator` is preferred over this when both are set (the\n * propagator runs inside the helper; this callback is the escape\n * hatch for hosts wanting full control).\n */\n getActiveTraceContext?: () => Readonly<Record<string, string>> | undefined\n /**\n * Optional in-process span redactor. When set, every potentially\n * sensitive string (tool input, tool result, system prompt) flows\n * through this fn before landing on a span attribute / event. Returning\n * the input unchanged is a no-op.\n *\n * Composes with the harness-wide `tracing:redact` hook: if a host has\n * already registered a hook handler that mutates `ctx.redacted`, the\n * tracer reuses that pipeline; setting `redact` here registers an extra\n * step that runs only for the tracer's attributes. The hook is the\n * canonical path; `redact` is the convenience surface for tracer-only\n * setups.\n */\n redact?: (kind: string, value: string, meta?: Readonly<Record<string, unknown>>) => string\n}\n\n/** Value returned by {@link createTracingHooks} — install entrypoint. */\nexport interface TracingHookSet {\n /**\n * Attach every hook handler to the given agent hooks instance.\n *\n * @returns an `uninstall` function that detaches every handler and closes any\n * still-open spans. Safe to call multiple times.\n */\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n// ---------------------------------------------------------------------------\n// Gen AI semantic-convention attribute keys\n// ---------------------------------------------------------------------------\n\n/**\n * Stable keys we emit unconditionally — present in BOTH Sentry and OTel\n * conventions with identical semantics, so no `conventions` branching is\n * needed at the call site.\n */\nconst GEN_AI = {\n system: 'gen_ai.system',\n operationName: 'gen_ai.operation.name',\n requestModel: 'gen_ai.request.model',\n responseModel: 'gen_ai.response.model',\n responseFinishReasons: 'gen_ai.response.finish_reasons',\n responseId: 'gen_ai.response.id',\n responseStreaming: 'gen_ai.response.streaming',\n responseTokensPerSecond: 'gen_ai.response.tokens_per_second',\n // Sentry uses seconds; OTel uses ms. We emit one or both based on `conventions`.\n responseTimeToFirstTokenSeconds: 'gen_ai.response.time_to_first_token',\n responseTimeToFirstTokenMs: 'gen_ai.client.time_to_first_token',\n // Usage — Sentry expects input_tokens to INCLUDE cached, with subkeys for cache slices.\n usageInputTokens: 'gen_ai.usage.input_tokens',\n usageOutputTokens: 'gen_ai.usage.output_tokens',\n usageTotalTokens: 'gen_ai.usage.total_tokens',\n // Sentry-specific cache keys.\n usageInputTokensCached: 'gen_ai.usage.input_tokens.cached',\n usageInputTokensCacheWrite: 'gen_ai.usage.input_tokens.cache_write',\n usageOutputTokensReasoning: 'gen_ai.usage.output_tokens.reasoning',\n // OTel cache keys (older / vendor-neutral).\n usageCacheReadInputTokens: 'gen_ai.usage.cache_read_input_tokens',\n usageCacheCreationInputTokens: 'gen_ai.usage.cache_creation_input_tokens',\n usageReasoningTokens: 'gen_ai.usage.reasoning_tokens',\n // Cost — Sentry splits, OTel rolls up.\n costTotalTokens: 'gen_ai.cost.total_tokens',\n costInputTokens: 'gen_ai.cost.input_tokens',\n costOutputTokens: 'gen_ai.cost.output_tokens',\n usageCostUsd: 'gen_ai.usage.cost_usd',\n // Tool — Sentry uses `tool.call.arguments` / `tool.call.result`; OTel uses `tool.input` / `tool.output` (deprecated in Sentry).\n toolName: 'gen_ai.tool.name',\n toolDescription: 'gen_ai.tool.description',\n toolType: 'gen_ai.tool.type',\n toolCallId: 'gen_ai.tool.call.id',\n toolCallArguments: 'gen_ai.tool.call.arguments',\n toolCallResult: 'gen_ai.tool.call.result',\n toolMessage: 'gen_ai.tool.message',\n toolInputDeprecated: 'gen_ai.tool.input',\n toolOutputDeprecated: 'gen_ai.tool.output',\n // Request configuration.\n requestMaxTokens: 'gen_ai.request.max_tokens',\n requestTemperature: 'gen_ai.request.temperature',\n requestTopP: 'gen_ai.request.top_p',\n requestTopK: 'gen_ai.request.top_k',\n requestSeed: 'gen_ai.request.seed',\n requestFrequencyPenalty: 'gen_ai.request.frequency_penalty',\n requestPresencePenalty: 'gen_ai.request.presence_penalty',\n // Message content surfaces (Sentry conversation viewer).\n inputMessages: 'gen_ai.input.messages',\n outputMessages: 'gen_ai.output.messages',\n systemInstructions: 'gen_ai.system_instructions',\n toolDefinitions: 'gen_ai.tool.definitions',\n // Agent identity.\n agentName: 'gen_ai.agent.name',\n agentRunId: 'gen_ai.agent.run.id',\n agentParentRunId: 'gen_ai.agent.parent_run.id',\n agentDepth: 'gen_ai.agent.depth',\n pipelineName: 'gen_ai.pipeline.name',\n turn: 'gen_ai.agent.turn',\n // MCP-specific (zidane extension; no Sentry op equivalent).\n mcpServer: 'gen_ai.mcp.server',\n mcpToolCount: 'gen_ai.mcp.tool_count',\n} as const\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Compose namespace prefix once. */\nfunction prefixed(namespace: string | undefined, name: string): string {\n return namespace ? `${namespace}/${name}` : name\n}\n\n/** Drop undefined entries — tracers vary on how they render `undefined`. */\nfunction compact(attrs: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(attrs)) {\n if (v !== undefined)\n out[k] = v\n }\n return out\n}\n\n/**\n * Convert a zidane `SessionMessage` (or `StreamOptions.messages` entry)\n * into Sentry's `{role, parts: [{type, content}]}` shape, JSON-stringify\n * the result. Returns `undefined` on serialization failure so the caller\n * skips the attribute rather than poisoning the span.\n *\n * Per Sentry footnote [7], binary blobs in image content are replaced\n * with `\"[Blob substitute]\"`. Tool calls become `{type: 'tool_call'}`\n * parts; tool results become `{type: 'tool_result'}` parts.\n */\ninterface SentryMessagePart {\n type: 'text' | 'tool_call' | 'tool_result' | 'reasoning'\n content?: string\n name?: string\n arguments?: string\n result?: string\n call_id?: string\n}\ninterface SentryMessage {\n role: 'user' | 'assistant' | 'system' | 'tool'\n parts: SentryMessagePart[]\n}\n\n/**\n * Wide-shape block record. `SessionContentBlock` is a discriminated\n * union — accepting the wide record lets us peek at the discriminator\n * without re-importing the entire union at this layer.\n */\ninterface AnyBlock { type: string, [k: string]: unknown }\n\nfunction blockToParts(block: AnyBlock): SentryMessagePart[] {\n switch (block.type) {\n case 'text':\n return [{ type: 'text', content: typeof block.text === 'string' ? block.text : '' }]\n case 'image':\n return [{ type: 'text', content: '[Blob substitute]' }]\n case 'tool_call':\n return [{\n type: 'tool_call',\n ...(typeof block.name === 'string' ? { name: block.name } : {}),\n ...(typeof block.id === 'string' ? { call_id: block.id } : {}),\n arguments: safeJson(block.input),\n }]\n case 'tool_result': {\n const output = block.output\n const text = typeof output === 'string'\n ? output\n : Array.isArray(output)\n ? (output as Array<{ type: string, text?: string }>)\n .map(b => b.type === 'image' ? '[Blob substitute]' : (b.text ?? ''))\n .join('\\n')\n : ''\n return [{\n type: 'tool_result',\n ...(typeof block.callId === 'string' ? { call_id: block.callId } : {}),\n content: text,\n }]\n }\n case 'thinking':\n return [{ type: 'reasoning', content: typeof block.text === 'string' ? block.text : '' }]\n default:\n return []\n }\n}\n\nfunction messageToSentry(msg: { role: 'user' | 'assistant', content: readonly AnyBlock[] }): SentryMessage {\n const parts: SentryMessagePart[] = []\n for (const block of msg.content)\n parts.push(...blockToParts(block))\n return { role: msg.role, parts }\n}\n\nfunction safeJson(value: unknown): string {\n try {\n return JSON.stringify(value ?? null)\n }\n catch {\n return '\"[unserializable]\"'\n }\n}\n\n/**\n * Stringify an array of `SessionMessage`s into Sentry's `gen_ai.input.messages`\n * format. Each entry's role + content is mapped via `messageToSentry`.\n * Returns `undefined` when the input is empty or unserializable.\n */\nfunction stringifyMessages(messages: readonly SessionMessage[]): string | undefined {\n if (messages.length === 0)\n return undefined\n try {\n const sentry = messages.map(m => messageToSentry({\n role: m.role,\n content: m.content as readonly AnyBlock[],\n }))\n return JSON.stringify(sentry)\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Stringify a `StreamOptions.tools` array into Sentry's\n * `gen_ai.tool.definitions` format: `[{name, description?, parameters?}, …]`.\n *\n * The input is `unknown[]` because `StreamOptions.tools` carries the\n * provider-formatted shape (Anthropic / OpenAI / OpenAI-compat all\n * differ). We probe the three names that overlap (`name`, `description`,\n * `inputSchema` / `parameters`) and degrade gracefully otherwise.\n */\nfunction stringifyToolDefs(tools: readonly unknown[] | undefined): string | undefined {\n if (!tools || tools.length === 0)\n return undefined\n try {\n return JSON.stringify(tools.map((raw) => {\n if (!raw || typeof raw !== 'object')\n return { name: 'anonymous' }\n const t = raw as Record<string, unknown>\n const name = typeof t.name === 'string' ? t.name : 'anonymous'\n const description = typeof t.description === 'string' ? t.description : undefined\n // OpenAI's wire format calls it `parameters`; Anthropic / ToolSpec\n // call it `input_schema` / `inputSchema`. Probe all three.\n const parameters = t.parameters ?? t.input_schema ?? t.inputSchema\n return {\n name,\n ...(description ? { description } : {}),\n ...(parameters !== undefined ? { parameters } : {}),\n }\n }))\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Build the usage attribute set per the configured conventions. Sentry\n * expects `input_tokens` to include cached, with `input_tokens.cached`\n * + `input_tokens.cache_write` subkeys; OTel splits cache into\n * separate top-level keys. `'both'` emits everything.\n */\nfunction buildUsageAttrs(\n usage: {\n input: number\n output: number\n cacheRead?: number\n cacheCreation?: number\n thinking?: number\n cost?: number\n modelId?: string\n finishReason?: string\n timeToFirstTokenMs?: number\n },\n conventions: TracingConventions,\n): Record<string, unknown> {\n const cacheRead = usage.cacheRead ?? 0\n const cacheWrite = usage.cacheCreation ?? 0\n // Per Sentry: `input_tokens` includes cached. zidane reports `input` as\n // the uncached new-input slice, so the headline number is the sum.\n const inputInclCache = usage.input + cacheRead + cacheWrite\n const totalTokens = inputInclCache + usage.output\n const wantSentry = conventions === 'sentry' || conventions === 'both'\n const wantOtel = conventions === 'otel' || conventions === 'both'\n\n const out: Record<string, unknown> = {\n [GEN_AI.responseModel]: usage.modelId,\n [GEN_AI.responseFinishReasons]: usage.finishReason ? [usage.finishReason] : undefined,\n [GEN_AI.usageOutputTokens]: usage.output,\n [GEN_AI.usageTotalTokens]: totalTokens,\n }\n\n if (wantSentry) {\n out[GEN_AI.usageInputTokens] = inputInclCache\n if (cacheRead > 0)\n out[GEN_AI.usageInputTokensCached] = cacheRead\n if (cacheWrite > 0)\n out[GEN_AI.usageInputTokensCacheWrite] = cacheWrite\n if (typeof usage.thinking === 'number' && usage.thinking > 0)\n out[GEN_AI.usageOutputTokensReasoning] = usage.thinking\n if (typeof usage.cost === 'number')\n out[GEN_AI.costTotalTokens] = usage.cost\n if (typeof usage.timeToFirstTokenMs === 'number')\n out[GEN_AI.responseTimeToFirstTokenSeconds] = usage.timeToFirstTokenMs / 1000\n }\n if (wantOtel) {\n // OTel: input_tokens is the uncached slice; cache is separate.\n if (!wantSentry)\n out[GEN_AI.usageInputTokens] = usage.input\n if (cacheRead > 0)\n out[GEN_AI.usageCacheReadInputTokens] = cacheRead\n if (cacheWrite > 0)\n out[GEN_AI.usageCacheCreationInputTokens] = cacheWrite\n if (typeof usage.thinking === 'number' && usage.thinking > 0)\n out[GEN_AI.usageReasoningTokens] = usage.thinking\n if (typeof usage.cost === 'number')\n out[GEN_AI.usageCostUsd] = usage.cost\n if (typeof usage.timeToFirstTokenMs === 'number')\n out[GEN_AI.responseTimeToFirstTokenMs] = usage.timeToFirstTokenMs\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build a set of tracing hook handlers that can be installed on an agent.\n *\n * @example Sentry\n * ```ts\n * const tracing = createTracingHooks({\n * startSpan: (name, attrs) => Sentry.startInactiveSpan({ name, attributes: attrs }),\n * })\n * const uninstall = tracing.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n *\n * @example OpenTelemetry with parent-context propagation\n * ```ts\n * import { trace, context, propagation } from '@opentelemetry/api'\n *\n * const tracer = trace.getTracer('zidane')\n * const tracing = createTracingHooks({\n * startSpan: (name, attrs, parentCtx) => {\n * const ctx = parentCtx ? propagation.extract(context.active(), parentCtx) : context.active()\n * return tracer.startSpan(name, { attributes: attrs }, ctx)\n * },\n * getActiveTraceContext: () => {\n * const carrier: Record<string, string> = {}\n * propagation.inject(context.active(), carrier)\n * return carrier\n * },\n * })\n * ```\n */\nexport function createTracingHooks(options: TracingHooksOptions): TracingHookSet {\n const namespace = options.namespace\n const legacy = options.legacyAttributes ?? true\n const conventions = options.conventions ?? 'sentry'\n const wantSentry = conventions === 'sentry' || conventions === 'both'\n const captureContent = options.captureMessageContent ?? true\n const onError = options.onError ?? (() => {})\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n // Span book-keeping. Each map keys by the identifier that's unique\n // across concurrent in-flight spans of that kind.\n const runSpans = new Map<string, Span>() // keyed by runId\n const turnSpans = new Map<string, Span>() // keyed by turnId\n const toolSpans = new Map<string, Span>() // keyed by callId\n const mcpSpans = new Map<string, Span>() // keyed by callId\n const spawnSpans = new Map<string, Span>() // keyed by spawn id\n const bootstrapSpans = new Map<string, Span>() // keyed by server name\n // Provider system identifier (`anthropic`, `openai`, …) captured at\n // `agent:start`. Stamped onto every chat / tool span belonging to\n // this run so Sentry's per-vendor breakdowns work. Reset on\n // `agent:done`.\n let activeSystem: string | undefined\n // Active turn book-keeping for tokens-per-second derivation.\n const turnTrack = new Map<string, { startedAt: number }>()\n // Active run span ref — used so non-span events (gate blocks,\n // budget hits, validation rejects, policy/pairing repairs) can\n // attach via `addEvent` on the in-flight run span. The run span is\n // the natural parent for cross-cutting policy events: it outlives\n // turn spans (which close before tool dispatch starts in the\n // current loop), and tool spans (which haven't opened on a\n // gate-block path).\n let activeRunSpan: Span | undefined\n\n function safeStartSpan(\n name: string,\n attrs: Record<string, unknown>,\n parentContext?: Readonly<Record<string, string>>,\n ): Span | undefined {\n try {\n return options.startSpan(prefixed(namespace, name), compact(attrs), parentContext)\n }\n catch (err) {\n try {\n onError('startSpan', err)\n }\n catch { /* ignore */ }\n return undefined\n }\n }\n\n function safeSetAttrs(span: Span | undefined, attrs: Record<string, unknown>): void {\n if (!span?.setAttributes)\n return\n try {\n span.setAttributes(compact(attrs))\n }\n catch (err) {\n try {\n onError('setAttributes', err)\n }\n catch { /* ignore */ }\n }\n }\n\n function safeEnd(map: Map<string, Span>, key: string): void {\n const span = map.get(key)\n if (!span)\n return\n try {\n span.end()\n }\n catch (err) {\n try {\n onError('end', err)\n }\n catch { /* ignore */ }\n }\n map.delete(key)\n }\n\n function endAll(map: Map<string, Span>): void {\n for (const [, span] of map) {\n try {\n span.end()\n }\n catch (err) {\n try {\n onError('end', err)\n }\n catch { /* ignore */ }\n }\n }\n map.clear()\n }\n\n function safeAddEvent(span: Span | undefined, name: string, attrs: Record<string, unknown>): void {\n if (!span)\n return\n if (typeof span.addEvent === 'function') {\n try {\n span.addEvent(name, compact(attrs))\n }\n catch (err) {\n try {\n onError('addEvent', err)\n }\n catch { /* ignore */ }\n }\n return\n }\n // Tracer doesn't support events — fall back to a single\n // `event.<name>.*` attribute so the data lands somewhere.\n safeSetAttrs(span, Object.fromEntries(\n Object.entries(compact(attrs)).map(([k, v]) => [`event.${name}.${k}`, v]),\n ))\n }\n\n function redact(kind: string, value: string, meta?: Readonly<Record<string, unknown>>): string {\n if (typeof options.redact !== 'function')\n return value\n try {\n return options.redact(kind, value, meta)\n }\n catch (err) {\n try {\n onError('redact', err)\n }\n catch { /* ignore */ }\n return value\n }\n }\n\n const unregisters: Array<() => void> = []\n\n // -----------------------------------------------------------------\n // agent.run (root)\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n activeSystem = ctx.providerName\n // Empty-string names produce `invoke_agent ` (trailing space).\n // Treat empty as \"no name\" so the span name reads cleanly.\n const displayName = ctx.agentName && ctx.agentName.length > 0\n ? ctx.agentName\n : 'agent'\n const span = safeStartSpan(\n `invoke_agent ${displayName}`,\n {\n [GEN_AI.operationName]: 'invoke_agent',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.agentName]: ctx.agentName,\n [GEN_AI.agentRunId]: ctx.runId,\n [GEN_AI.agentParentRunId]: ctx.parentRunId,\n [GEN_AI.agentDepth]: ctx.depth,\n // Sentry-specific span op hint — backends that key off\n // `op` (Sentry Python/JS) read this directly; OTel\n // backends ignore unknown keys, so it's harmless.\n 'sentry.op': 'gen_ai.invoke_agent',\n ...(legacy\n ? {\n runId: ctx.runId,\n parentRunId: ctx.parentRunId,\n agentName: ctx.agentName,\n depth: ctx.depth,\n }\n : {}),\n },\n ctx.tracingContext,\n )\n if (span) {\n runSpans.set(ctx.runId, span)\n activeRunSpan = span\n }\n }))\n\n unregisters.push(hooks.hook('agent:done', (stats) => {\n // `agent:done` doesn't carry a runId on its payload (it's `AgentStats`).\n // The 99% case is a single in-flight run — close every open run span\n // and stamp the totals on each. Multi-run-concurrent users plug a\n // runId into stats via their own integration; for now we close all.\n const usageAttrs = buildUsageAttrs({\n input: stats.totalIn,\n output: stats.totalOut,\n cacheRead: stats.totalCacheRead,\n cacheCreation: stats.totalCacheCreation,\n cost: stats.cost,\n timeToFirstTokenMs: stats.timeTillFirstTokenMs,\n }, conventions)\n for (const [, span] of runSpans) {\n safeSetAttrs(span, {\n ...usageAttrs,\n [GEN_AI.turn]: stats.turns,\n 'gen_ai.agent.elapsed_ms': stats.elapsed,\n ...(legacy\n ? {\n totalIn: stats.totalIn,\n totalOut: stats.totalOut,\n totalCacheRead: stats.totalCacheRead,\n totalCacheCreation: stats.totalCacheCreation,\n cost: stats.cost,\n turns: stats.turns,\n elapsed: stats.elapsed,\n }\n : {}),\n })\n }\n endAll(runSpans)\n // Defensive — close anything else still hanging open.\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n endAll(spawnSpans)\n endAll(bootstrapSpans)\n activeRunSpan = undefined\n activeSystem = undefined\n turnTrack.clear()\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n for (const [, span] of runSpans)\n safeSetAttrs(span, { 'gen_ai.agent.status': 'aborted' })\n }))\n\n // -----------------------------------------------------------------\n // Turn span — OTel Gen AI `chat` operation\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n const requestedModel = ctx.options.model\n // Built-in Sentry/OTel attrs that don't depend on content capture.\n const baseAttrs: Record<string, unknown> = {\n [GEN_AI.operationName]: 'chat',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.requestModel]: requestedModel,\n [GEN_AI.turn]: ctx.turn,\n [GEN_AI.responseStreaming]: true,\n [GEN_AI.requestMaxTokens]: ctx.options.maxTokens,\n 'sentry.op': 'gen_ai.chat',\n 'gen_ai.agent.turn_id': ctx.turnId,\n ...(legacy ? { turnId: ctx.turnId, turn: ctx.turn } : {}),\n }\n // Optionally surface conversation content — prompts, system\n // instructions, available tools. Each piece flows through the\n // redact seam so hosts can scrub secrets without losing the\n // structural attribute. Skipping content keeps the span cheap\n // and privacy-safe.\n if (captureContent) {\n const systemText = ctx.options.system\n if (systemText) {\n baseAttrs[GEN_AI.systemInstructions] = redact('system', systemText, {\n turnId: ctx.turnId,\n turn: ctx.turn,\n })\n }\n const inputMsgs = stringifyMessages(ctx.options.messages)\n if (inputMsgs) {\n baseAttrs[GEN_AI.inputMessages] = redact('prompt', inputMsgs, {\n turnId: ctx.turnId,\n turn: ctx.turn,\n })\n }\n const toolDefs = stringifyToolDefs(ctx.options.tools)\n if (toolDefs)\n baseAttrs[GEN_AI.toolDefinitions] = toolDefs\n }\n const span = safeStartSpan(\n `chat${requestedModel ? ` ${requestedModel}` : ''}`,\n baseAttrs,\n )\n if (span) {\n turnSpans.set(ctx.turnId, span)\n turnTrack.set(ctx.turnId, { startedAt: Date.now() })\n }\n }))\n\n unregisters.push(hooks.hook('stream:start', (ctx) => {\n safeAddEvent(turnSpans.get(ctx.turnId), 'gen_ai.stream.start', {\n startedAt: ctx.startedAt,\n })\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n const span = turnSpans.get(ctx.turnId)\n const tracked = turnTrack.get(ctx.turnId)\n turnTrack.delete(ctx.turnId)\n // Tokens-per-second derivation. Use wall-clock duration since the\n // turn span opened, fall back to TTFT-adjusted slice when present.\n let tokensPerSecond: number | undefined\n if (tracked && ctx.usage.output > 0) {\n const elapsedMs = Date.now() - tracked.startedAt\n if (elapsedMs > 0)\n tokensPerSecond = (ctx.usage.output / elapsedMs) * 1000\n }\n const usageAttrs = buildUsageAttrs(ctx.usage, conventions)\n // Cumulative-through-this-turn totals. We compute `input_tokens`\n // here to mirror `gen_ai.usage.input_tokens`'s semantic on the\n // same span: it INCLUDES cached when Sentry conventions are in\n // play, matches the uncached slice when OTel-only. Without this\n // alignment the two keys (`usage.*` vs `cumulative.*`) would\n // silently differ in arithmetic.\n const cumInputTokens = wantSentry\n ? ctx.cumulativeUsage.input + ctx.cumulativeUsage.cacheRead + ctx.cumulativeUsage.cacheCreation\n : ctx.cumulativeUsage.input\n const attrs: Record<string, unknown> = {\n ...usageAttrs,\n [GEN_AI.responseTokensPerSecond]: tokensPerSecond,\n 'gen_ai.agent.cumulative.input_tokens': cumInputTokens,\n 'gen_ai.agent.cumulative.output_tokens': ctx.cumulativeUsage.output,\n 'gen_ai.agent.cumulative.total_tokens': cumInputTokens + ctx.cumulativeUsage.output,\n 'gen_ai.agent.cumulative.cache_read_input_tokens': ctx.cumulativeUsage.cacheRead,\n 'gen_ai.agent.cumulative.cache_creation_input_tokens': ctx.cumulativeUsage.cacheCreation,\n 'gen_ai.agent.cumulative.cost_usd': ctx.cumulativeUsage.cost,\n 'gen_ai.agent.cumulative.turns': ctx.cumulativeUsage.turns,\n ...(legacy\n ? {\n inputTokens: ctx.usage.input,\n outputTokens: ctx.usage.output,\n finishReason: ctx.usage.finishReason,\n modelId: ctx.usage.modelId,\n }\n : {}),\n }\n // Output messages — the assistant turn that just landed. Sentry's\n // conversation viewer keys off this; skipped when content capture\n // is off. `system` turns aren't serialized (Sentry's schema only\n // accepts user / assistant / system / tool — and we never emit\n // system role from the loop).\n if (captureContent && ctx.message && ctx.message.role !== 'system') {\n const outputMsgs = stringifyMessages([{\n role: ctx.message.role,\n content: ctx.message.content,\n }])\n if (outputMsgs)\n attrs[GEN_AI.outputMessages] = redact('stream-text', outputMsgs, { turnId: ctx.turnId, turn: ctx.turn })\n }\n safeSetAttrs(span, attrs)\n safeEnd(turnSpans, ctx.turnId)\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n const span = turnSpans.get(ctx.turnId)\n const message = ctx.err instanceof Error ? ctx.err.message : String(ctx.err)\n safeSetAttrs(span, {\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n 'error.message': message,\n 'http.response.status_code': ctx.statusCode,\n 'gen_ai.request.id': ctx.requestId,\n })\n safeAddEvent(span, 'gen_ai.stream.error', {\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n 'error.message': message,\n 'http.response.status_code': ctx.statusCode,\n 'gen_ai.request.id': ctx.requestId,\n })\n }))\n\n // -----------------------------------------------------------------\n // Native tool span — OTel Gen AI `execute_tool` operation\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n const span = safeStartSpan(\n `execute_tool ${ctx.displayName}`,\n {\n [GEN_AI.operationName]: 'execute_tool',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolType]: 'function',\n [GEN_AI.toolCallId]: ctx.callId,\n 'sentry.op': 'gen_ai.execute_tool',\n 'gen_ai.tool.display_name': ctx.displayName,\n 'gen_ai.agent.turn_id': ctx.turnId,\n 'gen_ai.agent.depth': ctx.depth,\n 'gen_ai.agent.run.id': ctx.runId,\n 'gen_ai.agent.parent_run.id': ctx.parentRunId,\n ...(legacy\n ? {\n toolName: ctx.name,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n }\n : {}),\n },\n )\n if (span) {\n toolSpans.set(ctx.callId, span)\n if (captureContent) {\n // Attach (possibly redacted) input as an attribute. Sentry's\n // canonical key is `gen_ai.tool.call.arguments`; OTel's\n // older key is `gen_ai.tool.input` (now deprecated in\n // Sentry). Emit one or both based on `conventions`.\n try {\n const serialized = JSON.stringify(ctx.input)\n const redacted = redact('tool-input', serialized, {\n toolName: ctx.name,\n displayName: ctx.displayName,\n callId: ctx.callId,\n turnId: ctx.turnId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallArguments] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolInputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n catch {\n // Non-serializable input (circular refs) — skip the attr;\n // the tool name and call id are still on the span.\n }\n }\n }\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n const span = toolSpans.get(ctx.callId)\n safeSetAttrs(span, {\n 'gen_ai.tool.output_bytes': ctx.outputBytes,\n 'gen_ai.tool.coercions': ctx.coercions?.length ?? 0,\n })\n if (captureContent && typeof ctx.result === 'string') {\n const redacted = redact('tool-result', ctx.result, {\n toolName: ctx.name,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallResult] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolOutputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n safeEnd(toolSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n safeSetAttrs(toolSpans.get(ctx.callId), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(toolSpans, ctx.callId)\n }))\n\n // Tool gate / dispatch outcomes. Listen on `tool:dispatched` (fires\n // AFTER every `tool:gate` listener has had a chance to mutate\n // `block` / `result`) so the recorded `outcome` reflects the final\n // decision rather than the as-entered state. Attaches to the\n // in-flight run span; the chat span has already closed by tool\n // dispatch time in the current loop ordering, and no tool span has\n // opened for a `gate-block` / `unknown` / `invalid-input` path.\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n if (ctx.outcome === 'gate-block') {\n safeAddEvent(activeRunSpan, 'gen_ai.gate.block', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n reason: ctx.reason,\n })\n }\n else if (ctx.outcome === 'gate-substitute') {\n safeAddEvent(activeRunSpan, 'gen_ai.gate.substitute', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n else if (ctx.outcome === 'unknown') {\n safeAddEvent(activeRunSpan, 'gen_ai.tool.unknown', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n else if (ctx.outcome === 'invalid-input') {\n safeAddEvent(activeRunSpan, 'gen_ai.tool.invalid_input', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.validation.reject', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('validation:coerce', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.validation.coerce', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n coercions: ctx.coercions,\n })\n }))\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.budget.exceeded', {\n turnId: ctx.turnId,\n bytes: ctx.bytes,\n budget: ctx.budget,\n })\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.tool_budget.exceeded', {\n [GEN_AI.toolName]: ctx.tool,\n turnId: ctx.turnId,\n count: ctx.count,\n max: ctx.max,\n mode: ctx.mode,\n })\n }))\n\n unregisters.push(hooks.hook('pairing:repair', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.pairing.repair', {\n mode: ctx.mode,\n callId: ctx.callId,\n turnId: ctx.turnId,\n })\n }))\n\n unregisters.push(hooks.hook('oauth:refresh', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.oauth.refresh', {\n provider: ctx.provider,\n providerId: ctx.providerId,\n source: ctx.source,\n })\n }))\n\n // -----------------------------------------------------------------\n // MCP tool span — OTel Gen AI `execute_tool` operation, MCP variant\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:tool:before', (ctx) => {\n const span = safeStartSpan(\n `execute_tool ${ctx.server}.${ctx.tool}`,\n {\n [GEN_AI.operationName]: 'execute_tool',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.toolName]: ctx.displayName,\n // MCP tools are Sentry's \"extension\" tool type — backed by an\n // out-of-process server rather than a local function.\n [GEN_AI.toolType]: 'extension',\n [GEN_AI.toolCallId]: ctx.callId,\n [GEN_AI.mcpServer]: ctx.server,\n 'sentry.op': 'gen_ai.execute_tool',\n 'gen_ai.mcp.tool': ctx.tool,\n 'gen_ai.agent.turn_id': ctx.turnId,\n 'gen_ai.agent.run.id': ctx.runId,\n ...(legacy\n ? {\n server: ctx.server,\n tool: ctx.tool,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n }\n : {}),\n },\n )\n if (span) {\n mcpSpans.set(ctx.callId, span)\n if (captureContent) {\n try {\n const redacted = redact('mcp-tool-input', JSON.stringify(ctx.input), {\n server: ctx.server,\n tool: ctx.tool,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallArguments] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolInputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n catch { /* ignore */ }\n }\n }\n }))\n\n unregisters.push(hooks.hook('mcp:tool:after', (ctx) => {\n const span = mcpSpans.get(ctx.callId)\n safeSetAttrs(span, { 'gen_ai.tool.output_bytes': ctx.outputBytes })\n if (captureContent && typeof ctx.result === 'string') {\n const redacted = redact('mcp-tool-result', ctx.result, {\n server: ctx.server,\n tool: ctx.tool,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallResult] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolOutputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n safeEnd(mcpSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n safeSetAttrs(mcpSpans.get(ctx.callId), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(mcpSpans, ctx.callId)\n }))\n\n // -----------------------------------------------------------------\n // MCP server bootstrap span\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:bootstrap:start', (ctx) => {\n const span = safeStartSpan(\n `mcp.bootstrap ${ctx.name}`,\n {\n [GEN_AI.operationName]: 'mcp.bootstrap',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.mcpServer]: ctx.name,\n 'gen_ai.mcp.transport': ctx.transport,\n },\n )\n if (span)\n bootstrapSpans.set(ctx.name, span)\n }))\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n const span = bootstrapSpans.get(ctx.name)\n const ok = ctx.ok\n safeSetAttrs(span, {\n 'gen_ai.mcp.bootstrap.duration_ms': ctx.durationMs,\n 'gen_ai.mcp.bootstrap.ok': ok,\n ...(ok\n ? {\n [GEN_AI.mcpToolCount]: ctx.toolCount,\n 'gen_ai.mcp.lazy': ctx.lazy,\n 'gen_ai.mcp.cached': ctx.cached,\n }\n : {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n }),\n })\n safeEnd(bootstrapSpans, ctx.name)\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n const span = bootstrapSpans.get(ctx.name)\n safeAddEvent(span ?? activeRunSpan, 'gen_ai.mcp.auth.required', {\n server: ctx.name,\n transport: ctx.transport,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.mcp.error', {\n 'server': ctx.name,\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n }))\n\n // -----------------------------------------------------------------\n // Spawn span — parent-side. Also injects trace context for the child.\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('spawn:before', (ctx) => {\n // Sentry: \"handoff from {from_agent} to {to_agent}\". We don't\n // always know the child agent's name at spawn time — fall back\n // to the truncated task as the destination identifier.\n const toAgent = ctx.task.length > 60 ? `${ctx.task.slice(0, 57)}…` : ctx.task\n const span = safeStartSpan(\n `handoff to ${toAgent}`,\n {\n [GEN_AI.operationName]: 'handoff',\n [GEN_AI.system]: activeSystem,\n 'sentry.op': 'gen_ai.handoff',\n 'gen_ai.agent.spawn.id': ctx.id,\n 'gen_ai.agent.spawn.task': ctx.task,\n 'gen_ai.agent.depth': ctx.depth,\n },\n )\n if (span)\n spawnSpans.set(ctx.id, span)\n\n // Inject parent trace context for the child agent. The spawn tool\n // passes whatever we write into `ctx.tracingContext` to the child's\n // `agent.run({ tracingContext })` — see `tools/spawn.ts`.\n if (typeof options.getActiveTraceContext === 'function' && ctx.tracingContext) {\n let carrier: Readonly<Record<string, string>> | undefined\n try {\n carrier = options.getActiveTraceContext()\n }\n catch (err) {\n try {\n onError('getActiveTraceContext', err)\n }\n catch { /* ignore */ }\n }\n if (carrier) {\n for (const [k, v] of Object.entries(carrier))\n ctx.tracingContext[k] = v\n }\n }\n }))\n\n unregisters.push(hooks.hook('spawn:complete', (ctx) => {\n const span = spawnSpans.get(ctx.id)\n const childUsage = buildUsageAttrs({\n input: ctx.stats.totalIn,\n output: ctx.stats.totalOut,\n cacheRead: ctx.stats.totalCacheRead,\n cacheCreation: ctx.stats.totalCacheCreation,\n cost: ctx.stats.cost,\n }, conventions)\n safeSetAttrs(span, {\n 'gen_ai.agent.spawn.status': ctx.status ?? 'completed',\n 'gen_ai.agent.spawn.depth': ctx.depth,\n ...childUsage,\n })\n safeEnd(spawnSpans, ctx.id)\n }))\n\n unregisters.push(hooks.hook('spawn:error', (ctx) => {\n safeSetAttrs(spawnSpans.get(ctx.id), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(spawnSpans, ctx.id)\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n endAll(runSpans)\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n endAll(spawnSpans)\n endAll(bootstrapSpans)\n activeRunSpan = undefined\n }\n },\n }\n}\n\n/**\n * OpenTelemetry Gen AI semantic-convention attribute keys used by the\n * built-in tracer. Exported so consumers integrating with raw OTel SDKs\n * (instead of going through `createTracingHooks`) can stay aligned with\n * the names the harness itself emits.\n */\nexport const GEN_AI_ATTRIBUTES = GEN_AI\n","/**\n * Zod v4 integration helper.\n *\n * Normalizes the output of z.toJsonSchema() for use as ToolSpec.inputSchema.\n * Zod is an optional peer dependency — consumers call z.toJsonSchema() themselves.\n *\n * Usage:\n * import { z } from 'zod'\n * import { zodToJsonSchema } from 'zidane'\n * const schema = zodToJsonSchema(z.toJsonSchema(z.object({ name: z.string() })))\n */\n\n/**\n * Normalize a JSON Schema (e.g. from zod v4's z.toJsonSchema()) for use\n * as a ToolSpec.inputSchema.\n *\n * Strips the $schema key that some providers reject.\n */\nexport function zodToJsonSchema(jsonSchema: Record<string, unknown>): Record<string, unknown> {\n const { $schema, ...rest } = jsonSchema\n return rest\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { i as AgentToolNotAllowedError, s as errorMessage } from "./errors-Byb0F8B9.js";
1
+ import { i as AgentToolNotAllowedError, s as errorMessage } from "./errors-CDwtPIMX.js";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { homedir, tmpdir } from "node:os";
@@ -366,12 +366,84 @@ const WHITESPACE_SPLIT_RE = /\s+/;
366
366
  const PARAGRAPH_SPLIT_RE = /\n\n/;
367
367
  const COMMA_OR_SPACE_RE = /[,\s]+/;
368
368
  /**
369
+ * Block-list bullet (`- value`) under an indented frontmatter key. A
370
+ * single dash followed by either a space or end-of-line; bare `-` is a
371
+ * list item with an empty value (preserved by callers as `""` because
372
+ * the spec doesn't disallow it).
373
+ */
374
+ const LIST_ITEM_RE = /^-(?:\s+|$)/;
375
+ /**
376
+ * Flow-style list scalar (`[a, b, c]`). Anchored, non-nested, single
377
+ * pass — we trim the outer brackets and let the caller split on
378
+ * commas. Anything more complex (nested brackets, quoted commas)
379
+ * falls through to the whitespace-split branch, where the bracket
380
+ * characters become visible authoring errors instead of silent garbage.
381
+ *
382
+ * Carefully written without overlapping `\s*` runs so it's not
383
+ * ReDoS-prone: the body class `[^[\]]*` already swallows whitespace,
384
+ * so wrapping it in `\s*…\s*` would let the regex engine
385
+ * non-deterministically split a run of spaces between the body and
386
+ * the boundary quantifiers.
387
+ */
388
+ const FLOW_LIST_RE = /^\[([^[\]]*)\]$/;
389
+ /**
390
+ * Common kebab/camel/snake misspellings of multi-word spec frontmatter
391
+ * keys. The Agent Skills spec mandates kebab-case (`allowed-tools`), but
392
+ * authors raised on JS / Python idioms naturally reach for camelCase
393
+ * (`allowedTools`) or snake_case (`allowed_tools`) — and the consequences
394
+ * of a silent mismatch on `allowed-tools` specifically are nasty: the
395
+ * skill loads fine, looks active in the UI, the model reads the body
396
+ * and tells the user "tools are restricted", but the `tool:gate`
397
+ * middleware finds nothing in the union and runs permissively. So we
398
+ * look for these alts at parse time, migrate the value to the canonical
399
+ * key, and emit a diagnostic so the next person sees the issue clearly.
400
+ *
401
+ * Keep the value space tight — accepting too many shapes erodes the
402
+ * "lenient load, strict authoring" principle. Add entries only for keys
403
+ * that are (a) multi-word (single-word keys have no ambiguity), (b) in
404
+ * active use, and (c) cause silent failures when missing (gate keys,
405
+ * security gates, …).
406
+ */
407
+ const FRONTMATTER_KEY_ALIASES = new Map([["allowed-tools", ["allowedTools", "allowed_tools"]]]);
408
+ /**
409
+ * Mutate `frontmatter` so any well-known alt-spelling of a canonical
410
+ * spec key gets folded into the canonical slot. Idempotent — when the
411
+ * canonical key is already set the function is a no-op even if an
412
+ * alt-spelling is also present (canonical wins; alt is ignored).
413
+ *
414
+ * Pushes a `frontmatter-key-alt-spelling` diagnostic per migration so
415
+ * the SkillConfig's `.diagnostics` carries an actionable hint into any
416
+ * host UI surfacing skill load issues.
417
+ */
418
+ function normalizeKeyAliases(frontmatter, diagnostics) {
419
+ for (const [canonical, aliases] of FRONTMATTER_KEY_ALIASES) {
420
+ if (frontmatter[canonical] !== void 0) continue;
421
+ for (const alias of aliases) {
422
+ if (frontmatter[alias] === void 0) continue;
423
+ frontmatter[canonical] = frontmatter[alias];
424
+ diagnostics.push({
425
+ severity: "warning",
426
+ code: "frontmatter-key-alt-spelling",
427
+ message: `Frontmatter field "${alias}" is not a spec key — the spec uses "${canonical}" (kebab-case). Reading the value from "${alias}" as a courtesy; rename it to "${canonical}" to silence this warning.`,
428
+ field: alias
429
+ });
430
+ break;
431
+ }
432
+ }
433
+ }
434
+ /**
369
435
  * Parse a SKILL.md file into frontmatter + body.
370
436
  *
371
437
  * Uses a simple regex-based YAML extractor that handles:
372
438
  * - Flat key: value pairs
373
439
  * - Quoted values
374
440
  * - One-level nested maps (for `metadata:`)
441
+ * - One-level nested block lists, `key:` followed by indented `- item`
442
+ * lines (for `allowed-tools` and any future array fields). Items are
443
+ * unquoted with the same rules as scalar values.
444
+ * - Flow-style scalars are preserved verbatim — interpretation (e.g.
445
+ * `[a, b]` as a list) is left to per-field consumers via
446
+ * {@link takeStringOrArray}.
375
447
  * - Lenient recovery from unquoted-colon values (e.g.
376
448
  * `description: Use when: the user asks …`) via a quote-wrap retry.
377
449
  */
@@ -388,31 +460,59 @@ function parseFrontmatter(content) {
388
460
  const frontmatter = {};
389
461
  let currentKey = null;
390
462
  let currentMap = null;
463
+ let currentList = null;
464
+ const flushPending = () => {
465
+ if (currentKey === null) return;
466
+ if (currentList !== null) frontmatter[currentKey] = currentList;
467
+ else if (currentMap !== null) frontmatter[currentKey] = currentMap;
468
+ currentKey = null;
469
+ currentMap = null;
470
+ currentList = null;
471
+ };
391
472
  for (const line of yamlBlock.split("\n")) {
392
473
  if (!line.trim() || line.trim().startsWith("#")) continue;
393
- if (currentKey && currentMap && INDENT_RE.test(line)) {
394
- const nestedMatch = line.trim().match(KV_RE);
474
+ if (currentKey !== null && INDENT_RE.test(line)) {
475
+ const stripped = line.trim();
476
+ if (LIST_ITEM_RE.test(stripped)) {
477
+ if (currentMap !== null && Object.keys(currentMap).length > 0) {
478
+ diagnostics.push({
479
+ severity: "warning",
480
+ code: "mixed-list-and-map",
481
+ message: `Frontmatter field "${currentKey}" mixes list items and map entries — keeping the map and dropping the list items.`,
482
+ field: currentKey
483
+ });
484
+ continue;
485
+ }
486
+ currentList = currentList ?? [];
487
+ currentList.push(unquoteYaml(stripped.slice(1).trim()));
488
+ continue;
489
+ }
490
+ const nestedMatch = stripped.match(KV_RE);
395
491
  if (nestedMatch) {
492
+ if (currentList !== null) {
493
+ diagnostics.push({
494
+ severity: "warning",
495
+ code: "mixed-list-and-map",
496
+ message: `Frontmatter field "${currentKey}" mixes list items and map entries — keeping the list and dropping the map entries.`,
497
+ field: currentKey
498
+ });
499
+ continue;
500
+ }
501
+ currentMap = currentMap ?? {};
396
502
  const val = nestedMatch[2].trim();
397
503
  currentMap[nestedMatch[1].trim()] = unquoteYaml(val);
398
504
  }
399
505
  continue;
400
506
  }
401
- if (currentKey && currentMap) {
402
- frontmatter[currentKey] = currentMap;
403
- currentKey = null;
404
- currentMap = null;
405
- }
507
+ flushPending();
406
508
  const kvMatch = matchFirstColon(line);
407
509
  if (!kvMatch) continue;
408
510
  const key = kvMatch.key.trim();
409
511
  const rawValue = kvMatch.value.trim();
410
- if (!rawValue) {
411
- currentKey = key;
412
- currentMap = {};
413
- } else frontmatter[key] = unquoteYaml(rawValue);
512
+ if (!rawValue) currentKey = key;
513
+ else frontmatter[key] = unquoteYaml(rawValue);
414
514
  }
415
- if (currentKey && currentMap) frontmatter[currentKey] = currentMap;
515
+ flushPending();
416
516
  return {
417
517
  frontmatter,
418
518
  body,
@@ -477,10 +577,46 @@ function takeString(frontmatter, key, diagnostics) {
477
577
  diagnostics.push({
478
578
  severity: "warning",
479
579
  code: "invalid-field-type",
480
- message: `Frontmatter field "${key}" expected string, got ${typeof raw}. Coerced.`,
580
+ message: `Frontmatter field "${key}" expected string, got ${Array.isArray(raw) ? "array" : typeof raw}. Skipping.`,
581
+ field: key
582
+ });
583
+ }
584
+ /**
585
+ * Accept either a YAML scalar (whitespace-separated tokens, the
586
+ * spec-canonical form) OR a YAML block / flow list (`- item` or
587
+ * `[a, b]`). Returns the field as a normalized `string[]`, or
588
+ * `undefined` when the field is absent or empty.
589
+ *
590
+ * Used for list-shaped fields like `allowed-tools` whose authoring
591
+ * surface has always been "either a space-separated string OR a
592
+ * list" in skill files in the wild (Claude Code's docs use both
593
+ * interchangeably). Pre-fix, the parser silently turned the list
594
+ * forms into `[object Object]` / `["[a", "b]"]` and the
595
+ * allowed-tools gate would never match anything — opening it wide.
596
+ *
597
+ * Diagnostics are pushed for unexpected shapes (number, plain
598
+ * object, etc.) and the field is dropped to avoid silent corruption.
599
+ */
600
+ function takeStringOrArray(frontmatter, key, diagnostics) {
601
+ const raw = frontmatter[key];
602
+ if (raw === void 0 || raw === null) return void 0;
603
+ if (Array.isArray(raw)) {
604
+ const items = raw.filter((v) => v !== null && v !== void 0).map((v) => typeof v === "string" ? v : String(v)).map((v) => v.trim()).filter((v) => v.length > 0);
605
+ return items.length > 0 ? items : void 0;
606
+ }
607
+ if (typeof raw === "string") {
608
+ const trimmed = raw.trim();
609
+ if (trimmed.length === 0) return void 0;
610
+ const flowMatch = trimmed.match(FLOW_LIST_RE);
611
+ if (flowMatch) return flowMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
612
+ return trimmed.split(WHITESPACE_SPLIT_RE).filter(Boolean);
613
+ }
614
+ diagnostics.push({
615
+ severity: "warning",
616
+ code: "invalid-field-type",
617
+ message: `Frontmatter field "${key}" expected string or list, got ${typeof raw}. Skipping.`,
481
618
  field: key
482
619
  });
483
- return String(raw);
484
620
  }
485
621
  const RESOURCE_DIRS = {
486
622
  scripts: "script",
@@ -529,6 +665,7 @@ async function parseSkillFile(filePath, options = {}) {
529
665
  const absPath = resolve(filePath);
530
666
  if (!existsSync(absPath)) return null;
531
667
  const { frontmatter, body, diagnostics } = parseFrontmatter(readFileSync(absPath, "utf-8"));
668
+ normalizeKeyAliases(frontmatter, diagnostics);
532
669
  let description = takeString(frontmatter, "description", diagnostics);
533
670
  if (!description && body) {
534
671
  const firstParagraph = body.split(PARAGRAPH_SPLIT_RE)[0]?.trim();
@@ -638,8 +775,8 @@ async function parseSkillFile(filePath, options = {}) {
638
775
  });
639
776
  }
640
777
  if (Object.keys(metadata).length > 0) config.metadata = metadata;
641
- const allowedTools = takeString(frontmatter, "allowed-tools", diagnostics);
642
- if (allowedTools) config.allowedTools = allowedTools.split(WHITESPACE_SPLIT_RE).filter(Boolean);
778
+ const allowedTools = takeStringOrArray(frontmatter, "allowed-tools", diagnostics);
779
+ if (allowedTools && allowedTools.length > 0) config.allowedTools = allowedTools;
643
780
  if (diagnostics.length > 0) config.diagnostics = diagnostics;
644
781
  return config;
645
782
  }
@@ -892,4 +1029,4 @@ async function interpolateShellCommands(instructions, execution, handle) {
892
1029
  //#endregion
893
1030
  export { validateResourcePath as _, discoverSkills as a, createSkillActivationState as b, parseFrontmatter as c, escapeXml as d, IMPLICITLY_ALLOWED_SKILL_TOOLS as f, parseAllowedToolPattern as g, matchesAllowedTool as h, writeSkillsToDisk as i, parseSkillFile as l, isToolAllowedByUnion as m, resolveSkills as n, getDefaultScanPaths as o, installAllowedToolsGate as p, writeSkillToDisk as r, inferSource as s, interpolateShellCommands as t, buildCatalog as u, validateSkillForWrite as v, validateSkillName as y };
894
1031
 
895
- //# sourceMappingURL=interpolate-ERgZUxgg.js.map
1032
+ //# sourceMappingURL=interpolate-CmtjEyRJ.js.map