All files / src/sdk otel.ts

54.83% Statements 17/31
34.37% Branches 11/32
87.5% Functions 7/8
54.83% Lines 17/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                              16x       16x 16x 15x     1x         16x         23x 23x 23x 23x 23x         23x                                                                   23x 23x 23x                                       23x                 15x                
import type { UsageStats } from '../types/index.js';
 
interface SpanLike {
  setAttribute(
    key: string,
    value: string | number | boolean | string[] | undefined,
  ): unknown;
}
 
interface GenAiMessage {
  role: string;
  content: unknown;
}
 
function providerFromModel(model: string | undefined): string | undefined {
  Iif (!model) {
    return undefined;
  }
 
  const slashIndex = model.indexOf('/');
  if (slashIndex > 0) {
    return model.slice(0, slashIndex);
  }
 
  return undefined;
}
 
/** Resolve the OpenTelemetry GenAI provider name from runtime and model selectors. */
export function genAiProviderName(runtime: string | undefined, model: string | undefined): string {
  return providerFromModel(model) ?? (runtime === 'pi' ? 'pi' : 'anthropic');
}
 
/** Set GenAI token usage attributes expected by Sentry AI monitoring. */
export function setGenAiUsageAttrs(span: SpanLike, usage: UsageStats): void {
  span.setAttribute('gen_ai.usage.input_tokens', usage.inputTokens);
  span.setAttribute('gen_ai.usage.output_tokens', usage.outputTokens);
  span.setAttribute('gen_ai.usage.input_tokens.cached', usage.cacheReadInputTokens ?? 0);
  span.setAttribute('gen_ai.usage.input_tokens.cache_write', usage.cacheCreationInputTokens ?? 0);
  span.setAttribute('gen_ai.usage.total_tokens', usage.inputTokens + usage.outputTokens);
}
 
/** Set OpenTelemetry GenAI system-instruction attributes for prompt spans. */
export function setGenAiSystemInstructionsAttr(span: SpanLike, systemPrompt: string): void {
  span.setAttribute('gen_ai.system_instructions', JSON.stringify([
    { type: 'text', content: systemPrompt },
  ]));
}
 
function normalizeContentPart(part: unknown): Record<string, unknown> {
  if (!part || typeof part !== 'object') {
    return { type: 'text', content: String(part ?? '') };
  }
 
  const block = part as Record<string, unknown>;
  if (block['type'] === 'text' && typeof block['text'] === 'string') {
    return { type: 'text', content: block['text'] };
  }
  if (block['type'] === 'tool_use') {
    return {
      type: 'tool_call',
      id: block['id'],
      name: block['name'],
      arguments: block['input'],
    };
  }
  if (block['type'] === 'tool_result') {
    return {
      type: 'tool_call_response',
      id: block['tool_use_id'],
      result: block['content'],
    };
  }
 
  return { ...block };
}
 
function normalizeMessage(message: GenAiMessage): Record<string, unknown> {
  const { role, content } = message;
  Eif (typeof content === 'string') {
    return {
      role,
      parts: [{ type: 'text', content }],
    };
  }
  if (Array.isArray(content)) {
    return {
      role,
      parts: content.map(normalizeContentPart),
    };
  }
 
  return {
    role,
    parts: [normalizeContentPart(content)],
  };
}
 
/** Set OpenTelemetry GenAI input message attributes using the current schema. */
export function setGenAiInputMessagesAttr(span: SpanLike, messages: GenAiMessage[]): void {
  span.setAttribute('gen_ai.input.messages', JSON.stringify(messages.map(normalizeMessage)));
}
 
/** Set OpenTelemetry GenAI output message attributes for text responses. */
export function setGenAiOutputMessagesAttr(
  span: SpanLike,
  responseText: string,
  finishReason?: string | null,
): void {
  span.setAttribute('gen_ai.output.messages', JSON.stringify([
    {
      role: 'assistant',
      parts: [{ type: 'text', content: responseText }],
      ...(finishReason ? { finish_reason: finishReason } : {}),
    },
  ]));
}