|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
Config, |
|
|
ToolCallRequestInfo, |
|
|
executeToolCall, |
|
|
ToolRegistry, |
|
|
shutdownTelemetry, |
|
|
isTelemetrySdkInitialized, |
|
|
} from '@google/gemini-cli-core'; |
|
|
import { |
|
|
Content, |
|
|
Part, |
|
|
FunctionCall, |
|
|
GenerateContentResponse, |
|
|
} from '@google/genai'; |
|
|
|
|
|
import { parseAndFormatApiError } from './ui/utils/errorParsing.js'; |
|
|
|
|
|
function getResponseText(response: GenerateContentResponse): string | null { |
|
|
if (response.candidates && response.candidates.length > 0) { |
|
|
const candidate = response.candidates[0]; |
|
|
if ( |
|
|
candidate.content && |
|
|
candidate.content.parts && |
|
|
candidate.content.parts.length > 0 |
|
|
) { |
|
|
|
|
|
const thoughtPart = candidate.content.parts[0]; |
|
|
if (thoughtPart?.thought) { |
|
|
return null; |
|
|
} |
|
|
return candidate.content.parts |
|
|
.filter((part) => part.text) |
|
|
.map((part) => part.text) |
|
|
.join(''); |
|
|
} |
|
|
} |
|
|
return null; |
|
|
} |
|
|
|
|
|
export async function runNonInteractive( |
|
|
config: Config, |
|
|
input: string, |
|
|
): Promise<void> { |
|
|
|
|
|
process.stdout.on('error', (err: NodeJS.ErrnoException) => { |
|
|
if (err.code === 'EPIPE') { |
|
|
|
|
|
process.exit(0); |
|
|
} |
|
|
}); |
|
|
|
|
|
const geminiClient = config.getGeminiClient(); |
|
|
const toolRegistry: ToolRegistry = await config.getToolRegistry(); |
|
|
|
|
|
const chat = await geminiClient.getChat(); |
|
|
const abortController = new AbortController(); |
|
|
let currentMessages: Content[] = [{ role: 'user', parts: [{ text: input }] }]; |
|
|
|
|
|
try { |
|
|
while (true) { |
|
|
const functionCalls: FunctionCall[] = []; |
|
|
|
|
|
const responseStream = await chat.sendMessageStream({ |
|
|
message: currentMessages[0]?.parts || [], |
|
|
config: { |
|
|
abortSignal: abortController.signal, |
|
|
tools: [ |
|
|
{ functionDeclarations: toolRegistry.getFunctionDeclarations() }, |
|
|
], |
|
|
}, |
|
|
}); |
|
|
|
|
|
for await (const resp of responseStream) { |
|
|
if (abortController.signal.aborted) { |
|
|
console.error('Operation cancelled.'); |
|
|
return; |
|
|
} |
|
|
const textPart = getResponseText(resp); |
|
|
if (textPart) { |
|
|
process.stdout.write(textPart); |
|
|
} |
|
|
if (resp.functionCalls) { |
|
|
if (Array.isArray(resp.functionCalls)) { |
|
|
functionCalls.push(...resp.functionCalls); |
|
|
} else { |
|
|
console.error('functionCalls is not an array:', resp.functionCalls); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (functionCalls.length > 0) { |
|
|
const toolResponseParts: Part[] = []; |
|
|
|
|
|
for (const fc of functionCalls) { |
|
|
const callId = fc.id ?? `${fc.name}-${Date.now()}`; |
|
|
const requestInfo: ToolCallRequestInfo = { |
|
|
callId, |
|
|
name: fc.name as string, |
|
|
args: (fc.args ?? {}) as Record<string, unknown>, |
|
|
isClientInitiated: false, |
|
|
}; |
|
|
|
|
|
const toolResponse = await executeToolCall( |
|
|
config, |
|
|
requestInfo, |
|
|
toolRegistry, |
|
|
abortController.signal, |
|
|
); |
|
|
|
|
|
if (toolResponse.error) { |
|
|
console.error( |
|
|
`Error executing tool ${fc.name}: ${toolResponse.resultDisplay || toolResponse.error.message}`, |
|
|
); |
|
|
process.exit(1); |
|
|
} |
|
|
|
|
|
if (toolResponse.responseParts) { |
|
|
const parts = Array.isArray(toolResponse.responseParts) |
|
|
? toolResponse.responseParts |
|
|
: [toolResponse.responseParts]; |
|
|
for (const part of parts) { |
|
|
if (typeof part === 'string') { |
|
|
toolResponseParts.push({ text: part }); |
|
|
} else if (part) { |
|
|
toolResponseParts.push(part); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
currentMessages = [{ role: 'user', parts: toolResponseParts }]; |
|
|
} else { |
|
|
process.stdout.write('\n'); |
|
|
return; |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Raw error:', error); |
|
|
if (error instanceof Error) { |
|
|
console.error('Error stack:', error.stack); |
|
|
} |
|
|
console.error( |
|
|
parseAndFormatApiError( |
|
|
error, |
|
|
config.getContentGeneratorConfig().authType, |
|
|
), |
|
|
); |
|
|
process.exit(1); |
|
|
} finally { |
|
|
if (isTelemetrySdkInitialized()) { |
|
|
await shutdownTelemetry(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|