const PROFILES = { hosted: { baseUrl: 'http://127.0.0.1:8645/v1', apiKey: 'local-proxy', model: 'stepfun/step-3.7-flash:free', processor: 'third-party', }, selfhost: { baseUrl: 'http://127.0.0.1:8080/v1', apiKey: 'local-selfhost', model: 'SmolVLM2-2.2B-Instruct', processor: 'self-hosted', }, }; export function resolveVisionConfig(env = process.env) { const profile = env.TIMMY_VISION_PROFILE || 'hosted'; if (!PROFILES[profile]) throw new Error('TIMMY_VISION_PROFILE must be hosted or selfhost.'); const defaults = PROFILES[profile]; const config = { enabled: !['0', 'false'].includes(String(env.TIMMY_VISION_ENABLED || '').toLowerCase()), profile, processor: defaults.processor, baseUrl: env.TIMMY_VISION_BASE_URL || defaults.baseUrl, apiKey: env.TIMMY_VISION_API_KEY || defaults.apiKey, model: env.TIMMY_VISION_MODEL || defaults.model, requestTimeoutMs: Number(env.TIMMY_VISION_TIMEOUT_MS || (profile === 'selfhost' ? 120_000 : 60_000)), }; config.publicStatus = () => ({ enabled: config.enabled, profile: config.profile, processor: config.processor, model: config.enabled ? config.model : null, }); return config; } function modelsEndpoint(baseUrl) { const url = new URL(baseUrl); if (!['http:', 'https:'].includes(url.protocol)) throw new Error('Invalid AI provider URL.'); return `${url.toString().replace(/\/$/, '')}/models`; } export async function probeVisionProvider(config, fetchImpl = fetch) { if (!config.enabled) return { ready: false, modelSeen: false }; try { const response = await fetchImpl(modelsEndpoint(config.baseUrl), { headers: { authorization: `Bearer ${config.apiKey}` }, signal: AbortSignal.timeout(2_000), }); if (!response.ok) return { ready: false, modelSeen: false }; const body = await response.json(); const ids = Array.isArray(body?.data) ? body.data.map(item => item?.id).filter(Boolean) : []; return { ready: ids.length > 0, modelSeen: ids.some(id => id === config.model || id.includes(config.model)) }; } catch { return { ready: false, modelSeen: false }; } }