Files
continue/core/llm/toolSupport.ts
2026-01-19 10:39:17 -05:00

418 lines
11 KiB
TypeScript

import { parseProxyModelName } from "@continuedev/config-yaml";
import { ModelDescription } from "..";
export const PROVIDER_TOOL_SUPPORT: Record<string, (model: string) => boolean> =
{
"continue-proxy": (model) => {
try {
const { provider, model: _model } = parseProxyModelName(model);
if (provider && _model && provider !== "continue-proxy") {
const fn = PROVIDER_TOOL_SUPPORT[provider];
if (fn) {
return fn(_model);
}
}
} catch (e) {}
return ["claude", "gpt-4", "o3", "gemini", "gemma"].some((part) =>
model.toLowerCase().startsWith(part),
);
},
anthropic: (model) => {
if (model.includes("claude-2") || model.includes("claude-instant")) {
return false;
}
if (["claude"].some((part) => model.toLowerCase().startsWith(part))) {
return true;
}
return false;
},
azure: (model) => {
if (
model.toLowerCase().startsWith("gpt-4") ||
model.toLowerCase().startsWith("o3")
)
return true;
return false;
},
openai: (model) => {
const lower = model.toLowerCase();
// https://platform.openai.com/docs/guides/function-calling#models-supporting-function-calling
if (
lower.startsWith("gpt-4") ||
lower.startsWith("gpt-5") ||
lower.startsWith("o3")
) {
return true;
}
// LGAI EXAONE models expose an OpenAI-compatible API with tool
// calling support when served via frameworks like vLLM
if (lower.includes("exaone")) {
return true;
}
if (lower.includes("gpt-oss")) {
return true;
}
// https://ai.google.dev/gemma/docs/capabilities/function-calling
if (lower.startsWith("gemma")) {
return true;
}
// firworks-ai https://docs.fireworks.ai/guides/function-calling
if (model.startsWith("accounts/fireworks/models/")) {
switch (model.substring(26)) {
case "llama-v3p1-405b-instruct":
case "llama-v3p1-70b-instruct":
case "qwen2p5-72b-instruct":
case "firefunction-v1":
case "firefunction-v2":
return true;
default:
return false;
}
}
return false;
},
cohere: (model) => {
const lower = model.toLowerCase();
if (lower.startsWith("command-a-vision")) {
return false;
}
return lower.startsWith("command");
},
gemini: (model) => {
// All gemini models support function calling
return model.toLowerCase().includes("gemini");
},
vertexai: (model) => {
const lowerCaseModel = model.toLowerCase();
// All gemini models except flash 2.0 lite support function calling
if (lowerCaseModel.includes("lite")) {
return false;
}
return ["claude", "gemini"].some((val) => lowerCaseModel.includes(val));
},
xAI: (model) => {
const lowerCaseModel = model.toLowerCase();
return ["grok-3", "grok-4", "grok-4-1", "grok-code"].some((val) =>
lowerCaseModel.includes(val),
);
},
bedrock: (model) => {
if (model.includes("claude-2") || model.includes("claude-instant")) {
return false;
}
if (
[
"claude",
"nova-lite",
"nova-pro",
"nova-micro",
"nova-premier",
"gpt-oss",
].some((part) => model.toLowerCase().includes(part))
) {
return true;
}
return false;
},
mistral: (model) => {
// https://docs.mistral.ai/capabilities/function_calling/
return (
!model.toLowerCase().includes("mamba") &&
[
"devstral",
"codestral",
"mistral-large",
"mistral-small",
"pixtral",
"ministral",
"mistral-nemo",
"devstral",
].some((part) => model.toLowerCase().includes(part))
);
},
// https://ollama.com/search?c=tools
ollama: (model) => {
let modelName = "";
// Extract the model name after the last slash to support other registries
if (model.includes("/")) {
let parts = model.split("/");
modelName = parts[parts.length - 1];
} else {
modelName = model;
}
if (
["vision", "math", "guard", "mistrallite", "mistral-openorca"].some(
(part) => modelName.toLowerCase().includes(part),
)
) {
return false;
}
if (
[
"cogito",
"llama3.3",
"qwq",
"llama3.2",
"llama3.1",
"qwen2",
"qwen3",
"mixtral",
"command-r",
"command-a",
"smollm2",
"hermes3",
"athene-v2",
"nemotron",
"llama3-groq",
"granite3",
"granite-3",
"granite4",
"granite-4",
"aya-expanse",
"firefunction-v2",
"mistral",
"devstral",
"exaone",
"gpt-oss",
].some((part) => modelName.toLowerCase().includes(part))
) {
return true;
}
return false;
},
sambanova: (model) => {
// https://docs.sambanova.ai/cloud/docs/capabilities/function-calling
if (
model.toLowerCase().startsWith("meta-llama-3") ||
model.toLowerCase().includes("llama-4") ||
model.toLowerCase().includes("deepseek") ||
model.toLowerCase().includes("gpt") ||
model.toLowerCase().includes("qwen")
) {
return true;
}
return false;
},
deepseek: (model) => {
// https://api-docs.deepseek.com/quick_start/pricing
// https://api-docs.deepseek.com/guides/function_calling
if (model === "deepseek-reasoner" || model === "deepseek-chat") {
return true;
}
return false;
},
watsonx: (model) => {
if (model.toLowerCase().includes("guard")) {
return false;
}
if (
[
"llama-3",
"llama-4",
"mistral",
"codestral",
"granite-3",
"devstral",
].some((part) => model.toLowerCase().includes(part))
) {
return true;
}
return false;
},
openrouter: (model) => {
// https://openrouter.ai/models?fmt=cards&supported_parameters=tools
// Specific free models that don't support tools
// Fixes issue #6619 - moonshotai/kimi-k2:free causing 400 errors
if (model.toLowerCase() === "moonshotai/kimi-k2:free") {
return false;
}
if (
["vision", "math", "guard", "mistrallite", "mistral-openorca"].some(
(part) => model.toLowerCase().includes(part),
)
) {
return false;
}
const supportedPrefixes = [
"openai/gpt-3.5",
"openai/gpt-4",
"openai/o1",
"openai/o3",
"openai/o4",
"openai/gpt-oss",
"anthropic/claude",
"microsoft/phi-3",
"google/gemini-flash-1.5",
"google/gemini-2",
"google/gemini-pro",
"x-ai/grok",
"qwen/qwen3",
"qwen/qwen-",
"cohere/command-r",
"cohere/command-a",
"ai21/jamba-1.6",
"mistralai/mistral",
"mistralai/ministral",
"mistralai/codestral",
"mistralai/mixtral",
"mistral/ministral",
"mistral/devstral",
"mistralai/pixtral",
"meta-llama/llama-3.3",
"amazon/nova",
"deepseek/deepseek-r1",
"deepseek/deepseek-chat",
"meta-llama/llama-4",
"all-hands/openhands-lm-32b",
"lgai-exaone/exaone",
];
for (const prefix of supportedPrefixes) {
if (model.toLowerCase().startsWith(prefix)) {
return true;
}
}
const specificModels = [
"qwen/qwq-32b",
"qwen/qwen-2.5-72b-instruct",
"meta-llama/llama-3.2-3b-instruct",
"meta-llama/llama-3-8b-instruct",
"meta-llama/llama-3-70b-instruct",
"arcee-ai/caller-large",
"nousresearch/hermes-3-llama-3.1-70b",
"moonshotai/kimi-k2",
];
for (const model of specificModels) {
if (model.toLowerCase() === model) {
return true;
}
}
const supportedContains = ["llama-3.1"];
for (const model of supportedContains) {
if (model.toLowerCase().includes(model)) {
return true;
}
}
return false;
},
moonshot: (model) => {
// support moonshot models
// https://platform.moonshot.ai/docs/pricing/chat#concepts
if (
model.toLowerCase().startsWith("kimi") &&
model.toLowerCase() !== "kimi-thinking-preview"
) {
return true;
}
if (model.toLowerCase().startsWith("moonshot")) {
return true;
}
return false;
},
novita: (model) => {
const lower = model.toLowerCase();
// Exact match models
const exactMatches = [
"deepseek/deepseek-r1-0528",
"deepseek/deepseek-r1-turbo",
"deepseek/deepseek-v3-0324",
"deepseek/deepseek-v3-turbo",
"meta-llama/llama-3.3-70b-instruct",
"qwen/qwen-2.5-72b-instruct",
"zai-org/glm-4.5",
"moonshotai/kimi-k2-instruct",
];
if (exactMatches.includes(lower)) {
return true;
}
// Prefix match models
const prefixMatches = ["qwen/qwen3", "openai/gpt-oss"];
for (const prefix of prefixMatches) {
if (lower.startsWith(prefix)) {
return true;
}
}
return false;
},
ovhcloud: (model) => {
const lower = model.toLowerCase();
// Models that support tools according to OVHcloud AI Endpoints catalog
const toolSupportingModels = [
"llama-3.1-8b-instruct",
"qwen3-32b",
"qwen3-coder-30b-a3b-instruct",
"meta-llama-3_3-70b-instruct",
"deepseek-r1-distill-llama-70b",
"mistral-small-3.2-24b-instruct-2506",
"gpt-oss-120b",
"mistral-nemo-instruct-2407",
"gpt-oss-20b",
"qwen2.5-coder-32b-instruct",
];
if (toolSupportingModels.some((m) => lower === m)) {
return true;
}
return false;
},
};
export function isRecommendedAgentModel(modelName: string): boolean {
// AND behavior
const recs: RegExp[][] = [
[/o[134]/],
[/deepseek/, /r1|reasoner/],
[/gemini/, /2\.5/, /pro/],
[/gemini/, /3-pro/],
[/gpt/, /-5|5\.1/],
[/claude/, /sonnet/, /3\.7|3-7|-4/],
[/claude/, /opus/, /-4/],
[/grok-code/],
[/grok-4-1|grok-4\.1/],
[/claude/, /4-5/],
];
for (const combo of recs) {
if (combo.every((regex) => modelName.toLowerCase().match(regex))) {
return true;
}
}
return false;
}
export function modelSupportsNativeTools(modelDescription: ModelDescription) {
if (modelDescription.capabilities?.tools !== undefined) {
return modelDescription.capabilities.tools;
}
const providerSupport = PROVIDER_TOOL_SUPPORT[modelDescription.provider];
if (!providerSupport) {
return false;
}
return providerSupport(modelDescription.model) ?? false;
}