Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/api/providers/__tests__/xai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -25,6 +35,7 @@ describe("XAIHandler", () => {
// Reset all mocks
vi.clearAllMocks()
mockCreate.mockClear()
mockCaptureException.mockClear()

// Create handler with mock
handler = new XAIHandler({})
Expand Down Expand Up @@ -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"

Expand Down
9 changes: 8 additions & 1 deletion src/api/providers/xai.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}
}
Expand Down
Loading