fix(executors): strip params unsupported by provider/model

Add config-driven stripUnsupportedParams helper and use it in the default and github executors. Removes the deprecated temperature param for claude-opus-4 models (Anthropic 400) and consolidates github's scattered capability checks into one rule table.

Fixes #1748

Co-Authored-By: fjia <fjia@suntekcorps.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
fjia
2026-06-17 10:34:53 +07:00
committed by decolua
parent 87e5c1c6dd
commit 7ae9fff663
3 changed files with 36 additions and 38 deletions

View File

@@ -6,6 +6,7 @@ import { buildClineHeaders } from "../shared/clineAuth.js";
import { getCachedClaudeHeaders } from "../utils/claudeHeaderCache.js";
import { proxyAwareFetch } from "../utils/proxyFetch.js";
import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
import { stripUnsupportedParams } from "../translator/concerns/paramSupport.js";
// Auth header descriptors — derived from registry transport.auth, fallback to hardcoded defaults.
const BEARER = { combined: true, header: "Authorization", scheme: "bearer" };
@@ -88,6 +89,7 @@ export class DefaultExecutor extends BaseExecutor {
if (this.config.quirks?.dropClientMetadata) {
delete transformed.client_metadata;
}
stripUnsupportedParams(this.provider, model, transformed);
}
return injectReasoningContent({ provider: this.provider, model, body: transformed });

View File

@@ -7,6 +7,7 @@ import { openaiResponsesToOpenAIResponse } from "../translator/response/openai-r
import { initState } from "../translator/index.js";
import { parseSSELine, formatSSE } from "../utils/streamHelpers.js";
import { proxyAwareFetch } from "../utils/proxyFetch.js";
import { stripUnsupportedParams } from "../translator/concerns/paramSupport.js";
import { SSE_DONE } from "../utils/sseConstants.js";
import crypto from "crypto";
@@ -109,54 +110,18 @@ export class GithubExecutor extends BaseExecutor {
return /gpt-5|o[134]-/i.test(model);
}
// Some models (like gpt-5.4) don't support the temperature parameter
supportsTemperature(model) {
// gpt-5.4 and similar newer models don't support temperature
return !/gpt-5\.4/i.test(model);
}
// GitHub Copilot /chat/completions rejects Claude-style thinking payloads
// (OpenClaw sends thinking: { type: "enabled" } → upstream 400).
// GPT-5 family on Copilot DOES honor reasoning_effort, so only strip for Claude. (#713)
supportsThinking(model) {
return !/claude/i.test(model);
}
// reasoning_effort works for GPT-5 family AND Claude Opus 4.6 / Sonnet 4.6
// on GitHub Copilot. Only strip for models that don't support it:
// Claude Haiku 4.5, Claude Opus 4.7 (rejected upstream).
supportsReasoningEffort(model) {
const m = model.toLowerCase();
// Claude models that DO support reasoning_effort
if (/claude.*opus.*4\.6/i.test(m) || /claude.*sonnet.*4\.6/i.test(m)) return true;
// All other Claude models: strip
if (/claude/i.test(model)) return false;
// GPT-5 family, Gemini, etc.: keep
return true;
}
transformRequest(model, body, stream, credentials) {
const transformed = { ...body };
if (this.requiresMaxCompletionTokens(model) && transformed.max_tokens !== undefined) {
transformed.max_completion_tokens = transformed.max_tokens;
delete transformed.max_tokens;
}
// Strip temperature for models that don't support it
if (!this.supportsTemperature(model) && transformed.temperature !== undefined) {
delete transformed.temperature;
}
// Always strip Claude-style thinking payload (Copilot doesn't understand it)
if (!this.supportsThinking(model)) {
delete transformed.thinking;
}
// "none" means no thinking — strip it so models that don't support "none" don't 400
if (transformed.reasoning_effort === "none") {
delete transformed.reasoning_effort;
}
// Strip reasoning_effort only for models that reject it
if (!this.supportsReasoningEffort(model) && transformed.reasoning_effort !== undefined) {
delete transformed.reasoning_effort;
}
// Config-driven strip of params unsupported by this provider/model
stripUnsupportedParams("github", model, transformed);
return transformed;
}

View File

@@ -0,0 +1,31 @@
// Strip request params a given provider/model rejects upstream (e.g. HTTP 400).
// Config-driven: add a rule instead of scattering `delete body.x` across executors.
// Each rule: optional provider, regex match on model, list of params to drop.
// A param is removed only when it is present (!== undefined).
const STRIP_RULES = [
// claude-opus-4 series: temperature is deprecated (Anthropic 400). #1748
{ match: /claude-opus-4/i, drop: ["temperature"] },
// GitHub Copilot gpt-5.4: temperature unsupported.
{ provider: "github", match: /gpt-5\.4/i, drop: ["temperature"] },
// GitHub Copilot Claude (except opus/sonnet 4.6): thinking + reasoning_effort rejected. #713
{ provider: "github", match: (m) => /claude/i.test(m) && !/claude.*(opus|sonnet).*4\.6/i.test(m), drop: ["thinking", "reasoning_effort"] },
];
// Test a rule's match (regex or predicate) against the model id.
function matches(rule, model) {
return typeof rule.match === "function" ? rule.match(model) : rule.match.test(model);
}
// Remove unsupported params from body in place; returns body.
export function stripUnsupportedParams(provider, model, body) {
if (!model || !body || typeof body !== "object") return body;
for (const rule of STRIP_RULES) {
if (rule.provider && rule.provider !== provider) continue;
if (!matches(rule, model)) continue;
for (const key of rule.drop) {
if (body[key] !== undefined) delete body[key];
}
}
return body;
}