| import { feature } from 'bun:bundle' |
| import { APIError } from '@anthropic-ai/sdk' |
| import type { |
| BetaStopReason, |
| BetaUsage as Usage, |
| } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' |
| import { |
| addToTotalDurationState, |
| consumePostCompaction, |
| getIsNonInteractiveSession, |
| getLastApiCompletionTimestamp, |
| getTeleportedSessionInfo, |
| markFirstTeleportMessageLogged, |
| setLastApiCompletionTimestamp, |
| } from 'src/bootstrap/state.js' |
| import type { QueryChainTracking } from 'src/Tool.js' |
| import { isConnectorTextBlock } from 'src/types/connectorText.js' |
| import type { AssistantMessage } from 'src/types/message.js' |
| import { logForDebugging } from 'src/utils/debug.js' |
| import type { EffortLevel } from 'src/utils/effort.js' |
| import { logError } from 'src/utils/log.js' |
| import { getAPIProviderForStatsig } from 'src/utils/model/providers.js' |
| import type { PermissionMode } from 'src/utils/permissions/PermissionMode.js' |
| import { jsonStringify } from 'src/utils/slowOperations.js' |
| import { logOTelEvent } from 'src/utils/telemetry/events.js' |
| import { |
| endLLMRequestSpan, |
| isBetaTracingEnabled, |
| type Span, |
| } from 'src/utils/telemetry/sessionTracing.js' |
| import type { NonNullableUsage } from '../../entrypoints/sdk/sdkUtilityTypes.js' |
| import { consumeInvokingRequestId } from '../../utils/agentContext.js' |
| import { |
| type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| logEvent, |
| } from '../analytics/index.js' |
| import { sanitizeToolNameForAnalytics } from '../analytics/metadata.js' |
| import { EMPTY_USAGE } from './emptyUsage.js' |
| import { classifyAPIError } from './errors.js' |
| import { extractConnectionErrorDetails } from './errorUtils.js' |
|
|
| export type { NonNullableUsage } |
| export { EMPTY_USAGE } |
|
|
| |
| export type GlobalCacheStrategy = 'tool_based' | 'system_prompt' | 'none' |
|
|
| function getErrorMessage(error: unknown): string { |
| if (error instanceof APIError) { |
| const body = error.error as { error?: { message?: string } } | undefined |
| if (body?.error?.message) return body.error.message |
| } |
| return error instanceof Error ? error.message : String(error) |
| } |
|
|
| type KnownGateway = |
| | 'litellm' |
| | 'helicone' |
| | 'portkey' |
| | 'cloudflare-ai-gateway' |
| | 'kong' |
| | 'braintrust' |
| | 'databricks' |
|
|
| |
| const GATEWAY_FINGERPRINTS: Partial< |
| Record<KnownGateway, { prefixes: string[] }> |
| > = { |
| |
| litellm: { |
| prefixes: ['x-litellm-'], |
| }, |
| |
| helicone: { |
| prefixes: ['helicone-'], |
| }, |
| |
| portkey: { |
| prefixes: ['x-portkey-'], |
| }, |
| |
| 'cloudflare-ai-gateway': { |
| prefixes: ['cf-aig-'], |
| }, |
| |
| kong: { |
| prefixes: ['x-kong-'], |
| }, |
| |
| braintrust: { |
| prefixes: ['x-bt-'], |
| }, |
| } |
|
|
| |
| |
| |
| const GATEWAY_HOST_SUFFIXES: Partial<Record<KnownGateway, string[]>> = { |
| |
| databricks: [ |
| '.cloud.databricks.com', |
| '.azuredatabricks.net', |
| '.gcp.databricks.com', |
| ], |
| } |
|
|
| function detectGateway({ |
| headers, |
| baseUrl, |
| }: { |
| headers?: globalThis.Headers |
| baseUrl?: string |
| }): KnownGateway | undefined { |
| if (headers) { |
| |
| const headerNames: string[] = [] |
| |
| |
| const headersAny = headers as unknown |
| if (typeof (headersAny as Headers).forEach === 'function') { |
| ;(headersAny as Headers).forEach((_, key) => headerNames.push(key)) |
| } else if (headersAny && typeof headersAny === 'object') { |
| for (const key of Object.keys(headersAny as Record<string, unknown>)) { |
| headerNames.push(key.toLowerCase()) |
| } |
| } |
| for (const [gw, { prefixes }] of Object.entries(GATEWAY_FINGERPRINTS)) { |
| if (prefixes.some(p => headerNames.some(h => h.startsWith(p)))) { |
| return gw as KnownGateway |
| } |
| } |
| } |
|
|
| if (baseUrl) { |
| try { |
| const host = new URL(baseUrl).hostname.toLowerCase() |
| for (const [gw, suffixes] of Object.entries(GATEWAY_HOST_SUFFIXES)) { |
| if (suffixes.some(s => host.endsWith(s))) { |
| return gw as KnownGateway |
| } |
| } |
| } catch { |
| |
| } |
| } |
|
|
| return undefined |
| } |
|
|
| function getAnthropicEnvMetadata() { |
| return { |
| ...(process.env.ANTHROPIC_BASE_URL |
| ? { |
| baseUrl: process.env |
| .ANTHROPIC_BASE_URL as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(process.env.ANTHROPIC_MODEL |
| ? { |
| envModel: process.env |
| .ANTHROPIC_MODEL as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(process.env.ANTHROPIC_SMALL_FAST_MODEL |
| ? { |
| envSmallFastModel: process.env |
| .ANTHROPIC_SMALL_FAST_MODEL as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| } |
| } |
|
|
| function getBuildAgeMinutes(): number | undefined { |
| if (!MACRO.BUILD_TIME) return undefined |
| const buildTime = new Date(MACRO.BUILD_TIME).getTime() |
| if (isNaN(buildTime)) return undefined |
| return Math.floor((Date.now() - buildTime) / 60000) |
| } |
|
|
| export function logAPIQuery({ |
| model, |
| messagesLength, |
| temperature, |
| betas, |
| permissionMode, |
| querySource, |
| queryTracking, |
| thinkingType, |
| effortValue, |
| fastMode, |
| previousRequestId, |
| }: { |
| model: string |
| messagesLength: number |
| temperature: number |
| betas?: string[] |
| permissionMode?: PermissionMode |
| querySource: string |
| queryTracking?: QueryChainTracking |
| thinkingType?: 'adaptive' | 'enabled' | 'disabled' |
| effortValue?: EffortLevel | null |
| fastMode?: boolean |
| previousRequestId?: string | null |
| }): void { |
| logEvent('tengu_api_query', { |
| model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| messagesLength, |
| temperature: temperature, |
| provider: getAPIProviderForStatsig(), |
| buildAgeMins: getBuildAgeMinutes(), |
| ...(betas?.length |
| ? { |
| betas: betas.join( |
| ',', |
| ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| permissionMode: |
| permissionMode as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| querySource: |
| querySource as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| ...(queryTracking |
| ? { |
| queryChainId: |
| queryTracking.chainId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| queryDepth: queryTracking.depth, |
| } |
| : {}), |
| thinkingType: |
| thinkingType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| effortValue: |
| effortValue as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| fastMode, |
| ...(previousRequestId |
| ? { |
| previousRequestId: |
| previousRequestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...getAnthropicEnvMetadata(), |
| }) |
| } |
|
|
| export function logAPIError({ |
| error, |
| model, |
| messageCount, |
| messageTokens, |
| durationMs, |
| durationMsIncludingRetries, |
| attempt, |
| requestId, |
| clientRequestId, |
| didFallBackToNonStreaming, |
| promptCategory, |
| headers, |
| queryTracking, |
| querySource, |
| llmSpan, |
| fastMode, |
| previousRequestId, |
| }: { |
| error: unknown |
| model: string |
| messageCount: number |
| messageTokens?: number |
| durationMs: number |
| durationMsIncludingRetries: number |
| attempt: number |
| requestId?: string | null |
| /** Client-generated ID sent as x-client-request-id header (survives timeouts) */ |
| clientRequestId?: string |
| didFallBackToNonStreaming?: boolean |
| promptCategory?: string |
| headers?: globalThis.Headers |
| queryTracking?: QueryChainTracking |
| querySource?: string |
| /** The span from startLLMRequestSpan - pass this to correctly match responses to requests */ |
| llmSpan?: Span |
| fastMode?: boolean |
| previousRequestId?: string | null |
| }): void { |
| const gateway = detectGateway({ |
| headers: |
| error instanceof APIError && error.headers ? error.headers : headers, |
| baseUrl: process.env.ANTHROPIC_BASE_URL, |
| }) |
|
|
| const errStr = getErrorMessage(error) |
| const status = error instanceof APIError ? String(error.status) : undefined |
| const errorType = classifyAPIError(error) |
|
|
| |
| const connectionDetails = extractConnectionErrorDetails(error) |
| if (connectionDetails) { |
| const sslLabel = connectionDetails.isSSLError ? ' (SSL error)' : '' |
| logForDebugging( |
| `Connection error details: code=${connectionDetails.code}${sslLabel}, message=${connectionDetails.message}`, |
| { level: 'error' }, |
| ) |
| } |
|
|
| const invocation = consumeInvokingRequestId() |
|
|
| if (clientRequestId) { |
| logForDebugging( |
| `API error x-client-request-id=${clientRequestId} (give this to the API team for server-log lookup)`, |
| { level: 'error' }, |
| ) |
| } |
|
|
| logError(error as Error) |
| logEvent('tengu_api_error', { |
| model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| error: errStr as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| status: |
| status as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| errorType: |
| errorType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| messageCount, |
| messageTokens, |
| durationMs, |
| durationMsIncludingRetries, |
| attempt, |
| provider: getAPIProviderForStatsig(), |
| requestId: |
| (requestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) || |
| undefined, |
| ...(invocation |
| ? { |
| invokingRequestId: |
| invocation.invokingRequestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| invocationKind: |
| invocation.invocationKind as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| clientRequestId: |
| (clientRequestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) || |
| undefined, |
| didFallBackToNonStreaming, |
| ...(promptCategory |
| ? { |
| promptCategory: |
| promptCategory as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(gateway |
| ? { |
| gateway: |
| gateway as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(queryTracking |
| ? { |
| queryChainId: |
| queryTracking.chainId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| queryDepth: queryTracking.depth, |
| } |
| : {}), |
| ...(querySource |
| ? { |
| querySource: |
| querySource as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| fastMode, |
| ...(previousRequestId |
| ? { |
| previousRequestId: |
| previousRequestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...getAnthropicEnvMetadata(), |
| }) |
|
|
| |
| void logOTelEvent('api_error', { |
| model: model, |
| error: errStr, |
| status_code: String(status), |
| duration_ms: String(durationMs), |
| attempt: String(attempt), |
| speed: fastMode ? 'fast' : 'normal', |
| }) |
|
|
| |
| endLLMRequestSpan(llmSpan, { |
| success: false, |
| statusCode: status ? parseInt(status) : undefined, |
| error: errStr, |
| attempt, |
| }) |
|
|
| |
| const teleportInfo = getTeleportedSessionInfo() |
| if (teleportInfo?.isTeleported && !teleportInfo.hasLoggedFirstMessage) { |
| logEvent('tengu_teleport_first_message_error', { |
| session_id: |
| teleportInfo.sessionId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| error_type: |
| errorType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| }) |
| markFirstTeleportMessageLogged() |
| } |
| } |
|
|
| function logAPISuccess({ |
| model, |
| preNormalizedModel, |
| messageCount, |
| messageTokens, |
| usage, |
| durationMs, |
| durationMsIncludingRetries, |
| attempt, |
| ttftMs, |
| requestId, |
| stopReason, |
| costUSD, |
| didFallBackToNonStreaming, |
| querySource, |
| gateway, |
| queryTracking, |
| permissionMode, |
| globalCacheStrategy, |
| textContentLength, |
| thinkingContentLength, |
| toolUseContentLengths, |
| connectorTextBlockCount, |
| fastMode, |
| previousRequestId, |
| betas, |
| }: { |
| model: string |
| preNormalizedModel: string |
| messageCount: number |
| messageTokens: number |
| usage: Usage |
| durationMs: number |
| durationMsIncludingRetries: number |
| attempt: number |
| ttftMs: number | null |
| requestId: string | null |
| stopReason: BetaStopReason | null |
| costUSD: number |
| didFallBackToNonStreaming: boolean |
| querySource: string |
| gateway?: KnownGateway |
| queryTracking?: QueryChainTracking |
| permissionMode?: PermissionMode |
| globalCacheStrategy?: GlobalCacheStrategy |
| textContentLength?: number |
| thinkingContentLength?: number |
| toolUseContentLengths?: Record<string, number> |
| connectorTextBlockCount?: number |
| fastMode?: boolean |
| previousRequestId?: string | null |
| betas?: string[] |
| }): void { |
| const isNonInteractiveSession = getIsNonInteractiveSession() |
| const isPostCompaction = consumePostCompaction() |
| const hasPrintFlag = |
| process.argv.includes('-p') || process.argv.includes('--print') |
|
|
| const now = Date.now() |
| const lastCompletion = getLastApiCompletionTimestamp() |
| const timeSinceLastApiCallMs = |
| lastCompletion !== null ? now - lastCompletion : undefined |
|
|
| const invocation = consumeInvokingRequestId() |
|
|
| logEvent('tengu_api_success', { |
| model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| ...(preNormalizedModel !== model |
| ? { |
| preNormalizedModel: |
| preNormalizedModel as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(betas?.length |
| ? { |
| betas: betas.join( |
| ',', |
| ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| messageCount, |
| messageTokens, |
| inputTokens: usage.input_tokens, |
| outputTokens: usage.output_tokens, |
| cachedInputTokens: usage.cache_read_input_tokens ?? 0, |
| uncachedInputTokens: usage.cache_creation_input_tokens ?? 0, |
| durationMs: durationMs, |
| durationMsIncludingRetries: durationMsIncludingRetries, |
| attempt: attempt, |
| ttftMs: ttftMs ?? undefined, |
| buildAgeMins: getBuildAgeMinutes(), |
| provider: getAPIProviderForStatsig(), |
| requestId: |
| (requestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) ?? |
| undefined, |
| ...(invocation |
| ? { |
| invokingRequestId: |
| invocation.invokingRequestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| invocationKind: |
| invocation.invocationKind as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| stop_reason: |
| (stopReason as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) ?? |
| undefined, |
| costUSD, |
| didFallBackToNonStreaming, |
| isNonInteractiveSession, |
| print: hasPrintFlag, |
| isTTY: process.stdout.isTTY ?? false, |
| querySource: |
| querySource as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| ...(gateway |
| ? { |
| gateway: |
| gateway as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(queryTracking |
| ? { |
| queryChainId: |
| queryTracking.chainId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| queryDepth: queryTracking.depth, |
| } |
| : {}), |
| permissionMode: |
| permissionMode as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| ...(globalCacheStrategy |
| ? { |
| globalCacheStrategy: |
| globalCacheStrategy as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(textContentLength !== undefined |
| ? ({ |
| textContentLength, |
| } as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) |
| : {}), |
| ...(thinkingContentLength !== undefined |
| ? ({ |
| thinkingContentLength, |
| } as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) |
| : {}), |
| ...(toolUseContentLengths !== undefined |
| ? ({ |
| toolUseContentLengths: jsonStringify( |
| toolUseContentLengths, |
| ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) |
| : {}), |
| ...(connectorTextBlockCount !== undefined |
| ? ({ |
| connectorTextBlockCount, |
| } as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) |
| : {}), |
| fastMode, |
| |
| |
| |
| ...(feature('CACHED_MICROCOMPACT') && |
| ((usage as unknown as { cache_deleted_input_tokens?: number }) |
| .cache_deleted_input_tokens ?? 0) > 0 |
| ? { |
| cacheDeletedInputTokens: ( |
| usage as unknown as { cache_deleted_input_tokens: number } |
| ).cache_deleted_input_tokens, |
| } |
| : {}), |
| ...(previousRequestId |
| ? { |
| previousRequestId: |
| previousRequestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| } |
| : {}), |
| ...(isPostCompaction ? { isPostCompaction } : {}), |
| ...getAnthropicEnvMetadata(), |
| timeSinceLastApiCallMs, |
| }) |
|
|
| setLastApiCompletionTimestamp(now) |
| } |
|
|
| export function logAPISuccessAndDuration({ |
| model, |
| preNormalizedModel, |
| start, |
| startIncludingRetries, |
| ttftMs, |
| usage, |
| attempt, |
| messageCount, |
| messageTokens, |
| requestId, |
| stopReason, |
| didFallBackToNonStreaming, |
| querySource, |
| headers, |
| costUSD, |
| queryTracking, |
| permissionMode, |
| newMessages, |
| llmSpan, |
| globalCacheStrategy, |
| requestSetupMs, |
| attemptStartTimes, |
| fastMode, |
| previousRequestId, |
| betas, |
| }: { |
| model: string |
| preNormalizedModel: string |
| start: number |
| startIncludingRetries: number |
| ttftMs: number | null |
| usage: NonNullableUsage |
| attempt: number |
| messageCount: number |
| messageTokens: number |
| requestId: string | null |
| stopReason: BetaStopReason | null |
| didFallBackToNonStreaming: boolean |
| querySource: string |
| headers?: globalThis.Headers |
| costUSD: number |
| queryTracking?: QueryChainTracking |
| permissionMode?: PermissionMode |
| /** Assistant messages from the response - used to extract model_output and thinking_output |
| * when beta tracing is enabled */ |
| newMessages?: AssistantMessage[] |
| /** The span from startLLMRequestSpan - pass this to correctly match responses to requests */ |
| llmSpan?: Span |
| /** Strategy used for global prompt caching: 'tool_based', 'system_prompt', or 'none' */ |
| globalCacheStrategy?: GlobalCacheStrategy |
| /** Time spent in pre-request setup before the successful attempt */ |
| requestSetupMs?: number |
| /** Timestamps (Date.now()) of each attempt start — used for retry sub-spans in Perfetto */ |
| attemptStartTimes?: number[] |
| fastMode?: boolean |
| /** Request ID from the previous API call in this session */ |
| previousRequestId?: string | null |
| betas?: string[] |
| }): void { |
| const gateway = detectGateway({ |
| headers, |
| baseUrl: process.env.ANTHROPIC_BASE_URL, |
| }) |
|
|
| let textContentLength: number | undefined |
| let thinkingContentLength: number | undefined |
| let toolUseContentLengths: Record<string, number> | undefined |
| let connectorTextBlockCount: number | undefined |
|
|
| if (newMessages) { |
| let textLen = 0 |
| let thinkingLen = 0 |
| let hasToolUse = false |
| const toolLengths: Record<string, number> = {} |
| let connectorCount = 0 |
|
|
| for (const msg of newMessages) { |
| for (const block of msg.message.content) { |
| if (block.type === 'text') { |
| textLen += block.text.length |
| } else if (feature('CONNECTOR_TEXT') && isConnectorTextBlock(block)) { |
| connectorCount++ |
| } else if (block.type === 'thinking') { |
| thinkingLen += block.thinking.length |
| } else if ( |
| block.type === 'tool_use' || |
| block.type === 'server_tool_use' || |
| block.type === 'mcp_tool_use' |
| ) { |
| const inputLen = jsonStringify(block.input).length |
| const sanitizedName = sanitizeToolNameForAnalytics(block.name) |
| toolLengths[sanitizedName] = |
| (toolLengths[sanitizedName] ?? 0) + inputLen |
| hasToolUse = true |
| } |
| } |
| } |
|
|
| textContentLength = textLen |
| thinkingContentLength = thinkingLen > 0 ? thinkingLen : undefined |
| toolUseContentLengths = hasToolUse ? toolLengths : undefined |
| connectorTextBlockCount = connectorCount > 0 ? connectorCount : undefined |
| } |
|
|
| const durationMs = Date.now() - start |
| const durationMsIncludingRetries = Date.now() - startIncludingRetries |
| addToTotalDurationState(durationMsIncludingRetries, durationMs) |
|
|
| logAPISuccess({ |
| model, |
| preNormalizedModel, |
| messageCount, |
| messageTokens, |
| usage, |
| durationMs, |
| durationMsIncludingRetries, |
| attempt, |
| ttftMs, |
| requestId, |
| stopReason, |
| costUSD, |
| didFallBackToNonStreaming, |
| querySource, |
| gateway, |
| queryTracking, |
| permissionMode, |
| globalCacheStrategy, |
| textContentLength, |
| thinkingContentLength, |
| toolUseContentLengths, |
| connectorTextBlockCount, |
| fastMode, |
| previousRequestId, |
| betas, |
| }) |
| |
| void logOTelEvent('api_request', { |
| model, |
| input_tokens: String(usage.input_tokens), |
| output_tokens: String(usage.output_tokens), |
| cache_read_tokens: String(usage.cache_read_input_tokens), |
| cache_creation_tokens: String(usage.cache_creation_input_tokens), |
| cost_usd: String(costUSD), |
| duration_ms: String(durationMs), |
| speed: fastMode ? 'fast' : 'normal', |
| }) |
|
|
| |
| let modelOutput: string | undefined |
| let thinkingOutput: string | undefined |
| let hasToolCall: boolean | undefined |
|
|
| if (isBetaTracingEnabled() && newMessages) { |
| |
| modelOutput = |
| newMessages |
| .flatMap(m => |
| m.message.content |
| .filter(c => c.type === 'text') |
| .map(c => (c as { type: 'text'; text: string }).text), |
| ) |
| .join('\n') || undefined |
|
|
| |
| if (process.env.USER_TYPE === 'ant') { |
| thinkingOutput = |
| newMessages |
| .flatMap(m => |
| m.message.content |
| .filter(c => c.type === 'thinking') |
| .map(c => (c as { type: 'thinking'; thinking: string }).thinking), |
| ) |
| .join('\n') || undefined |
| } |
|
|
| |
| hasToolCall = newMessages.some(m => |
| m.message.content.some(c => c.type === 'tool_use'), |
| ) |
| } |
|
|
| |
| endLLMRequestSpan(llmSpan, { |
| success: true, |
| inputTokens: usage.input_tokens, |
| outputTokens: usage.output_tokens, |
| cacheReadTokens: usage.cache_read_input_tokens, |
| cacheCreationTokens: usage.cache_creation_input_tokens, |
| attempt, |
| modelOutput, |
| thinkingOutput, |
| hasToolCall, |
| ttftMs: ttftMs ?? undefined, |
| requestSetupMs, |
| attemptStartTimes, |
| }) |
|
|
| |
| const teleportInfo = getTeleportedSessionInfo() |
| if (teleportInfo?.isTeleported && !teleportInfo.hasLoggedFirstMessage) { |
| logEvent('tengu_teleport_first_message_success', { |
| session_id: |
| teleportInfo.sessionId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| }) |
| markFirstTeleportMessageLogged() |
| } |
| } |
|
|