(toolResult?: string, resultTokens?: number)
| 689 | } |
| 690 | |
| 691 | export function endToolSpan(toolResult?: string, resultTokens?: number): void { |
| 692 | const toolSpanContext = toolContext.getStore() |
| 693 | |
| 694 | if (!toolSpanContext) { |
| 695 | return |
| 696 | } |
| 697 | |
| 698 | // End Perfetto span |
| 699 | if (toolSpanContext.perfettoSpanId) { |
| 700 | endToolPerfettoSpan(toolSpanContext.perfettoSpanId, { |
| 701 | success: true, |
| 702 | resultTokens, |
| 703 | }) |
| 704 | } |
| 705 | |
| 706 | if (!isAnyTracingEnabled()) { |
| 707 | const spanId = getSpanId(toolSpanContext.span) |
| 708 | activeSpans.delete(spanId) |
| 709 | // Same reasoning as interactionContext above: clear so subsequent async |
| 710 | // work doesn't hold a stale reference to the ended tool span. |
| 711 | toolContext.enterWith(undefined) |
| 712 | return |
| 713 | } |
| 714 | |
| 715 | const duration = Date.now() - toolSpanContext.startTime |
| 716 | const endAttributes: Record<string, string | number | boolean> = { |
| 717 | duration_ms: duration, |
| 718 | } |
| 719 | |
| 720 | // Add experimental tool result attributes (new_context) |
| 721 | if (toolResult) { |
| 722 | const toolName = toolSpanContext.attributes['tool_name'] || 'unknown' |
| 723 | addBetaToolResultAttributes(endAttributes, toolName, toolResult) |
| 724 | } |
| 725 | |
| 726 | if (resultTokens !== undefined) { |
| 727 | endAttributes['result_tokens'] = resultTokens |
| 728 | } |
| 729 | |
| 730 | toolSpanContext.span.setAttributes(endAttributes) |
| 731 | toolSpanContext.span.end() |
| 732 | |
| 733 | const spanId = getSpanId(toolSpanContext.span) |
| 734 | activeSpans.delete(spanId) |
| 735 | toolContext.enterWith(undefined) |
| 736 | } |
| 737 | |
| 738 | function isToolContentLoggingEnabled(): boolean { |
| 739 | return isEnvTruthy(process.env.OTEL_LOG_TOOL_CONTENT) |
no test coverage detected