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
236 changes: 236 additions & 0 deletions src/api/providers/__tests__/vscode-lm-content-filtering.test.ts
Original file line number Diff line number Diff line change
@@ -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/)
})
})
})
75 changes: 75 additions & 0 deletions src/api/providers/vscode-lm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:", {
Expand Down Expand Up @@ -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
Expand Down