π File detail
services/api/logging.ts
π― Use case
This file lives under βservices/β, which covers long-lived services (LSP, MCP, OAuth, tool execution, memory, compaction, voice, settings sync, β¦). On the API surface it exposes GlobalCacheStrategy, logAPIQuery, logAPIError, logAPISuccessAndDuration, and EMPTY_USAGE β mainly functions, hooks, or classes. Dependencies touch bun:bundle, @anthropic-ai, and src. It composes internal code from entrypoints, utils, analytics, emptyUsage, and errors (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import { feature } from 'bun:bundle' import { APIError } from '@anthropic-ai/sdk' import type { BetaStopReason, BetaUsage as Usage,
π€ Exports (heuristic)
GlobalCacheStrategylogAPIQuerylogAPIErrorlogAPISuccessAndDurationEMPTY_USAGE
π External import roots
Package roots from from "β¦" (relative paths omitted).
bun:bundle@anthropic-aisrc
π₯οΈ Source preview
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 }
// Strategy used for global prompt caching
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'
// Gateway fingerprints for detecting AI gateways from response headers
const GATEWAY_FINGERPRINTS: Partial<
Record<KnownGateway, { prefixes: string[] }>
> = {
// https://docs.litellm.ai/docs/proxy/response_headers
litellm: {
prefixes: ['x-litellm-'],
},
// https://docs.helicone.ai/helicone-headers/header-directory
helicone: {
prefixes: ['helicone-'],
},
// https://portkey.ai/docs/api-reference/response-schema
portkey: {
prefixes: ['x-portkey-'],
},
// https://developers.cloudflare.com/ai-gateway/evaluations/add-human-feedback-api/
'cloudflare-ai-gateway': {
prefixes: ['cf-aig-'],
},
// https://developer.konghq.com/ai-gateway/ β X-Kong-Upstream-Latency, X-Kong-Proxy-Latency
kong: {
prefixes: ['x-kong-'],
},
// https://www.braintrust.dev/docs/guides/proxy β x-bt-used-endpoint, x-bt-cached
braintrust: {
prefixes: ['x-bt-'],
},
}
// Gateways that use provider-owned domains (not self-hosted), so the
// ANTHROPIC_BASE_URL hostname is a reliable signal even without a
// distinctive response header.
const GATEWAY_HOST_SUFFIXES: Partial<Record<KnownGateway, string[]>> = {
// https://docs.databricks.com/aws/en/ai-gateway/
databricks: [
'.cloud.databricks.com',
'.azuredatabricks.net',
'.gcp.databricks.com',
],
}
function detectGateway({
headers,
baseUrl,
}: {
headers?: globalThis.Headers
baseUrl?: string
}): KnownGateway | undefined {
if (headers) {
// Header names are already lowercase from the Headers API
const headerNames: string[] = []
headers.forEach((_, key) => headerNames.push(key))
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 {
// malformed URL β ignore
}
}
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)
// Log detailed connection error info to debug logs (visible via --debug)
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(),
})
// Log API error event for OTLP
void logOTelEvent('api_error', {
model: model,
error: errStr,
status_code: String(status),
duration_ms: String(durationMs),
attempt: String(attempt),
speed: fastMode ? 'fast' : 'normal',
})
// Pass the span to correctly match responses to requests when beta tracing is enabled
endLLMRequestSpan(llmSpan, {
success: false,
statusCode: status ? parseInt(status) : undefined,
error: errStr,
attempt,
})
// Log first error for teleported sessions (reliability tracking)
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,
// Log cache_deleted_input_tokens for cache editing analysis. Casts needed
// because the field is intentionally not on NonNullableUsage (excluded from
// external builds). Set by updateUsage() when cache editing is active.
...(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,
})
// Log API request event for OTLP
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',
})
// Extract model output, thinking output, and tool call flag when beta tracing is enabled
let modelOutput: string | undefined
let thinkingOutput: string | undefined
let hasToolCall: boolean | undefined
if (isBetaTracingEnabled() && newMessages) {
// Model output - visible to all users
modelOutput =
newMessages
.flatMap(m =>
m.message.content
.filter(c => c.type === 'text')
.map(c => (c as { type: 'text'; text: string }).text),
)
.join('\n') || undefined
// Thinking output - Ant-only (build-time gated)
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
}
// Check if any tool_use blocks were in the output
hasToolCall = newMessages.some(m =>
m.message.content.some(c => c.type === 'tool_use'),
)
}
// Pass the span to correctly match responses to requests when beta tracing is enabled
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,
})
// Log first successful message for teleported sessions (reliability tracking)
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()
}
}