Skip to content

bug(decopilot): web_search uses chat model's provider instead of deep-research model's provider #3367

@viktormarinho

Description

@viktormarinho

Summary

web_search (Decopilot deep-research tool) uses the chat / thinking model's provider to call the deep-research model. When the two are configured on different providers — e.g. chat on OpenRouter and deep-research on Gemini — the call fails with 400 not a valid model ID because OpenRouter is asked to route a Gemini-only model id.

Reproduction

  1. Configure the "thinking" tier to a model on OpenRouter (e.g. anthropic/claude-opus-4.7).
  2. Configure the "deep research" tier to a Gemini Deep Research model (e.g. deep-research-max-preview-04-2026).
  3. Send a chat message in web-search mode.

Observed

The web_search tool errors instantly (~30–65 ms) with:

data: {"type":"tool-output-error", "errorText":"No output generated. Check the stream for errors."}

Server-side logs:

deep-research-max-preview-04-2026 is not a valid model ID
AI_APICallError (from @openrouter/ai-sdk-provider)

The model dispatches a recovery text response from its own training data instead.

Root cause

apps/mesh/src/api/routes/decopilot/built-in-tools/index.ts:205-213 wires the tool with a single provider:

if (provider && models.deepResearch) {
  tools.web_search = createWebSearchTool(writer, {
    provider,                                   // resolved for thinking model
    deepResearchModelInfo: models.deepResearch, // might be a different provider's id
    ctx, toolOutputMap, taskId,
  });
}

Inside web-search.ts:67-69, 166:

const modelId = deepResearchModelInfo.id;
const asyncResearch = provider.asyncResearch;
const useAsyncResearch = asyncResearch?.canHandle(modelId) === true;
// ...
const model = provider.aiSdk.languageModel(deepResearchModelInfo.id);

provider is the chat-side provider, so:

  1. provider.asyncResearch?.canHandle(geminiModelId) returns false (OpenRouter has no path for Gemini's Interactions API)
  2. Code falls into the synchronous branch → provider.aiSdk.languageModel(geminiModelId) → OpenRouter POST → 400.

Fix sketch

The models.deepResearch info needs to carry its own provider (or a providerId the tool resolves), so createWebSearchTool is called with the deep-research model's actual provider instead of the chat-side one.

Concretely:

  • Tier resolution that produces models.deepResearch already knows which credential/provider that model came from — currently that info is dropped before reaching the tool.
  • Plumb deepResearchProvider through createWebSearchTool (alongside deepResearchModelInfo), and have the tool use it for both the asyncResearch.canHandle check and the aiSdk.languageModel(...) call.
  • The existing image tool path (createGenerateImageTool with imageModelInfo) likely has the same issue and should be audited.

Workarounds

  • Set the deep-research tier to a model reachable through the chat model's provider (e.g. perplexity/sonar-deep-research on OpenRouter), or
  • Switch the chat tier to a Gemini model so both share the Gemini provider.

Out of scope

This is unrelated to #3353 (the JetStream / subscribe-model refactor) — the streaming layer surfaces the tool error correctly. This is a tool-registration / provider-routing issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions