mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-06 02:47:56 +00:00
fix: filter out OpenRouter models without tool calling support
Models that do not report "tools" in their supported_parameters from
OpenRouter now get filtered out of both getOpenRouterModels() and
getOpenRouterModelEndpoints(). This matches the existing behavior in
the Ollama provider and prevents users from selecting models that
cannot work with Roo Code's tool-based architecture.
Without this filtering, models like Nemotron 3 Super 120b-a12b that
may not support tool calling through OpenRouter produce broken output
(e.g. wrapping code in [{...}] formatting) because OpenRouter's
transforms do not reliably convert tool call syntax for all models.
Fixes #11968
This commit is contained in:
parent
137d3f4fd8
commit
48be6c2f05
2 changed files with 142 additions and 1 deletions
|
|
@ -89,6 +89,7 @@ describe("OpenRouter API", () => {
|
|||
input_modalities: ["text", "image"],
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
supported_parameters: ["tools", "max_tokens", "temperature", "reasoning"],
|
||||
endpoints: [
|
||||
{
|
||||
provider_name: "Google Vertex",
|
||||
|
|
@ -196,6 +197,7 @@ describe("OpenRouter API", () => {
|
|||
input_modalities: ["text", "image"],
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
supported_parameters: ["tools", "max_tokens", "temperature", "reasoning"],
|
||||
endpoints: [
|
||||
{
|
||||
provider_name: "Anthropic",
|
||||
|
|
@ -265,6 +267,132 @@ describe("OpenRouter API", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("getOpenRouterModels - tool support filtering", () => {
|
||||
it("filters out models without tool support in supported_parameters", async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
id: "test/with-tools",
|
||||
name: "Model With Tools",
|
||||
context_length: 128000,
|
||||
architecture: {
|
||||
input_modalities: ["text"],
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
top_provider: { max_completion_tokens: 8192 },
|
||||
supported_parameters: ["tools", "max_tokens", "temperature"],
|
||||
},
|
||||
{
|
||||
id: "test/without-tools",
|
||||
name: "Model Without Tools",
|
||||
context_length: 128000,
|
||||
architecture: {
|
||||
input_modalities: ["text"],
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
top_provider: { max_completion_tokens: 8192 },
|
||||
supported_parameters: ["max_tokens", "temperature"],
|
||||
},
|
||||
{
|
||||
id: "test/no-params",
|
||||
name: "Model With No Params",
|
||||
context_length: 128000,
|
||||
architecture: {
|
||||
input_modalities: ["text"],
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
top_provider: { max_completion_tokens: 8192 },
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const axios = await import("axios")
|
||||
const getSpy = vi.spyOn(axios.default, "get").mockResolvedValue(mockResponse)
|
||||
|
||||
const models = await getOpenRouterModels()
|
||||
|
||||
// Only the model with tools support should be included
|
||||
expect(models["test/with-tools"]).toBeDefined()
|
||||
expect(models["test/without-tools"]).toBeUndefined()
|
||||
expect(models["test/no-params"]).toBeUndefined()
|
||||
|
||||
getSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe("getOpenRouterModelEndpoints - tool support filtering", () => {
|
||||
it("returns empty when model does not support tools", async () => {
|
||||
const mockEndpointsResponse = {
|
||||
data: {
|
||||
data: {
|
||||
id: "test/no-tools-model",
|
||||
name: "No Tools Model",
|
||||
architecture: {
|
||||
input_modalities: ["text"],
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
supported_parameters: ["max_tokens", "temperature"],
|
||||
endpoints: [
|
||||
{
|
||||
provider_name: "Provider A",
|
||||
name: "No Tools Model",
|
||||
context_length: 128000,
|
||||
max_completion_tokens: 8192,
|
||||
pricing: { prompt: "0.000003", completion: "0.000015" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const axios = await import("axios")
|
||||
const getSpy = vi.spyOn(axios.default, "get").mockResolvedValue(mockEndpointsResponse)
|
||||
|
||||
const endpoints = await getOpenRouterModelEndpoints("test/no-tools-model")
|
||||
|
||||
expect(Object.keys(endpoints).length).toBe(0)
|
||||
|
||||
getSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("returns endpoints when model supports tools", async () => {
|
||||
const mockEndpointsResponse = {
|
||||
data: {
|
||||
data: {
|
||||
id: "test/tools-model",
|
||||
name: "Tools Model",
|
||||
architecture: {
|
||||
input_modalities: ["text"],
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
supported_parameters: ["tools", "max_tokens", "temperature"],
|
||||
endpoints: [
|
||||
{
|
||||
provider_name: "Provider A",
|
||||
name: "Tools Model",
|
||||
context_length: 128000,
|
||||
max_completion_tokens: 8192,
|
||||
pricing: { prompt: "0.000003", completion: "0.000015" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const axios = await import("axios")
|
||||
const getSpy = vi.spyOn(axios.default, "get").mockResolvedValue(mockEndpointsResponse)
|
||||
|
||||
const endpoints = await getOpenRouterModelEndpoints("test/tools-model")
|
||||
|
||||
expect(Object.keys(endpoints).length).toBe(1)
|
||||
expect(endpoints["Provider A"]).toBeDefined()
|
||||
|
||||
getSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe("parseOpenRouterModel", () => {
|
||||
it("sets claude-sonnet-4.6 model to Anthropic max tokens", () => {
|
||||
const mockModel = {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,14 @@ export async function getOpenRouterModels(options?: ApiHandlerOptions): Promise<
|
|||
continue
|
||||
}
|
||||
|
||||
// Skip models that don't support tool calling. Models without tool
|
||||
// capability produce broken output (e.g. wrapping code in [{...}]
|
||||
// formatting) because Roo Code relies on native tool calling.
|
||||
// This matches the filtering behavior used by the Ollama provider.
|
||||
if (!supported_parameters.includes("tools")) {
|
||||
continue
|
||||
}
|
||||
|
||||
const parsedModel = parseOpenRouterModel({
|
||||
id,
|
||||
model,
|
||||
|
|
@ -155,13 +163,18 @@ export async function getOpenRouterModelEndpoints(
|
|||
console.error("OpenRouter model endpoints response is invalid", result.error.format())
|
||||
}
|
||||
|
||||
const { id, architecture, endpoints } = data
|
||||
const { id, architecture, supported_parameters, endpoints } = data
|
||||
|
||||
// Skip image generation models (models that output images)
|
||||
if (architecture?.output_modalities?.includes("image")) {
|
||||
return models
|
||||
}
|
||||
|
||||
// Skip models that don't support tool calling (same rationale as getOpenRouterModels)
|
||||
if (!supported_parameters?.includes("tools")) {
|
||||
return models
|
||||
}
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
models[endpoint.tag ?? endpoint.provider_name] = parseOpenRouterModel({
|
||||
id,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue