Fix #5162: Enhanced VS Code Language Model content filtering error handling

- Added specific error detection for VS Code content filtering scenarios
- Implemented helper methods to identify filtering errors in both Error objects and error-like objects
- Enhanced error messages to provide clear guidance when content is filtered
- Added comprehensive test coverage for content filtering detection
- Supports case-insensitive detection of filtering keywords like 'filtered', 'content policy', 'inappropriate', 'safety', 'blocked', etc.
- Maintains existing error handling for cancellation and other error types
This commit is contained in:
Roo Code 2025-06-30 08:28:22 +00:00
parent 3a8ba27615
commit 186fe01bfd
2 changed files with 311 additions and 0 deletions

View file

@ -0,0 +1,236 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import * as vscode from "vscode"
import { VsCodeLmHandler } from "../vscode-lm"
// Mock vscode module
vi.mock("vscode", () => ({
workspace: {
onDidChangeConfiguration: vi.fn(() => ({ dispose: vi.fn() })),
},
lm: {
selectChatModels: vi.fn(),
},
LanguageModelChatMessage: {
Assistant: vi.fn((content) => ({ role: "assistant", content })),
User: vi.fn((content) => ({ role: "user", content })),
},
CancellationError: class extends Error {
constructor(message?: string) {
super(message)
this.name = "CancellationError"
}
},
CancellationTokenSource: vi.fn(() => ({
token: {},
cancel: vi.fn(),
dispose: vi.fn(),
})),
}))
describe("VsCodeLmHandler Content Filtering", () => {
let handler: VsCodeLmHandler
let mockClient: any
beforeEach(() => {
vi.clearAllMocks()
mockClient = {
id: "test-model",
name: "Test Model",
vendor: "test",
family: "test",
version: "1.0",
maxInputTokens: 8192,
sendRequest: vi.fn(),
countTokens: vi.fn().mockResolvedValue(10),
}
vi.mocked(vscode.lm.selectChatModels).mockResolvedValue([mockClient])
handler = new VsCodeLmHandler({
vsCodeLmModelSelector: { vendor: "test", family: "test" },
})
})
describe("Content Filtering Error Detection", () => {
it("should detect content filtering errors in Error objects", async () => {
const filteringError = new Error("Response was filtered by content policy")
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
it("should detect content filtering errors with 'inappropriate' keyword", async () => {
const filteringError = new Error("Content deemed inappropriate")
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
it("should detect content filtering errors with 'safety' keyword", async () => {
const filteringError = new Error("Safety violation detected")
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
it("should detect content filtering errors with 'blocked' keyword", async () => {
const filteringError = new Error("Request blocked by policy")
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
it("should detect content filtering errors in error objects", async () => {
const filteringError = {
message: "Content filtered",
code: "CONTENT_POLICY_VIOLATION",
}
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
it("should detect content filtering errors in error objects with reason property", async () => {
const filteringError = {
reason: "Content policy violation",
type: "FILTERING_ERROR",
}
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
it("should not detect content filtering for regular errors", async () => {
const regularError = new Error("Network timeout")
mockClient.sendRequest.mockRejectedValue(regularError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow("Network timeout")
})
it("should handle cancellation errors correctly", async () => {
const cancellationError = new vscode.CancellationError()
mockClient.sendRequest.mockRejectedValue(cancellationError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Request cancelled by user/)
})
it("should provide helpful error message for content filtering", async () => {
const filteringError = new Error("Response got filtered FIX!!")
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
try {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
} catch (error) {
expect(error).toBeInstanceOf(Error)
const errorMessage = (error as Error).message
expect(errorMessage).toContain("Response was filtered by VS Code's content policy")
expect(errorMessage).toContain("Try rephrasing your request")
expect(errorMessage).toContain("Original error: Response got filtered FIX!!")
}
})
})
describe("Case Insensitive Detection", () => {
it("should detect content filtering errors case-insensitively", async () => {
const filteringError = new Error("CONTENT POLICY VIOLATION")
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
it("should detect content filtering in error name case-insensitively", async () => {
const filteringError = new Error("Some error")
filteringError.name = "CONTENT_FILTERED_ERROR"
mockClient.sendRequest.mockRejectedValue(filteringError)
const systemPrompt = "You are a helpful assistant"
const messages = [{ role: "user" as const, content: "Hello" }]
await expect(async () => {
const generator = handler.createMessage(systemPrompt, messages)
for await (const chunk of generator) {
// Should not reach here
}
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
})
})
})

View file

@ -287,6 +287,60 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
}
}
private isContentFilteringError(error: Error): boolean {
const message = error.message.toLowerCase()
const name = error.name.toLowerCase()
// Common patterns that indicate content filtering
const filteringPatterns = [
"filtered",
"content policy",
"inappropriate",
"safety",
"blocked",
"restricted",
"content violation",
"policy violation",
"harmful content",
"unsafe content",
]
return filteringPatterns.some((pattern) => message.includes(pattern) || name.includes(pattern))
}
private isContentFilteringErrorObject(error: any): boolean {
if (!error || typeof error !== "object") {
return false
}
// Check various properties that might indicate content filtering
const checkProperties = ["message", "reason", "code", "type", "error", "description"]
for (const prop of checkProperties) {
if (error[prop] && typeof error[prop] === "string") {
const value = error[prop].toLowerCase()
const filteringPatterns = [
"filtered",
"content policy",
"inappropriate",
"safety",
"blocked",
"restricted",
"content violation",
"policy violation",
"harmful content",
"unsafe content",
]
if (filteringPatterns.some((pattern) => value.includes(pattern))) {
return true
}
}
}
return false
}
private async getClient(): Promise<vscode.LanguageModelChat> {
if (!this.client) {
console.debug("Roo Code <Language Model API>: Getting client with options:", {
@ -467,12 +521,33 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
name: error.name,
})
// Check for content filtering errors
if (this.isContentFilteringError(error)) {
throw new Error(
"Roo Code <Language Model API>: Response was filtered by VS Code's content policy. " +
"This may happen when the model's response contains content that VS Code considers inappropriate. " +
"Try rephrasing your request or using a different approach. " +
`Original error: ${error.message}`,
)
}
// Return original error if it's already an Error instance
throw error
} else if (typeof error === "object" && error !== null) {
// Handle error-like objects
const errorDetails = JSON.stringify(error, null, 2)
console.error("Roo Code <Language Model API>: Stream error object:", errorDetails)
// Check if the error object indicates content filtering
if (this.isContentFilteringErrorObject(error)) {
throw new Error(
"Roo Code <Language Model API>: Response was filtered by VS Code's content policy. " +
"This may happen when the model's response contains content that VS Code considers inappropriate. " +
"Try rephrasing your request or using a different approach. " +
`Original error: ${errorDetails}`,
)
}
throw new Error(`Roo Code <Language Model API>: Response stream error: ${errorDetails}`)
} else {
// Fallback for unknown error types