Skip to content

Conversation

@daryllundy
Copy link
Contributor

@daryllundy daryllundy commented Jan 19, 2026

Description

This change updates Jazz's LLM integration to detect tool support on a per‑model basis and only enable tools when the selected model actually supports them. This prevents tool calls from being sent to chat‑only models and surfaces a clear signal back through the agent pipeline when tools are disabled.

Concretely:

  • Extend ModelInfo with a supportsTools flag and wire it through static and dynamically fetched provider models.
  • Cache fetched model metadata per provider so support flags can be reused across requests.
  • Add a buildToolConfig helper in the AI SDK service to:
    • Skip passing tools/toolChoice when the model does not support tools.
    • Record when tools were requested but disabled for the chosen model.
  • Propagate a toolsDisabled flag into ChatCompletionResponse and AgentResponse, so the agent layer and UI can inform users when tools were skipped.
  • Keep existing behavior for models that do support tools, including tool conversion and tool choice handling.

This aligns Jazz's behavior with providers that distinguish between tool‑capable and chat‑only models and avoids confusing "silent failures" when tools are configured but unusable.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📚 Documentation
  • ♻️ Refactoring
  • ⚡ Performance
  • ✅ Tests

How it works

  • For static models in STATIC_PROVIDER_MODELS, each entry now declares supportsTools, defaulting to false unless explicitly enabled.
  • For dynamically fetched models (e.g., OpenRouter/Ollama), ModelFetcherService normalizes provider metadata into ModelInfo and sets supportsTools based on each provider's capabilities.
  • AISDKService resolves the selected model's ModelInfo, calls buildToolConfig, and:
    • Builds an AI SDK tools map only when the model supports tools.
    • Passes a converted toolChoice only when tools are allowed.
    • Logs when tools were requested but skipped for a non‑tool model.
  • The batch and streaming executors attach toolsDisabled: true to AgentResponse when tools were skipped, enabling the UI to show a "tools disabled for this model" hint instead of silently failing tool calls.

Testing

  • Verified agent runs against:
    • A tool‑capable model with tools configured:
      • Tools are passed and executed as before.
      • toolsDisabled remains false.
    • A chat‑only model with tools configured:
      • No tools or toolChoice are sent to the provider.
      • toolsDisabled is set on ChatCompletionResponse and AgentResponse.
      • A log line is emitted indicating that tools were skipped due to lack of support.
  • Confirmed that:
    • Static model lists render correctly with the new supportsTools field.
    • Model fetching and caching still work when provider APIs are unavailable (falls back to static models where applicable).

Checklist

  • Code follows project style guidelines
  • Tests added/updated and passing (or existing tests updated where necessary)
  • Documentation updated or inline comments added where behavior is non‑obvious

Note

Introduces per-model tool capability detection and propagates a clear signal when tools are skipped.

  • Extends ModelInfo with supportsTools; updates static model lists and dynamic fetchers (OpenRouter, Ollama) to populate it; caches fetched model metadata per provider
  • Adds buildToolConfig and toAISDKToolChoice in AI SDK service to omit tools/toolChoice for non-tool models and record toolsDisabled
  • Propagates toolsDisabled through ChatCompletionResponse and AgentResponse; batch/streaming executors attach it to responses; stream processor includes it in final events
  • Minor cleanups: cache model infos, consistent warning formatting

Written by Cursor Bugbot for commit bd84d2e. This will update automatically on new commits. Configure here.

claude and others added 4 commits November 25, 2025 15:54
Implements Option A from issue lvndry#70 to detect and conditionally pass tools
to models based on their capabilities.

Changes:
- Extended ModelInfo interface with optional supportsTools field
- Added supportsTools: true to all static models (OpenAI, Anthropic, etc.)
- Enhanced OpenRouter transformer to detect tool support from supported_parameters
- Enhanced Ollama transformer with pattern-based tool support detection
- Modified ai-sdk-service to conditionally pass tools only when supported
- Added logging and warnings when tools are skipped for incompatible models

This provides maximum compatibility, allowing users to use any model while
gracefully degrading functionality when tools aren't supported.

Fixes lvndry#70
…-support

Resolved conflicts in:
- src/core/constants/models.ts: Took fix/model-detect changes with supportsTools: false for dynamic detection
- src/core/types/llm.ts: Made supportsTools required (not optional)
- src/services/llm/ai-sdk-service.ts: Used async resolveModelInfo with provider-level caching
- src/services/llm/model-fetcher.ts: Used TOOL_PARAMS set and looksToolCapable function for detection
@lvndry lvndry self-requested a review January 19, 2026 20:54
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most models in this list actually support tools

