/** * Copyright 2025 Vybestack LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @plan PLAN-20250120-DEBUGLOGGING.P15 * @requirement REQ-INT-001.1 */ import OpenAI from 'openai'; import { type IContent } from '../../services/history/IContent.js'; import { type IProviderConfig } from '../types/IProviderConfig.js'; import { BaseProvider, type NormalizedGenerateChatOptions } from '../BaseProvider.js'; import { type OAuthManager } from '../../auth/precedence.js'; import { type IModel } from '../IModel.js'; import { type IProvider } from '../IProvider.js'; export declare class OpenAIProvider extends BaseProvider implements IProvider { private readonly textToolParser; private readonly toolCallPipeline; private readonly toolCallProcessingMode; private getLogger; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Constructor reduced to minimal initialization - no state captured */ constructor(apiKey: string | undefined, baseURL?: string, config?: IProviderConfig, oauthManager?: OAuthManager); /** * Create HTTP/HTTPS agents with socket configuration for local AI servers * Returns undefined if no socket settings are configured * * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Now sources ephemeral settings from call options instead of provider config */ private createHttpAgents; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-002 * Extract model parameters from normalized options instead of settings service */ private extractModelParamsFromOptions; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Resolve runtime key from normalized options for client scoping */ private resolveRuntimeKey; /** * Tool formatter instances cannot be shared between stateless calls, * so construct a fresh one for every invocation. * * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 */ private createToolFormatter; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P09 * @requirement:REQ-SP4-002 * Instantiates a fresh OpenAI client per call to preserve stateless behaviour. */ private instantiateClient; /** * Coerce provider "content" (which may be a string or an array-of-parts) * into a plain string. Defensive for OpenAI-compatible providers that emit * structured content blocks. */ private coerceMessageContentToString; /** * Sanitize raw tool argument payloads before JSON parsing: * - Remove thinking blocks (..., etc.). * - Strip Markdown code fences (```json ... ```). * - Try to isolate the main JSON object if wrapped in prose. */ private sanitizeToolArgumentsString; /** * Parse Kimi-K2 `<|tool_calls_section_begin|> ... <|tool_calls_section_end|>` * blocks out of a text string. * * - Returns cleanedText with the whole section removed. * - Returns ToolCallBlock[] constructed from the section contents. * * This is used for HF/vLLM-style Kimi deployments where `tool_calls` is empty * and all tool info is only encoded in the text template. */ private extractKimiToolCallsFromText; /** * Clean Kimi K2 tool call tokens from thinking content. * Used when extracting thinking from tags that may contain embedded tool calls. * @issue #749 */ private cleanThinkingContent; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P09 * @requirement:REQ-SP4-002 * @requirement:REQ-LOCAL-001 * Creates a client scoped to the active runtime metadata without caching. * Local endpoints (localhost, private IPs) are allowed without authentication * to support local AI servers like Ollama. */ private mergeInvocationHeaders; protected getClient(options: NormalizedGenerateChatOptions): Promise; /** * Check if OAuth is supported for this provider * Qwen endpoints support OAuth, standard OpenAI does not */ protected supportsOAuth(): boolean; getModels(): Promise; private getFallbackModels; getDefaultModel(): string; /** * Get the currently selected model */ getCurrentModel(): string; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P09 * @requirement:REQ-SP4-002 * No-op retained for compatibility because clients are no longer cached. */ clearClientCache(runtimeKey?: string): void; /** * Override isAuthenticated for qwen provider to check OAuth directly */ isAuthenticated(): Promise; /** * Clear all provider state (for provider switching) * Clears both OpenAI client cache and auth token cache */ clearState(): void; getServerTools(): string[]; invokeServerTool(toolName: string, _params: unknown, _config?: unknown, _signal?: AbortSignal): Promise; /** * @plan PLAN-20250218-STATELESSPROVIDER.P04 * @requirement REQ-SP-001 * @pseudocode base-provider.md lines 7-15 * @pseudocode provider-invocation.md lines 8-12 */ /** * @plan:PLAN-20251023-STATELESS-HARDENING.P09 * @requirement:REQ-SP4-002 * Generate chat completion with per-call client instantiation. */ protected generateChatCompletionWithOptions(options: NormalizedGenerateChatOptions): AsyncIterableIterator; private normalizeToolCallArguments; private buildToolResponseContent; /** * Build messages with optional reasoning_content based on settings. * * @plan PLAN-20251202-THINKING.P14 * @requirement REQ-THINK-004, REQ-THINK-006 */ private buildMessagesWithReasoning; /** * Validates tool message sequence to ensure each tool message has a corresponding tool_calls * This prevents "messages with role 'tool' must be a response to a preceeding message with 'tool_calls'" errors * * Only validates when there are tool_calls present in conversation to avoid breaking isolated tool response tests * * @param messages - The converted OpenAI messages to validate * @returns The validated messages with invalid tool messages removed */ private validateToolMessageSequence; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Legacy implementation for chat completion using accumulated tool calls approach */ private generateLegacyChatCompletionImpl; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-002 * Memoization of model parameters disabled for stateless provider */ setModelParams(_params: Record | undefined): void; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Gets model parameters from SettingsService per call (stateless) */ getModelParams(): Record | undefined; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Internal implementation for chat completion using normalized options * Routes to appropriate implementation based on toolCallProcessingMode */ private generateChatCompletionImpl; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Pipeline implementation for chat completion using optimized tool call pipeline */ private generatePipelineChatCompletionImpl; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Legacy implementation for chat completion using accumulated tool calls approach */ getToolFormat(): string; /** * Parse tool response from API (placeholder for future response parsing) * @param response The raw API response * @returns Parsed tool response */ parseToolResponse(response: unknown): unknown; /** * @plan:PLAN-20251023-STATELESS-HARDENING.P08 * @requirement:REQ-SP4-003 * Determines whether a response should be retried based on error codes * @param error The error object from the API response * @returns true if the request should be retried, false otherwise */ shouldRetryResponse(error: unknown): boolean; /** * Parse reasoning_content from streaming delta. * * @plan PLAN-20251202-THINKING.P11, PLAN-20251202-THINKING.P16 * @requirement REQ-THINK-003.1, REQ-THINK-003.3, REQ-THINK-003.4, REQ-KIMI-REASONING-001.1 * @issue #749 */ private parseStreamingReasoningDelta; /** * Parse reasoning_content from non-streaming message. * * @plan PLAN-20251202-THINKING.P11, PLAN-20251202-THINKING.P16 * @requirement REQ-THINK-003.2, REQ-THINK-003.3, REQ-THINK-003.4, REQ-KIMI-REASONING-001.2 * @issue #749 */ private parseNonStreamingReasoning; private buildContinuationMessages; /** * Request continuation after tool calls when model returned no text. * This is a helper to avoid code duplication between legacy and pipeline paths. * * @plan PLAN-20250120-DEBUGLOGGING.P15 * @issue #584, #764 (CodeRabbit review) */ private requestContinuationAfterToolCalls; }