/**
 * Model capability resolution (P3b — A6).
 *
 * The only capability we care about today is **vision**: can the configured
 * inference model accept image input? Image captioning / OCR (the P3b indexing
 * pass) and the composer's vision attachments (P6) both gate on this.
 *
 * Source of truth, in order:
 *   1. pi-ai model metadata — `Model.input` is a `("text" | "image")[]`, so
 *      `input.includes('image')` is authoritative WHEN pi-ai knows the model.
 *   2. A **maintained fallback map** — pi-ai's generated metadata is missing or
 *      wrong for custom / gateway / newly-released models. The Studiograph
 *      Credits gateway in particular routes through its own client and is
 *      asserted vision-capable here (decision A6).
 *
 * Why a map and not "trust pi-ai or default false": defaulting unknown models
 * to false would silently disable captioning the moment a tenant points at a
 * model pi-ai hasn't catalogued yet — a quiet regression. The map lets us
 * assert capability for the providers we actually ship against.
 *
 * Note: vision *capability* is necessary but not sufficient for captioning.
 * The Credits gateway is vision-capable yet `completeSimple()` cannot reach it
 * (see asset-caption-service.ts) — that reachability gate is separate.
 */
import { getModel } from '@mariozechner/pi-ai';
const VISION_FALLBACK = [
    // Studiograph Credits gateway — asserted vision-capable (A6). pi-ai has no
    // metadata for the synthetic gateway model id.
    { provider: 'studiograph-cloud', vision: true },
    { provider: 'studiograph-cloud-inference', vision: true },
    // Anthropic — every shipping Claude 3+/4 model takes images.
    { provider: 'anthropic', modelSubstrings: ['claude-'], vision: true },
    // OpenAI — 4o / 4.1 / 5 families (unambiguous substrings) plus the o-series,
    // which is anchored to the id prefix so a stray "o1" inside an unrelated id
    // can't falsely report vision. The text-only 3.5 line matches nothing here.
    {
        provider: 'openai',
        modelSubstrings: ['gpt-4o', 'gpt-4.1', 'gpt-5', 'gpt-4-vision'],
        modelPatterns: [/^o[134](-|$)/],
        vision: true,
    },
    // Google Gemini — 1.5/2.x are multimodal.
    { provider: 'google', modelSubstrings: ['gemini-'], vision: true },
];
/** Lowercase + trim for stable matching. */
const norm = (s) => s.trim().toLowerCase();
/**
 * Resolve vision capability from the maintained fallback map. Returns
 * `undefined` when no rule matches (caller decides the conservative default).
 */
function visionFromFallback(provider, modelId) {
    const p = norm(provider);
    const m = norm(modelId);
    let result;
    for (const rule of VISION_FALLBACK) {
        if (rule.provider !== p)
            continue;
        if (!rule.modelSubstrings || rule.modelSubstrings.length === 0) {
            // Whole-provider rule.
            result = rule.vision;
            continue;
        }
        const matched = rule.modelSubstrings.some(sub => m.includes(sub))
            || (rule.modelPatterns?.some(re => re.test(m)) ?? false);
        if (matched) {
            return rule.vision;
        }
        // Provider matched but no model substring did — record a provider-level
        // "false" only if no positive rule fires later. We don't early-return
        // here so a more specific positive substring rule can still win.
        if (result === undefined)
            result = false;
    }
    return result;
}
/**
 * Does the model that pi-ai resolves for `(provider, modelId)` accept image
 * input? pi-ai metadata wins when present; otherwise the maintained fallback
 * map; otherwise `false` (conservative — captioning degrades to filename-only
 * rather than firing a vision call the model would reject).
 */
export function supportsVision(provider, modelId) {
    // 1. pi-ai metadata, when it knows the model.
    try {
        const model = getModel(provider, modelId);
        if (model?.input && Array.isArray(model.input)) {
            return model.input.includes('image');
        }
    }
    catch {
        // Unknown to pi-ai (custom/gateway/new model) — fall through to the map.
    }
    // 2. Maintained fallback map. 3. Conservative default.
    return visionFromFallback(provider, modelId) ?? false;
}
/**
 * Capability check against an already-resolved model object (e.g. one from
 * `getModel`/`studiographCreditsModel`). Falls back to the (provider, modelId)
 * map when the object carries no usable `input` array.
 */
export function modelSupportsVision(model, provider, modelId) {
    if (model?.input && Array.isArray(model.input)) {
        return model.input.includes('image');
    }
    return visionFromFallback(provider, modelId) ?? false;
}
//# sourceMappingURL=model-capabilities.js.map