Project Files
src / capabilities.ts
// Centralized model capability detection and Files API eligibility
// Policy (current):
// - Strict separation of capabilities per model.
// - "Model Families" are ignored in favor of explicit configuration.
export type ImageGenerationConfig = {
numberOfImages: number;
numberOfImagesDefault: number;
imageSize: string[];
imageSizeDefault: string;
aspectRatio: string[];
aspectRatioDefault: string;
/** Maximum number of input images for image-to-image / multi-image composition */
maxInputImages?: number;
};
export type ThinkingConfig = {
levels: string[];
defaultLevel: string;
};
export type ModelCapabilities = {
supportsTools: boolean;
supportsVision: boolean;
supportsImage: boolean;
imageGeneration?: ImageGenerationConfig;
supportsThinking: boolean;
thinking?: ThinkingConfig;
supportsStreaming: boolean;
responseModalities?: string[];
/** Maximum number of attachments to visually promote (base64/Files API) */
maxPromotedAttachments?: number;
};
const CAPABILITY_MAP: Record<string, ModelCapabilities> = {
"gemini-3.1-pro-preview": {
supportsTools: true,
supportsVision: true,
supportsImage: false, // Text-only model, no image generation
supportsThinking: true,
thinking: {
levels: ["low", "medium", "high"],
defaultLevel: "low",
},
supportsStreaming: true,
// Text-only: default 2 attachments for vision context
maxPromotedAttachments: 2,
},
"gemini-3-pro-image-preview": {
supportsTools: true, // LM Studio local tools work via function declarations
supportsVision: true,
supportsImage: true,
imageGeneration: {
numberOfImages: 4,
numberOfImagesDefault: 1,
imageSize: ["1K", "2K", "4K"], // "Nano Banana Pro" supports higher res
imageSizeDefault: "1K",
aspectRatio: ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"],
aspectRatioDefault: "1:1",
// Google Docs: "up to 14 reference images" (6 objects + 5 humans + others)
maxInputImages: 14,
},
supportsThinking: true,
thinking: {
levels: [], // Does not support adjustable thinking levels
defaultLevel: "",
},
supportsStreaming: true,
responseModalities: ["TEXT", "IMAGE"],
// Native image generation: promote ALL attachments (up to 14)
maxPromotedAttachments: 4,
},
};
const DEFAULT_CAPABILITIES: ModelCapabilities = {
supportsTools: false,
supportsVision: true,
supportsImage: false,
supportsThinking: false,
supportsStreaming: true,
maxPromotedAttachments: 2, // Default: only last 2 attachments visually promoted
};
/**
* Detect capabilities for this plugin based on strict model ID lookup.
*/
export function detectCapabilities(modelId: string): ModelCapabilities {
const m = (modelId || "").toLowerCase();
// Direct lookup first
if (CAPABILITY_MAP[modelId]) {
return CAPABILITY_MAP[modelId];
}
// Fallback lookup (case-insensitive)
const key = Object.keys(CAPABILITY_MAP).find(k => k.toLowerCase() === m);
if (key) {
return CAPABILITY_MAP[key];
}
console.warn(`[Capabilities] Unknown model ID "${modelId}", using defaults.`);
return DEFAULT_CAPABILITIES;
}
/**
* Get the maximum number of attachments to visually promote for a model.
*
* - Image-capable models (Nano Banana, Nano Banana Pro): higher limits (10-14)
* because user may reference any [aN] for image2image operations
* - Text-only or tool-based image models: default 2 (last two attachments)
*/
export function getMaxPromotedAttachments(modelId: string): number {
const caps = detectCapabilities(modelId);
return caps.maxPromotedAttachments ?? 2;
}
/**
* Decide if Files API should be used for the given model under current policy.
* - Requires user toggle ON.
* - Kein separates Flash-Gating mehr: alle Modelle folgen dem Toggle.
*/
export function shouldUseFilesApiForModel(modelId: string, userToggle: boolean): boolean {
if (!userToggle) return false;
// Aktuell: Files API für alle Modelle, sobald das Toggle aktiv ist.
return true;
}