UNPKG

openlit

Version:

OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications, facilitating the integration of observability into your GenAI-driven projects

1,031 lines 48.3 kB
"use strict"; /** * Claude Agent SDK wrapper — OTel GenAI semantic convention compliant. * * Wraps the `query()` async generator to produce `invoke_agent`, `execute_tool`, * and `chat` child spans. Tool spans are created via SDK hooks (PreToolUse / * PostToolUse / PostToolUseFailure). A message-based fallback handles cases * where hooks cannot be injected. * * Mirrors the Python SDK instrumentation in * sdk/python/src/openlit/instrumentation/claude_agent_sdk/. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.patchQuery = patchQuery; const api_1 = require("@opentelemetry/api"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const semantic_convention_1 = __importDefault(require("../../semantic-convention")); const config_1 = __importDefault(require("../../config")); const helpers_1 = __importStar(require("../../helpers")); const constant_1 = require("../../constant"); const metrics_1 = __importDefault(require("../../otel/metrics")); // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const [SERVER_ADDRESS, SERVER_PORT] = (0, helpers_1.getServerAddressForProvider)('anthropic'); const GEN_AI_SYSTEM_ATTR = 'gen_ai.system'; const GEN_AI_SYSTEM_VALUE = 'anthropic'; const ANTHROPIC_FINISH_REASON_MAP = { end_turn: 'stop', max_tokens: 'length', stop_sequence: 'stop', tool_use: 'tool_call', }; const OPERATION_MAP = { query: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT, execute_tool: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_TOOLS, subagent: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT, chat: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT, create_agent: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CREATE_AGENT, }; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function truncateContent(content) { const maxLen = config_1.default.maxContentLength; if (maxLen != null && maxLen > 0 && content.length > maxLen) { return content.slice(0, maxLen); } return content; } function mapFinishReason(rawReason) { if (!rawReason) return 'stop'; return ANTHROPIC_FINISH_REASON_MAP[rawReason] || rawReason; } function resolveAgentName(options) { if (!options) return null; for (const key of ['agent_name', 'agentName', 'name']) { const val = options[key]; if (val && typeof val === 'string' && val.trim()) return val.trim(); } return null; } function generateSpanName(endpoint, entityName) { const operation = OPERATION_MAP[endpoint] || semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT; if (entityName) return `${operation} ${entityName}`; return operation; } function extractUsage(usage) { const attrs = {}; if (!usage) return attrs; const rawInput = parseInt(usage.input_tokens, 10) || 0; const outputTokens = parseInt(usage.output_tokens, 10); if (!isNaN(outputTokens)) { attrs[semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS] = outputTokens; } let cacheReadInt = 0; const cacheRead = usage.cache_read_input_tokens; if (cacheRead != null) { cacheReadInt = parseInt(cacheRead, 10) || 0; if (cacheReadInt) { attrs[semantic_convention_1.default.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] = cacheReadInt; } } let cacheCreationInt = 0; const cacheCreation = usage.cache_creation_input_tokens ?? usage.cache_write_input_tokens; if (cacheCreation != null) { cacheCreationInt = parseInt(cacheCreation, 10) || 0; if (cacheCreationInt) { attrs[semantic_convention_1.default.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS] = cacheCreationInt; } } attrs[semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS] = rawInput + cacheReadInt + cacheCreationInt; return attrs; } // --------------------------------------------------------------------------- // ToolSpanTracker — manages in-flight tool spans created by SDK hooks // --------------------------------------------------------------------------- class ToolSpanTracker { constructor(tracer, parentSpan, captureContent) { this._inFlight = new Map(); this._completed = new Set(); this._tracer = tracer; this._parentSpan = parentSpan; this._captureContent = captureContent; } startTool(toolName, toolInput, toolUseId) { const spanName = generateSpanName('execute_tool', toolName); const parentCtx = api_1.trace.setSpan(api_1.context.active(), this._parentSpan); const span = this._tracer.startSpan(spanName, { kind: api_1.SpanKind.INTERNAL, attributes: { [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_TOOLS, [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_CLAUDE_AGENT_SDK, }, }, parentCtx); setToolSpanAttributes(span, toolName, toolInput, toolUseId, this._captureContent); this._inFlight.set(toolUseId, span); } endTool(toolUseId, toolResponse) { const span = this._inFlight.get(toolUseId); if (!span) return; this._inFlight.delete(toolUseId); finalizeToolSpan(span, toolResponse, this._captureContent); span.end(); this._completed.add(toolUseId); } endToolError(toolUseId, error) { const span = this._inFlight.get(toolUseId); if (!span) return; this._inFlight.delete(toolUseId); finalizeToolSpan(span, null, this._captureContent, true, error); span.end(); this._completed.add(toolUseId); } endAll() { for (const [_toolUseId, span] of this._inFlight) { finalizeToolSpan(span, null, this._captureContent, true, 'abandoned'); span.end(); } this._inFlight.clear(); } } // --------------------------------------------------------------------------- // SubagentSpanTracker — manages subagent spans for Task tool // --------------------------------------------------------------------------- class SubagentSpanTracker { constructor(tracer, toolTracker) { this._inFlight = new Map(); this._toolUseToTask = new Map(); this._tracer = tracer; this._toolTracker = toolTracker; } startSubagent(taskId, description, toolUseId) { const name = description || taskId || 'subagent'; const spanName = generateSpanName('subagent', name); if (toolUseId) { this._toolUseToTask.set(toolUseId, taskId); } let parentSpan; if (toolUseId) { parentSpan = this._toolTracker._inFlight.get(toolUseId); } const ctx = parentSpan ? api_1.trace.setSpan(api_1.context.active(), parentSpan) : undefined; const span = this._tracer.startSpan(spanName, { kind: api_1.SpanKind.INTERNAL, attributes: { [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT, [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_CLAUDE_AGENT_SDK, }, }, ctx); span.setAttribute(GEN_AI_SYSTEM_ATTR, GEN_AI_SYSTEM_VALUE); span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_NAME, String(name)); span.setAttribute(semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT, config_1.default.environment ?? 'default'); span.setAttribute(semantic_conventions_1.ATTR_SERVICE_NAME, config_1.default.applicationName ?? 'default'); span.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, constant_1.SDK_VERSION); this._inFlight.set(taskId, span); } endSubagent(taskId, isError = false, errorMessage, usage) { const span = this._inFlight.get(taskId); if (!span) return; this._inFlight.delete(taskId); if (usage) { if (usage.total_tokens != null) { span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS, Number(usage.total_tokens) || 0); } if (usage.tool_uses != null) { span.setAttribute('gen_ai.agent.tool_uses', Number(usage.tool_uses) || 0); } if (usage.duration_ms != null) { span.setAttribute('gen_ai.agent.duration_ms', Number(usage.duration_ms) || 0); } } if (isError) { const err = errorMessage ? String(errorMessage) : 'task failed'; span.setAttribute(semantic_convention_1.default.ERROR_TYPE, 'SubagentError'); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err }); } else { span.setStatus({ code: api_1.SpanStatusCode.OK }); } span.end(); } getSpanForToolUseId(toolUseId) { const taskId = this._toolUseToTask.get(toolUseId); return taskId ? this._inFlight.get(taskId) : undefined; } endAll() { for (const taskId of this._inFlight.keys()) { this.endSubagent(taskId, true, 'abandoned'); } } } // --------------------------------------------------------------------------- // Tool span attributes // --------------------------------------------------------------------------- function setToolSpanAttributes(span, toolName, toolInput, toolUseId, captureContent) { span.setAttribute(GEN_AI_SYSTEM_ATTR, GEN_AI_SYSTEM_VALUE); span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_NAME, String(toolName)); const toolType = String(toolName).startsWith('mcp__') ? 'extension' : 'function'; span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_TYPE, toolType); if (toolUseId) { span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ID, String(toolUseId)); } span.setAttribute(semantic_convention_1.default.SERVER_ADDRESS, SERVER_ADDRESS); span.setAttribute(semantic_convention_1.default.SERVER_PORT, SERVER_PORT); span.setAttribute(semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT, config_1.default.environment ?? 'default'); span.setAttribute(semantic_conventions_1.ATTR_SERVICE_NAME, config_1.default.applicationName ?? 'default'); span.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, constant_1.SDK_VERSION); if (captureContent && toolInput != null) { try { const argsStr = typeof toolInput === 'string' ? toolInput : JSON.stringify(toolInput); span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_ARGUMENTS, truncateContent(argsStr)); } catch { /* ignore */ } } (0, helpers_1.applyCustomSpanAttributes)(span); } function finalizeToolSpan(span, toolResponse, captureContent, isError = false, errorMessage) { if (isError) { const errMsg = errorMessage ? String(errorMessage) : 'tool execution failed'; span.setAttribute(semantic_convention_1.default.ERROR_TYPE, 'ToolExecutionError'); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: errMsg }); } else { if (captureContent && toolResponse != null) { span.setAttribute(semantic_convention_1.default.GEN_AI_TOOL_CALL_RESULT, truncateContent(String(toolResponse))); } span.setStatus({ code: api_1.SpanStatusCode.OK }); } } // --------------------------------------------------------------------------- // Hook injection — merges OpenLIT hooks into user-provided options // --------------------------------------------------------------------------- function injectHooks(options, toolTracker, subagentTracker) { if (!options.hooks) { options.hooks = {}; } const preToolUse = async (input, toolUseId) => { try { const toolName = input.tool_name || 'unknown'; const toolInput = input.tool_input; const id = toolUseId || input.tool_use_id; if (id) toolTracker.startTool(toolName, toolInput, id); } catch { /* swallow */ } return {}; }; const postToolUse = async (input, toolUseId) => { try { const toolResponse = input.tool_response; const id = toolUseId || input.tool_use_id; if (id) toolTracker.endTool(id, toolResponse); } catch { /* swallow */ } return {}; }; const postToolUseFailure = async (input, toolUseId) => { try { const error = input.error || 'unknown error'; const id = toolUseId || input.tool_use_id; if (id) toolTracker.endToolError(id, error); } catch { /* swallow */ } return {}; }; const subagentStart = async (input, toolUseId) => { try { const agentId = input.agent_id; if (agentId) { const description = input.description || agentId; subagentTracker.startSubagent(agentId, description, toolUseId ?? undefined); } } catch { /* swallow */ } return {}; }; const subagentStop = async (input) => { try { const agentId = input.agent_id; if (!agentId) return {}; const error = input.error; subagentTracker.endSubagent(agentId, !!error, error); } catch { /* swallow */ } return {}; }; const hookPairs = [ ['PreToolUse', preToolUse], ['PostToolUse', postToolUse], ['PostToolUseFailure', postToolUseFailure], ['SubagentStart', subagentStart], ['SubagentStop', subagentStop], ]; for (const [event, callback] of hookPairs) { const matcher = { hooks: [callback] }; if (options.hooks[event]) { options.hooks[event].push(matcher); } else { options.hooks[event] = [matcher]; } } } function hasLlmCallData(msg) { return msg.message?.model != null && msg.message?.usage != null; } function bufferChatMessage(sdkMsg, chatState) { if (!hasLlmCallData(sdkMsg)) return; chatState.pendingChatMsg = sdkMsg; chatState.pendingChatMsgId = sdkMsg.message?.id; chatState.pendingStartMs = chatState.lastBoundaryMs; chatState.pendingEndMs = Date.now(); } function flushPendingChat(tracer, parentSpan, chatState, captureContent, subagentTracker) { const sdkMsg = chatState.pendingChatMsg; if (!sdkMsg) return; delete chatState.pendingChatMsg; delete chatState.pendingChatMsgId; const endMs = chatState.pendingEndMs ?? Date.now(); const savedStartMs = chatState.pendingStartMs; delete chatState.pendingStartMs; delete chatState.pendingEndMs; const betaMessage = sdkMsg.message; const model = String(betaMessage?.model || 'unknown'); const spanName = generateSpanName('chat', model); let effectiveParent = parentSpan; const parentToolUseId = sdkMsg.parent_tool_use_id; if (parentToolUseId) { const subagentSpan = subagentTracker.getSpanForToolUseId(parentToolUseId); if (subagentSpan) effectiveParent = subagentSpan; } const parentCtx = api_1.trace.setSpan(api_1.context.active(), effectiveParent); const startMs = savedStartMs ?? chatState.lastBoundaryMs ?? endMs; const chatSpan = tracer.startSpan(spanName, { kind: api_1.SpanKind.CLIENT, attributes: { [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT, [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_ANTHROPIC, }, startTime: new Date(startMs), }, parentCtx); const inputMessages = chatState.pendingInput; delete chatState.pendingInput; setChatSpanAttributes(chatSpan, sdkMsg, captureContent, inputMessages); chatSpan.end(new Date(endMs)); chatState.lastBoundaryMs = endMs; } // --------------------------------------------------------------------------- // Chat span attributes // --------------------------------------------------------------------------- function setChatSpanAttributes(span, sdkMsg, captureContent, inputMessages) { try { const betaMessage = sdkMsg.message; const model = betaMessage?.model ? String(betaMessage.model) : null; span.setAttribute(GEN_AI_SYSTEM_ATTR, GEN_AI_SYSTEM_VALUE); span.setAttribute(semantic_convention_1.default.SERVER_ADDRESS, SERVER_ADDRESS); span.setAttribute(semantic_convention_1.default.SERVER_PORT, SERVER_PORT); span.setAttribute(semantic_conventions_1.ATTR_TELEMETRY_SDK_NAME, constant_1.SDK_NAME); span.setAttribute(semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT, config_1.default.environment ?? 'default'); span.setAttribute(semantic_conventions_1.ATTR_SERVICE_NAME, config_1.default.applicationName ?? 'default'); span.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, constant_1.SDK_VERSION); if (model) { span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MODEL, model); span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, model); } const usage = betaMessage?.usage; const usageAttrs = usage ? extractUsage(usage) : {}; for (const [key, value] of Object.entries(usageAttrs)) { span.setAttribute(key, value); } let stopReason = betaMessage?.stop_reason; if (!stopReason) { const content = betaMessage?.content; if (Array.isArray(content)) { for (const block of content) { if (block.type === 'tool_use') { stopReason = 'tool_use'; break; } } } } const mappedReason = mapFinishReason(stopReason); span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON, [mappedReason]); const messageId = betaMessage?.id; if (messageId) { span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_ID, String(messageId)); } const sessionId = sdkMsg.session_id; if (sessionId) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, String(sessionId)); } const inputTokens = usageAttrs[semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS] ?? 0; const outputTokens = usageAttrs[semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS] ?? 0; const pricingInfo = config_1.default.pricingInfo || {}; const cost = model ? helpers_1.default.getChatModelCost(model, pricingInfo, inputTokens, outputTokens) : 0; span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_COST, cost); let outputMessages = null; if (captureContent) { if (inputMessages) { span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, JSON.stringify(inputMessages)); } outputMessages = buildOutputMessages(betaMessage, mappedReason); if (outputMessages) { span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, JSON.stringify(outputMessages)); } } (0, helpers_1.applyCustomSpanAttributes)(span); span.setStatus({ code: api_1.SpanStatusCode.OK }); if (captureContent) { emitChatInferenceEvent(span, model, messageId, sessionId, mappedReason, usageAttrs, inputMessages, outputMessages); } if (!config_1.default.disableMetrics) { recordChatMetrics(model, inputTokens, outputTokens, cost); } } catch { /* swallow */ } } // --------------------------------------------------------------------------- // Build OTel-compliant output messages from BetaMessage content blocks // --------------------------------------------------------------------------- function buildOutputMessages(betaMessage, mappedFinishReason) { try { const content = betaMessage?.content; if (!content || !Array.isArray(content)) return null; const parts = []; for (const block of content) { if (block.type === 'text') { if (block.text) { parts.push({ type: 'text', content: truncateContent(String(block.text)) }); } } else if (block.type === 'thinking') { if (block.thinking) { parts.push({ type: 'reasoning', content: truncateContent(String(block.thinking)) }); } } else if (block.type === 'tool_use') { let toolInput = block.input || {}; if (typeof toolInput !== 'object') { try { toolInput = JSON.parse(String(toolInput)); } catch { toolInput = {}; } } parts.push({ type: 'tool_call', id: String(block.id || ''), name: String(block.name || 'unknown'), arguments: toolInput, }); } } if (parts.length === 0) return null; return [{ role: 'assistant', parts, finish_reason: mappedFinishReason }]; } catch { return null; } } // --------------------------------------------------------------------------- // Build OTel input from UserMessage tool results // --------------------------------------------------------------------------- function buildInputFromToolResults(sdkMsg) { try { const messageParam = sdkMsg.message; const content = messageParam?.content; if (!content || !Array.isArray(content)) return null; const parts = []; for (const block of content) { if (block.type === 'tool_result') { const toolUseId = block.tool_use_id; let resultContent = block.content; if (Array.isArray(resultContent)) { resultContent = resultContent.map((c) => c.text || JSON.stringify(c)).join(''); } parts.push({ type: 'tool_call_response', id: toolUseId ? String(toolUseId) : '', response: resultContent ? truncateContent(String(resultContent)) : '', }); } } if (parts.length === 0) return null; return [{ role: 'user', parts }]; } catch { return null; } } // --------------------------------------------------------------------------- // Emit gen_ai.client.inference.operation.details event for chat spans // --------------------------------------------------------------------------- function emitChatInferenceEvent(span, model, messageId, sessionId, mappedReason, usageAttrs, inputMessages, outputMessages) { if (config_1.default.disableEvents) return; try { const attributes = { [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT, [semantic_convention_1.default.SERVER_ADDRESS]: SERVER_ADDRESS, [semantic_convention_1.default.SERVER_PORT]: SERVER_PORT, }; if (model) { attributes[semantic_convention_1.default.GEN_AI_REQUEST_MODEL] = model; attributes[semantic_convention_1.default.GEN_AI_RESPONSE_MODEL] = model; } if (messageId) { attributes[semantic_convention_1.default.GEN_AI_RESPONSE_ID] = String(messageId); } if (sessionId) { attributes[semantic_convention_1.default.GEN_AI_CONVERSATION_ID] = String(sessionId); } if (mappedReason) { attributes[semantic_convention_1.default.GEN_AI_RESPONSE_FINISH_REASON] = [mappedReason]; } for (const [key, value] of Object.entries(usageAttrs)) { attributes[key] = value; } if (inputMessages != null) { attributes[semantic_convention_1.default.GEN_AI_INPUT_MESSAGES] = inputMessages; } if (outputMessages != null) { attributes[semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES] = outputMessages; } helpers_1.default.emitInferenceEvent(span, attributes); } catch { /* swallow */ } } // --------------------------------------------------------------------------- // Chat metrics // --------------------------------------------------------------------------- function recordChatMetrics(model, inputTokens, outputTokens, cost) { try { const attributes = { [semantic_conventions_1.ATTR_TELEMETRY_SDK_NAME]: constant_1.SDK_NAME, [semantic_conventions_1.ATTR_SERVICE_NAME]: config_1.default.applicationName ?? 'default', [semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT]: config_1.default.environment ?? 'default', [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_ANTHROPIC, [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_CHAT, [semantic_convention_1.default.SERVER_ADDRESS]: SERVER_ADDRESS, [semantic_convention_1.default.SERVER_PORT]: SERVER_PORT, }; if (model) { attributes[semantic_convention_1.default.GEN_AI_REQUEST_MODEL] = model; } if (inputTokens && metrics_1.default.genaiClientUsageTokens) { metrics_1.default.genaiClientUsageTokens.record(inputTokens, { ...attributes, [semantic_convention_1.default.GEN_AI_TOKEN_TYPE]: semantic_convention_1.default.GEN_AI_TOKEN_TYPE_INPUT, }); } if (outputTokens && metrics_1.default.genaiClientUsageTokens) { metrics_1.default.genaiClientUsageTokens.record(outputTokens, { ...attributes, [semantic_convention_1.default.GEN_AI_TOKEN_TYPE]: semantic_convention_1.default.GEN_AI_TOKEN_TYPE_OUTPUT, }); } if (cost && metrics_1.default.genaiCost) { metrics_1.default.genaiCost.record(cost, attributes); } } catch { /* swallow */ } } // --------------------------------------------------------------------------- // Process result message — finalize root span with usage/cost data // --------------------------------------------------------------------------- function processResultMessage(span, sdkMsg, captureContent) { const resultUsage = { inputTokens: 0, outputTokens: 0 }; try { const sessionId = sdkMsg.session_id; if (sessionId) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, String(sessionId)); } const usage = sdkMsg.usage; if (usage) { const usageAttrs = extractUsage(usage); for (const [key, value] of Object.entries(usageAttrs)) { span.setAttribute(key, value); } resultUsage.inputTokens = usageAttrs[semantic_convention_1.default.GEN_AI_USAGE_INPUT_TOKENS] ?? 0; resultUsage.outputTokens = usageAttrs[semantic_convention_1.default.GEN_AI_USAGE_OUTPUT_TOKENS] ?? 0; } const totalCost = sdkMsg.total_cost_usd; if (totalCost != null) { span.setAttribute(semantic_convention_1.default.GEN_AI_USAGE_COST, Number(totalCost) || 0); } const modelUsage = sdkMsg.modelUsage; if (modelUsage && typeof modelUsage === 'object') { const modelNames = Object.keys(modelUsage); if (modelNames.length > 0) { span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, String(modelNames[0])); } } if (sdkMsg.num_turns != null) { span.setAttribute('gen_ai.agent.num_turns', Number(sdkMsg.num_turns) || 0); } if (sdkMsg.duration_ms != null) { span.setAttribute('gen_ai.agent.duration_ms', Number(sdkMsg.duration_ms) || 0); } if (sdkMsg.duration_api_ms != null) { span.setAttribute('gen_ai.agent.duration_api_ms', Number(sdkMsg.duration_api_ms) || 0); } if (sdkMsg.is_error) { const errResult = sdkMsg.errors?.join('; ') || sdkMsg.result || 'unknown error'; span.setAttribute(semantic_convention_1.default.ERROR_TYPE, 'AgentError'); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: String(errResult) }); } else { span.setStatus({ code: api_1.SpanStatusCode.OK }); } if (captureContent) { const result = sdkMsg.result; if (result) { span.setAttribute(semantic_convention_1.default.GEN_AI_OUTPUT_MESSAGES, JSON.stringify([{ role: 'assistant', parts: [{ type: 'text', content: truncateContent(String(result)) }], }])); } } } catch { /* swallow */ } return resultUsage; } // --------------------------------------------------------------------------- // Message stream processor // --------------------------------------------------------------------------- function processMessage(sdkMsg, span, toolTracker, subagentTracker, captureContent, tracer, chatState) { const msgType = sdkMsg.type; let resultUsage = null; if (msgType === 'assistant') { updateRootFromAssistant(span, sdkMsg); if (hasLlmCallData(sdkMsg)) { const newMsgId = sdkMsg.message?.id; const pendingMsgId = chatState.pendingChatMsgId; if (pendingMsgId != null && newMsgId !== pendingMsgId) { flushPendingChat(tracer, span, chatState, captureContent, subagentTracker); } bufferChatMessage(sdkMsg, chatState); } } else if (msgType === 'user') { flushPendingChat(tracer, span, chatState, captureContent, subagentTracker); if (captureContent) { const toolInput = buildInputFromToolResults(sdkMsg); if (toolInput) { chatState.pendingInput = toolInput; } } } else if (msgType === 'result') { flushPendingChat(tracer, span, chatState, captureContent, subagentTracker); resultUsage = processResultMessage(span, sdkMsg, captureContent); } else if (msgType === 'system' && sdkMsg.subtype === 'task_started') { flushPendingChat(tracer, span, chatState, captureContent, subagentTracker); try { const taskId = sdkMsg.task_id; const description = sdkMsg.description; const toolUseId = sdkMsg.tool_use_id; if (taskId) { subagentTracker.startSubagent(taskId, description, toolUseId); } } catch { /* swallow */ } } else if (msgType === 'system' && sdkMsg.subtype === 'task_notification') { flushPendingChat(tracer, span, chatState, captureContent, subagentTracker); try { const taskId = sdkMsg.task_id; const status = sdkMsg.status; const isError = status === 'failed' || status === 'stopped'; const errorMsg = isError ? sdkMsg.summary : null; const taskUsage = sdkMsg.usage; if (taskId) { subagentTracker.endSubagent(taskId, isError, errorMsg, taskUsage); } } catch { /* swallow */ } } chatState.lastBoundaryMs = Date.now(); return resultUsage; } function updateRootFromAssistant(span, sdkMsg) { try { const model = sdkMsg.message?.model; if (model) { span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MODEL, String(model)); span.setAttribute(semantic_convention_1.default.GEN_AI_RESPONSE_MODEL, String(model)); } const sessionId = sdkMsg.session_id; if (sessionId) { span.setAttribute(semantic_convention_1.default.GEN_AI_CONVERSATION_ID, String(sessionId)); } } catch { /* swallow */ } } // --------------------------------------------------------------------------- // Message-based tool span fallback (when hooks don't fire) // --------------------------------------------------------------------------- function processToolBlocksFromMessages(sdkMsg, toolTracker, subagentTracker) { const msgType = sdkMsg.type; if (msgType === 'assistant') { const content = sdkMsg.message?.content; if (!content || !Array.isArray(content)) return; const parentToolUseId = sdkMsg.parent_tool_use_id; let effectiveParent; if (parentToolUseId) { effectiveParent = subagentTracker.getSpanForToolUseId(parentToolUseId); } for (const block of content) { if (block.type === 'tool_use') { const toolName = block.name || 'unknown'; const toolInput = block.input; const toolId = block.id; if (toolId && !toolTracker._inFlight.has(toolId) && !toolTracker._completed.has(toolId)) { if (effectiveParent) { const spanName = generateSpanName('execute_tool', toolName); const parentCtx = api_1.trace.setSpan(api_1.context.active(), effectiveParent); const span = toolTracker['_tracer'].startSpan(spanName, { kind: api_1.SpanKind.INTERNAL, attributes: { [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_TOOLS, [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_CLAUDE_AGENT_SDK, }, }, parentCtx); setToolSpanAttributes(span, toolName, toolInput, toolId, config_1.default.captureMessageContent ?? true); toolTracker._inFlight.set(toolId, span); } else { toolTracker.startTool(toolName, toolInput, toolId); } } } } } else if (msgType === 'user') { const content = sdkMsg.message?.content; if (!content || !Array.isArray(content)) return; for (const block of content) { if (block.type === 'tool_result') { const toolUseId = block.tool_use_id; const isError = block.is_error; const resultContent = block.content; if (toolUseId && toolTracker._inFlight.has(toolUseId)) { if (isError) { toolTracker.endToolError(toolUseId, resultContent); } else { toolTracker.endTool(toolUseId, resultContent); } } } } } } // --------------------------------------------------------------------------- // Set initial span attributes on the root invoke_agent span // --------------------------------------------------------------------------- function setInitialSpanAttributes(span, options, prompt, captureContent) { try { span.setAttribute(semantic_convention_1.default.GEN_AI_OPERATION, semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT); span.setAttribute(semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL, semantic_convention_1.default.GEN_AI_SYSTEM_CLAUDE_AGENT_SDK); span.setAttribute(GEN_AI_SYSTEM_ATTR, GEN_AI_SYSTEM_VALUE); span.setAttribute(semantic_conventions_1.ATTR_TELEMETRY_SDK_NAME, constant_1.SDK_NAME); span.setAttribute(semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT, config_1.default.environment ?? 'default'); span.setAttribute(semantic_conventions_1.ATTR_SERVICE_NAME, config_1.default.applicationName ?? 'default'); span.setAttribute(semantic_convention_1.default.GEN_AI_SDK_VERSION, constant_1.SDK_VERSION); span.setAttribute(semantic_convention_1.default.SERVER_ADDRESS, SERVER_ADDRESS); span.setAttribute(semantic_convention_1.default.SERVER_PORT, SERVER_PORT); const agentName = resolveAgentName(options); if (agentName) { span.setAttribute(semantic_convention_1.default.GEN_AI_AGENT_NAME, agentName); } const model = options?.model; if (model) { span.setAttribute(semantic_convention_1.default.GEN_AI_REQUEST_MODEL, String(model)); } if (captureContent) { const systemPrompt = options?.systemPrompt; if (systemPrompt && typeof systemPrompt === 'string') { span.setAttribute(semantic_convention_1.default.GEN_AI_SYSTEM_INSTRUCTIONS, JSON.stringify([{ type: 'text', content: truncateContent(systemPrompt) }])); } if (prompt && typeof prompt === 'string') { span.setAttribute(semantic_convention_1.default.GEN_AI_INPUT_MESSAGES, JSON.stringify([{ role: 'user', parts: [{ type: 'text', content: truncateContent(prompt) }], }])); } } (0, helpers_1.applyCustomSpanAttributes)(span); } catch { /* swallow */ } } // --------------------------------------------------------------------------- // Finalize root span — record duration and token usage metrics // --------------------------------------------------------------------------- function finalizeSpan(span, startTime, inputTokens, outputTokens) { try { const duration = (Date.now() / 1000) - startTime; span.setAttribute(semantic_convention_1.default.GEN_AI_CLIENT_OPERATION_DURATION, duration); if (!config_1.default.disableMetrics) { const attributes = { [semantic_conventions_1.ATTR_TELEMETRY_SDK_NAME]: constant_1.SDK_NAME, [semantic_conventions_1.ATTR_SERVICE_NAME]: config_1.default.applicationName ?? 'default', [semantic_convention_1.default.ATTR_DEPLOYMENT_ENVIRONMENT]: config_1.default.environment ?? 'default', [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_CLAUDE_AGENT_SDK, [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT, [GEN_AI_SYSTEM_ATTR]: GEN_AI_SYSTEM_VALUE, [semantic_convention_1.default.SERVER_ADDRESS]: SERVER_ADDRESS, [semantic_convention_1.default.SERVER_PORT]: SERVER_PORT, }; if (metrics_1.default.genaiClientOperationDuration) { metrics_1.default.genaiClientOperationDuration.record(duration, attributes); } if (inputTokens && metrics_1.default.genaiClientUsageTokens) { metrics_1.default.genaiClientUsageTokens.record(inputTokens, { ...attributes, [semantic_convention_1.default.GEN_AI_TOKEN_TYPE]: semantic_convention_1.default.GEN_AI_TOKEN_TYPE_INPUT, }); } if (outputTokens && metrics_1.default.genaiClientUsageTokens) { metrics_1.default.genaiClientUsageTokens.record(outputTokens, { ...attributes, [semantic_convention_1.default.GEN_AI_TOKEN_TYPE]: semantic_convention_1.default.GEN_AI_TOKEN_TYPE_OUTPUT, }); } } } catch { /* swallow */ } } // --------------------------------------------------------------------------- // patchQuery — wraps the `query()` export from @anthropic-ai/claude-agent-sdk // --------------------------------------------------------------------------- function patchQuery(tracer) { return (originalQuery) => { return function wrappedQuery(params) { const captureContent = config_1.default.captureMessageContent ?? true; const prompt = params.prompt; const userOptions = params.options; const options = userOptions ? { ...userOptions } : {}; const agentName = resolveAgentName(options); const spanName = generateSpanName('query', agentName); const span = tracer.startSpan(spanName, { kind: api_1.SpanKind.INTERNAL, attributes: { [semantic_convention_1.default.GEN_AI_OPERATION]: semantic_convention_1.default.GEN_AI_OPERATION_TYPE_AGENT, [semantic_convention_1.default.GEN_AI_PROVIDER_NAME_OTEL]: semantic_convention_1.default.GEN_AI_SYSTEM_CLAUDE_AGENT_SDK, }, }); const spanContext = api_1.trace.setSpan(api_1.context.active(), span); const startTime = Date.now() / 1000; const chatState = { lastBoundaryMs: Date.now() }; const toolTracker = new ToolSpanTracker(tracer, span, captureContent); const subagentTracker = new SubagentSpanTracker(tracer, toolTracker); const aggregateUsage = { inputTokens: 0, outputTokens: 0 }; if (prompt && typeof prompt === 'string' && captureContent) { chatState.pendingInput = [{ role: 'user', parts: [{ type: 'text', content: truncateContent(prompt) }], }]; } injectHooks(options, toolTracker, subagentTracker); setInitialSpanAttributes(span, options, prompt, captureContent); (0, helpers_1.setFrameworkLlmActive)(); let query; try { query = api_1.context.with(spanContext, () => { return originalQuery.call(this, { prompt, options }); }); } catch (e) { (0, helpers_1.resetFrameworkLlmActive)(); helpers_1.default.handleException(span, e); span.end(); throw e; } let done = false; const cleanup = () => { if (done) return; done = true; (0, helpers_1.resetFrameworkLlmActive)(); subagentTracker.endAll(); toolTracker.endAll(); finalizeSpan(span, startTime, aggregateUsage.inputTokens, aggregateUsage.outputTokens); span.end(); }; const originalNext = query.next.bind(query); const originalReturn = query.return?.bind(query); const originalThrow = query.throw?.bind(query); return new Proxy(query, { get(target, prop, receiver) { if (prop === 'next') { return async function (...args) { try { const result = await originalNext(...args); if (result.done) { if (!done) { flushPendingChat(tracer, span, chatState, captureContent, subagentTracker); if (aggregateUsage.inputTokens === 0 && aggregateUsage.outputTokens === 0) { span.setStatus({ code: api_1.SpanStatusCode.OK }); } } cleanup(); return result; } const sdkMsg = result.value; try { const msgUsage = processMessage(sdkMsg, span, toolTracker, subagentTracker, captureContent, tracer, chatState); if (msgUsage) { aggregateUsage.inputTokens = msgUsage.inputTokens; aggregateUsage.outputTokens = msgUsage.outputTokens; } processToolBlocksFromMessages(sdkMsg, toolTracker, subagentTracker); } catch { /* swallow processing errors */ } return result; } catch (e) { if (!done) { helpers_1.default.handleException(span, e); } cleanup(); throw e; } }; } if (prop === 'return') { return async function (value) { cleanup(); return originalReturn ? originalReturn(value) : { done: true, value }; }; } if (prop === 'throw') { return async function (e) { if (!done) { helpers_1.default.handleException(span, e instanceof Error ? e : new Error(String(e))); } cleanup(); return originalThrow ? originalThrow(e) : { done: true, value: undefined }; }; } if (prop === Symbol.asyncIterator) { return function () { return receiver; }; } return Reflect.get(target, prop, receiver); }, }); }; }; } //# sourceMappingURL=wrapper.js.map