UNPKG

@mymediset/sap-ai-provider

Version:

SAP AI Core provider for AI SDK (powered by @sap-ai-sdk/orchestration)

1 lines 59.2 kB
{"version":3,"sources":["../src/index.ts","../src/sap-ai-chat-language-model.ts","../src/convert-to-sap-messages.ts","../src/sap-ai-provider.ts","../src/sap-ai-chat-settings.ts","../src/sap-ai-error.ts","../src/types/completion-response.ts"],"sourcesContent":["// Provider exports\nexport { createSAPAIProvider, sapai } from \"./sap-ai-provider\";\nexport type {\n SAPAIProvider,\n SAPAIProviderSettings,\n DeploymentConfig,\n} from \"./sap-ai-provider\";\n\n// Settings and model types\nexport type { SAPAISettings, SAPAIModelId } from \"./sap-ai-chat-settings\";\n\n// Re-export masking/filtering module types and helpers from SAP AI SDK\nexport type { MaskingModule, FilteringModule } from \"./sap-ai-chat-settings\";\nexport {\n buildDpiMaskingProvider,\n buildAzureContentSafetyFilter,\n buildLlamaGuard38BFilter,\n buildDocumentGroundingConfig,\n buildTranslationConfig,\n} from \"./sap-ai-chat-settings\";\n\n// Error handling\nexport { SAPAIError } from \"./sap-ai-error\";\nexport type { OrchestrationErrorResponse } from \"./sap-ai-error\";\n\n// Re-export useful types from SAP AI SDK for advanced usage\nexport type {\n OrchestrationModuleConfig,\n ChatCompletionRequest,\n PromptTemplatingModule,\n GroundingModule,\n TranslationModule,\n LlmModelParams,\n LlmModelDetails,\n ChatCompletionTool,\n FunctionObject,\n} from \"./types/completion-request\";\n\nexport type {\n ChatMessage,\n SystemChatMessage,\n UserChatMessage,\n AssistantChatMessage,\n ToolChatMessage,\n DeveloperChatMessage,\n} from \"./types/completion-response\";\n\nexport {\n OrchestrationResponse,\n OrchestrationStreamResponse,\n OrchestrationStreamChunkResponse,\n} from \"./types/completion-response\";\n\n// Re-export OrchestrationClient for advanced usage\nexport { OrchestrationClient } from \"@sap-ai-sdk/orchestration\";\n","import {\n LanguageModelV2,\n LanguageModelV2CallOptions,\n LanguageModelV2CallWarning,\n LanguageModelV2Content,\n LanguageModelV2FinishReason,\n LanguageModelV2FunctionTool,\n LanguageModelV2StreamPart,\n LanguageModelV2Usage,\n} from \"@ai-sdk/provider\";\nimport {\n OrchestrationClient,\n OrchestrationModuleConfig,\n ChatMessage,\n ChatCompletionTool,\n} from \"@sap-ai-sdk/orchestration\";\nimport type { HttpDestinationOrFetchOptions } from \"@sap-cloud-sdk/connectivity\";\nimport type {\n ResourceGroupConfig,\n DeploymentIdConfig,\n} from \"@sap-ai-sdk/ai-api/internal.js\";\n// Note: zodToJsonSchema and isZodSchema are kept for potential future use\n// when AI SDK Zod conversion issues are resolved\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\n// Import ZodSchema from zod/v3 for zod-to-json-schema\nimport type { ZodSchema } from \"zod/v3\";\nimport { convertToSAPMessages } from \"./convert-to-sap-messages\";\nimport { SAPAIModelId, SAPAISettings } from \"./sap-ai-chat-settings\";\n\n/**\n * Type guard to check if an object is a Zod schema.\n * @internal\n */\nfunction isZodSchema(obj: unknown): obj is ZodSchema {\n return (\n obj !== null &&\n typeof obj === \"object\" &&\n \"_def\" in obj &&\n \"parse\" in obj &&\n typeof (obj as { parse: unknown }).parse === \"function\"\n );\n}\n\n/**\n * Internal configuration for the SAP AI Chat Language Model.\n * @internal\n */\ninterface SAPAIConfig {\n /** Provider identifier */\n provider: string;\n /** Deployment configuration for SAP AI SDK */\n deploymentConfig: ResourceGroupConfig | DeploymentIdConfig;\n /** Optional custom destination */\n destination?: HttpDestinationOrFetchOptions;\n}\n\n/**\n * SAP AI Chat Language Model implementation.\n *\n * This class implements the Vercel AI SDK's `LanguageModelV2` interface,\n * providing a bridge between the AI SDK and SAP AI Core's Orchestration API\n * using the official SAP AI SDK (@sap-ai-sdk/orchestration).\n *\n * **Features:**\n * - Text generation (streaming and non-streaming)\n * - Tool calling (function calling)\n * - Multi-modal input (text + images)\n * - Data masking (SAP DPI)\n * - Content filtering\n *\n * **Model Support:**\n * - Azure OpenAI models (gpt-4o, gpt-4o-mini, o1, o3, etc.)\n * - Google Vertex AI models (gemini-2.0-flash, gemini-2.5-pro, etc.)\n * - AWS Bedrock models (anthropic--claude-*, amazon--nova-*, etc.)\n * - AI Core open source models (mistralai--, cohere--, etc.)\n *\n * @example\n * ```typescript\n * // Create via provider\n * const provider = createSAPAIProvider();\n * const model = provider('gpt-4o');\n *\n * // Use with AI SDK\n * const result = await generateText({\n * model,\n * prompt: 'Hello, world!'\n * });\n * ```\n *\n * @implements {LanguageModelV2}\n */\nexport class SAPAIChatLanguageModel implements LanguageModelV2 {\n /** AI SDK specification version */\n readonly specificationVersion = \"v2\";\n /** Default object generation mode */\n readonly defaultObjectGenerationMode = \"json\";\n /** Whether the model supports image URLs */\n readonly supportsImageUrls = true;\n /** The model identifier (e.g., 'gpt-4o', 'anthropic--claude-3.5-sonnet') */\n readonly modelId: SAPAIModelId;\n /** Whether the model supports structured outputs */\n readonly supportsStructuredOutputs = true;\n\n /** Internal configuration */\n private readonly config: SAPAIConfig;\n /** Model-specific settings */\n private readonly settings: SAPAISettings;\n\n /**\n * Creates a new SAP AI Chat Language Model instance.\n *\n * @param modelId - The model identifier\n * @param settings - Model-specific configuration settings\n * @param config - Internal configuration (deployment config, destination, etc.)\n *\n * @internal This constructor is not meant to be called directly.\n * Use the provider function instead.\n */\n constructor(\n modelId: SAPAIModelId,\n settings: SAPAISettings,\n config: SAPAIConfig,\n ) {\n this.settings = settings;\n this.config = config;\n this.modelId = modelId;\n }\n\n /**\n * Checks if a URL is supported for file/image uploads.\n *\n * @param url - The URL to check\n * @returns True if the URL protocol is HTTPS\n */\n supportsUrl(url: URL): boolean {\n return url.protocol === \"https:\";\n }\n\n /**\n * Returns supported URL patterns for different content types.\n *\n * @returns Record of content types to regex patterns\n */\n get supportedUrls(): Record<string, RegExp[]> {\n return {\n \"image/*\": [\n /^https:\\/\\/.*\\.(?:png|jpg|jpeg|gif|webp)$/i,\n /^data:image\\/.*$/,\n ],\n };\n }\n\n /**\n * Gets the provider identifier.\n *\n * @returns The provider name ('sap-ai')\n */\n get provider(): string {\n return this.config.provider;\n }\n\n /**\n * Builds orchestration module config for SAP AI SDK.\n *\n * @param options - Call options from the AI SDK\n * @returns Object containing orchestration config and warnings\n *\n * @internal\n */\n private buildOrchestrationConfig(options: LanguageModelV2CallOptions): {\n orchestrationConfig: OrchestrationModuleConfig;\n messages: ChatMessage[];\n warnings: LanguageModelV2CallWarning[];\n } {\n const warnings: LanguageModelV2CallWarning[] = [];\n\n // Convert AI SDK prompt to SAP messages\n const messages = convertToSAPMessages(options.prompt);\n\n // Get tools - prefer settings.tools if provided (proper JSON Schema),\n // otherwise try to convert from AI SDK tools\n let tools: ChatCompletionTool[] | undefined;\n\n if (this.settings.tools && this.settings.tools.length > 0) {\n // Use tools from settings (already in SAP format with proper schemas)\n tools = this.settings.tools;\n } else {\n // Extract tools from options and convert\n const availableTools = options.tools;\n\n tools = availableTools\n ?.map((tool): ChatCompletionTool | null => {\n if (tool.type === \"function\") {\n // Get the input schema - AI SDK provides this as JSONSchema7\n // But in some cases, it might be a Zod schema or have empty properties\n const inputSchema = tool.inputSchema as\n | Record<string, unknown>\n | undefined;\n\n // Also check for raw Zod schema in 'parameters' field (AI SDK internal)\n const toolWithParams = tool as LanguageModelV2FunctionTool & {\n parameters?: unknown;\n };\n\n // Build parameters ensuring type: \"object\" is always present\n // SAP AI Core requires explicit type: \"object\" in the schema\n let parameters: Record<string, unknown>;\n\n // First, check if there's a Zod schema we need to convert\n if (\n toolWithParams.parameters &&\n isZodSchema(toolWithParams.parameters)\n ) {\n // Convert Zod schema to JSON Schema\n const jsonSchema = zodToJsonSchema(toolWithParams.parameters, {\n $refStrategy: \"none\",\n }) as Record<string, unknown>;\n // Remove $schema property as SAP doesn't need it\n delete jsonSchema.$schema;\n parameters = {\n type: \"object\",\n ...jsonSchema,\n };\n } else if (inputSchema && Object.keys(inputSchema).length > 0) {\n // Check if schema has properties (it's a proper object schema)\n const hasProperties =\n inputSchema.properties &&\n typeof inputSchema.properties === \"object\" &&\n Object.keys(inputSchema.properties).length > 0;\n\n if (hasProperties) {\n parameters = {\n type: \"object\",\n ...inputSchema,\n };\n } else {\n // Schema exists but has no properties - use default empty schema\n parameters = {\n type: \"object\",\n properties: {},\n required: [],\n };\n }\n } else {\n // No schema provided - use default empty schema\n parameters = {\n type: \"object\",\n properties: {},\n required: [],\n };\n }\n\n return {\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters,\n },\n };\n } else {\n warnings.push({\n type: \"unsupported-tool\",\n tool: tool,\n });\n return null;\n }\n })\n .filter((t): t is ChatCompletionTool => t !== null);\n }\n\n // Check if model supports certain features\n const supportsN =\n !this.modelId.startsWith(\"amazon--\") &&\n !this.modelId.startsWith(\"anthropic--\");\n\n // Build orchestration config\n const orchestrationConfig: OrchestrationModuleConfig = {\n promptTemplating: {\n model: {\n name: this.modelId,\n version: this.settings.modelVersion ?? \"latest\",\n params: {\n max_tokens: this.settings.modelParams?.maxTokens,\n temperature: this.settings.modelParams?.temperature,\n top_p: this.settings.modelParams?.topP,\n frequency_penalty: this.settings.modelParams?.frequencyPenalty,\n presence_penalty: this.settings.modelParams?.presencePenalty,\n n: supportsN ? (this.settings.modelParams?.n ?? 1) : undefined,\n },\n },\n prompt: {\n template: [],\n tools: tools && tools.length > 0 ? tools : undefined,\n },\n },\n // Include masking module if provided\n ...(this.settings.masking ? { masking: this.settings.masking } : {}),\n // Include filtering module if provided\n ...(this.settings.filtering\n ? { filtering: this.settings.filtering }\n : {}),\n };\n\n return { orchestrationConfig, messages, warnings };\n }\n\n /**\n * Creates an OrchestrationClient instance.\n *\n * @param config - Orchestration module configuration\n * @returns OrchestrationClient instance\n *\n * @internal\n */\n private createClient(config: OrchestrationModuleConfig): OrchestrationClient {\n return new OrchestrationClient(\n config,\n this.config.deploymentConfig,\n this.config.destination,\n );\n }\n\n /**\n * Generates a single completion (non-streaming).\n *\n * This method implements the `LanguageModelV2.doGenerate` interface,\n * sending a request to SAP AI Core and returning the complete response.\n *\n * **Features:**\n * - Tool calling support\n * - Multi-modal input (text + images)\n * - Data masking (if configured)\n * - Content filtering (if configured)\n *\n * @param options - Generation options including prompt, tools, and settings\n * @returns Promise resolving to the generation result with content, usage, and metadata\n *\n * @example\n * ```typescript\n * const result = await model.doGenerate({\n * prompt: [\n * { role: 'user', content: [{ type: 'text', text: 'Hello!' }] }\n * ]\n * });\n *\n * console.log(result.content); // Generated content\n * console.log(result.usage); // Token usage\n * ```\n */\n async doGenerate(options: LanguageModelV2CallOptions): Promise<{\n content: LanguageModelV2Content[];\n finishReason: LanguageModelV2FinishReason;\n usage: LanguageModelV2Usage;\n rawCall: { rawPrompt: unknown; rawSettings: Record<string, unknown> };\n warnings: LanguageModelV2CallWarning[];\n }> {\n const { orchestrationConfig, messages, warnings } =\n this.buildOrchestrationConfig(options);\n\n const client = this.createClient(orchestrationConfig);\n\n const response = await client.chatCompletion({\n messages,\n });\n\n const content: LanguageModelV2Content[] = [];\n\n // Extract text content\n const textContent = response.getContent();\n if (textContent) {\n content.push({\n type: \"text\",\n text: textContent,\n });\n }\n\n // Extract tool calls\n const toolCalls = response.getToolCalls();\n if (toolCalls) {\n for (const toolCall of toolCalls) {\n content.push({\n type: \"tool-call\",\n toolCallId: toolCall.id,\n toolName: toolCall.function.name,\n // AI SDK expects input as a JSON string, which it parses internally\n input: toolCall.function.arguments,\n });\n }\n }\n\n // Get usage\n const tokenUsage = response.getTokenUsage();\n\n // Map finish reason\n const finishReasonRaw = response.getFinishReason();\n const finishReason = mapFinishReason(finishReasonRaw);\n\n return {\n content,\n finishReason,\n usage: {\n inputTokens: tokenUsage.prompt_tokens,\n outputTokens: tokenUsage.completion_tokens,\n totalTokens: tokenUsage.total_tokens,\n },\n rawCall: {\n rawPrompt: { config: orchestrationConfig, messages },\n rawSettings: {},\n },\n warnings,\n };\n }\n\n /**\n * Generates a streaming completion.\n *\n * This method implements the `LanguageModelV2.doStream` interface,\n * sending a streaming request to SAP AI Core and returning a stream of response parts.\n *\n * **Stream Events:**\n * - `stream-start` - Stream initialization\n * - `response-metadata` - Response metadata (model, timestamp)\n * - `text-start` - Text generation starts\n * - `text-delta` - Incremental text chunks\n * - `text-end` - Text generation completes\n * - `tool-call` - Tool call detected\n * - `finish` - Stream completes with usage and finish reason\n * - `error` - Error occurred\n *\n * @param options - Streaming options including prompt, tools, and settings\n * @returns Promise resolving to stream and raw call metadata\n *\n * @example\n * ```typescript\n * const { stream } = await model.doStream({\n * prompt: [\n * { role: 'user', content: [{ type: 'text', text: 'Write a story' }] }\n * ]\n * });\n *\n * for await (const part of stream) {\n * if (part.type === 'text-delta') {\n * process.stdout.write(part.delta);\n * }\n * }\n * ```\n */\n async doStream(options: LanguageModelV2CallOptions): Promise<{\n stream: ReadableStream<LanguageModelV2StreamPart>;\n rawCall: { rawPrompt: unknown; rawSettings: Record<string, unknown> };\n }> {\n const { orchestrationConfig, messages, warnings } =\n this.buildOrchestrationConfig(options);\n\n const client = this.createClient(orchestrationConfig);\n\n const streamResponse = await client.stream(\n { messages },\n options.abortSignal,\n { promptTemplating: { include_usage: true } },\n );\n\n let finishReason: LanguageModelV2FinishReason = \"unknown\";\n const usage: LanguageModelV2Usage = {\n inputTokens: undefined,\n outputTokens: undefined,\n totalTokens: undefined,\n };\n\n let isFirstChunk = true;\n let activeText = false;\n\n // Track tool calls being built up\n const toolCallsInProgress = new Map<\n number,\n { id: string; name: string; arguments: string }\n >();\n\n const sdkStream = streamResponse.stream;\n\n const transformedStream = new ReadableStream<LanguageModelV2StreamPart>({\n async start(controller) {\n controller.enqueue({ type: \"stream-start\", warnings });\n\n try {\n for await (const chunk of sdkStream) {\n if (isFirstChunk) {\n isFirstChunk = false;\n controller.enqueue({\n type: \"response-metadata\",\n id: undefined,\n modelId: undefined,\n timestamp: new Date(),\n });\n }\n\n // Get delta content\n const deltaContent = chunk.getDeltaContent();\n if (deltaContent) {\n if (!activeText) {\n controller.enqueue({ type: \"text-start\", id: \"0\" });\n activeText = true;\n }\n controller.enqueue({\n type: \"text-delta\",\n id: \"0\",\n delta: deltaContent,\n });\n }\n\n // Handle tool calls\n const deltaToolCalls = chunk.getDeltaToolCalls();\n if (deltaToolCalls) {\n for (const toolCallChunk of deltaToolCalls) {\n const index = toolCallChunk.index;\n\n // Initialize tool call if new\n if (!toolCallsInProgress.has(index)) {\n toolCallsInProgress.set(index, {\n id: toolCallChunk.id ?? `tool_${String(index)}`,\n name: toolCallChunk.function?.name ?? \"\",\n arguments: \"\",\n });\n\n // Emit tool-input-start\n const tc = toolCallsInProgress.get(index);\n if (!tc) continue;\n if (toolCallChunk.function?.name) {\n controller.enqueue({\n type: \"tool-input-start\",\n id: tc.id,\n toolName: tc.name,\n });\n }\n }\n\n const tc = toolCallsInProgress.get(index);\n if (!tc) continue;\n\n // Update tool call ID if provided\n if (toolCallChunk.id) {\n tc.id = toolCallChunk.id;\n }\n\n // Update function name if provided\n if (toolCallChunk.function?.name) {\n tc.name = toolCallChunk.function.name;\n }\n\n // Accumulate arguments\n if (toolCallChunk.function?.arguments) {\n tc.arguments += toolCallChunk.function.arguments;\n controller.enqueue({\n type: \"tool-input-delta\",\n id: tc.id,\n delta: toolCallChunk.function.arguments,\n });\n }\n }\n }\n\n // Check for finish reason\n const chunkFinishReason = chunk.getFinishReason();\n if (chunkFinishReason) {\n finishReason = mapFinishReason(chunkFinishReason);\n }\n\n // Get usage from chunk\n const chunkUsage = chunk.getTokenUsage();\n if (chunkUsage) {\n usage.inputTokens = chunkUsage.prompt_tokens;\n usage.outputTokens = chunkUsage.completion_tokens;\n usage.totalTokens = chunkUsage.total_tokens;\n }\n }\n\n // Emit completed tool calls\n const toolCalls = Array.from(toolCallsInProgress.values());\n for (const tc of toolCalls) {\n controller.enqueue({\n type: \"tool-input-end\",\n id: tc.id,\n });\n controller.enqueue({\n type: \"tool-call\",\n toolCallId: tc.id,\n toolName: tc.name,\n input: tc.arguments,\n });\n }\n\n if (activeText) {\n controller.enqueue({ type: \"text-end\", id: \"0\" });\n }\n\n // Try to get final usage from stream response\n const finalUsage = streamResponse.getTokenUsage();\n if (finalUsage) {\n usage.inputTokens = finalUsage.prompt_tokens;\n usage.outputTokens = finalUsage.completion_tokens;\n usage.totalTokens = finalUsage.total_tokens;\n }\n\n // Get final finish reason\n const finalFinishReason = streamResponse.getFinishReason();\n if (finalFinishReason) {\n finishReason = mapFinishReason(finalFinishReason);\n }\n\n controller.enqueue({\n type: \"finish\",\n finishReason,\n usage,\n });\n\n controller.close();\n } catch (error) {\n controller.enqueue({\n type: \"error\",\n error: error instanceof Error ? error : new Error(String(error)),\n });\n controller.close();\n }\n },\n });\n\n return {\n stream: transformedStream,\n rawCall: {\n rawPrompt: { config: orchestrationConfig, messages },\n rawSettings: {},\n },\n };\n }\n}\n\n/**\n * Maps SAP AI Core finish reasons to AI SDK finish reasons.\n */\nfunction mapFinishReason(\n reason: string | undefined,\n): LanguageModelV2FinishReason {\n if (!reason) return \"unknown\";\n\n switch (reason.toLowerCase()) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n case \"function_call\":\n return \"tool-calls\";\n case \"content_filter\":\n return \"content-filter\";\n default:\n return \"unknown\";\n }\n}\n","import {\n LanguageModelV2Prompt,\n UnsupportedFunctionalityError,\n} from \"@ai-sdk/provider\";\nimport type {\n ChatMessage,\n SystemChatMessage,\n UserChatMessage,\n AssistantChatMessage,\n ToolChatMessage,\n} from \"@sap-ai-sdk/orchestration\";\n\n/**\n * User chat message content item for multi-modal messages.\n */\ninterface UserContentItem {\n type: \"text\" | \"image_url\";\n text?: string;\n image_url?: {\n url: string;\n };\n}\n\n/**\n * Converts Vercel AI SDK prompt format to SAP AI SDK ChatMessage format.\n *\n * This function transforms the standardized LanguageModelV2Prompt format\n * used by the Vercel AI SDK into the ChatMessage format expected\n * by SAP AI SDK's OrchestrationClient.\n *\n * **Supported Features:**\n * - Text messages (system, user, assistant)\n * - Multi-modal messages (text + images)\n * - Tool calls and tool results\n * - Conversation history\n *\n * **Limitations:**\n * - Images must be in data URL format or accessible HTTP URLs\n * - Audio messages are not supported\n * - File attachments (non-image) are not supported\n *\n * @param prompt - The Vercel AI SDK prompt to convert\n * @returns Array of SAP AI SDK compatible ChatMessage objects\n *\n * @throws {UnsupportedFunctionalityError} When unsupported message types are encountered\n *\n * @example\n * ```typescript\n * const prompt = [\n * { role: 'system', content: 'You are a helpful assistant.' },\n * { role: 'user', content: [{ type: 'text', text: 'Hello!' }] }\n * ];\n *\n * const sapMessages = convertToSAPMessages(prompt);\n * // Result: [\n * // { role: 'system', content: 'You are a helpful assistant.' },\n * // { role: 'user', content: 'Hello!' }\n * // ]\n * ```\n *\n * @example\n * **Multi-modal with Image**\n * ```typescript\n * const prompt = [\n * {\n * role: 'user',\n * content: [\n * { type: 'text', text: 'What do you see in this image?' },\n * { type: 'file', mediaType: 'image/jpeg', data: 'base64...' }\n * ]\n * }\n * ];\n *\n * const sapMessages = convertToSAPMessages(prompt);\n * ```\n */\nexport function convertToSAPMessages(\n prompt: LanguageModelV2Prompt,\n): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n for (const message of prompt) {\n switch (message.role) {\n case \"system\": {\n const systemMessage: SystemChatMessage = {\n role: \"system\",\n content: message.content,\n };\n messages.push(systemMessage);\n break;\n }\n\n case \"user\": {\n // Build content parts for user messages\n const contentParts: UserContentItem[] = [];\n\n for (const part of message.content) {\n switch (part.type) {\n case \"text\": {\n contentParts.push({\n type: \"text\",\n text: part.text,\n });\n break;\n }\n case \"file\": {\n // SAP AI Core only supports image files\n if (!part.mediaType.startsWith(\"image/\")) {\n throw new UnsupportedFunctionalityError({\n functionality: \"Only image files are supported\",\n });\n }\n\n const imageUrl =\n part.data instanceof URL\n ? part.data.toString()\n : `data:${part.mediaType};base64,${String(part.data)}`;\n\n contentParts.push({\n type: \"image_url\",\n image_url: {\n url: imageUrl,\n },\n });\n break;\n }\n default: {\n throw new UnsupportedFunctionalityError({\n functionality: `Content type ${(part as { type: string }).type}`,\n });\n }\n }\n }\n\n // If only text content, use simple string format\n // Otherwise use array format for multi-modal\n const userMessage: UserChatMessage =\n contentParts.length === 1 && contentParts[0].type === \"text\"\n ? {\n role: \"user\",\n content: contentParts[0].text ?? \"\",\n }\n : {\n role: \"user\",\n content: contentParts as UserChatMessage[\"content\"],\n };\n\n messages.push(userMessage);\n break;\n }\n\n case \"assistant\": {\n let text = \"\";\n const toolCalls: {\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }[] = [];\n\n for (const part of message.content) {\n switch (part.type) {\n case \"text\": {\n text += part.text;\n break;\n }\n case \"tool-call\": {\n toolCalls.push({\n id: part.toolCallId,\n type: \"function\",\n function: {\n name: part.toolName,\n arguments: JSON.stringify(part.input),\n },\n });\n break;\n }\n }\n }\n\n const assistantMessage: AssistantChatMessage = {\n role: \"assistant\",\n content: text || \"\",\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n };\n messages.push(assistantMessage);\n break;\n }\n\n case \"tool\": {\n // Convert tool results to tool messages\n for (const part of message.content) {\n const toolMessage: ToolChatMessage = {\n role: \"tool\",\n tool_call_id: part.toolCallId,\n content: JSON.stringify(part.output),\n };\n messages.push(toolMessage);\n }\n break;\n }\n\n default: {\n const _exhaustiveCheck: never = message;\n throw new Error(\n `Unsupported role: ${(_exhaustiveCheck as { role: string }).role}`,\n );\n }\n }\n }\n\n return messages;\n}\n","import { ProviderV2 } from \"@ai-sdk/provider\";\nimport type { HttpDestinationOrFetchOptions } from \"@sap-cloud-sdk/connectivity\";\nimport type {\n ResourceGroupConfig,\n DeploymentIdConfig,\n} from \"@sap-ai-sdk/ai-api/internal.js\";\nimport { SAPAIChatLanguageModel } from \"./sap-ai-chat-language-model\";\nimport { SAPAIModelId, SAPAISettings } from \"./sap-ai-chat-settings\";\n\n/**\n * SAP AI Provider interface.\n *\n * This is the main interface for creating and configuring SAP AI Core models.\n * It extends the standard Vercel AI SDK ProviderV2 interface with SAP-specific functionality.\n *\n * @example\n * ```typescript\n * const provider = createSAPAIProvider({\n * resourceGroup: 'default'\n * });\n *\n * // Create a model instance\n * const model = provider('gpt-4o', {\n * modelParams: {\n * temperature: 0.7,\n * maxTokens: 1000\n * }\n * });\n *\n * // Or use the explicit chat method\n * const chatModel = provider.chat('gpt-4o');\n * ```\n */\nexport interface SAPAIProvider extends ProviderV2 {\n /**\n * Create a language model instance.\n *\n * @param modelId - The SAP AI Core model identifier (e.g., 'gpt-4o', 'anthropic--claude-3.5-sonnet')\n * @param settings - Optional model configuration settings\n * @returns Configured SAP AI chat language model instance\n */\n (modelId: SAPAIModelId, settings?: SAPAISettings): SAPAIChatLanguageModel;\n\n /**\n * Explicit method for creating chat models.\n *\n * This method is equivalent to calling the provider function directly,\n * but provides a more explicit API for chat-based interactions.\n *\n * @param modelId - The SAP AI Core model identifier\n * @param settings - Optional model configuration settings\n * @returns Configured SAP AI chat language model instance\n */\n chat(modelId: SAPAIModelId, settings?: SAPAISettings): SAPAIChatLanguageModel;\n}\n\n/**\n * Configuration settings for the SAP AI Provider.\n *\n * This interface defines all available options for configuring the SAP AI Core connection\n * using the official SAP AI SDK. The SDK handles authentication automatically when\n * running on SAP BTP (via service binding) or locally (via AICORE_SERVICE_KEY env var).\n *\n * @example\n * ```typescript\n * // Using default configuration (auto-detects service binding or env var)\n * const provider = createSAPAIProvider();\n *\n * // With specific resource group\n * const provider = createSAPAIProvider({\n * resourceGroup: 'production'\n * });\n *\n * // With custom destination\n * const provider = createSAPAIProvider({\n * destination: {\n * url: 'https://my-ai-core-instance.cfapps.eu10.hana.ondemand.com'\n * }\n * });\n * ```\n */\nexport interface SAPAIProviderSettings {\n /**\n * SAP AI Core resource group.\n *\n * Logical grouping of AI resources in SAP AI Core.\n * Used for resource isolation and access control.\n * Different resource groups can have different permissions and quotas.\n *\n * @default 'default'\n * @example\n * ```typescript\n * resourceGroup: 'default' // Default resource group\n * resourceGroup: 'production' // Production environment\n * resourceGroup: 'development' // Development environment\n * ```\n */\n resourceGroup?: string;\n\n /**\n * SAP AI Core deployment ID.\n *\n * A specific deployment ID to use for orchestration requests.\n * If not provided, the SDK will resolve the deployment automatically.\n *\n * @example\n * ```typescript\n * deploymentId: 'd65d81e7c077e583'\n * ```\n */\n deploymentId?: string;\n\n /**\n * Custom destination configuration for SAP AI Core.\n *\n * Override the default destination detection. Useful for:\n * - Custom proxy configurations\n * - Non-standard SAP AI Core setups\n * - Testing environments\n *\n * @example\n * ```typescript\n * destination: {\n * url: 'https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com'\n * }\n * ```\n */\n destination?: HttpDestinationOrFetchOptions;\n\n /**\n * Default model settings applied to every model instance created by this provider.\n * Per-call settings provided to the model will override these.\n */\n defaultSettings?: SAPAISettings;\n}\n\n/**\n * Deployment configuration type used by SAP AI SDK.\n */\nexport type DeploymentConfig = ResourceGroupConfig | DeploymentIdConfig;\n\n/**\n * Creates a SAP AI Core provider instance for use with Vercel AI SDK.\n *\n * This is the main entry point for integrating SAP AI Core with the Vercel AI SDK.\n * It uses the official SAP AI SDK (@sap-ai-sdk/orchestration) under the hood,\n * which handles authentication and API communication automatically.\n *\n * **Authentication:**\n * The SAP AI SDK automatically handles authentication:\n * 1. On SAP BTP: Uses service binding (VCAP_SERVICES)\n * 2. Locally: Uses AICORE_SERVICE_KEY environment variable\n *\n * **Key Features:**\n * - Automatic authentication via SAP AI SDK\n * - Support for all SAP AI Core orchestration models\n * - Streaming and non-streaming responses\n * - Tool calling support\n * - Data masking (DPI)\n * - Content filtering\n *\n * @param options - Configuration options for the provider\n * @returns A configured SAP AI provider\n *\n * @example\n * **Basic Usage**\n * ```typescript\n * import { createSAPAIProvider } from '@mymediset/sap-ai-provider';\n * import { generateText } from 'ai';\n *\n * const provider = createSAPAIProvider();\n *\n * const result = await generateText({\n * model: provider('gpt-4o'),\n * prompt: 'Hello, world!'\n * });\n * ```\n *\n * @example\n * **With Resource Group**\n * ```typescript\n * const provider = createSAPAIProvider({\n * resourceGroup: 'production'\n * });\n *\n * const model = provider('anthropic--claude-3.5-sonnet', {\n * modelParams: {\n * temperature: 0.3,\n * maxTokens: 2000\n * }\n * });\n * ```\n *\n * @example\n * **With Default Settings**\n * ```typescript\n * const provider = createSAPAIProvider({\n * defaultSettings: {\n * modelParams: {\n * temperature: 0.7\n * }\n * }\n * });\n * ```\n */\nexport function createSAPAIProvider(\n options: SAPAIProviderSettings = {},\n): SAPAIProvider {\n const resourceGroup = options.resourceGroup ?? \"default\";\n\n // Build deployment config for SAP AI SDK\n const deploymentConfig: DeploymentConfig = options.deploymentId\n ? { deploymentId: options.deploymentId }\n : { resourceGroup };\n\n // Create the model factory function\n const createModel = (modelId: SAPAIModelId, settings: SAPAISettings = {}) => {\n const mergedSettings: SAPAISettings = {\n ...options.defaultSettings,\n ...settings,\n modelParams: {\n ...(options.defaultSettings?.modelParams ?? {}),\n ...(settings.modelParams ?? {}),\n },\n };\n\n return new SAPAIChatLanguageModel(modelId, mergedSettings, {\n provider: \"sap-ai\",\n deploymentConfig,\n destination: options.destination,\n });\n };\n\n // Create the provider function\n const provider = function (modelId: SAPAIModelId, settings?: SAPAISettings) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (new.target) {\n throw new Error(\n \"The SAP AI provider function cannot be called with the new keyword.\",\n );\n }\n\n return createModel(modelId, settings);\n };\n\n provider.chat = createModel;\n\n return provider as SAPAIProvider;\n}\n\n/**\n * Default SAP AI provider instance.\n *\n * Uses the default configuration which auto-detects authentication\n * from service binding (SAP BTP) or AICORE_SERVICE_KEY environment variable.\n *\n * @example\n * ```typescript\n * import { sapai } from '@mymediset/sap-ai-provider';\n * import { generateText } from 'ai';\n *\n * const result = await generateText({\n * model: sapai('gpt-4o'),\n * prompt: 'Hello!'\n * });\n * ```\n */\nexport const sapai = createSAPAIProvider();\n","import type {\n MaskingModule,\n FilteringModule,\n ChatModel,\n ChatCompletionTool,\n} from \"@sap-ai-sdk/orchestration\";\n\n/**\n * Settings for configuring SAP AI Core model behavior.\n */\nexport interface SAPAISettings {\n /**\n * Specific version of the model to use.\n * If not provided, the latest version will be used.\n */\n modelVersion?: string;\n\n /**\n * Model generation parameters that control the output.\n */\n modelParams?: {\n /**\n * Maximum number of tokens to generate.\n * Higher values allow for longer responses but increase latency and cost.\n */\n maxTokens?: number;\n\n /**\n * Sampling temperature between 0 and 2.\n * Higher values make output more random, lower values more deterministic.\n * No default; omitted when unspecified or unsupported by the target model.\n */\n temperature?: number;\n\n /**\n * Nucleus sampling parameter between 0 and 1.\n * Controls diversity via cumulative probability cutoff.\n * @default 1\n */\n topP?: number;\n\n /**\n * Frequency penalty between -2.0 and 2.0.\n * Positive values penalize tokens based on their frequency.\n * @default 0\n */\n frequencyPenalty?: number;\n\n /**\n * Presence penalty between -2.0 and 2.0.\n * Positive values penalize tokens that have appeared in the text.\n * @default 0\n */\n presencePenalty?: number;\n\n /**\n * Number of completions to generate.\n * Multiple completions provide alternative responses.\n * Note: Not supported by Amazon and Anthropic models.\n * @default 1\n */\n n?: number;\n\n /**\n * Whether to enable parallel tool calls.\n * When enabled, the model can call multiple tools in parallel.\n */\n parallel_tool_calls?: boolean;\n };\n\n /**\n * Masking configuration for SAP AI Core orchestration.\n * When provided, sensitive information in prompts can be anonymized or\n * pseudonymized by SAP Data Privacy Integration (DPI).\n *\n * @example\n * ```typescript\n * import { buildDpiMaskingProvider } from '@sap-ai-sdk/orchestration';\n *\n * const model = provider('gpt-4o', {\n * masking: {\n * masking_providers: [\n * buildDpiMaskingProvider({\n * method: 'anonymization',\n * entities: ['profile-email', 'profile-phone']\n * })\n * ]\n * }\n * });\n * ```\n */\n masking?: MaskingModule;\n\n /**\n * Filtering configuration for input and output content safety.\n * Supports Azure Content Safety and Llama Guard filters.\n *\n * @example\n * ```typescript\n * import { buildAzureContentSafetyFilter } from '@sap-ai-sdk/orchestration';\n *\n * const model = provider('gpt-4o', {\n * filtering: {\n * input: {\n * filters: [\n * buildAzureContentSafetyFilter('input', {\n * hate: 'ALLOW_SAFE',\n * violence: 'ALLOW_SAFE_LOW_MEDIUM'\n * })\n * ]\n * }\n * }\n * });\n * ```\n */\n filtering?: FilteringModule;\n\n /**\n * Response format for templating prompt (OpenAI-compatible).\n * Allows specifying structured output formats.\n *\n * @example\n * ```typescript\n * const model = provider('gpt-4o', {\n * responseFormat: {\n * type: 'json_schema',\n * json_schema: {\n * name: 'response',\n * schema: { type: 'object', properties: { answer: { type: 'string' } } }\n * }\n * }\n * });\n * ```\n */\n responseFormat?:\n | { type: \"text\" }\n | { type: \"json_object\" }\n | {\n type: \"json_schema\";\n json_schema: {\n name: string;\n description?: string;\n schema?: unknown;\n strict?: boolean | null;\n };\n };\n\n /**\n * Tool definitions in SAP AI SDK format.\n *\n * Use this to pass tools directly with proper JSON Schema definitions.\n * This bypasses the AI SDK's Zod conversion which may have issues.\n *\n * Note: This should be used in conjunction with AI SDK's tool handling\n * to provide the actual tool implementations (execute functions).\n *\n * @example\n * ```typescript\n * const model = provider('gpt-4o', {\n * tools: [\n * {\n * type: 'function',\n * function: {\n * name: 'get_weather',\n * description: 'Get weather for a location',\n * parameters: {\n * type: 'object',\n * properties: {\n * location: { type: 'string', description: 'City name' }\n * },\n * required: ['location']\n * }\n * }\n * }\n * ]\n * });\n * ```\n */\n tools?: ChatCompletionTool[];\n}\n\n/**\n * Supported model IDs in SAP AI Core.\n *\n * These models are available through the SAP AI Core Orchestration service.\n * Model availability depends on your subscription and region.\n *\n * **Azure OpenAI Models:**\n * - gpt-4o, gpt-4o-mini\n * - gpt-4.1, gpt-4.1-mini, gpt-4.1-nano\n * - o1, o3, o3-mini, o4-mini\n *\n * **Google Vertex AI Models:**\n * - gemini-2.0-flash, gemini-2.0-flash-lite\n * - gemini-2.5-flash, gemini-2.5-pro\n *\n * **AWS Bedrock Models:**\n * - anthropic--claude-3-haiku, anthropic--claude-3-sonnet, anthropic--claude-3-opus\n * - anthropic--claude-3.5-sonnet, anthropic--claude-3.7-sonnet\n * - anthropic--claude-4-sonnet, anthropic--claude-4-opus\n * - amazon--nova-pro, amazon--nova-lite, amazon--nova-micro, amazon--nova-premier\n *\n * **AI Core Open Source Models:**\n * - mistralai--mistral-large-instruct, mistralai--mistral-medium-instruct, mistralai--mistral-small-instruct\n * - cohere--command-a-reasoning\n */\nexport type SAPAIModelId = ChatModel;\n\n// Re-export useful types from SAP AI SDK for convenience\nexport type { MaskingModule, FilteringModule } from \"@sap-ai-sdk/orchestration\";\n\n// Re-export DPI masking helpers\nexport {\n buildDpiMaskingProvider,\n buildAzureContentSafetyFilter,\n buildLlamaGuard38BFilter,\n buildDocumentGroundingConfig,\n buildTranslationConfig,\n} from \"@sap-ai-sdk/orchestration\";\n","import type { OrchestrationErrorResponse } from \"@sap-ai-sdk/orchestration\";\n\n/**\n * Custom error class for SAP AI Core errors.\n * Provides structured access to error details returned by the API.\n *\n * The SAP AI SDK handles most error responses internally, but this class\n * can be used to wrap and provide additional context for errors.\n *\n * @example\n * ```typescript\n * try {\n * await model.doGenerate({ prompt });\n * } catch (error) {\n * if (error instanceof SAPAIError) {\n * console.error('Error Code:', error.code);\n * console.error('Request ID:', error.requestId);\n * console.error('Location:', error.location);\n * }\n * }\n * ```\n */\nexport class SAPAIError extends Error {\n /** HTTP status code or custom error code */\n public readonly code?: number;\n\n /** Where the error occurred (e.g., module name) */\n public readonly location?: string;\n\n /** Unique identifier for tracking the request */\n public readonly requestId?: string;\n\n /** Additional error context or debugging information */\n public readonly details?: string;\n\n /** Original cause of the error */\n public readonly cause?: unknown;\n\n constructor(\n message: string,\n options?: {\n code?: number;\n location?: string;\n requestId?: string;\n details?: string;\n cause?: unknown;\n },\n ) {\n super(message);\n this.name = \"SAPAIError\";\n this.code = options?.code;\n this.location = options?.location;\n this.requestId = options?.requestId;\n this.details = options?.details;\n this.cause = options?.cause;\n }\n\n /**\n * Creates a SAPAIError from an OrchestrationErrorResponse.\n *\n * @param errorResponse - The error response from SAP AI SDK\n * @returns A new SAPAIError instance\n */\n static fromOrchestrationError(\n errorResponse: OrchestrationErrorResponse,\n ): SAPAIError {\n const error = errorResponse.error;\n\n // Handle both single error and error list\n if (Array.isArray(error)) {\n // ErrorList - get first error\n const firstError = error[0];\n return new SAPAIError(\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n firstError?.message ?? \"Unknown orchestration error\",\n {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n code: firstError?.code,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n location: firstError?.location,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n requestId: firstError?.request_id,\n },\n );\n } else {\n // Single Error object\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return new SAPAIError(error.message ?? \"Unknown orchestration error\", {\n code: error.code,\n location: error.location,\n requestId: error.request_id,\n });\n }\n }\n\n /**\n * Creates a SAPAIError from a generic error.\n *\n * @param error - The original error\n * @param context - Optional context about where the error occurred\n * @returns A new SAPAIError instance\n */\n static fromError(error: unknown, context?: string): SAPAIError {\n if (error instanceof SAPAIError) {\n return error;\n }\n\n let message: string;\n if (error instanceof Error) {\n message = error.message;\n } else if (error == null) {\n message = \"Unknown error\";\n } else if (\n typeof error === \"string\" ||\n typeof error === \"number\" ||\n typeof error === \"boolean\" ||\n typeof error === \"bigint\"\n ) {\n // Primitives that can be safely stringified\n message = String(error);\n } else {\n // Objects, symbols, and other types\n try {\n message = JSON.stringify(error);\n } catch {\n message = \"[Unstringifiable Value]\";\n }\n }\n\n return new SAPAIError(context ? `${context}: ${message}` : message, {\n cause: error,\n });\n }\n}\n\n// Re-export the error response type from SAP AI SDK\nexport type { OrchestrationErrorResponse } from \"@sap-ai-sdk/orchestration\";\n","/**\n * Re-export types from SAP AI SDK for convenience.\n *\n * Note: With the migration to @sap-ai-sdk/orchestration, most request/response\n * handling is now managed by the SDK internally. These types are provided\n * for reference and advanced customization scenarios.\n */\nexport type {\n ChatMessage,\n SystemChatMessage,\n UserChatMessage,\n AssistantChatMessage,\n ToolChatMessage,\n DeveloperChatMessage,\n} from \"@sap-ai-sdk/orchestration\";\n\nexport {\n OrchestrationResponse,\n OrchestrationStreamResponse,\n OrchestrationStreamChunkResponse,\n} from \"@sap-ai-sdk/orchestration\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACUA,2BAKO;AAQP,gCAAgC;;;ACvBhC,sBAGO;AAyEA,SAAS,qBACd,QACe;AACf,QAAM,WAA0B,CAAC;AAEjC,aAAW,WAAW,QAAQ;AAC5B,YAAQ,QAAQ,MAAM;AAAA,MACpB,KAAK,UAAU;AACb,cAAM,gBAAmC;AAAA,UACvC,MAAM;AAAA,UACN,SAAS,QAAQ;AAAA,QACnB;AACA,iBAAS,KAAK,aAAa;AAC3B;AAAA,MACF;AAAA,MAEA,KAAK,QAAQ;AAEX,cAAM,eAAkC,CAAC;AAEzC,mBAAW,QAAQ,QAAQ,SAAS;AAClC,kBAAQ,KAAK,MAAM;AAAA,YACjB,KAAK,QAAQ;AACX,2BAAa,KAAK;AAAA,gBAChB,MAAM;AAAA,gBACN,MAAM,KAAK;AAAA,cACb,CAAC;AACD;AAAA,YACF;AAAA,YACA,KAAK,QAAQ;AAEX,kBAAI,CAAC,KAAK,UAAU,WAAW,QAAQ,GAAG;AACxC,sBAAM,IAAI,8CAA8B;AAAA,kBACtC,eAAe;AAAA,gBACjB,CAAC;AAAA,cACH;AAEA,oBAAM,WACJ,KAAK,gBAAgB,MACjB,KAAK,KAAK,SAAS,IACnB,QAAQ,KAAK,SAAS,WAAW,OAAO,KAAK,IAAI,CAAC;AAExD,2BAAa,KAAK;AAAA,gBAChB,MAAM;AAAA,gBACN,WAAW;AAAA,kBACT,KAAK;AAAA,gBACP;AAAA,cACF,CAAC;AACD;AAAA,YACF;AAAA,YACA,SAAS;AACP,oBAAM,IAAI,8CAA8B;AAAA,gBACtC,eAAe,gBAAiB,KAA0B,IAAI;AAAA,cAChE,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAIA,cAAM,cACJ,aAAa,WAAW,KAAK,aAAa,CAAC,EAAE,SAAS,SAClD;AAAA,UACE,MAAM;AAAA,UACN,SAAS,aAAa,CAAC,EAAE,QAAQ;AAAA,QACnC,IACA;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAEN,iBAAS,KAAK,WAAW;AACzB;AAAA,MACF;AAAA,MAEA,KAAK,aAAa;AAChB,YAAI,OAAO;AACX,cAAM,YAIA,CAAC;AAEP,mBAAW,QAAQ,QAAQ,SAAS;AAClC,kBAAQ,KAAK,MAAM;AAAA,YACjB,KAAK,QAAQ;AACX,sBAAQ,KAAK;AACb;AAAA,YACF;AAAA,YACA,KAAK,aAAa;AAChB,wBAAU,KAAK;AAAA,gBACb,IAAI,KAAK;AAAA,gBACT,MAAM;AAAA,gBACN,UAAU;AAAA,kBACR,MAAM,KAAK;AAAA,kBACX,WAAW,KAAK,UAAU,KAAK,KAAK;AAAA,gBACtC;AAAA,cACF,CAAC;AACD;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,cAAM,mBAAyC;AAAA,UAC7C,MAAM;AAAA,UACN,SAAS,QAAQ;AAAA,UACjB,YAAY,UAAU,SAAS,IAAI,YAAY;AAAA,QACjD;AACA,iBAAS,KAAK,gBAAgB;AAC9B;AAAA,MACF;AAAA,MAEA,KAAK,QAAQ;AAEX,mB