UNPKG

openagentic

Version:

A TypeScript framework for building AI agents with self-contained tool orchestration capabilities

1 lines โ€ข 532 kB
{"version":3,"sources":["../src/providers/manager.ts","../src/orchestrators/registry.ts","../src/orchestrator.ts","../src/types.ts","../src/tools/utils.ts","../src/tools/openai.ts","../src/utils/s3.ts","../src/tools/openai-image.ts","../src/tools/anthropic.ts","../src/tools/gemini.ts","../src/tools/github.ts","../src/tools/grok.ts","../src/tools/llama.ts","../src/tools/newsdata.ts","../src/tools/perplexity.ts","../src/tools/qrcode.ts","../src/tools/websearch.ts","../src/tools/elevenlabs.ts","../src/tools/video-generation.ts","../src/tools/gemini-tts.ts","../src/tools/inception-labs.ts","../src/tools/index.ts","../src/orchestrators/prompt-based.ts","../src/orchestrators/custom-logic.ts","../src/orchestrators/multi-ai.ts","../src/orchestrators/library/video-creator.ts","../src/orchestrators/library/code-assessment.ts","../src/index.ts","../src/streaming-orchestrator.ts","../src/orchestrators/index.ts"],"sourcesContent":["import type { AIModel, ApiKeyMap } from '../types';\n\n// Provider configuration with model metadata\nexport const providerConfigs = {\n openai: {\n baseURL: 'https://api.openai.com/v1',\n models: {\n 'gpt-4': { \n contextWindow: 8192, \n cost: { input: 0.03, output: 0.06 },\n description: 'Most capable GPT-4 model'\n },\n 'gpt-4-turbo': { \n contextWindow: 128000, \n cost: { input: 0.01, output: 0.03 },\n description: 'GPT-4 Turbo with larger context window'\n },\n 'gpt-4o': { \n contextWindow: 128000, \n cost: { input: 0.005, output: 0.015 },\n description: 'GPT-4 Omni - fastest and most cost-effective'\n },\n 'gpt-4o-mini': { \n contextWindow: 128000, \n cost: { input: 0.00015, output: 0.0006 },\n description: 'Smaller, faster GPT-4o variant'\n },\n 'o3': { \n contextWindow: 200000, \n cost: { input: 0.06, output: 0.24 },\n description: 'Latest reasoning model'\n },\n 'o3-mini': { \n contextWindow: 200000, \n cost: { input: 0.015, output: 0.06 },\n description: 'Smaller o3 variant with faster inference'\n },\n }\n },\n anthropic: {\n baseURL: 'https://api.anthropic.com',\n models: {\n 'claude-opus-4-20250514': { \n contextWindow: 200000, \n cost: { input: 0.015, output: 0.075 },\n description: 'Most capable Claude 4 model'\n },\n 'claude-sonnet-4-20250514': { \n contextWindow: 200000, \n cost: { input: 0.003, output: 0.015 },\n description: 'Balanced Claude 4 model for most use cases'\n },\n }\n },\n google: {\n baseURL: 'https://generativelanguage.googleapis.com/v1beta',\n models: {\n 'gemini-2.5-pro-preview-06-05': { \n contextWindow: 2000000, \n cost: { input: 0.001, output: 0.002 },\n description: 'Latest Gemini 2.5 Pro preview model'\n },\n 'gemini-2.5-flash-preview-05-20': { \n contextWindow: 1000000, \n cost: { input: 0.0005, output: 0.001 },\n description: 'Fast Gemini 2.5 Flash preview model'\n },\n 'gemini-1.5-pro': { \n contextWindow: 2000000, \n cost: { input: 0.00125, output: 0.005 },\n description: 'Gemini 1.5 Pro with large context window'\n },\n 'gemini-1.5-flash': { \n contextWindow: 1000000, \n cost: { input: 0.000075, output: 0.0003 },\n description: 'Fast and efficient Gemini 1.5 model'\n },\n }\n },\n 'google-vertex': {\n baseURL: 'https://us-central1-aiplatform.googleapis.com',\n models: {\n 'gemini-2.5-pro-preview-06-05': { \n contextWindow: 2000000, \n cost: { input: 0.001, output: 0.002 },\n description: 'Latest Gemini 2.5 Pro preview model via Vertex AI'\n },\n 'gemini-2.5-flash-preview-05-20': { \n contextWindow: 1000000, \n cost: { input: 0.0005, output: 0.001 },\n description: 'Fast Gemini 2.5 Flash preview model via Vertex AI'\n },\n 'gemini-1.5-pro': { \n contextWindow: 2000000, \n cost: { input: 0.00125, output: 0.005 },\n description: 'Gemini 1.5 Pro via Vertex AI'\n },\n 'gemini-1.5-flash': { \n contextWindow: 1000000, \n cost: { input: 0.000075, output: 0.0003 },\n description: 'Fast Gemini 1.5 model via Vertex AI'\n },\n }\n },\n perplexity: {\n baseURL: 'https://api.perplexity.ai',\n models: {\n 'llama-3.1-sonar-small-128k-online': { \n contextWindow: 127072, \n cost: { input: 0.0002, output: 0.0002 },\n description: 'Small Llama 3.1 Sonar with online search'\n },\n 'llama-3.1-sonar-large-128k-online': { \n contextWindow: 127072, \n cost: { input: 0.001, output: 0.001 },\n description: 'Large Llama 3.1 Sonar with online search'\n },\n 'llama-3.1-sonar-huge-128k-online': { \n contextWindow: 127072, \n cost: { input: 0.005, output: 0.005 },\n description: 'Huge Llama 3.1 Sonar with online search'\n },\n }\n },\n xai: {\n baseURL: 'https://api.x.ai/v1',\n models: {\n 'grok-beta': { \n contextWindow: 131072, \n cost: { input: 0.005, output: 0.015 },\n description: 'Grok conversational AI model'\n },\n }\n },\n};\n\n// =============================================================================\n// CENTRALIZED PROVIDER MANAGER\n// =============================================================================\n\nexport class ProviderManager {\n private static userApiKeys: ApiKeyMap | undefined;\n\n /**\n * Set user-provided API keys that take precedence over environment variables\n */\n static setUserApiKeys(apiKeys: ApiKeyMap | undefined): void {\n this.userApiKeys = apiKeys;\n \n if (apiKeys && Object.keys(apiKeys).length > 0) {\n console.log('๐Ÿ”‘ User API keys configured for providers:', Object.keys(apiKeys).length);\n }\n }\n\n /**\n * Create a model configuration from a string or AIModel object\n * Automatically detects provider from model name if string is provided\n */\n static createModel(input: string | AIModel): AIModel {\n if (typeof input === 'string') {\n return this.autoDetectProvider(input);\n }\n return this.validateAndNormalizeModel(input);\n }\n\n /**\n * Create an AI SDK provider instance for the given model\n */\n static async createProvider(model: AIModel): Promise<any> {\n const apiKey = model.apiKey ?? this.getDefaultApiKey(model.provider); // Fix: Use nullish coalescing\n \n switch (model.provider) {\n case 'openai': {\n const { createOpenAI } = await import('@ai-sdk/openai');\n const config: any = {};\n if (apiKey !== undefined) config.apiKey = apiKey; // Fix: Explicit undefined check\n if (model.baseURL !== undefined) config.baseURL = model.baseURL;\n return createOpenAI(config);\n }\n case 'anthropic': {\n const { createAnthropic } = await import('@ai-sdk/anthropic');\n const config: any = {};\n if (apiKey !== undefined) config.apiKey = apiKey;\n return createAnthropic(config);\n }\n case 'google': {\n const { createGoogleGenerativeAI } = await import('@ai-sdk/google');\n const config: any = {};\n if (apiKey !== undefined) config.apiKey = apiKey;\n return createGoogleGenerativeAI(config);\n }\n case 'google-vertex': {\n const { createVertex } = await import('@ai-sdk/google-vertex');\n const config: any = {};\n if (model.project !== undefined) config.project = model.project;\n if (model.location !== undefined) config.location = model.location;\n return createVertex(config);\n }\n case 'perplexity': {\n const { createPerplexity } = await import('@ai-sdk/perplexity');\n const config: any = {};\n if (apiKey !== undefined) config.apiKey = apiKey;\n return createPerplexity(config);\n }\n case 'xai': {\n const { createXai } = await import('@ai-sdk/xai');\n const config: any = {};\n if (apiKey !== undefined) config.apiKey = apiKey;\n return createXai(config);\n }\n case 'custom': {\n if (!model.baseURL) {\n throw new Error('Custom provider requires baseURL');\n }\n const { createOpenAI } = await import('@ai-sdk/openai');\n const config: any = {\n baseURL: model.baseURL,\n };\n if (apiKey !== undefined) config.apiKey = apiKey;\n return createOpenAI(config);\n }\n default:\n throw new Error(`Unsupported provider: ${model.provider}`);\n }\n }\n\n /**\n * Create a provider for a specific provider name (for tool context)\n */\n static async createProviderByName(providerName: string, apiKey?: string): Promise<any> {\n const key = apiKey ?? this.getDefaultApiKey(providerName as AIModel['provider']); // Fix: Use nullish coalescing\n \n switch (providerName) {\n case 'openai': {\n const { createOpenAI } = await import('@ai-sdk/openai');\n if (!key) throw new Error('OpenAI API key not found');\n return createOpenAI({ apiKey: key });\n }\n case 'anthropic': {\n const { createAnthropic } = await import('@ai-sdk/anthropic');\n if (!key) throw new Error('Anthropic API key not found');\n return createAnthropic({ apiKey: key });\n }\n case 'google': {\n const { createGoogleGenerativeAI } = await import('@ai-sdk/google');\n if (!key) throw new Error('Google API key not found');\n return createGoogleGenerativeAI({ apiKey: key });\n }\n case 'perplexity': {\n const { createPerplexity } = await import('@ai-sdk/perplexity');\n if (!key) throw new Error('Perplexity API key not found');\n return createPerplexity({ apiKey: key });\n }\n case 'xai': {\n const { createXai } = await import('@ai-sdk/xai');\n if (!key) throw new Error('xAI API key not found');\n return createXai({ apiKey: key });\n }\n default:\n throw new Error(`Unsupported provider: ${providerName}`);\n }\n }\n\n /**\n * Get all available providers and their models\n */\n static getAllProviders(): Array<{ provider: string; models: string[] }> {\n return Object.entries(providerConfigs).map(([provider, config]) => ({\n provider,\n models: Object.keys(config.models),\n }));\n }\n\n /**\n * Get supported models for a provider\n */\n static getProviderModels(provider: string): string[] {\n const config = providerConfigs[provider as keyof typeof providerConfigs];\n return config ? Object.keys(config.models) : [];\n }\n\n /**\n * Check if a model is supported by a provider\n */\n static isModelSupported(provider: string, model: string): boolean {\n const models = this.getProviderModels(provider);\n return models.includes(model);\n }\n\n /**\n * Get model information (context window, cost, description)\n */\n static getModelInfo(provider: string, model: string) {\n const config = providerConfigs[provider as keyof typeof providerConfigs];\n if (!config) {\n throw new Error(`Unknown provider: ${provider}`);\n }\n \n const modelInfo = config.models[model as keyof typeof config.models];\n if (!modelInfo) {\n throw new Error(`Unknown model: ${model} for provider: ${provider}`);\n }\n \n return modelInfo;\n }\n\n // Private methods\n private static autoDetectProvider(modelName: string): AIModel {\n let provider: AIModel['provider'];\n let apiKey: string | undefined;\n\n // OpenAI models\n if (modelName.includes('gpt') || modelName.includes('o3')) {\n provider = 'openai';\n apiKey = this.getDefaultApiKey('openai');\n }\n // Anthropic models\n else if (modelName.includes('claude')) {\n provider = 'anthropic';\n apiKey = this.getDefaultApiKey('anthropic');\n }\n // Google models\n else if (modelName.includes('gemini')) {\n provider = 'google';\n apiKey = this.getDefaultApiKey('google');\n }\n // xAI models\n else if (modelName.includes('grok')) {\n provider = 'xai';\n apiKey = this.getDefaultApiKey('xai');\n }\n // Perplexity models\n else if (modelName.includes('llama') && modelName.includes('sonar')) {\n provider = 'perplexity';\n apiKey = this.getDefaultApiKey('perplexity');\n }\n // Default to OpenAI for unknown models\n else {\n provider = 'openai';\n apiKey = this.getDefaultApiKey('openai');\n console.warn(`Unknown model \"${modelName}\", defaulting to OpenAI provider`);\n }\n\n // Validate model is supported by detected provider\n if (!this.isModelSupported(provider, modelName)) {\n throw new Error(`Model \"${modelName}\" not found in ${provider} configuration`);\n }\n\n return {\n provider,\n model: modelName,\n apiKey,\n temperature: 0.7,\n };\n }\n\n private static validateAndNormalizeModel(model: AIModel): AIModel {\n // Ensure required fields are present\n if (!model.provider || !model.model) {\n throw new Error('AIModel must have provider and model fields');\n }\n\n // Create a normalized copy to avoid mutating the original\n const normalizedModel: AIModel = { ...model };\n\n // Add default API key if not provided\n if (normalizedModel.apiKey === undefined) {\n normalizedModel.apiKey = this.getDefaultApiKey(normalizedModel.provider);\n }\n\n // Add default temperature if not provided\n if (normalizedModel.temperature === undefined) {\n normalizedModel.temperature = 0.7;\n }\n\n return normalizedModel;\n }\n\n private static getDefaultApiKey(provider: AIModel['provider']): string | undefined {\n // First check user-provided API keys\n if (this.userApiKeys && provider in this.userApiKeys) {\n return this.userApiKeys[provider];\n }\n \n // Then fall back to environment variables\n switch (provider) {\n case 'openai':\n return process.env.OPENAI_API_KEY;\n case 'anthropic':\n return process.env.ANTHROPIC_API_KEY;\n case 'google':\n return process.env.GOOGLE_API_KEY;\n case 'google-vertex':\n return undefined; // Vertex uses service account auth\n case 'perplexity':\n return process.env.PERPLEXITY_API_KEY;\n case 'xai':\n return process.env.XAI_API_KEY;\n case 'custom':\n return undefined; // Custom providers handle their own auth\n default:\n return undefined;\n }\n }\n}","import type { BaseOrchestrator, OrchestratorType } from '../types';\n\n// =============================================================================\n// ORCHESTRATOR REGISTRY\n// =============================================================================\n\n/**\n * Global registry for storing orchestrator instances\n */\nconst orchestratorRegistry = new Map<string, BaseOrchestrator>();\n\n/**\n * Register an orchestrator in the global registry\n * @param orchestrator - The orchestrator to register\n * @throws {Error} If an orchestrator with the same ID already exists\n */\nexport function registerOrchestrator(orchestrator: BaseOrchestrator): void {\n if (!orchestrator || !orchestrator.id) {\n throw new Error('Orchestrator must have a valid ID');\n }\n\n if (orchestratorRegistry.has(orchestrator.id)) {\n throw new Error(`Orchestrator with ID \"${orchestrator.id}\" already exists`);\n }\n\n // Validate orchestrator structure\n validateOrchestrator(orchestrator);\n\n orchestratorRegistry.set(orchestrator.id, orchestrator);\n \n console.log(`๐ŸŽญ Orchestrator registered: ${orchestrator.id} (${orchestrator.type})`);\n}\n\n/**\n * Get an orchestrator by ID\n * @param id - The orchestrator ID\n * @returns The orchestrator instance or undefined if not found\n */\nexport function getOrchestrator(id: string): BaseOrchestrator | undefined {\n if (!id || typeof id !== 'string') {\n return undefined;\n }\n\n return orchestratorRegistry.get(id);\n}\n\n/**\n * List all registered orchestrators\n * @returns Array of all registered orchestrators\n */\nexport function listOrchestrators(): BaseOrchestrator[] {\n return Array.from(orchestratorRegistry.values());\n}\n\n/**\n * Get orchestrators by type\n * @param type - The orchestrator type to filter by\n * @returns Array of orchestrators matching the type\n */\nexport function getOrchestratorsByType(type: OrchestratorType): BaseOrchestrator[] {\n return Array.from(orchestratorRegistry.values()).filter(o => o.type === type);\n}\n\n/**\n * Check if an orchestrator is registered\n * @param id - The orchestrator ID\n * @returns True if the orchestrator is registered\n */\nexport function hasOrchestrator(id: string): boolean {\n return orchestratorRegistry.has(id);\n}\n\n/**\n * Unregister an orchestrator\n * @param id - The orchestrator ID to remove\n * @returns True if the orchestrator was removed, false if it didn't exist\n */\nexport function unregisterOrchestrator(id: string): boolean {\n const removed = orchestratorRegistry.delete(id);\n \n if (removed) {\n console.log(`๐ŸŽญ Orchestrator unregistered: ${id}`);\n }\n \n return removed;\n}\n\n/**\n * Clear all registered orchestrators\n */\nexport function clearOrchestratorRegistry(): void {\n const count = orchestratorRegistry.size;\n orchestratorRegistry.clear();\n \n console.log(`๐ŸŽญ Orchestrator registry cleared: ${count} orchestrators removed`);\n}\n\n/**\n * Get registry statistics\n * @returns Object with registry statistics\n */\nexport function getRegistryStats(): {\n total: number;\n byType: Record<OrchestratorType, number>;\n orchestratorIds: string[];\n} {\n const orchestrators = Array.from(orchestratorRegistry.values());\n \n const byType: Record<OrchestratorType, number> = {\n 'prompt-based': 0,\n 'custom-logic': 0,\n };\n\n orchestrators.forEach(o => {\n byType[o.type]++;\n });\n\n return {\n total: orchestrators.length,\n byType,\n orchestratorIds: orchestrators.map(o => o.id),\n };\n}\n\n// =============================================================================\n// VALIDATION HELPERS\n// =============================================================================\n\n/**\n * Validate orchestrator structure and required methods\n * @param orchestrator - The orchestrator to validate\n * @throws {Error} If the orchestrator is invalid\n */\nfunction validateOrchestrator(orchestrator: BaseOrchestrator): void {\n // Required properties\n if (!orchestrator.id || typeof orchestrator.id !== 'string') {\n throw new Error('Orchestrator must have a valid string ID');\n }\n\n if (!orchestrator.name || typeof orchestrator.name !== 'string') {\n throw new Error('Orchestrator must have a valid string name');\n }\n\n if (!orchestrator.description || typeof orchestrator.description !== 'string') {\n throw new Error('Orchestrator must have a valid string description');\n }\n\n if (!orchestrator.type || !['prompt-based', 'custom-logic'].includes(orchestrator.type)) {\n throw new Error('Orchestrator must have a valid type (prompt-based or custom-logic)');\n }\n\n // Required methods\n if (typeof orchestrator.execute !== 'function') {\n throw new Error('Orchestrator must implement execute method');\n }\n\n if (typeof orchestrator.getName !== 'function') {\n throw new Error('Orchestrator must implement getName method');\n }\n\n if (typeof orchestrator.getDescription !== 'function') {\n throw new Error('Orchestrator must implement getDescription method');\n }\n\n if (typeof orchestrator.getType !== 'function') {\n throw new Error('Orchestrator must implement getType method');\n }\n\n // Type-specific validation\n if (orchestrator.type === 'prompt-based') {\n const promptOrchestrator = orchestrator as any;\n if (!promptOrchestrator.systemPrompt || typeof promptOrchestrator.systemPrompt !== 'string') {\n throw new Error('Prompt-based orchestrator must have a valid systemPrompt property');\n }\n\n if (typeof promptOrchestrator.getSystemPrompt !== 'function') {\n throw new Error('Prompt-based orchestrator must implement getSystemPrompt method');\n }\n }\n\n if (orchestrator.type === 'custom-logic') {\n const customOrchestrator = orchestrator as any;\n if (typeof customOrchestrator.customLogic !== 'function') {\n throw new Error('Custom-logic orchestrator must implement customLogic method');\n }\n }\n\n // Optional method validation\n if (orchestrator.validate && typeof orchestrator.validate !== 'function') {\n throw new Error('Orchestrator validate property must be a function if provided');\n }\n\n if (orchestrator.initialize && typeof orchestrator.initialize !== 'function') {\n throw new Error('Orchestrator initialize property must be a function if provided');\n }\n\n if (orchestrator.cleanup && typeof orchestrator.cleanup !== 'function') {\n throw new Error('Orchestrator cleanup property must be a function if provided');\n }\n}\n\n/**\n * Resolve orchestrator from string ID or instance\n * @param orchestratorInput - String ID or orchestrator instance\n * @returns The orchestrator instance or undefined if not found\n */\nexport function resolveOrchestrator(orchestratorInput: string | BaseOrchestrator | undefined): BaseOrchestrator | undefined {\n if (!orchestratorInput) {\n return undefined;\n }\n\n if (typeof orchestratorInput === 'string') {\n return getOrchestrator(orchestratorInput);\n }\n\n if (typeof orchestratorInput === 'object' && orchestratorInput.id) {\n // Validate the orchestrator object\n try {\n validateOrchestrator(orchestratorInput);\n return orchestratorInput;\n } catch (error) {\n console.warn(`๐ŸŽญ Invalid orchestrator object: ${error instanceof Error ? error.message : String(error)}`);\n return undefined;\n }\n }\n\n return undefined;\n}","import { generateText } from 'ai';\nimport type { AIModel, Message, CoreMessage, ExecutionResult, OpenAgenticTool, LoggingConfig, LogLevel, ExecutionStats, StepInfo, BaseOrchestrator, OrchestratorContext, OrchestratorOptions, PromptBasedOrchestrator, CustomLogicOrchestrator } from './types';\nimport { ProviderManager } from './providers/manager';\nimport { resolveOrchestrator } from './orchestrators/registry';\n\nexport class Orchestrator {\n private model: AIModel;\n private tools: Record<string, OpenAgenticTool> = {};\n private messages: Message[] = [];\n private iterations = 0;\n private maxIterations: number;\n private customLogic?: (input: string, context: any) => Promise<any>;\n \n // Orchestrator support\n private orchestrator?: BaseOrchestrator;\n private orchestratorOptions: OrchestratorOptions;\n \n // Logging configuration\n private loggingConfig: LoggingConfig;\n private executionStartTime = 0;\n private stepTimings: number[] = [];\n private toolCallTimings: number[] = [];\n private stepsExecuted = 0;\n private toolCallsExecuted = 0;\n\n constructor(options: {\n model: string | AIModel;\n tools?: any[];\n systemPrompt?: string;\n maxIterations?: number;\n customLogic?: (input: string, context: any) => Promise<any>;\n enableDebugLogging?: boolean;\n logLevel?: LogLevel;\n enableStepLogging?: boolean;\n enableToolLogging?: boolean;\n enableTimingLogging?: boolean;\n enableStatisticsLogging?: boolean;\n } & OrchestratorOptions) {\n // Use ProviderManager for centralized model creation\n this.model = ProviderManager.createModel(options.model);\n this.maxIterations = options.maxIterations || 10;\n this.customLogic = options.customLogic;\n \n // Store orchestrator options\n this.orchestratorOptions = {\n orchestrator: options.orchestrator,\n orchestratorId: options.orchestratorId,\n orchestratorParams: options.orchestratorParams,\n allowOrchestratorPromptOverride: options.allowOrchestratorPromptOverride ?? true,\n allowOrchestratorToolControl: options.allowOrchestratorToolControl ?? true,\n };\n \n // Resolve orchestrator if provided\n this.orchestrator = resolveOrchestrator(\n options.orchestrator || options.orchestratorId\n );\n \n // Configure logging\n this.loggingConfig = {\n enableDebugLogging: options.enableDebugLogging ?? false,\n logLevel: options.logLevel ?? 'basic',\n enableStepLogging: options.enableStepLogging ?? false,\n enableToolLogging: options.enableToolLogging ?? false,\n enableTimingLogging: options.enableTimingLogging ?? false,\n enableStatisticsLogging: options.enableStatisticsLogging ?? false,\n };\n \n // Register tools with validation\n if (options.tools) {\n options.tools.forEach((tool, index) => {\n const toolName = tool.toolId;\n \n // Ensure toolId uniqueness\n if (this.tools[toolName]) {\n throw new Error(`Tool with name ${toolName} already exists`);\n }\n \n this.tools[toolName] = tool;\n });\n }\n \n // Add system prompt if provided (orchestrator may override this)\n if (options.systemPrompt) {\n this.messages.push({\n role: 'system',\n content: options.systemPrompt,\n });\n }\n\n this.log('๐Ÿ”ง', 'Orchestrator initialized', {\n model: `${this.model.provider}/${this.model.model}`,\n toolsCount: Object.keys(this.tools).length,\n maxIterations: this.maxIterations,\n loggingLevel: this.loggingConfig.logLevel,\n hasCustomLogic: !!this.customLogic,\n hasOrchestrator: !!this.orchestrator,\n orchestratorId: this.orchestrator?.id,\n orchestratorType: this.orchestrator?.type,\n });\n }\n\n // Core execution method - supports both string and message array inputs\n public async execute(input: string): Promise<ExecutionResult>;\n public async execute(messages: CoreMessage[]): Promise<ExecutionResult>;\n public async execute(input: string | CoreMessage[]): Promise<ExecutionResult> {\n this.executionStartTime = Date.now();\n this.resetExecutionStats();\n \n try {\n // Validate input before logging to prevent errors\n const inputType = typeof input === 'string' ? 'string' : \n Array.isArray(input) ? 'message_array' : \n 'invalid';\n const inputLength = typeof input === 'string' ? input.length : \n Array.isArray(input) ? input.length : \n 'unknown';\n \n this.log('๐Ÿš€', 'Execution starting', {\n inputType,\n inputLength,\n modelInfo: `${this.model.provider}/${this.model.model}`,\n toolsAvailable: Object.keys(this.tools).length,\n maxSteps: this.maxIterations,\n hasCustomLogic: !!this.customLogic,\n hasOrchestrator: !!this.orchestrator,\n orchestratorType: this.orchestrator?.type,\n });\n\n // If custom logic is provided, use it (takes precedence over orchestrator)\n if (this.customLogic) {\n return await this.executeWithCustomLogic(input);\n }\n\n // If orchestrator is available, delegate to it\n if (this.orchestrator) {\n return await this.executeWithOrchestrator(input);\n }\n\n // Handle different input types with standard execution\n let result: ExecutionResult;\n if (typeof input === 'string') {\n result = await this.executeWithString(input);\n } else if (Array.isArray(input)) {\n result = await this.executeWithMessages(input);\n } else {\n throw new Error('Input must be either a string or an array of messages');\n }\n\n // Add execution statistics to result\n const executionStats = this.calculateExecutionStats();\n result.executionStats = executionStats;\n\n this.log('โœ…', 'Execution completed successfully', {\n totalDuration: executionStats.totalDuration,\n stepsExecuted: executionStats.stepsExecuted,\n toolCallsExecuted: executionStats.toolCallsExecuted,\n averageStepDuration: executionStats.averageStepDuration,\n resultLength: result.result?.length || 0,\n });\n\n return result;\n } catch (error) {\n const executionStats = this.calculateExecutionStats();\n const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n \n this.log('โŒ', 'Execution failed', {\n error: errorMessage,\n totalDuration: executionStats.totalDuration,\n stepsExecuted: executionStats.stepsExecuted,\n toolCallsExecuted: executionStats.toolCallsExecuted,\n stackTrace: error instanceof Error ? error.stack : undefined,\n });\n \n const errorResult: ExecutionResult = {\n success: false,\n error: errorMessage,\n messages: this.messages,\n iterations: this.iterations,\n toolCallsUsed: [],\n executionStats,\n };\n\n return errorResult;\n }\n }\n\n // Execute with orchestrator delegation\n private async executeWithOrchestrator(input: string | CoreMessage[]): Promise<ExecutionResult> {\n if (!this.orchestrator) {\n throw new Error('Orchestrator not available');\n }\n\n this.log('๐ŸŽญ', 'Delegating to orchestrator', {\n orchestratorId: this.orchestrator.id,\n orchestratorType: this.orchestrator.type,\n orchestratorName: this.orchestrator.name,\n });\n\n try {\n // For prompt-based orchestrators, we can handle them specially\n if (this.orchestrator.type === 'prompt-based') {\n return await this.executeWithPromptBasedOrchestrator(input, this.orchestrator as PromptBasedOrchestrator);\n }\n\n // For custom-logic orchestrators, delegate completely\n if (this.orchestrator.type === 'custom-logic') {\n return await this.executeWithCustomLogicOrchestrator(input, this.orchestrator as CustomLogicOrchestrator);\n }\n\n throw new Error(`Unknown orchestrator type: ${this.orchestrator.type}`);\n\n } catch (error) {\n this.log('โŒ', 'Orchestrator execution failed', {\n orchestratorId: this.orchestrator.id,\n error: error instanceof Error ? error.message : String(error),\n stackTrace: error instanceof Error ? error.stack : undefined,\n });\n\n throw error;\n }\n }\n\n // Execute with custom logic orchestrator\n private async executeWithCustomLogicOrchestrator(\n input: string | CoreMessage[],\n orchestrator: CustomLogicOrchestrator\n ): Promise<ExecutionResult> {\n this.log('๐ŸŽญ', 'Executing with custom logic orchestrator', {\n orchestratorId: orchestrator.id,\n orchestratorName: orchestrator.name,\n });\n\n try {\n // Build orchestrator context\n const context: OrchestratorContext = {\n model: this.model,\n tools: Object.values(this.tools),\n messages: this.messages,\n iterations: this.iterations,\n maxIterations: this.maxIterations,\n loggingConfig: this.loggingConfig,\n orchestratorParams: this.orchestratorOptions.orchestratorParams,\n };\n\n // Initialize orchestrator if it has an initialize method\n if (orchestrator.initialize) {\n await orchestrator.initialize(context);\n }\n\n // Validate input if orchestrator has a validate method\n if (orchestrator.validate) {\n const isValid = await orchestrator.validate(input, context);\n if (!isValid) {\n throw new Error('Orchestrator validation failed');\n }\n }\n\n // Execute with orchestrator\n const result = await orchestrator.execute(input, context);\n\n // Update our internal state based on orchestrator result\n this.iterations = result.iterations || 0;\n this.stepsExecuted = result.iterations || 0;\n\n // Cleanup orchestrator if it has a cleanup method\n if (orchestrator.cleanup) {\n await orchestrator.cleanup(context);\n }\n\n this.log('โœ…', 'Custom logic orchestrator execution completed', {\n orchestratorId: orchestrator.id,\n success: result.success,\n iterations: result.iterations,\n toolCallsUsed: result.toolCallsUsed?.length || 0,\n });\n\n return result;\n } catch (error) {\n this.log('โŒ', 'Custom logic orchestrator execution failed', {\n orchestratorId: orchestrator.id,\n error: error instanceof Error ? error.message : String(error),\n stackTrace: error instanceof Error ? error.stack : undefined,\n });\n\n throw error;\n }\n }\n\n // Execute with prompt-based orchestrator (optimized path)\n private async executeWithPromptBasedOrchestrator(\n input: string | CoreMessage[],\n orchestrator: PromptBasedOrchestrator\n ): Promise<ExecutionResult> {\n this.log('๐ŸŽญ', 'Executing with prompt-based orchestrator', {\n orchestratorId: orchestrator.id,\n orchestratorName: orchestrator.name,\n allowPromptOverride: this.orchestratorOptions.allowOrchestratorPromptOverride,\n allowToolControl: this.orchestratorOptions.allowOrchestratorToolControl,\n });\n\n // Build context for orchestrator\n const context: OrchestratorContext = {\n model: this.model,\n tools: Object.values(this.tools),\n messages: this.messages,\n iterations: this.iterations,\n maxIterations: this.maxIterations,\n loggingConfig: this.loggingConfig,\n orchestratorParams: this.orchestratorOptions.orchestratorParams,\n };\n\n // Use the orchestrator's execute method which handles tool filtering and prompt override\n return await orchestrator.execute(input, context);\n }\n\n // Execute with string input (original behavior)\n private async executeWithString(input: string): Promise<ExecutionResult> {\n const provider = await ProviderManager.createProvider(this.model);\n\n const generateConfig: any = {\n model: provider(this.model.model),\n prompt: input,\n maxSteps: this.maxIterations,\n onStepFinish: this.createStepFinishCallback(),\n };\n\n // Add system message if it exists\n const systemMessage = this.messages.find(m => m.role === 'system');\n if (systemMessage) {\n generateConfig.system = systemMessage.content;\n }\n\n // Add tools if available\n if (Object.keys(this.tools).length > 0) {\n generateConfig.tools = this.convertToAISDKTools();\n }\n\n // Add model parameters conditionally\n if (this.model.temperature !== undefined) {\n generateConfig.temperature = this.model.temperature;\n }\n\n if (this.model.maxTokens !== undefined) {\n generateConfig.maxTokens = this.model.maxTokens;\n }\n\n if (this.model.topP !== undefined) {\n generateConfig.topP = this.model.topP;\n }\n \n this.log('๐Ÿ“', 'Starting text generation', {\n prompt: this.sanitizeForLogging(input),\n systemMessage: systemMessage ? 'present' : 'none',\n toolsEnabled: Object.keys(this.tools).length > 0,\n });\n\n const result = await generateText(generateConfig);\n\n // Update our internal state\n this.iterations = result.steps?.length || 1;\n this.stepsExecuted = this.iterations;\n \n // Store the conversation in our messages for future reference\n this.messages = [\n ...this.messages.filter(m => m.role === 'system'),\n { role: 'user', content: input },\n { role: 'assistant', content: result.text || '' }\n ];\n\n // Extract tool calls from steps if available\n const toolCallsUsed: string[] = [];\n if (result.steps) {\n result.steps.forEach(step => {\n if (step.toolCalls) {\n step.toolCalls.forEach(toolCall => {\n toolCallsUsed.push(toolCall.toolName || toolCall.toolCallId || 'unknown');\n });\n }\n });\n }\n\n this.log('๐Ÿ“Š', 'Text generation completed', {\n resultLength: result.text?.length || 0,\n stepsExecuted: this.iterations,\n toolCallsUsed: toolCallsUsed.length,\n tokensUsed: result.usage?.totalTokens,\n finishReason: result.finishReason,\n });\n\n const executionResult: ExecutionResult = {\n success: true,\n result: result.text,\n messages: this.messages,\n iterations: this.iterations,\n toolCallsUsed,\n };\n\n return executionResult;\n }\n\n // Execute with message array (new behavior)\n private async executeWithMessages(inputMessages: CoreMessage[]): Promise<ExecutionResult> {\n // Handle empty message arrays\n if (inputMessages.length === 0) {\n const errorResult: ExecutionResult = {\n success: false,\n error: 'Empty message array provided. At least one message is required.',\n messages: this.messages,\n iterations: this.iterations,\n toolCallsUsed: [],\n };\n return errorResult;\n }\n \n const provider = await ProviderManager.createProvider(this.model);\n\n // Convert CoreMessage[] to AI SDK compatible format\n const convertedMessages = this.convertCoresToAISDK(inputMessages);\n\n this.log('๐Ÿ“', 'Processing message array', {\n messageCount: inputMessages.length,\n messageTypes: inputMessages.map(m => m.role),\n hasSystemMessage: inputMessages.some(m => m.role === 'system'),\n lastMessageRole: inputMessages[inputMessages.length - 1]?.role,\n });\n\n const generateConfig: any = {\n model: provider(this.model.model),\n messages: convertedMessages,\n maxSteps: this.maxIterations,\n onStepFinish: this.createStepFinishCallback(),\n };\n\n // Extract system message from input if present, otherwise use existing one\n const systemMessage = inputMessages.find(m => m.role === 'system');\n if (systemMessage) {\n generateConfig.system = typeof systemMessage.content === 'string' \n ? systemMessage.content \n : JSON.stringify(systemMessage.content);\n } else {\n const existingSystemMessage = this.messages.find(m => m.role === 'system');\n if (existingSystemMessage) {\n generateConfig.system = existingSystemMessage.content;\n }\n }\n\n // Add tools if available\n if (Object.keys(this.tools).length > 0) {\n generateConfig.tools = this.convertToAISDKTools();\n }\n\n // Add model parameters conditionally\n if (this.model.temperature !== undefined) {\n generateConfig.temperature = this.model.temperature;\n }\n\n if (this.model.maxTokens !== undefined) {\n generateConfig.maxTokens = this.model.maxTokens;\n }\n\n if (this.model.topP !== undefined) {\n generateConfig.topP = this.model.topP;\n }\n \n const result = await generateText(generateConfig);\n\n // Update our internal state\n this.iterations = result.steps?.length || 1;\n this.stepsExecuted = this.iterations;\n \n // Update internal messages with the full conversation context\n this.messages = [\n ...this.messages.filter(m => m.role === 'system'),\n ...this.convertCoreToInternal(inputMessages.filter(m => m.role !== 'system')),\n { role: 'assistant', content: result.text || '' }\n ];\n\n // Extract tool calls from steps if available\n const toolCallsUsed: string[] = [];\n if (result.steps) {\n result.steps.forEach(step => {\n if (step.toolCalls) {\n step.toolCalls.forEach(toolCall => {\n toolCallsUsed.push(toolCall.toolName || toolCall.toolCallId || 'unknown');\n });\n }\n });\n }\n\n this.log('๐Ÿ“Š', 'Message array processing completed', {\n resultLength: result.text?.length || 0,\n stepsExecuted: this.iterations,\n toolCallsUsed: toolCallsUsed.length,\n messagesInHistory: this.messages.length,\n tokensUsed: result.usage?.totalTokens,\n finishReason: result.finishReason,\n });\n\n const executionResult: ExecutionResult = {\n success: true,\n result: result.text,\n messages: this.messages,\n iterations: this.iterations,\n toolCallsUsed,\n };\n\n return executionResult;\n }\n\n // Tool management methods\n public addTool(tool: OpenAgenticTool): void {\n const toolName = tool.toolId;\n \n // Ensure uniqueness\n if (this.tools[toolName]) {\n throw new Error(`Tool with name ${toolName} already exists`);\n }\n \n this.tools[toolName] = tool;\n this.log('๐Ÿ”ง', 'Tool added', { toolId: tool.toolId, toolName: tool.name });\n }\n\n public removeTool(toolName: string): void {\n if (this.tools[toolName]) {\n delete this.tools[toolName];\n this.log('๐Ÿ—‘๏ธ', 'Tool removed', { toolId: toolName });\n }\n }\n\n public getTool(toolName: string): any {\n return this.tools[toolName];\n }\n\n public getAllTools(): OpenAgenticTool[] {\n return Object.values(this.tools);\n }\n\n // Model switching using ProviderManager\n public switchModel(model: string | AIModel): void {\n const oldModel = `${this.model.provider}/${this.model.model}`;\n this.model = ProviderManager.createModel(model);\n const newModel = `${this.model.provider}/${this.model.model}`;\n \n this.log('๐Ÿ”„', 'Model switched', { \n from: oldModel, \n to: newModel,\n temperature: this.model.temperature,\n maxTokens: this.model.maxTokens,\n });\n }\n\n // Get model information\n public getModelInfo(): any {\n try {\n const modelInfo = ProviderManager.getModelInfo(this.model.provider, this.model.model) as any;\n return {\n provider: this.model.provider,\n model: this.model.model,\n contextWindow: modelInfo?.contextWindow,\n cost: modelInfo?.cost,\n description: modelInfo?.description,\n };\n } catch (error) {\n return {\n provider: this.model.provider,\n model: this.model.model,\n error: 'Model info not available',\n };\n }\n }\n\n // Orchestrator management methods\n public getOrchestrator(): BaseOrchestrator | undefined {\n return this.orchestrator;\n }\n\n public setOrchestrator(orchestrator: string | BaseOrchestrator | undefined): void {\n const resolvedOrchestrator = resolveOrchestrator(orchestrator);\n \n if (orchestrator && !resolvedOrchestrator) {\n throw new Error(`Failed to resolve orchestrator: ${typeof orchestrator === 'string' ? orchestrator : 'invalid orchestrator object'}`);\n }\n\n const oldId = this.orchestrator?.id;\n this.orchestrator = resolvedOrchestrator;\n \n this.log('๐ŸŽญ', 'Orchestrator changed', {\n from: oldId || 'none',\n to: this.orchestrator?.id || 'none',\n type: this.orchestrator?.type,\n });\n }\n\n public hasOrchestrator(): boolean {\n return !!this.orchestrator;\n }\n\n // Utility methods\n public getMessages(): Message[] {\n return [...this.messages];\n }\n\n public addMessage(message: Message): void {\n this.messages.push(message);\n this.log('๐Ÿ’ฌ', 'Message added', { role: message.role, contentLength: message.content.length });\n }\n\n public reset(): void {\n this.messages = this.messages.filter(m => m.role === 'system');\n this.iterations = 0;\n this.resetExecutionStats();\n this.log('๐Ÿ”„', 'Orchestrator reset', { systemMessagesRetained: this.messages.length });\n }\n\n public clear(): void {\n this.messages = [];\n this.iterations = 0;\n this.resetExecutionStats();\n this.log('๐Ÿงน', 'Orchestrator cleared', { allMessagesRemoved: true });\n }\n\n // Create step finish callback for AI SDK\n private createStepFinishCallback() {\n return (result: any) => {\n const stepDuration = Date.now() - this.executionStartTime;\n this.stepTimings.push(stepDuration);\n \n this.stepsExecuted++;\n \n const toolCallsInStep = result.toolCalls?.map((tc: any) => tc.toolName || tc.toolCallId || 'unknown') || [];\n \n this.log('๐Ÿ“', `Step ${this.stepsExecuted} completed`, {\n stepType: result.stepType || 'unknown',\n finishReason: result.finishReason || 'unknown',\n duration: `${stepDuration}ms`,\n toolCalls: toolCallsInStep,\n text: result.text ? `${result.text.length} chars` : 'no_result',\n reasoning: result.reasoning ? 'has_reasoning' : 'no_reasoning',\n tokensUsed: result.usage?.totalTokens || 'unknown',\n });\n };\n }\n\n // Helper methods for message conversion\n private convertCoresToAISDK(coreMessages: CoreMessage[]): any[] {\n return coreMessages\n .filter(m => m.role !== 'system') // System messages handled separately\n .map(m => {\n if (m.role === 'user') {\n return { \n role: 'user' as const, \n content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content)\n };\n } else if (m.role === 'assistant') {\n return { \n role: 'assistant' as const, \n content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content)\n };\n } else if (m.role === 'tool') {\n return { \n role: 'tool' as const, \n content: [{ \n type: 'tool-result' as const, \n toolCallId: m.toolCallId || '',\n toolName: 'unknown',\n result: typeof m.content === 'string' ? m.content : JSON.stringify(m.content)\n }] \n };\n }\n return { \n role: 'user' as const, \n content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content)\n };\n });\n }\n\n private convertCoreToInternal(coreMessages: CoreMessage[]): Message[] {\n return coreMessages.map(m => ({\n role: m.role,\n content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),\n toolCallId: m.toolCallId,\n toolCalls: m.toolCalls,\n }));\n }\n\n private convertToAISDKTools(): Record<string, any> {\n const tools: Record<string, any> = {};\n \n Object.entries(this.tools).forEach(([key, tool]) => {\n tools[key] = {\n description: tool.description,\n parameters: tool.parameters,\n execute: async (args: any, context?: any) => {\n const toolCallStartTime = Date.now();\n \n this.log('๐Ÿ”ง', `Tool execution starting: ${tool.toolId}`, {\n toolName: tool.name,\n parameters: this.sanitizeForLogging(args),\n context: context ? 'present' : 'none',\n });\n \n try {\n if (!tool.execute) {\n throw new Error(`Tool ${tool.toolId} has no execute function`);\n }\n \n const result = await tool.execute(args, context);\n const toolCallDuration = Date.now() - toolCallStartTime;\n this.toolCallTimings.push(toolCallDuration);\n this.toolCallsExecuted++;\n \n this.log('โœ…', `Tool execution completed: ${tool.toolId}`, {\n duration: `${toolCallDuration}ms`,\n resultType: typeof result,\n resultSize: typeof result === 'string' ? result.length : JSON.stringify(result).length,\n success: true,\n });\n \n return result;\n } catch (error) {\n const toolCallDuration = Date.now() - toolCallStartTime;\n this.toolCallTimings.push(toolCallDuration);\n \n this.log('โŒ', `Tool execution failed: ${tool.toolId}`, {\n duration: `${toolCallDuration}ms`,\n error: error instanceof Error ? error.message : JSON.stringify(error),\n stackTrace: error instanceof Error ? error.stack : undefined,\n parameters: this.sanitizeForLogging(args),\n });\n \n throw error;\n }\n },\n };\n });\n \n return tools;\n }\n\n // Logging and statistics methods\n private log(emoji: string, message: string, data?: any): void {\n if (!this.loggingConfig.enableDebugLogging || this.loggingConfig.logLevel === 'none') {\n return;\n }\n\n const timestamp = new Date().toISOString();\n const logLevel = this.loggingConfig.logLevel;\n \n if (logLevel === 'basic') {\n console.log(`${emoji} [${timestamp}] ${message}`);\n } else if (logLevel === 'detailed' && data) {\n console.log(`${emoji} [${timestamp}] ${message}`, data);\n }\n }\n\n private sanitizeForLogging(data: any): any {\n if (typeof data === 'string') {\n // Limit string length and remove potential sensitive data patterns\n const sanitized = data.length > 200 ? `${data.substring(0, 200)}...` : data;\n return sanitized.replace(/\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/g, '[EMAIL]')\n .replace(/\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g, '[CARD]')\n .replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, '[SSN]');\n }\n \n if (typeof data === 'object' && data !== null) {\n const sanitized = { ...data };\n // Sanitize common sensitive field names\n const sensitiveFields = ['password', 'apiKey', 'token', 'secret', 'key', 'auth'];\n sensitiveFields.forEach(field => {\n if (field in sanitized) {\n sanitized[field] = '[REDACTED]';\n }\n });\n return sanitized;\n }\n \n return data;\n }\n\n private resetExecutionStats(): void {\n this.stepTimings = [];\n this.toolCallTimings = [];\n this.stepsExecuted = 0;\n this.toolCallsExecuted = 0;\n }\n\n private calculateExecutionStats(): ExecutionStats {\n const totalDuration = Date.now() - this.executionStartTime;\n const averageStepDuration = this.stepTimings.length > 0 \n ? this.