refactor(core): extract static concerns from CoreToolScheduler (#15589)

This commit is contained in:
Abhi
2025-12-26 15:51:39 -05:00
committed by GitHub
parent 65e2144b3d
commit 5566292cc8
16 changed files with 983 additions and 948 deletions
@@ -8,8 +8,125 @@ import type {
GenerateContentResponse,
Part,
FunctionCall,
PartListUnion,
} from '@google/genai';
import { getResponseText } from './partUtils.js';
import { supportsMultimodalFunctionResponse } from '../config/models.js';
import { debugLogger } from './debugLogger.js';
/**
* Formats tool output for a Gemini FunctionResponse.
*/
function createFunctionResponsePart(
callId: string,
toolName: string,
output: string,
): Part {
return {
functionResponse: {
id: callId,
name: toolName,
response: { output },
},
};
}
function toParts(input: PartListUnion): Part[] {
const parts: Part[] = [];
for (const part of Array.isArray(input) ? input : [input]) {
if (typeof part === 'string') {
parts.push({ text: part });
} else if (part) {
parts.push(part);
}
}
return parts;
}
export function convertToFunctionResponse(
toolName: string,
callId: string,
llmContent: PartListUnion,
model: string,
): Part[] {
if (typeof llmContent === 'string') {
return [createFunctionResponsePart(callId, toolName, llmContent)];
}
const parts = toParts(llmContent);
// Separate text from binary types
const textParts: string[] = [];
const inlineDataParts: Part[] = [];
const fileDataParts: Part[] = [];
for (const part of parts) {
if (part.text !== undefined) {
textParts.push(part.text);
} else if (part.inlineData) {
inlineDataParts.push(part);
} else if (part.fileData) {
fileDataParts.push(part);
} else if (part.functionResponse) {
if (parts.length > 1) {
debugLogger.warn(
'convertToFunctionResponse received multiple parts with a functionResponse. Only the functionResponse will be used, other parts will be ignored',
);
}
// Handle passthrough case
return [
{
functionResponse: {
id: callId,
name: toolName,
response: part.functionResponse.response,
},
},
];
}
// Ignore other part types
}
// Build the primary response part
const part: Part = {
functionResponse: {
id: callId,
name: toolName,
response: textParts.length > 0 ? { output: textParts.join('\n') } : {},
},
};
const isMultimodalFRSupported = supportsMultimodalFunctionResponse(model);
const siblingParts: Part[] = [...fileDataParts];
if (inlineDataParts.length > 0) {
if (isMultimodalFRSupported) {
// Nest inlineData if supported by the model
(part.functionResponse as unknown as { parts: Part[] }).parts =
inlineDataParts;
} else {
// Otherwise treat as siblings
siblingParts.push(...inlineDataParts);
}
}
// Add descriptive text if the response object is empty but we have binary content
if (
textParts.length === 0 &&
(inlineDataParts.length > 0 || fileDataParts.length > 0)
) {
const totalBinaryItems = inlineDataParts.length + fileDataParts.length;
part.functionResponse!.response = {
output: `Binary content provided (${totalBinaryItems} item(s)).`,
};
}
if (siblingParts.length > 0) {
return [part, ...siblingParts];
}
return [part];
}
export function getResponseTextFromParts(parts: Part[]): string | undefined {
if (!parts) {