import type { ImportedModelWebSearchCapability } from './imported-model-profile'; import { normalizeImportedModelWebSearchCapability } from './user-model-config'; function object(value: unknown): Record { return value && typeof value === 'object' && !Array.isArray(value) ? value as Record : {}; } function field(value: Record, snake: string, camel: string): unknown { return Object.hasOwn(value, snake) ? value[snake] : value[camel]; } const bool = (v: unknown): boolean | null => typeof v === 'boolean' ? v : null; const str = (v: unknown): string | null => typeof v === 'string' && v.trim() ? v : null; const num = (v: unknown): number | null => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0 ? v : null; const strings = (v: unknown): string[] | null => Array.isArray(v) && v.every(x => typeof x === 'string' && x.trim()) ? [...new Set(v as string[])] : null; export function normalizeManagedModelCatalog(value: unknown, ids?: readonly string[]): ManagedModelCatalog | undefined { const raw = object(value); if (field(raw, 'schema_version', 'schemaVersion') !== 2) return undefined; const models: ManagedModelCatalog['models'] = {}; for (const [id, item] of Object.entries(object(raw.models))) { if (ids && !ids.includes(id)) continue; const p = object(item), r = object(p.reasoning), b = object(r.budget), l = object(p.limits); const format = field(r, 'control_format', 'controlFormat'); const status = field(p, 'resolution_status', 'resolutionStatus'); const webSearch = normalizeImportedModelWebSearchCapability(field(p, 'web_search', 'webSearch')); models[id] = { inputModalities: strings(field(p, 'input_modalities', 'inputModalities')), outputModalities: strings(field(p, 'output_modalities', 'outputModalities')), reasoning: { supported: bool(r.supported), canDisable: bool(field(r, 'can_disable', 'canDisable')), defaultEnabled: bool(field(r, 'default_enabled', 'defaultEnabled')), effortValues: strings(field(r, 'effort_values', 'effortValues')), defaultEffort: str(field(r, 'default_effort', 'defaultEffort')), controlFormat: format === 'qwen' || format === 'deepseek' ? format : null, budget: r.budget ? { supported: bool(b.supported), minTokens: num(field(b, 'min_tokens', 'minTokens')), maxTokens: num(field(b, 'max_tokens', 'maxTokens')), defaultTokens: num(field(b, 'default_tokens', 'defaultTokens')), exclusiveWithEffort: bool(field(b, 'exclusive_with_effort', 'exclusiveWithEffort')), } : null, }, limits: p.limits ? { contextWindow: num(field(l, 'context_window', 'contextWindow')), maxInputTokens: num(field(l, 'max_input_tokens', 'maxInputTokens')), maxOutputTokens: num(field(l, 'max_output_tokens', 'maxOutputTokens')), } : null, resolutionStatus: status === 'ready' || status === 'partial' || status === 'conflict' ? status : 'unknown', issues: Array.isArray(p.issues) ? p.issues.flatMap(item => { const i = object(item); return typeof i.field === 'string' && typeof i.code === 'string' ? [{ field: i.field, code: i.code }] : []; }) : [], updatedAt: str(field(p, 'updated_at', 'updatedAt')), ...(webSearch ? { webSearch } : {}), }; } const status = field(raw, 'refresh_status', 'refreshStatus'); return { schemaVersion: 2, models, fetchedAt: str(field(raw, 'fetched_at', 'fetchedAt')), refreshStatus: status === 'fresh' || status === 'stale' ? status : 'unavailable' }; } export function parseManagedReasoningChoice(value: unknown): ManagedReasoningChoice | null { const v = object(value); if (v.mode === 'default' || v.mode === 'disabled') return { mode: v.mode }; if (v.mode === 'enabled' && (v.effort === undefined || typeof v.effort === 'string' && v.effort.trim())) { return { mode: 'enabled', ...(typeof v.effort === 'string' ? { effort: v.effort } : {}) }; } return null; } export type ManagedReasoningChoice = | { mode: 'default' } | { mode: 'disabled' } | { mode: 'enabled'; effort?: string }; export interface ManagedModelCapability { inputModalities: string[] | null; outputModalities: string[] | null; reasoning: { supported: boolean | null; canDisable: boolean | null; defaultEnabled: boolean | null; effortValues: string[] | null; defaultEffort: string | null; controlFormat: 'qwen' | 'deepseek' | null; budget: { supported: boolean | null; minTokens: number | null; maxTokens: number | null; defaultTokens: number | null; exclusiveWithEffort: boolean | null; } | null; }; limits: { contextWindow: number | null; maxInputTokens: number | null; maxOutputTokens: number | null; } | null; resolutionStatus: 'ready' | 'partial' | 'unknown' | 'conflict'; issues: { field: string; code: string }[]; updatedAt: string | null; webSearch?: ImportedModelWebSearchCapability; } export interface ManagedModelCatalog { schemaVersion: 2; models: Record; fetchedAt: string | null; refreshStatus: 'fresh' | 'stale' | 'unavailable'; } export interface ManagedModelRequest { modelId: string; choice: ManagedReasoningChoice; reasoningFields: Record; } export function unknownManagedModelCapability(): ManagedModelCapability { return { inputModalities: null, outputModalities: null, reasoning: { supported: null, canDisable: null, defaultEnabled: null, effortValues: null, defaultEffort: null, controlFormat: null, budget: null, }, limits: null, resolutionStatus: 'unknown', issues: [], updatedAt: null, }; } export function managedReasoningOptions(capability: ManagedModelCapability): { value: string; label: string; choice: ManagedReasoningChoice; }[] { const options: ReturnType = [ { value: 'default', label: '模型默认', choice: { mode: 'default' } }, ]; const r = capability.reasoning; if (r.supported !== true || !r.controlFormat) return options; if (r.canDisable === true) { options.push({ value: 'disabled', label: '关闭思考', choice: { mode: 'disabled' } }); options.push({ value: 'enabled', label: '开启(默认强度)', choice: { mode: 'enabled' } }); } for (const effort of (r.canDisable === true || r.defaultEnabled === true) ? r.effortValues ?? [] : []) { options.push({ value: 'effort:' + effort, label: effort, choice: { mode: 'enabled', effort } }); } return options; } export function managedReasoningChoiceKey(choice: ManagedReasoningChoice): string { return choice.mode === 'enabled' && choice.effort !== undefined ? 'effort:' + choice.effort : choice.mode; } export function validateManagedReasoningChoice( choice: ManagedReasoningChoice, capability: ManagedModelCapability, ): void { const key = managedReasoningChoiceKey(choice); if (!managedReasoningOptions(capability).some((option) => option.value === key)) { throw new Error('该模型的思考选项已不可用,请刷新模型配置后重新选择'); } } export function buildManagedModelRequest( modelId: string, choice: ManagedReasoningChoice, capability: ManagedModelCapability, ): ManagedModelRequest { validateManagedReasoningChoice(choice, capability); const fields: Record = {}; const r = capability.reasoning; if (choice.mode !== 'default') { if (r.canDisable === true) { if (r.controlFormat === 'qwen') fields.enable_thinking = choice.mode === 'enabled'; else fields.thinking = { type: choice.mode === 'enabled' ? 'enabled' : 'disabled' }; } if (choice.mode === 'enabled' && choice.effort !== undefined) { fields.reasoning_effort = choice.effort; } } return { modelId, choice: { ...choice }, reasoningFields: fields }; } // Pi's fixed enum is an execution detail. The request hook applies the validated // native choice; this carrier must never be persisted as the product selection. export function managedPiThinkingLevel(choice: ManagedReasoningChoice): 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' { if (choice.mode !== 'enabled') return 'off'; switch (choice.effort) { case 'minimal': case 'low': case 'medium': case 'high': case 'xhigh': case 'max': return choice.effort; default: return 'low'; } }