TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import type { ModelParameters, PolyModel, UnifiedGenerationSettings } from "./types";23export interface DroppedParameter {4 name: keyof UnifiedGenerationSettings;5 reason: string;6}78/**9 * Capability-driven parameter filter: given the unified settings and the model's10 * parameter sheet, keep only what the model accepts and clamp ranges. Adapters call this11 * BEFORE translating to provider fields so unsupported options are never sent.12 */13export function filterSettings(settings: UnifiedGenerationSettings | undefined, model: PolyModel | undefined): { settings: UnifiedGenerationSettings; dropped: DroppedParameter[] } {14 const s: UnifiedGenerationSettings = { ...(settings ?? {}) };15 const dropped: DroppedParameter[] = [];16 const p: ModelParameters = model?.parameters ?? {};17 const caps = model?.capabilities;1819 const drop = (name: keyof UnifiedGenerationSettings, reason: string) => {20 if (s[name] !== undefined) {21 delete s[name];22 dropped.push({ name, reason });23 }24 };2526 if (!model) return { settings: s, dropped };2728 if (!p.temperature) drop("temperature", "not supported by this model");29 else if (s.temperature !== undefined) {30 const r = p.temperatureRange ?? { min: 0, max: 2 };31 s.temperature = clamp(s.temperature, r.min, r.max);32 }33 if (!p.topP) drop("topP", "not supported by this model");34 else if (s.topP !== undefined) s.topP = clamp(s.topP, 0, 1);35 if (!p.topK) drop("topK", "not supported by this model");36 if (!p.maxTokens) drop("maxTokens", "not supported by this model");37 else if (s.maxTokens !== undefined && model.limits?.maxOutputTokens) s.maxTokens = Math.min(Math.max(1, Math.floor(s.maxTokens)), model.limits.maxOutputTokens);38 if (!p.stop) drop("stop", "not supported by this model");39 if (!p.seed) drop("seed", "not supported by this model");40 if (!p.frequencyPenalty) drop("frequencyPenalty", "not supported by this model");41 if (!p.presencePenalty) drop("presencePenalty", "not supported by this model");42 if (!p.verbosity) drop("verbosity", "not supported by this model");43 if (!p.reasoningEffort) drop("reasoningEffort", "model has no adjustable reasoning effort");44 else if (s.reasoningEffort && p.reasoningEffortLevels && !p.reasoningEffortLevels.includes(s.reasoningEffort)) {45 // snap to the closest accepted level rather than sending an invalid value46 s.reasoningEffort = snapEffort(s.reasoningEffort, p.reasoningEffortLevels);47 }48 if (!p.thinkingBudget) drop("thinkingBudget", "model has no thinking budget");49 else if (s.thinkingBudget !== undefined && p.thinkingBudgetRange) s.thinkingBudget = clamp(Math.floor(s.thinkingBudget), p.thinkingBudgetRange.min, p.thinkingBudgetRange.max);50 if (!caps?.reasoning) drop("includeReasoning", "model does not expose reasoning");51 if (!caps?.structuredOutput && s.responseFormat && s.responseFormat.type !== "text") drop("responseFormat", "structured output not supported");52 if (!caps?.tools) drop("toolChoice", "tools not supported");53 if (!caps?.webSearch) drop("webSearch", "web search not supported");54 if (!caps?.tools && s.codeExecution) drop("codeExecution", "code execution not supported");5556 return { settings: s, dropped };57}5859const EFFORT_ORDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] as const;6061export function snapEffort(effort: string, levels: string[]): UnifiedGenerationSettings["reasoningEffort"] {62 const idx = EFFORT_ORDER.indexOf(effort as (typeof EFFORT_ORDER)[number]);63 if (idx < 0) return levels[Math.floor(levels.length / 2)] as UnifiedGenerationSettings["reasoningEffort"];64 const ordered = EFFORT_ORDER.filter((l) => levels.includes(l));65 if (ordered.length === 0) return undefined;66 // Closest accepted level; on a tie prefer the HIGHER one so a reasoning request never silently becomes "none".67 let best = ordered[0];68 let bestDist = Infinity;69 for (const l of ordered) {70 const d = Math.abs(EFFORT_ORDER.indexOf(l) - idx);71 if (d < bestDist || (d === bestDist && EFFORT_ORDER.indexOf(l) > EFFORT_ORDER.indexOf(best))) {72 best = l;73 bestDist = d;74 }75 }76 return best as UnifiedGenerationSettings["reasoningEffort"];77}7879export function clamp(n: number, min: number, max: number): number {80 return Math.min(max, Math.max(min, n));81}8283/** Rough heuristic (~4 chars per token) used when a provider has no count endpoint. */84export function heuristicTokens(text: string): number {85 return Math.ceil(text.length / 4);86}87