import type { StreamFn } from "@mariozechner/pi-agent-core"; import type { Api, Model, SimpleStreamOptions } from "@mariozechner/pi-ai"; import { streamSimple } from "@mariozechner/pi-ai"; import type { MoltbotConfig } from "../../config/config.js"; import { log } from "./logger.js"; /** * 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: MoltbotConfig | undefined; provider: string; modelId: string; }): Record | undefined { const modelKey = `${params.provider}/${params.modelId}`; const modelConfig = params.cfg?.agents?.defaults?.models?.[modelKey]; return modelConfig?.params ? { ...modelConfig.params } : undefined; } type CacheControlTtl = "5m" | "1h"; function resolveCacheControlTtl( extraParams: Record | undefined, provider: string, modelId: string, ): CacheControlTtl | undefined { const raw = extraParams?.cacheControlTtl; if (raw !== "5m" && raw !== "1h") return undefined; if (provider === "anthropic") return raw; if (provider === "openrouter" && modelId.startsWith("anthropic/")) return raw; return undefined; } function createStreamFnWithExtraParams( baseStreamFn: StreamFn | undefined, extraParams: Record | undefined, provider: string, modelId: string, ): StreamFn | undefined { if (!extraParams || Object.keys(extraParams).length === 0) { return undefined; } const streamParams: Partial & { cacheControlTtl?: CacheControlTtl } = {}; if (typeof extraParams.temperature === "number") { streamParams.temperature = extraParams.temperature; } if (typeof extraParams.maxTokens === "number") { streamParams.maxTokens = extraParams.maxTokens; } const cacheControlTtl = resolveCacheControlTtl(extraParams, provider, modelId); if (cacheControlTtl) { streamParams.cacheControlTtl = cacheControlTtl; } 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 as Model, context, { ...streamParams, ...options, }); return wrappedStreamFn; } /** * Apply extra params (like temperature) to an agent's streamFn. * * @internal Exported for testing */ export function applyExtraParamsToAgent( agent: { streamFn?: StreamFn }, cfg: MoltbotConfig | undefined, provider: string, modelId: string, extraParamsOverride?: Record, ): 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, modelId); if (wrappedStreamFn) { log.debug(`applying extraParams to agent streamFn for ${provider}/${modelId}`); agent.streamFn = wrappedStreamFn; } }