Skip to content

Custom Endpoint Models (Messages API) Never Send thinking Parameter #319037

Description

@specatron

Custom Endpoint Models (Messages API) Never Send thinking Parameter

Environment

  • VS Code: 1.122.0-insider (build 4459d58b54)
  • Copilot Extension: v0.50.2026052902 (GitHub Copilot Chat)
  • OS: Windows 11 (23H2)
  • Extensions: Issue reproduces with all non-Copilot extensions disabled
  • Custom Endpoint: Anthropic Messages API via proxy (/v1/messages)
  • Dev Tools Console: No errors related to this issue (requests succeed with 200 OK, but response lacks thinking content)

Bug Summary

When using a custom endpoint model configured with apiType: "messages", the thinking parameter is never included in the request body sent to the upstream API - even when the model is configured with "thinking": true and "adaptiveThinking": true in chatLanguageModels.json. The model responds as if extended thinking is not enabled because the parameter is absent from the wire request.

In contrast, the built-in GitHub-hosted Claude models (e.g. claude-opus-4.6 via panel/editAgent) correctly send thinking: {type: "adaptive", display: "summarized"} and receive thinking blocks in responses.

Reproduction Steps

  1. Configure a custom endpoint model in VS Code settings with Messages API:
{
  "github.copilot.chat.models": [
    {
      "name": "my-anthropic-proxy",
      "vendor": "customendpoint",
      "apiKey": "...",
      "apiType": "messages",
      "models": [
        {
          "id": "claude-opus-4-6",
          "name": "Claude Opus 4.6",
          "url": "https://my-proxy.example.com/v1/messages",
          "toolCalling": true,
          "vision": true,
          "thinking": true,
          "adaptiveThinking": true,
          "maxInputTokens": 200000,
          "maxOutputTokens": 128000,
          "supportsReasoningEffort": ["low", "medium", "high"]
        }
      ]
    }
  ]
}
  1. Start a chat using the custom endpoint model.
  2. Inspect the HTTP request body sent to the proxy (e.g. via proxy logs).

Expected: Request body includes "thinking": {"type": "adaptive"} (or "adaptive" with display: "summarized").

Actual: Request body has no thinking field. The model responds without extended thinking.

Root Cause Analysis

Note: The following analysis is based on observing the request body at the proxy and inspecting the bundled extension.js to understand the code paths involved. Variable names are from the minified bundle.

The issue is caused by two gaps in the code path for custom endpoint models:

Issue 1: copilotLanguageModelWrapper does not pass enableThinking

When a custom endpoint model is invoked via the VS Code Language Model API (extensions calling lm.sendRequest()), the request flows through copilotLanguageModelWrapper. This wrapper constructs a modelCapabilities object that is passed to the internal request pipeline:

// In copilotLanguageModelWrapper (approx. offset 13807981 in extension.js)
modelCapabilities: {
  reasoningEffort: typeof o.modelConfiguration?.reasoningEffort == "string"
    ? o.modelConfiguration.reasoningEffort
    : void 0
}

Notice that enableThinking is not included in modelCapabilities. This is the first gate that prevents thinking.

Issue 2: LSn (Messages API body builder) requires enableThinking to set thinking

The function that constructs the Anthropic Messages API request body (minified as LSn, approx. offset 13555239) contains:

if (e.modelCapabilities?.enableThinking) {
  if (r.supportsAdaptiveThinking)
    m = { type: "adaptive", display: "summarized" };
  else if (r.maxThinkingBudget && r.minThinkingBudget) {
    let O = e.postOptions.max_tokens ?? 1024;
    let Q = r.minThinkingBudget ?? 1024;
    let F = 16e3 < Q ? Q : 16e3;
    let B = r.maxThinkingBudget ?? 32e3;
    let M = Math.min(B, O - 1, F);
    M && (m = { type: "enabled", budget_tokens: M });
  }
  // No else clause — m stays undefined if neither condition matches
}

