UNPKG

quantum-cli-core

Version:

Quantum CLI Core - Multi-LLM Collaboration System

126 lines 5.2 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { getResponseText } from '../utils/generateContentResponseUtilities.js'; import { reportError } from '../utils/errorReporting.js'; import { getErrorMessage, UnauthorizedError, toFriendlyError, } from '../utils/errors.js'; export var GeminiEventType; (function (GeminiEventType) { GeminiEventType["Content"] = "content"; GeminiEventType["ModelResponse"] = "model_response"; GeminiEventType["ToolCallRequest"] = "tool_call_request"; GeminiEventType["ToolCallResponse"] = "tool_call_response"; GeminiEventType["ToolCallConfirmation"] = "tool_call_confirmation"; GeminiEventType["UserCancelled"] = "user_cancelled"; GeminiEventType["Error"] = "error"; GeminiEventType["ChatCompressed"] = "chat_compressed"; GeminiEventType["Thought"] = "thought"; })(GeminiEventType || (GeminiEventType = {})); // A turn manages the agentic loop turn within the server context. export class Turn { chat; pendingToolCalls; debugResponses; constructor(chat) { this.chat = chat; this.pendingToolCalls = []; this.debugResponses = []; } // The run method yields simpler events suitable for server logic async *run(req, signal) { try { const responseStream = await this.chat.sendMessageStream({ message: req, config: { abortSignal: signal, }, }); for await (const resp of responseStream) { if (signal?.aborted) { yield { type: GeminiEventType.UserCancelled }; // Do not add resp to debugResponses if aborted before processing return; } this.debugResponses.push(resp); const thoughtPart = resp.candidates?.[0]?.content?.parts?.[0]; if (thoughtPart?.thought) { // Thought always has a bold "subject" part enclosed in double asterisks // (e.g., **Subject**). The rest of the string is considered the description. const rawText = thoughtPart.text ?? ''; const subjectStringMatches = rawText.match(/\*\*(.*?)\*\*/s); const subject = subjectStringMatches ? subjectStringMatches[1].trim() : ''; const description = rawText.replace(/\*\*(.*?)\*\*/s, '').trim(); const thought = { subject, description, }; yield { type: GeminiEventType.Thought, value: thought, }; continue; } const text = getResponseText(resp); if (text) { yield { type: GeminiEventType.Content, value: text }; } // Handle function calls (requesting tool execution) const functionCalls = resp.functionCalls ?? []; for (const fnCall of functionCalls) { const event = this.handlePendingFunctionCall(fnCall); if (event) { yield event; } } } } catch (e) { const error = toFriendlyError(e); if (error instanceof UnauthorizedError) { throw error; } if (signal.aborted) { yield { type: GeminiEventType.UserCancelled }; // Regular cancellation error, fail gracefully. return; } const contextForReport = [...this.chat.getHistory(/*curated*/ true), req]; await reportError(error, 'Error when talking to Gemini API', contextForReport, 'Turn.run-sendMessageStream'); const status = typeof error === 'object' && error !== null && 'status' in error && typeof error.status === 'number' ? error.status : undefined; const structuredError = { message: getErrorMessage(error), status, }; yield { type: GeminiEventType.Error, value: { error: structuredError } }; return; } } handlePendingFunctionCall(fnCall) { const callId = fnCall.id ?? `${fnCall.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`; const name = fnCall.name || 'undefined_tool_name'; const args = (fnCall.args || {}); const toolCallRequest = { callId, name, args, isClientInitiated: false, }; this.pendingToolCalls.push(toolCallRequest); // Yield a request for the tool call, not the pending/confirming status return { type: GeminiEventType.ToolCallRequest, value: toolCallRequest }; } getDebugResponses() { return this.debugResponses; } } //# sourceMappingURL=turn.js.map