From 9820ea04e8b45bec04b04acddddc89774de30ba1 Mon Sep 17 00:00:00 2001 From: victorbash400 Date: Sat, 3 Jan 2026 00:13:08 +0300 Subject: [PATCH 1/2] fix: prevent premature loop termination for Gemini 3 tool calls Gemini 3 models send an explicit STOP finish reason after tool calls, causing runAsyncImpl to terminate before processing the tool response. Changes: - Track function responses in runAsyncImpl loop - Continue loop if pending function response exists - Include gemini-3 in isGemini2Model() for code executor compat --- core/src/agents/llm_agent.ts | 6 +++++- core/src/utils/model_name.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/agents/llm_agent.ts b/core/src/agents/llm_agent.ts index e509c8b..0a89ed3 100644 --- a/core/src/agents/llm_agent.ts +++ b/core/src/agents/llm_agent.ts @@ -1498,13 +1498,17 @@ export class LlmAgent extends BaseAgent { ): AsyncGenerator { while (true) { let lastEvent: Event|undefined = undefined; + let hasFunctionResponse = false; for await (const event of this.runOneStepAsync(context)) { lastEvent = event; + if (getFunctionResponses(event).length > 0) { + hasFunctionResponse = true; + } this.maybeSaveOutputToState(event); yield event; } - if (!lastEvent || isFinalResponse(lastEvent)) { + if (!lastEvent || (isFinalResponse(lastEvent) && !hasFunctionResponse)) { break; } if (lastEvent.partial) { diff --git a/core/src/utils/model_name.ts b/core/src/utils/model_name.ts index 5428b39..d2dd367 100644 --- a/core/src/utils/model_name.ts +++ b/core/src/utils/model_name.ts @@ -57,5 +57,5 @@ export function isGemini1Model(modelString: string): boolean { export function isGemini2Model(modelString: string): boolean { const modelName = extractModelName(modelString); - return modelName.startsWith('gemini-2'); + return modelName.startsWith('gemini-2') || modelName.startsWith('gemini-3'); } \ No newline at end of file From a819d8cdc6184db425de436a2f0ac7b809e76ce9 Mon Sep 17 00:00:00 2001 From: victorbash400 Date: Mon, 5 Jan 2026 14:37:34 +0300 Subject: [PATCH 2/2] refactor: introduce isGemini2OrLaterModel helper for version checks --- .../code_executors/built_in_code_executor.ts | 4 +-- core/src/utils/model_name.ts | 26 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/core/src/code_executors/built_in_code_executor.ts b/core/src/code_executors/built_in_code_executor.ts index 3163b6c..aa74121 100644 --- a/core/src/code_executors/built_in_code_executor.ts +++ b/core/src/code_executors/built_in_code_executor.ts @@ -7,7 +7,7 @@ import {GenerateContentConfig} from '@google/genai' import {InvocationContext} from '../agents/invocation_context.js'; import {LlmRequest} from '../models/llm_request.js'; -import {isGemini2Model} from '../utils/model_name.js'; +import {isGemini2OrLaterModel} from '../utils/model_name.js'; import {BaseCodeExecutor, ExecuteCodeParams} from './base_code_executor.js'; import {CodeExecutionInput, CodeExecutionResult} from './code_execution_utils.js'; @@ -28,7 +28,7 @@ export class BuiltInCodeExecutor extends BaseCodeExecutor { } processLlmRequest(llmRequest: LlmRequest) { - if (llmRequest.model && isGemini2Model(llmRequest.model)) { + if (llmRequest.model && isGemini2OrLaterModel(llmRequest.model)) { llmRequest.config = llmRequest.config || {}; llmRequest.config.tools = llmRequest.config.tools || []; llmRequest.config.tools.push({codeExecution: {}}); diff --git a/core/src/utils/model_name.ts b/core/src/utils/model_name.ts index d2dd367..4650d93 100644 --- a/core/src/utils/model_name.ts +++ b/core/src/utils/model_name.ts @@ -5,7 +5,7 @@ */ const MODEL_NAME_PATTERN = - '^projects/[^/]+/locations/[^/]+/publishers/[^/]+/models/(.+)$'; + '^projects/[^/]+/locations/[^/]+/publishers/[^/]+/models/(.+)$'; /** * Extract the actual model name from either simple or path-based format. @@ -57,5 +57,27 @@ export function isGemini1Model(modelString: string): boolean { export function isGemini2Model(modelString: string): boolean { const modelName = extractModelName(modelString); - return modelName.startsWith('gemini-2') || modelName.startsWith('gemini-3'); + return modelName.startsWith('gemini-2'); +} + +/** + * Check if the model is a Gemini 3.x model using regex patterns. + * + * @param modelString Either a simple model name or path - based model name + * @return true if it's a Gemini 3.x model, false otherwise. + */ +export function isGemini3Model(modelString: string): boolean { + const modelName = extractModelName(modelString); + + return modelName.startsWith('gemini-3'); +} + +/** + * Check if the model is Gemini 2.0 or later (includes 2.x and 3.x). + * + * @param modelString Either a simple model name or path - based model name + * @return true if it's Gemini 2.0+, false otherwise. + */ +export function isGemini2OrLaterModel(modelString: string): boolean { + return isGemini2Model(modelString) || isGemini3Model(modelString); } \ No newline at end of file