Copy link
Owner

@lvndry lvndry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a better UX would be that if a model does not support tools we should not be able to add tools during creation.

  1. When user create an agent, if model don't support tools, we should not be able to select tools
  2. When user create an agent, if mode support tools we should be able to select tools
  3. When a user edit an agent, if model used to support tools and now doesn't, we clear tools list from config and they should not be able to add tools on edit (tool option should be visible but disabled)
  4. When a user edit an agent, if model support tools, they should be able to edit tools

That way we always make sure that the agent don't try to call tools they can't execute and users understand they the agent has no tools and thus don't except a different behavior

@lvndry lvndry changed the title Fix/70 detect dynamic model tool support chore: detect dynamic model tool support Jan 20, 2026
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

id: "deepseek-chat",
displayName: "DeepSeek Chat",
isReasoningModel: false,
supportsTools: false,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All static models incorrectly marked as non-tool-capable

High Severity

Every model in STATIC_PROVIDER_MODELS has supportsTools: false, including GPT-4o, Claude, Gemini, Mistral Large, and other models that definitely support function calling. This causes buildToolConfig to disable tools for all these providers, completely breaking tool functionality. The PR reviewer also flagged this: "Most models in this list actually support tools."

Fix in Cursor Fix in Web

function looksToolCapable(model: OllamaModel): boolean {
const name = model.name.toLowerCase();
if (KNOWN_OLLAMA_TOOL_MODELS.has(model.name)) return true;
if (name.includes("tool") || name.includes("function")) return true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ollama tool whitelist check uses wrong case

Low Severity

The looksToolCapable function creates a lowercase version of the model name (name = model.name.toLowerCase()) but then checks the original-cased model.name against KNOWN_OLLAMA_TOOL_MODELS, which contains lowercase strings. The substring checks on the next line correctly use the lowercase name. This inconsistency would cause the whitelist check to fail if Ollama returns model names with any uppercase characters.

Fix in Cursor Fix in Web

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⬆️

* main:
  chore: reduce bundle size by 50% (lvndry#129)
  feat: Agent skills (lvndry#127)
  feat: edit file show diff (lvndry#128)
  0.6.2
- Update STATIC_PROVIDER_MODELS to enable tool support for capable models (GPT-4o, Mistral Large, Gemini, etc).
- Create Agent: skip tool selection if model does not support tools.
- Edit Agent: auto-remove tools if model capabilities change; disable tool editing menu for unsupported models.
- Fix Ollama model capability check to be case-insensitive.
@daryllundy daryllundy force-pushed the fix/70-detect-dynamic-model-tool-support branch from bd84d2e to 1e916d8 Compare January 20, 2026 18:50
if (currentPredefinedAgent && currentPredefinedAgent.toolCategoryIds.length > 0) {
await Effect.runPromise(
terminal.warn(
`\n⚠️ The selected model (${llmModel}) does not support tools. The "${currentPredefinedAgent.displayName}" agent template's tools will be ignored.`,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note sure agent's template is the right name for it

Comment on lines 13 to +39
openai: [
{ id: "gpt-5.2-pro", displayName: "GPT-5.2 Pro", isReasoningModel: true },
{ id: "gpt-5.2", displayName: "GPT-5.2", isReasoningModel: true },
{ id: "gpt-5.2-codex", displayName: "GPT-5.2 Codex", isReasoningModel: true },
{ id: "gpt-5.1", displayName: "GPT-5.1", isReasoningModel: true },
{ id: "gpt-5.1-codex", displayName: "GPT-5.1 Codex", isReasoningModel: true },
{ id: "gpt-5.1-codex-mini", displayName: "GPT-5.1 Codex Mini", isReasoningModel: true },
{ id: "gpt-5-pro", displayName: "GPT-5 Pro", isReasoningModel: true },
{ id: "gpt-5", displayName: "GPT-5", isReasoningModel: true },
{ id: "gpt-5-mini", displayName: "GPT-5 Mini", isReasoningModel: true },
{ id: "gpt-5-nano", displayName: "GPT-5 Nano", isReasoningModel: true },
{ id: "gpt-5-codex", displayName: "GPT-5 Codex", isReasoningModel: true },
{ id: "gpt-4.1", displayName: "GPT-4.1", isReasoningModel: false },
{ id: "gpt-4.1-mini", displayName: "GPT-4.1 Mini", isReasoningModel: false },
{ id: "gpt-4.1-nano", displayName: "GPT-4.1 Nano", isReasoningModel: false },
{ id: "gpt-4o", displayName: "GPT-4o", isReasoningModel: false },
{ id: "gpt-4o-mini", displayName: "GPT-4o Mini", isReasoningModel: false },
{ id: "gpt-5.2-pro", displayName: "GPT-5.2 Pro", isReasoningModel: true, supportsTools: false },
{ id: "gpt-5.2", displayName: "GPT-5.2", isReasoningModel: true, supportsTools: false },
{
id: "gpt-5.2-codex",
displayName: "GPT-5.2 Codex",
isReasoningModel: true,
supportsTools: false,
},
{ id: "gpt-5.1", displayName: "GPT-5.1", isReasoningModel: true, supportsTools: false },
{
id: "gpt-5.1-codex",
displayName: "GPT-5.1 Codex",
isReasoningModel: true,
supportsTools: false,
},
{
id: "gpt-5.1-codex-mini",
displayName: "GPT-5.1 Codex Mini",
isReasoningModel: true,
supportsTools: false,
},
{ id: "gpt-5-pro", displayName: "GPT-5 Pro", isReasoningModel: true, supportsTools: false },
{ id: "gpt-5", displayName: "GPT-5", isReasoningModel: true, supportsTools: false },
{ id: "gpt-5-mini", displayName: "GPT-5 Mini", isReasoningModel: true, supportsTools: false },
{ id: "gpt-5-nano", displayName: "GPT-5 Nano", isReasoningModel: true, supportsTools: false },
{ id: "gpt-5-codex", displayName: "GPT-5 Codex", isReasoningModel: true, supportsTools: false },
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these models support tools

Comment on lines +163 to +173
id: "magistral-medium-2506",
displayName: "Magistral Medium",
isReasoningModel: true,
supportsTools: false,
},
{
id: "magistral-small-2506",
displayName: "Magistral Small",
isReasoningModel: true,
supportsTools: false,
},
Copy link
Owner

@lvndry lvndry Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use -latest models + they support tools

https://docs.mistral.ai/capabilities/function_calling

id: model.id,
displayName: `${model.owned_by.toLowerCase()}/${model.id.toLowerCase()}`,
isReasoningModel: false,
supportsTools: false,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function looksToolCapable(model: OllamaModel): boolean {
const name = model.name.toLowerCase();
if (KNOWN_OLLAMA_TOOL_MODELS.has(model.name)) return true;
if (name.includes("tool") || name.includes("function")) return true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⬆️

"response_format:json_schema",
]);

const KNOWN_OLLAMA_TOOL_MODELS = new Set<string>([
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const toolModels = [
"nemotron-3-nano",
"functiongemma",
"devstral-small-2",
"devstral-2",
"ministral-3",
"qwen3-vl",
"gpt-oss",
"deepseek-r1",
"qwen3-coder",
"llama3.1",
"llama3.2",
"mistral",
"qwen2.5",
"qwen3",
"qwen2.5-coder",
"qwen2",
"mistral-nemo",
"llama3.3",
"smollm2",
"mistral-small",
"granite3.1-moe",
"qwq",
"mixtral",
"cogito",
"mistral-small3.2",
"llama4",
"magistral",
"granite3.3",
"phi4-mini",
"granite3.2-vision",
"devstral",
"command-r",
"granite4",
"mistral-small3.1",
"hermes3",
"mistral-large",
"command-r-plus",
"granite3-dense",
"granite3.1-dense",
"deepseek-v3.1",
"qwen3-next",
"llama3-groq-tool-use",
"aya-expanse",
"granite3-moe",
"granite3.2",
"nemotron-mini",
"athene-v2",
"nemotron",
"firefunction-v2",
"command-r7b",
"command-a",
"command-r7b-arabic",
"olmo-3.1",
"gpt-oss-safeguard",
"rnj-1",
"glm-4.7-flash",
];


function looksToolCapable(model: OllamaModel): boolean {
const name = model.name.toLowerCase();
if (KNOWN_OLLAMA_TOOL_MODELS.has(name)) return true;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (KNOWN_OLLAMA_TOOL_MODELS.find((name_prefix) => name.startsWith(name_prefix)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants