UNPKG

openagentic

Version:

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

1 lines 432 kB
{"version":3,"sources":["../../src/tools/openai.ts","../../src/tools/utils.ts","../../src/types.ts","../../src/providers/manager.ts","../../src/tools/openai-image.ts","../../src/utils/s3.ts","../../src/tools/openai-vector-store.ts","../../src/tools/gemini-image.ts","../../src/tools/anthropic.ts","../../src/tools/cohere.ts","../../src/tools/gemini.ts","../../src/tools/github.ts","../../src/tools/grok.ts","../../src/tools/llama.ts","../../src/tools/mistral.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/html-composer.ts","../../src/tools/unsplash.ts","../../src/tools/slack-poster.ts","../../src/tools/groq.ts","../../src/tools/luma-image.ts","../../src/tools/index.ts"],"sourcesContent":["import { tool } from 'ai';\nimport { z } from 'zod';\nimport { generateText } from 'ai';\nimport { createOpenAI } from '@ai-sdk/openai';\nimport type { ToolDetails } from '../types';\nimport { toOpenAgenticTool } from './utils';\n\n// Supported OpenAI models with validation\nconst SUPPORTED_MODELS = [\n 'gpt-4o',\n 'gpt-4o-mini', \n 'gpt-4-turbo',\n 'gpt-4',\n 'gpt-3.5-turbo'\n] as const;\n\nconst rawOpenAITool = tool({\n description: 'Generate high-quality text responses using OpenAI GPT models with advanced parameter control',\n parameters: z.object({\n prompt: z.string()\n .min(1)\n .max(50000)\n .describe('The text prompt to send to OpenAI (required, max 50,000 characters)'),\n \n model: z.string()\n .optional()\n .default('gpt-4o-mini')\n .describe('OpenAI model to use (gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-4, gpt-3.5-turbo)'),\n \n maxTokens: z.number()\n .int()\n .min(1)\n .max(4096)\n .optional()\n .default(1000)\n .describe('Maximum number of tokens to generate (1-4096, default: 1000)'),\n \n temperature: z.number()\n .min(0)\n .max(2)\n .optional()\n .default(0.7)\n .describe('Controls randomness - lower values are more focused (0-2, default: 0.7)'),\n \n topP: z.number()\n .min(0)\n .max(1)\n .optional()\n .describe('Controls diversity via nucleus sampling (0-1, optional)'),\n \n presencePenalty: z.number()\n .min(-2)\n .max(2)\n .optional()\n .describe('Penalizes repeated tokens (-2 to 2, optional)'),\n \n frequencyPenalty: z.number()\n .min(-2)\n .max(2)\n .optional()\n .describe('Penalizes frequent tokens (-2 to 2, optional)'),\n }),\n \n execute: async ({ \n prompt, \n model = 'gpt-4o-mini',\n maxTokens = 1000,\n temperature = 0.7,\n topP,\n presencePenalty,\n frequencyPenalty\n }) => {\n // Validate API key\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new Error('OPENAI_API_KEY environment variable is required');\n }\n\n // Validate prompt\n if (!prompt || prompt.trim().length === 0) {\n throw new Error('Prompt cannot be empty');\n }\n\n if (prompt.length > 50000) {\n throw new Error('Prompt exceeds maximum length of 50,000 characters');\n }\n\n // Validate model\n if (!SUPPORTED_MODELS.includes(model as any)) {\n throw new Error(`Model \"${model}\" not in supported list`);\n }\n\n // Start logging\n console.log('🤖 OpenAI Tool - Generation started:', {\n model,\n promptLength: prompt.length,\n maxTokens,\n temperature,\n topP,\n presencePenalty,\n frequencyPenalty,\n });\n\n try {\n // Initialize OpenAI client\n const openai = createOpenAI({\n apiKey,\n });\n\n // Prepare generation config\n const generateConfig: any = {\n model: openai(model),\n prompt: prompt.trim(),\n maxTokens,\n temperature,\n };\n\n // Add optional parameters only if provided\n if (topP !== undefined) {\n generateConfig.topP = topP;\n }\n if (presencePenalty !== undefined) {\n generateConfig.presencePenalty = presencePenalty;\n }\n if (frequencyPenalty !== undefined) {\n generateConfig.frequencyPenalty = frequencyPenalty;\n }\n\n // Generate text\n const { text, usage, finishReason } = await generateText(generateConfig);\n\n // Log completion\n console.log('✅ OpenAI Tool - Generation completed:', {\n model,\n tokensUsed: usage?.totalTokens || 0,\n responseLength: text.length,\n finishReason,\n });\n\n // Return structured result\n return {\n success: true,\n text,\n model,\n usage: {\n promptTokens: usage?.promptTokens || 0,\n completionTokens: usage?.completionTokens || 0,\n totalTokens: usage?.totalTokens || 0,\n },\n finishReason,\n parameters: {\n temperature,\n maxTokens,\n topP,\n presencePenalty,\n frequencyPenalty,\n },\n metadata: {\n generatedAt: new Date().toISOString(),\n promptLength: prompt.length,\n responseLength: text.length,\n },\n };\n\n } catch (error) {\n console.error('❌ OpenAI Tool - Generation failed:', {\n model,\n promptLength: prompt.length,\n error: error instanceof Error ? error.message : JSON.stringify(error),\n });\n\n // Handle specific error types\n if (error instanceof Error) {\n // Rate limiting error\n if (error.message.includes('rate limit') || error.message.includes('429')) {\n throw new Error('OpenAI API rate limit exceeded. Please try again in a moment.');\n }\n \n // Authentication error\n if (error.message.includes('401') || error.message.includes('authentication')) {\n throw new Error('OpenAI API authentication failed. Please check your API key.');\n }\n \n // Token limit error\n if (error.message.includes('token') && error.message.includes('limit')) {\n throw new Error(`Token limit exceeded. Try reducing maxTokens or prompt length.`);\n }\n \n // Invalid model error\n if (error.message.includes('model') && error.message.includes('not found')) {\n throw new Error(`Invalid model \"${model}\". Please use a supported OpenAI model.`);\n }\n \n // Network errors\n if (error.message.includes('network') || error.message.includes('timeout')) {\n throw new Error('Network error connecting to OpenAI API. Please try again.');\n }\n }\n\n // Generic error fallback\n throw new Error(`OpenAI text generation failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`);\n }\n },\n});\n\nconst toolDetails: ToolDetails = {\n toolId: 'openai_text_generation',\n name: 'OpenAI Text Generation',\n useCases: [\n 'Generate creative content and stories',\n 'Answer questions and provide explanations',\n 'Summarize text and documents',\n 'Write code and technical documentation',\n 'Translate text between languages',\n 'Proofread and edit content',\n 'Generate marketing copy and descriptions',\n 'Create blog posts and articles',\n 'Brainstorm ideas and concepts',\n 'Generate product descriptions',\n 'Write emails and communications',\n ],\n logo: 'https://www.openagentic.org/tools/openai.svg',\n};\n\nexport const openaiTool = toOpenAgenticTool(rawOpenAITool, toolDetails);","import { type Tool } from 'ai';\nimport { tool } from 'ai';\nimport { z } from 'zod';\nimport { type OpenAgenticTool, type ToolDetails } from '../types';\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';\nimport { ProviderManager } from '../providers/manager';\n\n// =============================================================================\n// LANGCHAIN TOOL COMPATIBILITY\n// =============================================================================\n\n/**\n * Check if an object is a LangChain Tool or StructuredTool\n */\nfunction isLangChainTool(tool: any): boolean {\n // Check for typical LangChain tool properties\n return (\n tool &&\n typeof tool === 'object' &&\n typeof tool.name === 'string' &&\n typeof tool.description === 'string' &&\n (typeof tool.call === 'function' || typeof tool.invoke === 'function')\n );\n}\n\n/**\n * Convert LangChain's zod schema to AI SDK compatible zod schema\n */\nfunction convertLangChainSchema(schema: any): z.ZodType<any> {\n if (!schema) {\n // If no schema, return empty object schema\n return z.object({});\n }\n \n // If it's already a Zod schema, return as-is\n if (schema._def) {\n return schema;\n }\n \n // If it's a JSON schema, try to convert to Zod\n if (typeof schema === 'object' && schema.type) {\n return convertJsonSchemaToZod(schema);\n }\n \n // Fallback to any schema\n return z.any();\n}\n\n/**\n * Convert JSON Schema to Zod schema (basic implementation)\n */\nfunction convertJsonSchemaToZod(jsonSchema: any): z.ZodType<any> {\n if (jsonSchema.type === 'object') {\n const shape: Record<string, z.ZodType<any>> = {};\n const properties = jsonSchema.properties || {};\n \n for (const [key, prop] of Object.entries(properties)) {\n shape[key] = convertJsonSchemaToZod(prop as any);\n }\n \n return z.object(shape);\n }\n \n if (jsonSchema.type === 'string') {\n let schema = z.string();\n if (jsonSchema.description) {\n schema = schema.describe(jsonSchema.description);\n }\n return schema;\n }\n \n if (jsonSchema.type === 'number') {\n return z.number();\n }\n \n if (jsonSchema.type === 'boolean') {\n return z.boolean();\n }\n \n if (jsonSchema.type === 'array') {\n const itemSchema = jsonSchema.items ? convertJsonSchemaToZod(jsonSchema.items) : z.any();\n return z.array(itemSchema);\n }\n \n return z.any();\n}\n\n/**\n * Convert a LangChain Tool to OpenAgentic format\n * @param lcTool - LangChain Tool or StructuredTool instance\n * @param opts - Optional configuration\n * @returns OpenAgentic compatible tool\n */\nexport async function convertLangchainTool(\n lcTool: any,\n opts?: {\n toolId?: string;\n useCases?: string[];\n logo?: string;\n paramsSchema?: z.ZodType<any>;\n }\n): Promise<OpenAgenticTool> {\n if (!isLangChainTool(lcTool)) {\n throw new Error('Provided tool is not a valid LangChain Tool or StructuredTool');\n }\n\n // Extract schema from LangChain tool\n let schema: z.ZodType<any>;\n \n if (opts?.paramsSchema) {\n schema = opts.paramsSchema;\n } else if (lcTool.schema) {\n schema = convertLangChainSchema(lcTool.schema);\n } else {\n // Default to empty object schema for basic tools\n schema = z.object({\n input: z.string().describe('Input text for the tool')\n });\n }\n\n // Create AI SDK tool\n const aiTool = tool({\n description: lcTool.description,\n parameters: schema,\n execute: async (args: any) => {\n try {\n let result: any;\n \n // Try invoke first (for newer LangChain tools), then fall back to call\n if (typeof lcTool.invoke === 'function') {\n // For StructuredTool, pass the args object directly\n result = await lcTool.invoke(args);\n } else if (typeof lcTool.call === 'function') {\n // For basic Tool, pass the input string or first argument\n const input = typeof args === 'object' && args.input !== undefined \n ? args.input \n : Object.keys(args).length === 1 \n ? Object.values(args)[0] \n : JSON.stringify(args);\n result = await lcTool.call(input);\n } else {\n throw new Error('LangChain tool has no callable invoke or call method');\n }\n\n // Ensure result is a string (LangChain tools typically return strings)\n if (typeof result === 'object') {\n return JSON.stringify(result);\n }\n return String(result);\n \n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`LangChain tool \"${lcTool.name}\" execution failed:`, errorMessage);\n throw new Error(`Tool execution failed: ${errorMessage}`);\n }\n }\n });\n\n // Create tool details\n const toolDetails: ToolDetails = {\n toolId: opts?.toolId || `langchain_${lcTool.name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`,\n name: lcTool.name,\n useCases: opts?.useCases || [\n `LangChain ${lcTool.name} integration`,\n lcTool.description || 'Imported from LangChain'\n ],\n logo: opts?.logo || '🔗' // LangChain emoji\n };\n\n return {\n ...aiTool,\n ...toolDetails\n };\n}\n\n/**\n * Auto-detect and convert LangChain tools in a tools array\n * @param tools - Array of tools that may include LangChain tools\n * @returns Promise resolving to array with LangChain tools converted\n */\nexport async function autoConvertLangChainTools(tools: any[]): Promise<any[]> {\n if (!Array.isArray(tools)) {\n return tools;\n }\n\n const convertedTools = await Promise.all(\n tools.map(async (tool) => {\n if (isLangChainTool(tool)) {\n console.log(`🔗 Auto-converting LangChain tool: ${tool.name}`);\n return await convertLangchainTool(tool);\n }\n return tool;\n })\n );\n\n return convertedTools;\n}\n\n/**\n * Check if a tools array contains any LangChain tools\n * @param tools - Array of tools to check\n * @returns Boolean indicating if LangChain tools are present\n */\nexport function hasLangChainTools(tools: any[]): boolean {\n if (!Array.isArray(tools)) {\n return false;\n }\n return tools.some(tool => isLangChainTool(tool));\n}\n\n/**\n * Synchronous version of convertLangchainTool for constructor use\n * @param lcTool - LangChain Tool or StructuredTool instance\n * @param opts - Optional configuration\n * @returns OpenAgentic compatible tool (synchronously)\n */\nexport function convertLangchainToolSync(\n lcTool: any,\n opts?: {\n toolId?: string;\n useCases?: string[];\n logo?: string;\n paramsSchema?: z.ZodType<any>;\n }\n): OpenAgenticTool {\n if (!isLangChainTool(lcTool)) {\n throw new Error('Provided tool is not a valid LangChain Tool or StructuredTool');\n }\n\n // Extract schema from LangChain tool\n let schema: z.ZodType<any>;\n \n if (opts?.paramsSchema) {\n schema = opts.paramsSchema;\n } else if (lcTool.schema) {\n schema = convertLangChainSchema(lcTool.schema);\n } else {\n // Default to empty object schema for basic tools\n schema = z.object({\n input: z.string().describe('Input text for the tool')\n });\n }\n\n // Create AI SDK tool\n const aiTool = tool({\n description: lcTool.description,\n parameters: schema,\n execute: async (args: any) => {\n try {\n let result: any;\n \n // Try invoke first (for newer LangChain tools), then fall back to call\n if (typeof lcTool.invoke === 'function') {\n // For StructuredTool, pass the args object directly\n result = await lcTool.invoke(args);\n } else if (typeof lcTool.call === 'function') {\n // For basic Tool, pass the input string or first argument\n const input = typeof args === 'object' && args.input !== undefined \n ? args.input \n : Object.keys(args).length === 1 \n ? Object.values(args)[0] \n : JSON.stringify(args);\n result = await lcTool.call(input);\n } else {\n throw new Error('LangChain tool has no callable invoke or call method');\n }\n\n // Ensure result is a string (LangChain tools typically return strings)\n if (typeof result === 'object') {\n return JSON.stringify(result);\n }\n return String(result);\n \n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`LangChain tool \"${lcTool.name}\" execution failed:`, errorMessage);\n throw new Error(`Tool execution failed: ${errorMessage}`);\n }\n }\n });\n\n // Create tool details\n const toolDetails: ToolDetails = {\n toolId: opts?.toolId || `langchain_${lcTool.name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`,\n name: lcTool.name,\n useCases: opts?.useCases || [\n `LangChain ${lcTool.name} integration`,\n lcTool.description || 'Imported from LangChain'\n ],\n logo: opts?.logo || '🔗' // LangChain emoji\n };\n\n return {\n ...aiTool,\n ...toolDetails\n };\n}\n\n// =============================================================================\n// OPENAGENTIC UTILITY FUNCTIONS\n// =============================================================================\n\n/**\n * Helper function to add a custom toolId to an AI SDK tool\n * This provides better identification and debugging capabilities\n * \n * @param tool - AI SDK tool created with tool() function, see ai's ToolParameters type for more details\n * @param toolId - Unique identifier for the tool\n * @returns Tool with toolId property for better identification\n */\nexport function toOpenAgenticTool(tool: Tool, details: ToolDetails): OpenAgenticTool {\n return {\n ...tool,\n ...details,\n }\n}\n\nexport function getModel(model: string): string {\n const bedrockCredentials = ProviderManager.getBedrockCredentials();\n if (bedrockCredentials.accessKeyId && bedrockCredentials.secretAccessKey) {\n if(model.includes('sonnet')) {\n return 'us.anthropic.claude-sonnet-4-20250514-v1:0';\n } else if(model.includes('opus')) {\n return 'us.anthropic.claude-4-opus-20250514-v1:0';\n } else {\n return model;\n }\n } else {\n return model;\n }\n}\n\nexport function getAnthropicModelInstance(model: string): any {\n const bedrockCredentials = ProviderManager.getBedrockCredentials();\n let modelInstance: any;\n let provider: any;\n if (bedrockCredentials.accessKeyId && bedrockCredentials.secretAccessKey) {\n console.log('Using Bedrock');\n provider = createAmazonBedrock({\n region: bedrockCredentials.region,\n accessKeyId: bedrockCredentials.accessKeyId,\n secretAccessKey: bedrockCredentials.secretAccessKey,\n });\n // TODO: Add support for other Bedrock model versions\n if(model.includes('sonnet')) {\n modelInstance = provider('us.anthropic.claude-sonnet-4-20250514-v1:0');\n console.log('Model: Claude Sonnet 4');\n } else if(model.includes('opus')) {\n modelInstance = provider('us.anthropic.claude-4-opus-20250514-v1:0');\n console.log('Model: Claude Opus 4');\n } else {\n throw new Error(`Model \"${model}\" not supported`);\n }\n } else {\n console.log('Using Anthropic');\n // Validate API key\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n throw new Error('ANTHROPIC_API_KEY environment variable is required');\n }\n provider = createAnthropic({\n apiKey,\n });\n modelInstance = provider(model);\n console.log('Model:', model);\n }\n return {provider, modelInstance};\n}\n","import { z } from 'zod';\nimport { type Tool } from 'ai';\n\n// =============================================================================\n// CORE TYPES FOR OPENAGENTIC FRAMEWORK\n// =============================================================================\n\nexport const AIModelSchema = z.object({\n provider: z.enum(['openai', 'anthropic', 'google', 'google-vertex', 'perplexity', 'xai', 'custom']),\n model: z.string(),\n apiKey: z.string().optional(),\n baseURL: z.string().optional(),\n temperature: z.number().min(0).max(2).optional().default(0.7),\n maxTokens: z.number().positive().optional(),\n topP: z.number().min(0).max(1).optional(),\n project: z.string().optional(),\n location: z.string().optional(),\n});\n\nexport type AIModel = z.infer<typeof AIModelSchema>;\n\nexport const MessageSchema = z.object({\n role: z.enum(['system', 'user', 'assistant', 'tool']),\n content: z.string(),\n toolCallId: z.string().optional(),\n toolCalls: z.array(z.object({\n toolCallId: z.string(),\n toolName: z.string(),\n args: z.record(z.any()),\n })).optional(),\n});\n\nexport type Message = z.infer<typeof MessageSchema>;\n\n// =============================================================================\n// API KEY MAP TYPE\n// =============================================================================\n\nexport type ApiKeyMap = Record<string, string> & {\n // AWS credentials for S3 and other services\n awsAccessKeyId?: string;\n awsSecretAccessKey?: string;\n awsRegion?: string;\n awsS3Bucket?: string;\n // AWS Bedrock credentials (for Anthropic via Bedrock)\n bedrockAccessKeyId?: string;\n bedrockSecretAccessKey?: string;\n bedrockRegion?: string;\n};\n\n// =============================================================================\n// CORE MESSAGE TYPES (AI SDK COMPATIBLE)\n// =============================================================================\n\nexport interface CoreMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string | Array<any>;\n toolCallId?: string;\n toolCalls?: Array<{\n toolCallId: string;\n toolName: string;\n args: Record<string, any>;\n }>;\n}\n\n// =============================================================================\n// LOGGING AND DEBUG TYPES\n// =============================================================================\n\nexport type LogLevel = 'none' | 'basic' | 'detailed';\n\nexport interface LoggingConfig {\n enableDebugLogging?: boolean;\n logLevel?: LogLevel;\n enableStepLogging?: boolean;\n enableToolLogging?: boolean;\n enableTimingLogging?: boolean;\n enableStatisticsLogging?: boolean;\n}\n\nexport interface ExecutionStats {\n totalDuration: number;\n stepsExecuted: number;\n toolCallsExecuted: number;\n tokensUsed?: number;\n averageStepDuration: number;\n averageToolCallDuration: number;\n}\n\nexport interface StepInfo {\n stepIndex: number;\n stepType: string;\n duration: number;\n toolCalls: string[];\n errors: string[];\n startTime: number;\n endTime: number;\n}\n\n// =============================================================================\n// OPENAGENTIC TOOL TYPES\n// =============================================================================\n\nexport type ToolDetails = {\n toolId: string;\n name: string;\n useCases: string[];\n logo: string;\n internal?: boolean;\n}\n\nexport type OpenAgenticTool = Tool & ToolDetails;\n\n// =============================================================================\n// EXECUTION RESULT TYPES\n// =============================================================================\n\nexport const ExecutionResultSchema = z.object({\n success: z.boolean(),\n result: z.any().optional(),\n error: z.string().optional(),\n messages: z.array(MessageSchema),\n iterations: z.number(),\n toolCallsUsed: z.array(z.string()),\n executionStats: z.object({\n totalDuration: z.number(),\n stepsExecuted: z.number(),\n toolCallsExecuted: z.number(),\n tokensUsed: z.number().optional(),\n averageStepDuration: z.number(),\n averageToolCallDuration: z.number(),\n }).optional(),\n usage: z.object({\n totalTokens: z.number(),\n promptTokens: z.number(),\n completionTokens: z.number(),\n }).optional(),\n});\n\nexport type ExecutionResult = z.infer<typeof ExecutionResultSchema>;\n\n// =============================================================================\n// ORCHESTRATOR TYPES\n// =============================================================================\n\n/**\n * Supported orchestrator types\n */\nexport type OrchestratorType = 'prompt-based' | 'custom-logic';\n\n/**\n * Context object passed to orchestrator execute methods\n */\nexport interface OrchestratorContext {\n model: AIModel;\n tools: OpenAgenticTool[];\n messages: Message[];\n iterations: number;\n maxIterations: number;\n loggingConfig: LoggingConfig;\n orchestratorParams?: Record<string, any>;\n}\n\n/**\n * Base interface for all orchestrators\n */\nexport interface BaseOrchestrator {\n /** Unique identifier for the orchestrator */\n id: string;\n \n /** Human-readable name */\n name: string;\n \n /** Description of what this orchestrator does */\n description: string;\n \n /** Type of orchestrator */\n type: OrchestratorType;\n \n /** Execute the orchestration logic */\n execute(input: string | CoreMessage[], context: OrchestratorContext): Promise<ExecutionResult>;\n \n /** Get the orchestrator name */\n getName(): string;\n \n /** Get the orchestrator description */\n getDescription(): string;\n \n /** Get the orchestrator type */\n getType(): OrchestratorType;\n \n /** Optional validation method for inputs */\n validate?(input: string | CoreMessage[], context: OrchestratorContext): Promise<boolean>;\n \n /** Optional initialization method */\n initialize?(context: OrchestratorContext): Promise<void>;\n \n /** Optional cleanup method */\n cleanup?(context: OrchestratorContext): Promise<void>;\n}\n\n/**\n * Orchestrator that uses system prompts and standard LLM interaction\n */\nexport interface PromptBasedOrchestrator extends BaseOrchestrator {\n type: 'prompt-based';\n \n /** System prompt used for orchestration */\n systemPrompt: string;\n \n /** Get the system prompt */\n getSystemPrompt(): string;\n \n /** Optional method to modify system prompt based on context */\n buildSystemPrompt?(context: OrchestratorContext): string;\n}\n\n/**\n * Orchestrator that uses custom logic instead of standard LLM flow\n */\nexport interface CustomLogicOrchestrator extends BaseOrchestrator {\n type: 'custom-logic';\n \n /** Custom logic function for orchestration */\n customLogic(input: string | CoreMessage[], context: OrchestratorContext): Promise<any>;\n \n /** Optional method to determine if custom logic should be used */\n shouldUseCustomLogic?(input: string | CoreMessage[], context: OrchestratorContext): boolean;\n}\n\n/**\n * Options for creating orchestrator-enabled agents\n */\nexport interface OrchestratorOptions {\n /** Orchestrator instance or ID to use */\n orchestrator?: string | BaseOrchestrator;\n \n /** Alternative parameter name for orchestrator ID */\n orchestratorId?: string;\n \n /** Parameters to pass to the orchestrator */\n orchestratorParams?: Record<string, any>;\n \n /** Whether to allow orchestrator to override system prompt */\n allowOrchestratorPromptOverride?: boolean;\n \n /** Whether to allow orchestrator to modify tool execution */\n allowOrchestratorToolControl?: boolean;\n}","import { getAnthropicModelInstance } from '../tools/utils';\nimport 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 'claude-3-7-sonnet-latest': { \n contextWindow: 200000, \n cost: { input: 0.003, output: 0.015 },\n description: 'Latest Claude 3.7 Sonnet model'\n },\n 'claude-3-5-sonnet-latest': { \n contextWindow: 200000, \n cost: { input: 0.003, output: 0.015 },\n description: 'Latest Claude 3.5 Sonnet model'\n },\n }\n },\n google: {\n baseURL: 'https://generativelanguage.googleapis.com/v1beta',\n models: {\n 'gemini-2.5-pro': { \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': { \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 'gemini-2.5-flash-lite-preview-06-17': { \n contextWindow: 1000000, \n cost: { input: 0.000075, output: 0.0003 },\n description: 'Fast and efficient Gemini 2.5 Flash Lite preview model'\n },\n }\n },\n 'google-vertex': {\n baseURL: 'https://us-central1-aiplatform.googleapis.com',\n models: {\n 'gemini-2.5-pro': { \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': { \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 * Get AWS credentials from user API keys or environment variables\n */\n static getAwsCredentials(): {\n accessKeyId?: string;\n secretAccessKey?: string;\n region?: string;\n bucketName?: string;\n } {\n const userKeys = this.userApiKeys;\n \n return {\n accessKeyId: userKeys?.awsAccessKeyId || process.env.MY_AWS_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY_ID,\n secretAccessKey: userKeys?.awsSecretAccessKey || process.env.MY_AWS_SECRET_ACCESS_KEY || process.env.AWS_SECRET_ACCESS_KEY,\n region: userKeys?.awsRegion || process.env.MY_AWS_REGION || process.env.AWS_REGION,\n bucketName: userKeys?.awsS3Bucket || process.env.MY_S3_BUCKET_NAME || process.env.S3_BUCKET_NAME,\n };\n }\n\n /**\n * Get AWS Bedrock credentials from user API keys or environment variables\n */\n static getBedrockCredentials(): {\n accessKeyId?: string;\n secretAccessKey?: string;\n region?: string;\n } {\n const userKeys = this.userApiKeys;\n \n return {\n accessKeyId: userKeys?.bedrockAccessKeyId || process.env.BEDROCK_ACCESS_KEY_ID,\n secretAccessKey: userKeys?.bedrockSecretAccessKey || process.env.BEDROCK_SECRET_ACCESS_KEY,\n region: userKeys?.bedrockRegion || process.env.BEDROCK_REGION,\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 { provider } = getAnthropicModelInstance(model.model);\n return provider;\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 { tool, experimental_generateImage as generateImage} from 'ai';\nimport { z } from 'zod';\nimport type { ToolDetails } from '../types';\nimport { toOpenAgenticTool } from './utils';\nimport { uploadImageToS3, generateImageFileName } from '../utils/s3';\nimport { openai } from '@ai-sdk/openai';\n\n// Supported models with validation\n// TODO: Add DALL-E 3 and DALL-E 2 back when more stable\nconst SUPPORTED_MODELS = [\n // 'dall-e-3',\n // 'dall-e-2',\n 'gpt-image-1',\n] as const;\n\n// Supported image sizes for each model\nconst MODEL_SIZES = {\n // 'dall-e-3': ['1024x1024', '1024x1792', '1792x1024'],\n // 'dall-e-2': ['256x256', '512x512', '1024x1024'],\n 'gpt-image-1': ['1024x1024', '1536x1024', '1024x1536'],\n} as const;\n\nconst MODEL_QUALITY = {\n // 'dall-e-3': 'standard',\n // 'dall-e-2': 'standard',\n 'gpt-image-1': 'high',\n} as const;\n\nconst rawOpenAIImageTool = tool({\n description: 'Generate images using OpenAI models with automatic S3 upload and storage',\n parameters: z.object({\n prompt: z.string()\n .min(1)\n .max(4000)\n .describe('The text prompt to generate an image from (required, max 4000 characters)'),\n \n model: z.string()\n .optional()\n .default('gpt-image-1')\n .describe('The model to use (gpt-image-1)'),\n \n size: z.string()\n .optional()\n .default('1024x1024')\n .describe('The size of the image - 1024x1024, 1536x1024, 1024x1536'),\n \n // quality: z.string()\n // .optional()\n // .default('standard')\n // .describe('The quality of the image (auto, high, standard, hd) - DALL-E 3 only, default: high'),\n \n // style: z.string()\n // .optional()\n // .default('vivid')\n // .describe('The style of the image (vivid, natural) - DALL-E 3 only, default: vivid'),\n }),\n \n execute: async ({ \n prompt,\n model = 'gpt-image-1',\n size = '1024x1024',\n // quality = 'high',\n // style = 'vivid'\n }) => {\n // Validate API key\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new Error('OPENAI_API_KEY environment variable is required');\n }\n\n // Validate prompt\n if (!prompt || prompt.trim().length === 0) {\n throw new Error('Prompt cannot be empty');\n }\n\n if (prompt.length > 4000) {\n throw new Error('Prompt exceeds maximum length of 4000 characters');\n }\n\n // Validate model\n if (!SUPPORTED_MODELS.includes(model as any)) {\n throw new Error(`Model \"${model}\" not in supported list`);\n }\n\n // Validate size for model\n const validSizes = MODEL_SIZES[model as keyof typeof MODEL_SIZES] || MODEL_SIZES['gpt-image-1'];\n if (!validSizes.includes(size as any)) {\n throw new Error(`Invalid size \"${size}\" for model \"${model}\". Supported sizes: ${validSizes.join(', ')}`);\n }\n\n\n // Validate style parameter (only for DALL-E 3)\n // if (style !== 'vivid' && style !== 'natural') {\n // throw new Error('Style must be either \"vivid\" or \"natural\"');\n // }\n\n // if (model === 'dall-e-2' && style === 'natural') {\n // console.warn('Style parameter not supported for DALL-E 2, ignoring');\n // }\n\n // Start logging\n console.log('🎨 OpenAI Image Generation Tool - Generation started:', {\n timestamp: new Date().toISOString(),\n prompt: prompt.substring(0, 100) + (prompt.length > 100 ? '...' : ''),\n promptLength: prompt.length,\n model,\n size,\n quality: MODEL_QUALITY[model as keyof typeof MODEL_QUALITY],\n // style,\n });\n\n try {\n\n \n\n const { image } = await generateImage({\n model: openai.image(model),\n prompt: prompt.trim(),\n providerOptions: {\n openai: { quality: MODEL_QUALITY[model as keyof typeof MODEL_QUALITY] },\n },\n size: size as `${number}x${number}`,\n n: 1, // Generate one image\n });\n\n \n\n \n\n // Validate response structure\n if (!image) {\n throw new Error('Invalid response structure from OpenAI Images API');\n }\n\n const generatedImageBase64 = image.base64;\n if (!generatedImageBase64) {\n throw new Error('No base64 image data received from OpenAI Images API');\n }\n\n // Convert base64 to buffer\n const imageBuffer = Buffer.from(generatedImageBase64, 'base64');\n\n // Generate filename for S3 upload\n const fileName = generateImageFileName(prompt, 'png');\n\n // Upload to S3\n console.log('📤 Uploading generated image to S3...');\n const imageUrl = await uploadImageToS3(\n imageBuffer,\n fileName,\n 'image/png',\n `OpenAI ${model} generated image`\n );\n\n // Log completion\n console.log('✅ OpenAI Image Generation Tool - Generation completed:', {\n model,\n size,\n quality: MODEL_QUALITY[model as keyof typeof MODEL_QUALITY],\n imageUrl,\n fileName,\n imageSize: imageBuffer.length,\n });\n\n // Return structured result\n return {\n success: true,\n imageUrl,\n fileName,\n model,\n size,\n quality: MODEL_QUALITY[model as keyof typeof MODEL_QUALITY],\n originalPrompt: prompt.trim(),\n metadata: {\n generatedAt: new Date().toISOString(),\n promptLength: prompt.length,\n fileSize: imageBuffer.length,\n uploadedToS3: true,\n },\n };\n\n } catch (error) {\n console.error('❌ OpenAI Image Generation Tool - Generation failed:', {\n model,\n size,\n quality: MODEL_QUALITY[model as keyof typeof MODEL_QUALITY],\n promptLength: prompt.length,\n error: error instanceof Error ? error.message : JSON.stringify(error),\n });\n\n // Handle specific error types\n if (error instanceof Error) {\n // Rate limiting error\n if (error.message.includes('rate limit') || error.message.includes('429')) {\n throw new Error('OpenAI API rate limit exceeded. Please try again in a moment.');\n }\n \n // Authentication error\n if (error.message.includes('401') || error.message.includes('authentication')) {\n throw new Error('OpenAI API authentication failed. Please check your API key.');\n }\n \n // Invalid model error\n if (error.message.includes('model') && error.message.includes('not found')) {\n throw new Error(`Invalid model \"${model}\". Please use a supported DALL-E model.`);\n }\n \n // Content policy violation\n if (error.message.includes('content policy') || error.message.includes('safety')) {\n throw new Error('Image generation request violates OpenAI content policy. Please modify your prompt.');\n }\n \n // Prompt too long error\n if (error.message.includes('prompt') && error.message.includes('too