feat: add telemetry error capture to xAI provider

- Import TelemetryService from @roo-code/telemetry
- Import ApiProviderError from @roo-code/types
- Capture exceptions to telemetry in createMessage() catch block
- Capture exceptions to telemetry in completePrompt() catch block
- Add tests verifying telemetry capture on errors

Follows the same pattern as PR #10073 for OpenRouter.
This commit is contained in:
daniel-lxs 2025-12-15 18:02:52 -05:00
parent bf81fa7237
commit 5b17cf1b66
No known key found for this signature in database
GPG key ID: 21C74479048B3AA6
2 changed files with 79 additions and 1 deletions

View file

@ -11,6 +11,16 @@ vitest.mock("openai", () => {
}
})
const mockCaptureException = vitest.fn()
vitest.mock("@roo-code/telemetry", () => ({
TelemetryService: {
instance: {
captureException: (...args: unknown[]) => mockCaptureException(...args),
},
},
}))
import OpenAI from "openai"
import type { Anthropic } from "@anthropic-ai/sdk"
@ -25,6 +35,7 @@ describe("XAIHandler", () => {
// Reset all mocks
vi.clearAllMocks()
mockCreate.mockClear()
mockCaptureException.mockClear()
// Create handler with mock
handler = new XAIHandler({})
@ -140,6 +151,66 @@ describe("XAIHandler", () => {
await expect(handler.completePrompt("test prompt")).rejects.toThrow(`xAI completion error: ${errorMessage}`)
})
describe("Telemetry Error Capture", () => {
it("captures telemetry when createMessage throws an exception", async () => {
const errorMessage = "Connection failed"
mockCreate.mockRejectedValueOnce(new Error(errorMessage))
const generator = handler.createMessage("test prompt", [])
await expect(generator.next()).rejects.toThrow()
expect(mockCaptureException).toHaveBeenCalledWith(
expect.objectContaining({
message: errorMessage,
provider: "xAI",
modelId: xaiDefaultModelId,
operation: "createMessage",
}),
)
})
it("captures telemetry when completePrompt throws an exception", async () => {
const errorMessage = "API error"
mockCreate.mockRejectedValueOnce(new Error(errorMessage))
await expect(handler.completePrompt("test prompt")).rejects.toThrow()
expect(mockCaptureException).toHaveBeenCalledWith(
expect.objectContaining({
message: errorMessage,
provider: "xAI",
modelId: xaiDefaultModelId,
operation: "completePrompt",
}),
)
})
it("captures telemetry with correct model ID when using a specific model", async () => {
const specificModelHandler = new XAIHandler({ apiModelId: "grok-3" })
const errorMessage = "Model error"
mockCreate.mockRejectedValueOnce(new Error(errorMessage))
await expect(specificModelHandler.completePrompt("test prompt")).rejects.toThrow()
expect(mockCaptureException).toHaveBeenCalledWith(
expect.objectContaining({
message: errorMessage,
provider: "xAI",
modelId: "grok-3",
operation: "completePrompt",
}),
)
})
it("captures telemetry and still re-throws the error", async () => {
const errorMessage = "Network error"
mockCreate.mockRejectedValueOnce(new Error(errorMessage))
await expect(handler.completePrompt("test prompt")).rejects.toThrow(`xAI completion error: ${errorMessage}`)
expect(mockCaptureException).toHaveBeenCalledTimes(1)
})
})
it("createMessage should yield text content from stream", async () => {
const testContent = "This is test content"

View file

@ -1,7 +1,8 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { type XAIModelId, xaiDefaultModelId, xaiModels } from "@roo-code/types"
import { type XAIModelId, xaiDefaultModelId, xaiModels, ApiProviderError } from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
import type { ApiHandlerOptions } from "../../shared/api"
@ -79,6 +80,9 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
try {
stream = await this.client.chat.completions.create(requestOptions)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "createMessage")
TelemetryService.instance.captureException(apiError)
throw handleOpenAIError(error, this.providerName)
}
@ -158,6 +162,9 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
return response.choices[0]?.message.content || ""
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "completePrompt")
TelemetryService.instance.captureException(apiError)
throw handleOpenAIError(error, this.providerName)
}
}