* fix(openrouter): pass reasoning.effort to OpenRouter API (#14664) * Agents: pass thinkLevel to extra-params wrapper * Changelog: note fix/openrouter-reasoning-effort-14664 OpenRouter fix * Changelog: fix OpenRouter entry text --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -26,6 +26,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Providers/OpenRouter: map `/think` levels to `reasoning.effort` in embedded runs while preserving explicit `reasoning.max_tokens` payloads. (#17236) Thanks @robbyczgw-cla.
|
||||
- Gateway/OpenRouter: preserve stored session provider when model IDs are vendor-prefixed (for example, `anthropic/...`) so follow-up turns do not incorrectly route to direct provider APIs. (#22753) Thanks @dndodson.
|
||||
- Providers/OpenRouter: preserve the required `openrouter/` prefix for OpenRouter-native model IDs during model-ref normalization. (#12942) Thanks @omair445.
|
||||
- Providers/OpenRouter: pass through provider routing parameters from model params.provider to OpenRouter request payloads for provider selection controls. (#17148) Thanks @carrotRakko.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import type { SimpleStreamOptions } from "@mariozechner/pi-ai";
|
||||
import { streamSimple } from "@mariozechner/pi-ai";
|
||||
import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { log } from "./logger.js";
|
||||
|
||||
@@ -290,19 +291,62 @@ function createAnthropicBetaHeadersWrapper(
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a streamFn wrapper that adds OpenRouter app attribution headers.
|
||||
* These headers allow OpenClaw to appear on OpenRouter's leaderboard.
|
||||
* Map OpenClaw's ThinkLevel to OpenRouter's reasoning.effort values.
|
||||
* "off" maps to "none"; all other levels pass through as-is.
|
||||
*/
|
||||
function createOpenRouterHeadersWrapper(baseStreamFn: StreamFn | undefined): StreamFn {
|
||||
function mapThinkingLevelToOpenRouterReasoningEffort(
|
||||
thinkingLevel: ThinkLevel,
|
||||
): "none" | "minimal" | "low" | "medium" | "high" | "xhigh" {
|
||||
if (thinkingLevel === "off") {
|
||||
return "none";
|
||||
}
|
||||
return thinkingLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a streamFn wrapper that adds OpenRouter app attribution headers
|
||||
* and injects reasoning.effort based on the configured thinking level.
|
||||
*/
|
||||
function createOpenRouterWrapper(
|
||||
baseStreamFn: StreamFn | undefined,
|
||||
thinkingLevel?: ThinkLevel,
|
||||
): StreamFn {
|
||||
const underlying = baseStreamFn ?? streamSimple;
|
||||
return (model, context, options) =>
|
||||
underlying(model, context, {
|
||||
return (model, context, options) => {
|
||||
const onPayload = options?.onPayload;
|
||||
return underlying(model, context, {
|
||||
...options,
|
||||
headers: {
|
||||
...OPENROUTER_APP_HEADERS,
|
||||
...options?.headers,
|
||||
},
|
||||
onPayload: (payload) => {
|
||||
if (thinkingLevel && payload && typeof payload === "object") {
|
||||
const payloadObj = payload as Record<string, unknown>;
|
||||
const existingReasoning = payloadObj.reasoning;
|
||||
|
||||
// OpenRouter treats reasoning.effort and reasoning.max_tokens as
|
||||
// alternative controls. If max_tokens is already present, do not
|
||||
// inject effort and do not overwrite caller-supplied reasoning.
|
||||
if (
|
||||
existingReasoning &&
|
||||
typeof existingReasoning === "object" &&
|
||||
!Array.isArray(existingReasoning)
|
||||
) {
|
||||
const reasoningObj = existingReasoning as Record<string, unknown>;
|
||||
if (!("max_tokens" in reasoningObj) && !("effort" in reasoningObj)) {
|
||||
reasoningObj.effort = mapThinkingLevelToOpenRouterReasoningEffort(thinkingLevel);
|
||||
}
|
||||
} else if (!existingReasoning) {
|
||||
payloadObj.reasoning = {
|
||||
effort: mapThinkingLevelToOpenRouterReasoningEffort(thinkingLevel),
|
||||
};
|
||||
}
|
||||
}
|
||||
onPayload?.(payload);
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,6 +394,7 @@ export function applyExtraParamsToAgent(
|
||||
provider: string,
|
||||
modelId: string,
|
||||
extraParamsOverride?: Record<string, unknown>,
|
||||
thinkingLevel?: ThinkLevel,
|
||||
): void {
|
||||
const extraParams = resolveExtraParams({
|
||||
cfg,
|
||||
@@ -380,7 +425,7 @@ export function applyExtraParamsToAgent(
|
||||
|
||||
if (provider === "openrouter") {
|
||||
log.debug(`applying OpenRouter app attribution headers for ${provider}/${modelId}`);
|
||||
agent.streamFn = createOpenRouterHeadersWrapper(agent.streamFn);
|
||||
agent.streamFn = createOpenRouterWrapper(agent.streamFn, thinkingLevel);
|
||||
}
|
||||
|
||||
// Enable Z.AI tool_stream for real-time tool call streaming.
|
||||
|
||||
@@ -736,6 +736,7 @@ export async function runEmbeddedAttempt(
|
||||
params.provider,
|
||||
params.modelId,
|
||||
params.streamParams,
|
||||
params.thinkLevel,
|
||||
);
|
||||
|
||||
if (cacheTrace) {
|
||||
|
||||
Reference in New Issue
Block a user