Since enableThinking is never set (Issue 1), this entire block is skipped and m (the thinking config) remains undefined. Even if enableThinking were passed, the function has no fallback: if supportsAdaptiveThinking is falsy and maxThinkingBudget/minThinkingBudget are not set, m still stays undefined.

Contrast with built-in agent path

The built-in agent (panel/editAgent) uses a separate code path that constructs the request body directly:

// panel/editAgent path (approx. offset 13825471)
thinking: Q ? { type: "adaptive" } : P ? { type: "enabled", budget_tokens: P } : void 0

Here Q is derived from _knownModels (populated for GitHub-hosted models). For custom endpoints, _knownModels is void 0, so this also falls through to void 0.

Data Flow Summary

Custom Endpoint Model Request Flow:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
copilotLanguageModelWrapper
  → modelCapabilities: { reasoningEffort: "high" }  ← missing enableThinking!
  → makeChatRequest2()
    → chatMLFetcher.fetchOne()
      → endpoint.createRequestBody(e)
        → LSn(n, e, model, endpoint)
          → if (e.modelCapabilities?.enableThinking)  ← FALSE, skipped
          → thinking: m  ← m is undefined
          → Request body: { model, messages, ..., thinking: undefined }
Built-in GitHub Model Request Flow:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
panel/editAgent
  → Reads _knownModels for model capabilities
  → Q = supportsAdaptiveThinking from _knownModels
  → thinking: Q ? {type:"adaptive"} : P ? {type:"enabled",...} : void 0
  → Request body: { ..., thinking: {type:"adaptive", display:"summarized"} }

Proposed Fix

Fix 1: Pass enableThinking from model configuration

In copilotLanguageModelWrapper, include the model's thinking capability:

modelCapabilities: {
  reasoningEffort: typeof o.modelConfiguration?.reasoningEffort == "string"
    ? o.modelConfiguration.reasoningEffort
    : void 0,
  enableThinking: !!endpoint.supportsThinking || !!endpoint.supportsAdaptiveThinking
}

Fix 2: Add fallback in LSn for models that declare thinking: true but lack budget metadata

if (e.modelCapabilities?.enableThinking) {
  if (r.supportsAdaptiveThinking)
    m = { type: "adaptive", display: "summarized" };
  else if (r.maxThinkingBudget && r.minThinkingBudget) {
    // ... existing budget logic ...
  } else {
    // Fallback: if model declares thinking support but has no
    // adaptive/budget metadata, default to adaptive
    m = { type: "adaptive" };
  }
}

Fix 3 (panel/editAgent path): Respect user config thinking: true

The ternary that gates thinking on _knownModels should also check the endpoint's declared capabilities:

thinking: Q ? { type: "adaptive" }
  : P ? { type: "enabled", budget_tokens: P }
  : endpoint.supportsAdaptiveThinking ? { type: "adaptive" }
  : void 0

Impact

This affects all users of custom endpoint models (BYOK/enterprise proxy setups) who configure Anthropic-compatible endpoints with extended thinking. The model config correctly declares thinking: true and adaptiveThinking: true, but these values are never used in the request body construction for the copilotLanguageModelWrapper code path.

Workaround

Manually patching the minified extension.js to (1) add enableThinking: true to the wrapper's modelCapabilities and (2) add an else m = {type: "adaptive"} fallback in the Messages API body builder restores thinking for custom endpoints. This must be reapplied after every VS Code update.

Related

  • Custom endpoint model configuration: apiType: "messages" with thinking: true
  • The supportsAdaptiveThinking property IS correctly resolved from user config → vD()capabilities.supports.adaptive_thinking → endpoint property. The gap is only that it's never consulted by the wrapper/request builder.

Checklist

  • Searched existing issues — no matching issue found for custom endpoint + thinking
  • Reproduced with all non-Copilot extensions disabled
  • Minimal config provided to reproduce
  • Filed against microsoft/vscode (Copilot extension is bundled in VS Code; vscode-copilot-release is archived)

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions