mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat: Implement non-interactive mode for CLI (#675)
This commit is contained in:
114
packages/cli/src/nonInteractiveCli.ts
Normal file
114
packages/cli/src/nonInteractiveCli.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
Config,
|
||||
GeminiClient,
|
||||
ToolCallRequestInfo,
|
||||
executeToolCall,
|
||||
ToolRegistry,
|
||||
} from '@gemini-code/core';
|
||||
import {
|
||||
Content,
|
||||
Part,
|
||||
FunctionCall,
|
||||
GenerateContentResponse,
|
||||
} from '@google/genai';
|
||||
|
||||
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
|
||||
) {
|
||||
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> {
|
||||
const geminiClient = new GeminiClient(config);
|
||||
const toolRegistry: ToolRegistry = config.getToolRegistry();
|
||||
await toolRegistry.discoverTools();
|
||||
|
||||
const chat = await geminiClient.startChat();
|
||||
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 || [], // Ensure parts are always provided
|
||||
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) {
|
||||
functionCalls.push(...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>,
|
||||
};
|
||||
|
||||
const toolResponse = await executeToolCall(
|
||||
requestInfo,
|
||||
toolRegistry,
|
||||
abortController.signal,
|
||||
);
|
||||
|
||||
if (toolResponse.error) {
|
||||
console.error(
|
||||
`Error executing tool ${fc.name}: ${toolResponse.resultDisplay || toolResponse.error.message}`,
|
||||
);
|
||||
toolResponseParts.push(...(toolResponse.responseParts as Part[]));
|
||||
} else {
|
||||
toolResponseParts.push(...(toolResponse.responseParts as Part[]));
|
||||
}
|
||||
}
|
||||
currentMessages = [{ role: 'user', parts: toolResponseParts }];
|
||||
} else {
|
||||
process.stdout.write('\n'); // Ensure a final newline
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing input:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user