darkfire514's picture
Upload 2526 files
fb4d8fe verified
import type { StreamFn } from "@mariozechner/pi-agent-core";
import type { SimpleStreamOptions } from "@mariozechner/pi-ai";
import { streamSimple } from "@mariozechner/pi-ai";
import type { OpenClawConfig } from "../../config/config.js";
import { log } from "./logger.js";
const OPENROUTER_APP_HEADERS: Record<string, string> = {
"HTTP-Referer": "https://openclaw.ai",
"X-Title": "OpenClaw",
};
/**
* Resolve provider-specific extra params from model config.
* Used to pass through stream params like temperature/maxTokens.
*
* @internal Exported for testing only
*/
export function resolveExtraParams(params: {
cfg: OpenClawConfig | undefined;
provider: string;
modelId: string;
}): Record<string, unknown> | undefined {
const modelKey = `${params.provider}/${params.modelId}`;
const modelConfig = params.cfg?.agents?.defaults?.models?.[modelKey];
return modelConfig?.params ? { ...modelConfig.params } : undefined;
}
type CacheRetention = "none" | "short" | "long";
type CacheRetentionStreamOptions = Partial<SimpleStreamOptions> & {
cacheRetention?: CacheRetention;
};
/**
* Resolve cacheRetention from extraParams, supporting both new `cacheRetention`
* and legacy `cacheControlTtl` values for backwards compatibility.
*
* Mapping: "5m" → "short", "1h" → "long"
*
* Only applies to Anthropic provider (OpenRouter uses openai-completions API
* with hardcoded cache_control, not the cacheRetention stream option).
*/
function resolveCacheRetention(
extraParams: Record<string, unknown> | undefined,
provider: string,
): CacheRetention | undefined {
if (provider !== "anthropic") {
return undefined;
}
// Prefer new cacheRetention if present
const newVal = extraParams?.cacheRetention;
if (newVal === "none" || newVal === "short" || newVal === "long") {
return newVal;
}
// Fall back to legacy cacheControlTtl with mapping
const legacy = extraParams?.cacheControlTtl;
if (legacy === "5m") {
return "short";
}
if (legacy === "1h") {
return "long";
}
return undefined;
}
function createStreamFnWithExtraParams(
baseStreamFn: StreamFn | undefined,
extraParams: Record<string, unknown> | undefined,
provider: string,
): StreamFn | undefined {
if (!extraParams || Object.keys(extraParams).length === 0) {
return undefined;
}
const streamParams: CacheRetentionStreamOptions = {};
if (typeof extraParams.temperature === "number") {
streamParams.temperature = extraParams.temperature;
}
if (typeof extraParams.maxTokens === "number") {
streamParams.maxTokens = extraParams.maxTokens;
}
const cacheRetention = resolveCacheRetention(extraParams, provider);
if (cacheRetention) {
streamParams.cacheRetention = cacheRetention;
}
if (Object.keys(streamParams).length === 0) {
return undefined;
}
log.debug(`creating streamFn wrapper with params: ${JSON.stringify(streamParams)}`);
const underlying = baseStreamFn ?? streamSimple;
const wrappedStreamFn: StreamFn = (model, context, options) =>
underlying(model, context, {
...streamParams,
...options,
});
return wrappedStreamFn;
}
/**
* Create a streamFn wrapper that adds OpenRouter app attribution headers.
* These headers allow OpenClaw to appear on OpenRouter's leaderboard.
*/
function createOpenRouterHeadersWrapper(baseStreamFn: StreamFn | undefined): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) =>
underlying(model, context, {
...options,
headers: {
...OPENROUTER_APP_HEADERS,
...options?.headers,
},
});
}
/**
* Apply extra params (like temperature) to an agent's streamFn.
* Also adds OpenRouter app attribution headers when using the OpenRouter provider.
*
* @internal Exported for testing
*/
export function applyExtraParamsToAgent(
agent: { streamFn?: StreamFn },
cfg: OpenClawConfig | undefined,
provider: string,
modelId: string,
extraParamsOverride?: Record<string, unknown>,
): void {
const extraParams = resolveExtraParams({
cfg,
provider,
modelId,
});
const override =
extraParamsOverride && Object.keys(extraParamsOverride).length > 0
? Object.fromEntries(
Object.entries(extraParamsOverride).filter(([, value]) => value !== undefined),
)
: undefined;
const merged = Object.assign({}, extraParams, override);
const wrappedStreamFn = createStreamFnWithExtraParams(agent.streamFn, merged, provider);
if (wrappedStreamFn) {
log.debug(`applying extraParams to agent streamFn for ${provider}/${modelId}`);
agent.streamFn = wrappedStreamFn;
}
if (provider === "openrouter") {
log.debug(`applying OpenRouter app attribution headers for ${provider}/${modelId}`);
agent.streamFn = createOpenRouterHeadersWrapper(agent.streamFn);
}
}