Skip to content
Merged
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
63 changes: 63 additions & 0 deletions core/llm/toolSupport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,68 @@ describe("PROVIDER_TOOL_SUPPORT", () => {
});
});

describe("lmstudio", () => {
const supportsFn = PROVIDER_TOOL_SUPPORT["lmstudio"];

it("should return true for supported models (same as ollama)", () => {
expect(supportsFn("llama3.1")).toBe(true);
expect(supportsFn("llama3.2-8b")).toBe(true);
expect(supportsFn("qwen2")).toBe(true);
expect(supportsFn("mixtral-8x7b")).toBe(true);
expect(supportsFn("mistral-7b")).toBe(true);
});

it("should return true for LM Studio hyphenated model IDs", () => {
// LM Studio uses hyphenated model identifiers like "Meta-Llama-3.1-8B-Instruct-GGUF"
expect(supportsFn("Meta-Llama-3.1-8B-Instruct-GGUF")).toBe(true);
expect(supportsFn("Meta-Llama-3.2-3B-Instruct")).toBe(true);
expect(supportsFn("Qwen2-7B-Instruct")).toBe(true);
expect(supportsFn("Mixtral-8x7B-Instruct-v0.1")).toBe(true);
expect(supportsFn("Mistral-7B-Instruct-v0.2")).toBe(true);
expect(supportsFn("llama-3.1-8b-instruct")).toBe(true);
});

it("should return false for explicitly unsupported models (same as ollama)", () => {
expect(supportsFn("vision")).toBe(false);
expect(supportsFn("math")).toBe(false);
expect(supportsFn("guard")).toBe(false);
expect(supportsFn("mistrallite")).toBe(false);
expect(supportsFn("mistral-openorca")).toBe(false);
});

it("should return false for mistral-openorca in all forms", () => {
// Hyphenated form (matches Ollama's exclusion directly)
expect(supportsFn("mistral-openorca")).toBe(false);
// Non-hyphenated form (must not bypass exclusion via "mistral" support match)
expect(supportsFn("MistralOpenOrca")).toBe(false);
// With suffix
expect(supportsFn("Mistral-OpenOrca-7B")).toBe(false);
});

it("should return false for hyphenated unsupported model names", () => {
expect(supportsFn("Llama-Vision-Free")).toBe(false);
expect(supportsFn("Math-Solver-7B")).toBe(false);
expect(supportsFn("Guard-Model")).toBe(false);
});

it("should return false for mistrallite in hyphenated forms", () => {
// Hyphenated form "mistral-lite" normalizes to "mistrallite" which must be excluded
expect(supportsFn("Mistral-Lite")).toBe(false);
expect(supportsFn("mistral-lite")).toBe(false);
});

it("should handle case insensitivity (same as ollama)", () => {
expect(supportsFn("LLAMA3.1")).toBe(true);
expect(supportsFn("MIXTRAL-8x7b")).toBe(true);
expect(supportsFn("VISION")).toBe(false);
});

it("should handle case insensitivity with hyphenated names", () => {
expect(supportsFn("META-LLAMA-3.1-8B-INSTRUCT")).toBe(true);
expect(supportsFn("Qwen2-7B-Instruct-GGUF")).toBe(true);
});
});

describe("xAI", () => {
const supportsFn = PROVIDER_TOOL_SUPPORT["xAI"];

Expand Down Expand Up @@ -339,6 +401,7 @@ describe("PROVIDER_TOOL_SUPPORT", () => {
expect(PROVIDER_TOOL_SUPPORT["gemini"]("")).toBe(false);
expect(PROVIDER_TOOL_SUPPORT["bedrock"]("")).toBe(false);
expect(PROVIDER_TOOL_SUPPORT["ollama"]("")).toBe(false);
expect(PROVIDER_TOOL_SUPPORT["lmstudio"]("")).toBe(false);
expect(PROVIDER_TOOL_SUPPORT["novita"]("")).toBe(false);
});

Expand Down
36 changes: 36 additions & 0 deletions core/llm/toolSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,42 @@ export const PROVIDER_TOOL_SUPPORT: Record<string, (model: string) => boolean> =

return false;
},
lmstudio: (model) => {
// LM Studio uses hyphenated model IDs (e.g., "Meta-Llama-3.1-8B-Instruct-GGUF")
// that don't match Ollama's substring patterns (e.g., "llama3.1").
// We check exclusions against BOTH the raw lowercased name and the
// normalized (hyphen-stripped) form so that e.g. "mistral-lite"
// is correctly caught by the "mistrallite" exclusion.
const lower = model.toLowerCase();
const normalized = lower.replace(/-/g, "");

// Exclusions must be checked against both raw and normalized IDs.
// "mistrallite" catches both "mistrallite" (raw) and "mistral-lite"
// (normalized → "mistrallite"). "mistral-openorca" catches both the
// hyphenated form and "MistralOpenOrca" (normalized → "mistralopenorca").
const exclusions = [
"vision",
"math",
"guard",
"mistrallite",
"mistral-openorca",
];
const isExcluded = (name: string) =>
exclusions.some(
(part) =>
name.includes(part) || name.includes(part.replace(/-/g, "")),
);

if (isExcluded(lower) || isExcluded(normalized)) {
return false;
}

// Delegate to Ollama's heuristic with raw name first (covers patterns
// that contain hyphens, e.g. "command-r"), then with the normalized
// name (covers LM Studio IDs like "Meta-Llama-3.1-8B" → "llama3.1").
const ollamaFn = PROVIDER_TOOL_SUPPORT["ollama"];
return ollamaFn(model) || ollamaFn(normalized);
},
sambanova: (model) => {
// https://docs.sambanova.ai/cloud/docs/capabilities/function-calling
if (
Expand Down
Loading