Compare commits

...
Author SHA1 Message Date
Arafatkatze 91a1f479c7 Add API request checkpoint telemetry 2026-06-03 21:55:56 -07:00
2 changed files with 89 additions and 0 deletions
+57
View File
@@ -2017,21 +2017,78 @@ export class Task {
// saves task history item which we use to keep track of conversation history deleted range
}
const apiRequestCheckpointBase = {
ulid: this.ulid,
taskId: this.taskId,
provider: providerInfo.providerId,
model: providerInfo.model.id,
mode: providerInfo.mode,
apiRequestCount: this.taskState.apiRequestCount,
isNativeToolCall: this.useNativeToolCalls,
}
const apiRequestStartTime = performance.now()
telemetryService.captureApiRequestCheckpoint({
...apiRequestCheckpointBase,
stage: "create_message_start",
requestId: this.getApiRequestIdSafe(),
})
// Response API requires native tool calls to be enabled
const stream = this.api.createMessage(systemPrompt, contextManagementMetadata.truncatedConversationHistory, tools)
telemetryService.captureApiRequestCheckpoint({
...apiRequestCheckpointBase,
stage: "stream_created",
requestId: this.getApiRequestIdSafe(),
durationMs: Math.round(performance.now() - apiRequestStartTime),
})
const iterator = stream[Symbol.asyncIterator]()
try {
// awaiting first chunk to see if it will throw an error
this.taskState.isWaitingForFirstChunk = true
telemetryService.captureApiRequestCheckpoint({
...apiRequestCheckpointBase,
stage: "first_chunk_wait_start",
requestId: this.getApiRequestIdSafe(),
durationMs: Math.round(performance.now() - apiRequestStartTime),
})
const firstChunk = await iterator.next()
const firstChunkType =
firstChunk.value && typeof firstChunk.value === "object" && "type" in firstChunk.value
? String((firstChunk.value as { type?: unknown }).type)
: undefined
telemetryService.captureApiRequestCheckpoint({
...apiRequestCheckpointBase,
stage: "first_chunk_received",
requestId: this.getApiRequestIdSafe(),
durationMs: Math.round(performance.now() - apiRequestStartTime),
firstChunkType,
})
yield firstChunk.value
this.taskState.isWaitingForFirstChunk = false
} catch (error) {
const isContextWindowExceededError = checkContextWindowExceededError(error)
const { model, providerId } = this.getCurrentProviderInfo()
const clineError = ErrorService.get().toClineError(error, model.id, providerId)
const errorType = ClineError.getErrorType(clineError)
telemetryService.captureApiRequestCheckpoint({
...apiRequestCheckpointBase,
stage: "first_chunk_error",
requestId: clineError._error.request_id ?? this.getApiRequestIdSafe(),
durationMs: Math.round(performance.now() - apiRequestStartTime),
errorType,
errorStatus: clineError._error.status,
errorCode: clineError._error.code,
errorMessage: clineError.message,
})
// Capture provider failure telemetry using clineError
ErrorService.get().logMessage(clineError.message)
@@ -264,6 +264,8 @@ export class TelemetryService {
GEMINI_API_PERFORMANCE: "task.gemini_api_performance",
// Tracks when API providers return errors
PROVIDER_API_ERROR: "task.provider_api_error",
// Tracks client-side API request progress before provider output exists
API_REQUEST_CHECKPOINT: "task.api_request_checkpoint",
// Tracks when users enable the focus chain feature
FOCUS_CHAIN_ENABLED: "task.focus_chain_enabled",
// Tracks when users disable the focus chain feature
@@ -1400,6 +1402,36 @@ export class TelemetryService {
this.recordHistogram(TelemetryService.METRICS.ERRORS.PER_TASK, errorCount, errorAttributes)
}
/**
* Records client-side API request progress for diagnosing pre-first-token failures.
*/
public captureApiRequestCheckpoint(args: {
ulid: string
taskId: string
stage: string
provider?: string
model?: string
mode?: Mode
requestId?: string
apiRequestCount?: number
durationMs?: number
firstChunkType?: string
errorType?: string
errorStatus?: number
errorCode?: string
errorMessage?: string
isNativeToolCall?: boolean
}) {
this.capture({
event: TelemetryService.EVENTS.TASK.API_REQUEST_CHECKPOINT,
properties: {
...args,
errorMessage: args.errorMessage?.substring(0, MAX_ERROR_MESSAGE_LENGTH),
timestamp: new Date().toISOString(),
},
})
}
/**
* Records when focus chain is enabled/disabled by the user
* @param enabled Whether focus chain was enabled (true) or disabled (false)