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:
@@ -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 });
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
31
open-sse/translator/concerns/paramSupport.js
Normal file
31
open-sse/translator/concerns/paramSupport.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user