(target, thisArg, args: unknown[])
| 76 | |
| 77 | const wrappedInvoke = new Proxy(originalInvoke as (...args: unknown[]) => unknown, { |
| 78 | apply(target, thisArg, args: unknown[]): unknown { |
| 79 | const spanAttributes: SpanAttributes = { |
| 80 | [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN, |
| 81 | [SEMANTIC_ATTRIBUTE_SENTRY_OP]: GEN_AI_EXECUTE_TOOL_OPERATION_ATTRIBUTE, |
| 82 | [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'execute_tool', |
| 83 | [GEN_AI_TOOL_NAME_ATTRIBUTE]: toolName, |
| 84 | [GEN_AI_TOOL_TYPE_ATTRIBUTE]: 'function', |
| 85 | }; |
| 86 | |
| 87 | // Read agent name from LangChain's propagated config metadata at call time, |
| 88 | // so shared tools get the correct agent name for each invocation |
| 89 | const callConfig = args[1] as Record<string, unknown> | undefined; |
| 90 | const callAgentName = (callConfig?.metadata as Record<string, unknown>)?.lc_agent_name ?? agentName; |
| 91 | if (typeof callAgentName === 'string') { |
| 92 | spanAttributes[GEN_AI_AGENT_NAME_ATTRIBUTE] = callAgentName; |
| 93 | } |
| 94 | |
| 95 | if (toolDescription) { |
| 96 | spanAttributes[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE] = toolDescription; |
| 97 | } |
| 98 | |
| 99 | // LangGraph ToolNode passes { name, args, id, type: "tool_call" } |
| 100 | const input = args[0] as Record<string, unknown> | undefined; |
| 101 | if (typeof input === 'object' && !!input) { |
| 102 | if ('id' in input && typeof input.id === 'string') { |
| 103 | spanAttributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE] = input.id; |
| 104 | } |
| 105 | |
| 106 | if (options.recordInputs) { |
| 107 | const toolArgs = 'args' in input && typeof input.args === 'object' ? input.args : input; |
| 108 | try { |
| 109 | spanAttributes[GEN_AI_TOOL_INPUT_ATTRIBUTE] = JSON.stringify(toolArgs); |
| 110 | } catch { |
| 111 | // skip if not serializable |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | return startSpan( |
| 117 | { |
| 118 | op: GEN_AI_EXECUTE_TOOL_OPERATION_ATTRIBUTE, |
| 119 | name: `execute_tool ${toolName}`, |
| 120 | attributes: spanAttributes, |
| 121 | }, |
| 122 | async span => { |
| 123 | try { |
| 124 | const result = await Reflect.apply(target, thisArg, args); |
| 125 | |
| 126 | if (options.recordOutputs) { |
| 127 | try { |
| 128 | // ToolMessage objects wrap the result in .content |
| 129 | const resultObj = result as Record<string, unknown> | undefined; |
| 130 | const content = |
| 131 | resultObj && typeof resultObj === 'object' && 'content' in resultObj ? resultObj.content : result; |
| 132 | span.setAttribute( |
| 133 | GEN_AI_TOOL_OUTPUT_ATTRIBUTE, |
| 134 | typeof content === 'string' ? content : JSON.stringify(content), |
| 135 | ); |
nothing calls this directly
no test coverage detected