mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add telemetry error capture to Mistral provider
- Import TelemetryService and ApiProviderError - Add providerName property to MistralHandler class - Wrap createMessage() client.chat.stream() call in try/catch with telemetry capture - Add telemetry capture in completePrompt() catch block - Add tests to verify telemetry is called on error for both methods Follows the pattern established in PR #10073 for OpenRouter provider.
This commit is contained in:
parent
bf81fa7237
commit
cf5c75db36
2 changed files with 75 additions and 15 deletions
|
|
@ -1,6 +1,11 @@
|
|||
// Hoist mock functions so they can be used in vi.mock factories
|
||||
const { mockCreate, mockComplete, mockCaptureException } = vi.hoisted(() => ({
|
||||
mockCreate: vi.fn(),
|
||||
mockComplete: vi.fn(),
|
||||
mockCaptureException: vi.fn(),
|
||||
}))
|
||||
|
||||
// Mock Mistral client - must come before other imports
|
||||
const mockCreate = vi.fn()
|
||||
const mockComplete = vi.fn()
|
||||
vi.mock("@mistralai/mistralai", () => {
|
||||
return {
|
||||
Mistral: vi.fn().mockImplementation(() => ({
|
||||
|
|
@ -38,8 +43,18 @@ vi.mock("@mistralai/mistralai", () => {
|
|||
}
|
||||
})
|
||||
|
||||
// Mock TelemetryService
|
||||
vi.mock("@roo-code/telemetry", () => ({
|
||||
TelemetryService: {
|
||||
instance: {
|
||||
captureException: mockCaptureException,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import type OpenAI from "openai"
|
||||
import { ApiProviderError } from "@roo-code/types"
|
||||
import { MistralHandler } from "../mistral"
|
||||
import type { ApiHandlerOptions } from "../../../shared/api"
|
||||
import type { ApiHandlerCreateMessageMetadata } from "../../index"
|
||||
|
|
@ -59,6 +74,7 @@ describe("MistralHandler", () => {
|
|||
handler = new MistralHandler(mockOptions)
|
||||
mockCreate.mockClear()
|
||||
mockComplete.mockClear()
|
||||
mockCaptureException.mockClear()
|
||||
})
|
||||
|
||||
describe("constructor", () => {
|
||||
|
|
@ -135,7 +151,24 @@ describe("MistralHandler", () => {
|
|||
|
||||
it("should handle errors gracefully", async () => {
|
||||
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
||||
await expect(handler.createMessage(systemPrompt, messages).next()).rejects.toThrow("API Error")
|
||||
await expect(handler.createMessage(systemPrompt, messages).next()).rejects.toThrow(
|
||||
"Mistral completion error: API Error",
|
||||
)
|
||||
})
|
||||
|
||||
it("should capture telemetry exception on createMessage error", async () => {
|
||||
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
||||
|
||||
await expect(handler.createMessage(systemPrompt, messages).next()).rejects.toThrow()
|
||||
|
||||
expect(mockCaptureException).toHaveBeenCalledTimes(1)
|
||||
expect(mockCaptureException).toHaveBeenCalledWith(expect.any(ApiProviderError))
|
||||
|
||||
const capturedError = mockCaptureException.mock.calls[0][0] as ApiProviderError
|
||||
expect(capturedError.message).toBe("API Error")
|
||||
expect(capturedError.provider).toBe("Mistral")
|
||||
expect(capturedError.modelId).toBe("codestral-latest")
|
||||
expect(capturedError.operation).toBe("createMessage")
|
||||
})
|
||||
|
||||
it("should handle thinking content as reasoning chunks", async () => {
|
||||
|
|
@ -483,5 +516,20 @@ describe("MistralHandler", () => {
|
|||
mockComplete.mockRejectedValueOnce(new Error("API Error"))
|
||||
await expect(handler.completePrompt("Test prompt")).rejects.toThrow("Mistral completion error: API Error")
|
||||
})
|
||||
|
||||
it("should capture telemetry exception on completePrompt error", async () => {
|
||||
mockComplete.mockRejectedValueOnce(new Error("API Error"))
|
||||
|
||||
await expect(handler.completePrompt("Test prompt")).rejects.toThrow()
|
||||
|
||||
expect(mockCaptureException).toHaveBeenCalledTimes(1)
|
||||
expect(mockCaptureException).toHaveBeenCalledWith(expect.any(ApiProviderError))
|
||||
|
||||
const capturedError = mockCaptureException.mock.calls[0][0] as ApiProviderError
|
||||
expect(capturedError.message).toBe("API Error")
|
||||
expect(capturedError.provider).toBe("Mistral")
|
||||
expect(capturedError.modelId).toBe("codestral-latest")
|
||||
expect(capturedError.operation).toBe("completePrompt")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
|||
import { Mistral } from "@mistralai/mistralai"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import { type MistralModelId, mistralDefaultModelId, mistralModels, MISTRAL_DEFAULT_TEMPERATURE } from "@roo-code/types"
|
||||
import {
|
||||
type MistralModelId,
|
||||
mistralDefaultModelId,
|
||||
mistralModels,
|
||||
MISTRAL_DEFAULT_TEMPERATURE,
|
||||
ApiProviderError,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { ApiHandlerOptions } from "../../shared/api"
|
||||
|
||||
|
|
@ -43,6 +50,7 @@ type MistralTool = {
|
|||
export class MistralHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
protected options: ApiHandlerOptions
|
||||
private client: Mistral
|
||||
private readonly providerName = "Mistral"
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super()
|
||||
|
|
@ -93,10 +101,15 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
|
|||
requestOptions.toolChoice = "any"
|
||||
}
|
||||
|
||||
// Temporary debug log for QA
|
||||
// console.log("[MISTRAL DEBUG] Raw API request body:", requestOptions)
|
||||
|
||||
const response = await this.client.chat.stream(requestOptions)
|
||||
let response
|
||||
try {
|
||||
response = await this.client.chat.stream(requestOptions)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const apiError = new ApiProviderError(errorMessage, this.providerName, model, "createMessage")
|
||||
TelemetryService.instance.captureException(apiError)
|
||||
throw new Error(`Mistral completion error: ${errorMessage}`)
|
||||
}
|
||||
|
||||
for await (const event of response) {
|
||||
const delta = event.data.choices[0]?.delta
|
||||
|
|
@ -181,9 +194,9 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
|
|||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
try {
|
||||
const { id: model, temperature } = this.getModel()
|
||||
const { id: model, temperature } = this.getModel()
|
||||
|
||||
try {
|
||||
const response = await this.client.chat.complete({
|
||||
model,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
|
|
@ -202,11 +215,10 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
|
|||
|
||||
return content || ""
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`Mistral completion error: ${error.message}`)
|
||||
}
|
||||
|
||||
throw error
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const apiError = new ApiProviderError(errorMessage, this.providerName, model, "completePrompt")
|
||||
TelemetryService.instance.captureException(apiError)
|
||||
throw new Error(`Mistral completion error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue