/** * @license * Copyright 2025 QWEN * SPDX-License-Identifier: Apache-2.0 */ import type { CountTokensResponse, GenerateContentParameters, CountTokensParameters, EmbedContentResponse, EmbedContentParameters } from '@google/genai'; import { GenerateContentResponse } from '@google/genai'; import type { ContentGenerator } from './contentGenerator.js'; import OpenAI from 'openai'; import type { LlmRole } from '../telemetry/llmRole.js'; import type { Config } from '../config/config.js'; export declare class OpenAIContentGenerator implements ContentGenerator { protected client: OpenAI; private model; private config; protected readonly isOpenRouter: boolean; private streamingToolCalls; private streamingReasoningDetails; private streamingReasoningContent; private reasoningContentHistory; constructor(apiKey: string, model: string, config: Config); /** * Hook for subclasses to customize error handling behavior * @param error The error that occurred * @param request The original request * @returns true if error logging should be suppressed, false otherwise */ protected shouldSuppressErrorLogging(_error: unknown, _request: GenerateContentParameters): boolean; /** * Check if metadata should be included in the request * Only include metadata for specific providers that support it */ private shouldIncludeMetadata; /** * Build metadata object conditionally * @param userPromptId The prompt ID for this request * @returns metadata object if should be included, undefined otherwise */ private buildMetadata; /** * Check if an error is a timeout error */ private isTimeoutError; generateContent(request: GenerateContentParameters, userPromptId: string, _role?: LlmRole): Promise; generateContentStream(request: GenerateContentParameters, userPromptId: string, _role?: LlmRole): Promise>; private streamGenerator; /** * Combine streaming responses for logging purposes */ private combineStreamResponsesForLogging; countTokens(request: CountTokensParameters): Promise; embedContent(request: EmbedContentParameters): Promise; private convertGeminiParametersToOpenAI; private convertGeminiToolsToOpenAI; private convertToOpenAIFormat; /** * Check if the current model is a Gemini reasoning model (Gemini 3, 2.5 series) * These models require reasoning_details/thought_signature for tool calls */ private isGeminiReasoningModel; /** * Validate thoughtSignature to prevent "Corrupted thought signature" errors. * Valid thoughtSignature should have properly encrypted data (long base64 strings). * Invalid ones may contain short UUIDs or corrupted data. * * 验证 thoughtSignature 以防止 "Corrupted thought signature" 错误。 * 有效的 thoughtSignature 应该包含正确加密的数据(长 base64 字符串)。 * 无效的可能包含短 UUID 或损坏的数据。 */ private isValidThoughtSignature; /** * Add reasoning_content field to assistant messages for thinking-enabled models. * Models like Kimi K2.5 require the actual reasoning content to be echoed back; * models like DeepSeek accept an empty string. * We use the stored history when available, falling back to empty string. */ private addReasoningContentToAssistantMessages; /** * Filter out REASONING_DETAILS_MARKER from text to prevent it from leaking * into messages sent to the model. This can happen when AionUI includes * conversation history that contains the marker. * * 过滤文本中的 REASONING_DETAILS_MARKER,防止其泄露到发送给模型的消息中。 * 这种情况可能发生在 AionUI 包含含有 marker 的对话历史时。 */ private filterReasoningDetailsMarker; /** * 清理消息历史中的孤立工具调用,防止 OpenAI API 报错。 * 同时对相同 tool_call_id 的工具响应进行去重,防止不接受重复响应的 API 返回 400 错误。 * * Clean up orphaned tool calls from message history to prevent OpenAI API errors. * Also deduplicates tool responses with the same tool_call_id to prevent 400 errors * from providers that don't accept duplicate responses for the same tool call. */ private cleanOrphanedToolCalls; /** * Reorders tool response messages so each one immediately follows the * assistant message whose tool_calls it answers. * * The Gemini history may interleave user Content entries (e.g. system-prompt * injections) between a model's functionCall and the corresponding * functionResponse. In Gemini-native format that is fine (matching is by id), * but the OpenAI chat-completions API requires every assistant message with * tool_calls to be followed immediately by tool messages for each call id. */ private ensureToolResponseOrdering; /** * Merge consecutive assistant messages to combine split text and tool calls */ private mergeConsecutiveAssistantMessages; private convertToGeminiFormat; private convertStreamChunkToGeminiFormat; /** * Build sampling parameters with clear priority: * 1. Config-level sampling parameters (highest priority) * 2. Request-level parameters (medium priority) * 3. Default values (lowest priority) */ private buildSamplingParameters; private mapFinishReason; /** * Convert Gemini request format to OpenAI chat completion format for logging */ private convertGeminiRequestToOpenAI; /** * Clean up orphaned tool calls for logging purposes */ private cleanOrphanedToolCallsForLogging; /** * Logging variant of ensureToolResponseOrdering for the OpenAIMessage type. */ private ensureToolResponseOrderingForLogging; /** * Merge consecutive assistant messages to combine split text and tool calls for logging */ private mergeConsecutiveAssistantMessagesForLogging; /** * Convert Gemini response format to OpenAI chat completion format for logging */ private convertGeminiResponseToOpenAI; /** * Map Gemini finish reasons to OpenAI finish reasons */ private mapGeminiFinishReasonToOpenAI; }