This commit is contained in:
2026-05-13 16:07:47 +03:00
parent a411c6874a
commit 78932e82af
4 changed files with 30 additions and 5 deletions
+2
View File
@@ -119,6 +119,8 @@ export function getDefaultModelForPurpose(provider: AiProvider, purpose: AiRunti
} }
case AiProvider.GEMINI: case AiProvider.GEMINI:
switch (purpose) { switch (purpose) {
case "vision":
case "ocr":
case "outputImages": case "outputImages":
return Environment.GEMINI_IMAGE_MODEL; return Environment.GEMINI_IMAGE_MODEL;
case "speechToText": case "speechToText":
+3 -3
View File
@@ -164,14 +164,14 @@ export async function getModelCapabilities(
case AiProvider.GEMINI: { case AiProvider.GEMINI: {
const chatLike = lowerModelName(model).startsWith("gemini-") && !isGeminiNonChatModel(model); const chatLike = lowerModelName(model).startsWith("gemini-") && !isGeminiNonChatModel(model);
const reasoningModel = lowerModelName(model).includes("2.5") || lowerModelName(model).includes("thinking"); const reasoningModel = lowerModelName(model).includes("2.5") || lowerModelName(model).includes("thinking");
const imageTarget = resolveAiRuntimeTarget(provider, "outputImages"); const imageTarget = resolveAiRuntimeTarget(provider, "vision");
const speechTarget = resolveAiRuntimeTarget(provider, "speechToText"); const speechTarget = resolveAiRuntimeTarget(provider, "speechToText");
const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech"); const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech");
return buildCapabilities({ return buildCapabilities({
chat: capability(true, target, runtimeTarget), chat: capability(true, target, runtimeTarget),
vision: capability(chatLike, target, runtimeTarget), vision: capability(!!imageTarget.apiKey && !!imageTarget.model, imageTarget, runtimeTarget),
ocr: capability(chatLike, target, runtimeTarget), ocr: capability(!!imageTarget.apiKey && !!imageTarget.model, imageTarget, runtimeTarget),
thinking: capability(reasoningModel, target, runtimeTarget), thinking: capability(reasoningModel, target, runtimeTarget),
extendedThinking: capability(reasoningModel, target, runtimeTarget), extendedThinking: capability(reasoningModel, target, runtimeTarget),
tools: capability(chatLike, target, runtimeTarget), tools: capability(chatLike, target, runtimeTarget),
+22 -2
View File
@@ -6,7 +6,19 @@ import {GeminiMessage} from "./gemini-chat-message";
import {createGoogleGenAiClient} from "./ai-runtime-target"; import {createGoogleGenAiClient} from "./ai-runtime-target";
import {aiLog, aiLogDuration, aiLogProviderTarget, aiLogToolCall} from "../logging/ai-logger"; import {aiLog, aiLogDuration, aiLogProviderTarget, aiLogToolCall} from "../logging/ai-logger";
import {AsyncIterableStream, GeminiFunctionCallLike, GeminiResponseLike, MAX_TOOL_ROUNDS, RuntimeConfigSnapshot, ToolCallData, ToolExecutionMemory, executeToolBatch, roundStatus, safeJsonParseObject, GeminiGenerationRequest} from "./unified-ai-runner.shared"; import {
AsyncIterableStream,
executeToolBatch,
GeminiFunctionCallLike,
GeminiGenerationRequest,
GeminiResponseLike,
MAX_TOOL_ROUNDS,
roundStatus,
RuntimeConfigSnapshot,
safeJsonParseObject,
ToolCallData,
ToolExecutionMemory
} from "./unified-ai-runner.shared";
function collectGeminiResponseText(response: GeminiResponseLike & { text?: string }): string { function collectGeminiResponseText(response: GeminiResponseLike & { text?: string }): string {
if (typeof response.text === "string") return response.text; if (typeof response.text === "string") return response.text;
@@ -88,6 +100,14 @@ export async function runGemini(
hasToolInputFiles: !!toolContext.pythonInputFiles?.length, hasToolInputFiles: !!toolContext.pythonInputFiles?.length,
}); });
// TODO: 13.05.2026, Danil Nikolaev: find a better way?
const imageCount = messages.reduce((sum, m) => {
return sum + (m.parts.filter(p => "inlineData" in p && "mimeType" in p.inlineData && p.inlineData.mimeType.startsWith("image")).length)
}, 0);
const target = imageCount ? config.geminiImageTarget : config.geminiChatTarget;
const model = target.model;
const toolMemory: ToolExecutionMemory = new Map(); const toolMemory: ToolExecutionMemory = new Map();
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) { for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
@@ -99,7 +119,7 @@ export async function runGemini(
await streamMessage.flush(); await streamMessage.flush();
const request: GeminiGenerationRequest = { const request: GeminiGenerationRequest = {
model: config.geminiChatTarget.model, model: model,
contents: messages, contents: messages,
config: { config: {
tools: getGeminiTools(), tools: getGeminiTools(),
+3
View File
@@ -279,6 +279,7 @@ export type RuntimeConfigSnapshot = {
ollamaRagMaxArchiveDepth: number; ollamaRagMaxArchiveDepth: number;
geminiChatTarget: AiRuntimeTarget; geminiChatTarget: AiRuntimeTarget;
geminiImageTarget: AiRuntimeTarget;
mistralChatTarget: AiRuntimeTarget; mistralChatTarget: AiRuntimeTarget;
@@ -310,6 +311,7 @@ export function snapshotRuntimeConfig(): RuntimeConfigSnapshot {
ollamaRagMaxArchiveDepth: Environment.OLLAMA_RAG_MAX_ARCHIVE_DEPTH, ollamaRagMaxArchiveDepth: Environment.OLLAMA_RAG_MAX_ARCHIVE_DEPTH,
geminiChatTarget: resolveAiRuntimeTarget(AiProvider.GEMINI, "chat"), geminiChatTarget: resolveAiRuntimeTarget(AiProvider.GEMINI, "chat"),
geminiImageTarget: resolveAiRuntimeTarget(AiProvider.GEMINI, "vision"),
mistralChatTarget: resolveAiRuntimeTarget(AiProvider.MISTRAL, "chat"), mistralChatTarget: resolveAiRuntimeTarget(AiProvider.MISTRAL, "chat"),
@@ -641,6 +643,7 @@ export async function rejectUnsupportedAttachments(
if (!unsupported) return false; if (!unsupported) return false;
if (!kinds.has("audio")) { if (!kinds.has("audio")) {
// TODO: 13.05.2026, Danil Nikolaev: add "Regenerate" button
await replyToMessage({ await replyToMessage({
message: msg, message: msg,
text: unsupportedAttachmentText(provider, effectiveModel, unsupported), text: unsupportedAttachmentText(provider, effectiveModel, unsupported),