UNPKG

@mymediset/sap-ai-provider

Version:
711 lines (703 loc) 23.8 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { SAPAIError: () => SAPAIError, createSAPAIProvider: () => createSAPAIProvider, sapai: () => sapai }); module.exports = __toCommonJS(index_exports); // src/sap-ai-provider.ts var import_provider_utils3 = require("@ai-sdk/provider-utils"); // src/sap-ai-chat-language-model.ts var import_provider_utils2 = require("@ai-sdk/provider-utils"); // src/convert-to-sap-messages.ts var import_provider = require("@ai-sdk/provider"); function convertToSAPMessages(prompt) { const messages = []; for (const message of prompt) { switch (message.role) { case "system": { messages.push({ role: "system", content: message.content }); break; } case "user": { const contentParts = []; for (const part of message.content) { switch (part.type) { case "text": { contentParts.push({ type: "text", text: part.text }); break; } case "file": { if (!part.mediaType.startsWith("image/")) { throw new import_provider.UnsupportedFunctionalityError({ functionality: "Only image files are supported" }); } const imageUrl = part.data instanceof URL ? part.data.toString() : `data:${part.mediaType};base64,${part.data}`; contentParts.push({ type: "image_url", image_url: { url: imageUrl } }); break; } default: { throw new import_provider.UnsupportedFunctionalityError({ functionality: `Content type ${part.type}` }); } } } if (contentParts.length === 1 && contentParts[0].type === "text") { messages.push({ role: "user", content: contentParts[0].text }); } else { messages.push({ role: "user", content: contentParts }); } break; } case "assistant": { let text = ""; const toolCalls = []; for (const part of message.content) { switch (part.type) { case "text": { text += part.text; break; } case "tool-call": { toolCalls.push({ id: part.toolCallId, type: "function", function: { name: part.toolName, arguments: JSON.stringify(part.input) } }); break; } } } messages.push({ role: "assistant", content: text, tool_calls: toolCalls.length > 0 ? toolCalls : void 0 }); break; } case "tool": { for (const part of message.content) { if (part.type === "tool-result") { messages.push({ role: "tool", tool_call_id: part.toolCallId, content: JSON.stringify(part.output) }); } } break; } default: { const _exhaustiveCheck = message; throw new Error(`Unsupported role: ${_exhaustiveCheck}`); } } } return messages; } // src/sap-ai-error.ts var import_provider_utils = require("@ai-sdk/provider-utils"); var import_zod = require("zod"); var sapAIErrorSchema = import_zod.z.object({ /** Unique identifier for tracking the request */ request_id: import_zod.z.string().optional(), /** HTTP status code or custom error code */ code: import_zod.z.number().optional(), /** Human-readable error message */ message: import_zod.z.string().optional(), /** Where the error occurred (e.g., endpoint, function) */ location: import_zod.z.string().optional(), /** Detailed error information */ error: import_zod.z.object({ /** Specific error message */ message: import_zod.z.string().optional(), /** Error type code */ code: import_zod.z.string().optional(), /** Parameter that caused the error */ param: import_zod.z.string().optional(), /** Error category (e.g., 'validation', 'auth') */ type: import_zod.z.string().optional() }).optional(), /** Additional error context or debugging information */ details: import_zod.z.string().optional() }).optional(); var SAPAIError = class extends Error { constructor(message, data, response) { super(message); this.data = data; this.response = response; this.name = "SAPAIError"; if (data) { this.code = data.code; this.location = data.location; this.requestId = data.request_id; this.details = data.details; } } }; var sapAIFailedResponseHandler = (0, import_provider_utils.createJsonErrorResponseHandler)({ errorSchema: sapAIErrorSchema, errorToMessage: (data) => { return data?.error?.message || data?.message || "An error occurred during the SAP AI Core request."; }, isRetryable: (response, error) => { const status = response.status; return status === 429 || status === 500 || status === 502 || status === 503 || status === 504; } }); // src/types/completion-response.ts var import_zod2 = require("zod"); var sapAIToolCallSchema = import_zod2.z.object({ id: import_zod2.z.string(), type: import_zod2.z.literal("function"), function: import_zod2.z.object({ name: import_zod2.z.string(), arguments: import_zod2.z.string() }) }); var sapAIContentPartSchema = import_zod2.z.union([ import_zod2.z.object({ type: import_zod2.z.literal("text"), text: import_zod2.z.string() }), import_zod2.z.object({ type: import_zod2.z.literal("image_url"), image_url: import_zod2.z.object({ url: import_zod2.z.string(), detail: import_zod2.z.string().optional() }) }) ]); var sapAITemplatingMessageSchema = import_zod2.z.object({ role: import_zod2.z.enum(["assistant", "user", "system", "tool"]), content: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(sapAIContentPartSchema)]), tool_calls: import_zod2.z.array(sapAIToolCallSchema).optional(), tool_call_id: import_zod2.z.string().optional() }); var sapAIMessageSchema = import_zod2.z.object({ role: import_zod2.z.enum(["assistant", "user", "system", "tool"]), content: import_zod2.z.string().nullable(), tool_calls: import_zod2.z.array(sapAIToolCallSchema).optional(), tool_call_id: import_zod2.z.string().optional() }); var sapAIChoiceSchema = import_zod2.z.object({ message: sapAIMessageSchema, finish_reason: import_zod2.z.string() }); var sapAIUsageSchema = import_zod2.z.object({ prompt_tokens: import_zod2.z.number(), completion_tokens: import_zod2.z.number(), total_tokens: import_zod2.z.number() }); var sapAILLMResultSchema = import_zod2.z.object({ choices: import_zod2.z.array(sapAIChoiceSchema), usage: sapAIUsageSchema, id: import_zod2.z.string().optional(), object: import_zod2.z.string().optional(), created: import_zod2.z.number().optional(), model: import_zod2.z.string().optional(), system_fingerprint: import_zod2.z.string().optional() }); var sapAITemplatingResultSchema = import_zod2.z.array(sapAITemplatingMessageSchema); var sapAIResponseSchema = import_zod2.z.object({ request_id: import_zod2.z.string(), module_results: import_zod2.z.object({ llm: sapAILLMResultSchema, templating: sapAITemplatingResultSchema }), orchestration_results: import_zod2.z.object({ id: import_zod2.z.string().optional(), object: import_zod2.z.string().optional(), created: import_zod2.z.number().optional(), model: import_zod2.z.string().optional(), system_fingerprint: import_zod2.z.string().optional(), choices: import_zod2.z.array(sapAIChoiceSchema).optional(), usage: sapAIUsageSchema.optional() }).optional() }); var sapAIStreamChoiceSchema = import_zod2.z.object({ delta: import_zod2.z.object({ role: import_zod2.z.enum(["assistant"]).optional(), content: import_zod2.z.string().optional(), tool_calls: import_zod2.z.array(sapAIToolCallSchema).optional() }), finish_reason: import_zod2.z.string().nullish(), index: import_zod2.z.number() }); var sapAIStreamLLMResultSchema = import_zod2.z.object({ choices: import_zod2.z.array(sapAIStreamChoiceSchema), usage: sapAIUsageSchema.nullish(), id: import_zod2.z.string().optional(), object: import_zod2.z.string().optional(), created: import_zod2.z.number().optional(), model: import_zod2.z.string().optional(), system_fingerprint: import_zod2.z.string().optional() }); var sapAIStreamResponseSchema = import_zod2.z.object({ request_id: import_zod2.z.string(), module_results: import_zod2.z.object({ llm: sapAIStreamLLMResultSchema.optional(), templating: import_zod2.z.array(sapAITemplatingMessageSchema).optional() }), orchestration_result: import_zod2.z.object({ id: import_zod2.z.string().optional(), object: import_zod2.z.string().optional(), created: import_zod2.z.number().optional(), model: import_zod2.z.string().optional(), system_fingerprint: import_zod2.z.string().optional(), choices: import_zod2.z.array(sapAIStreamChoiceSchema).optional(), usage: sapAIUsageSchema.optional() }).optional() }); // src/sap-ai-chat-language-model.ts var SAPAIChatLanguageModel = class { constructor(modelId, settings, config) { this.specificationVersion = "v2"; this.defaultObjectGenerationMode = "json"; this.supportsImageUrls = true; this.supportsStructuredOutputs = true; this.settings = settings; this.config = config; this.modelId = modelId; } supportsUrl(url) { return url.protocol === "https:"; } get supportedUrls() { return { "image/*": [ /^https:\/\/.*\.(?:png|jpg|jpeg|gif|webp)$/i, /^data:image\/.*$/ ] }; } get provider() { return this.config.provider; } getArgs(options, streaming = false) { const warnings = []; const availableTools = options.tools; const supportsStructuredOutputs = !this.modelId.startsWith("anthropic--") && !this.modelId.startsWith("claude-") && !this.modelId.startsWith("amazon--"); const supportsN = !this.modelId.startsWith("amazon--"); const templatingConfig = { template: convertToSAPMessages(options.prompt), defaults: {}, tools: availableTools?.map((tool) => { if (tool.type === "function") { let parameters = tool.inputSchema; return { type: tool.type, function: { name: tool.name, description: tool.description, parameters: parameters || { type: "object", properties: {}, required: [] } } }; } else { warnings.push({ type: "unsupported-tool", tool }); return null; } }).filter(Boolean) }; if (supportsStructuredOutputs && !availableTools?.length) { templatingConfig.response_format = { type: "json_schema", json_schema: { name: "chat_completion_response", strict: true, schema: { type: "object", properties: { role: { type: "string", enum: ["assistant"] }, content: { type: "string" } }, required: ["role", "content"], additionalProperties: false } } }; } const args = { orchestration_config: { stream: streaming, module_configurations: { llm_module_config: { model_name: this.modelId, model_version: this.settings.modelVersion ?? "latest", model_params: { temperature: this.settings.modelParams?.temperature ?? 0.7, max_tokens: this.settings.modelParams?.maxTokens ?? 1e3, top_p: this.settings.modelParams?.topP, frequency_penalty: this.settings.modelParams?.frequencyPenalty, presence_penalty: this.settings.modelParams?.presencePenalty, n: supportsN ? this.settings.modelParams?.n ?? 1 : void 0 } }, templating_module_config: templatingConfig } } }; return { args, warnings }; } async doGenerate(options) { const { args, warnings } = this.getArgs(options); const headers = (0, import_provider_utils2.combineHeaders)( this.config.headers(), options.headers ?? {} ); const { value: response } = await (0, import_provider_utils2.postJsonToApi)({ url: this.config.baseURL, headers, body: args, failedResponseHandler: sapAIFailedResponseHandler, successfulResponseHandler: (0, import_provider_utils2.createJsonResponseHandler)( sapAIResponseSchema ), fetch: this.config.fetch, abortSignal: options.abortSignal }); const firstChoice = response.module_results.llm.choices[0]; const usage = response.module_results.llm.usage; const content = []; if (firstChoice.message.content) { let text; try { const parsed = JSON.parse(firstChoice.message.content); text = parsed.content || firstChoice.message.content; } catch { text = firstChoice.message.content; } if (text) { content.push({ type: "text", text }); } } if (firstChoice.message.tool_calls) { for (const toolCall of firstChoice.message.tool_calls) { content.push({ type: "tool-call", toolCallId: toolCall.id, toolName: toolCall.function.name, input: toolCall.function.arguments }); } } return { content, finishReason: firstChoice.finish_reason, usage: { inputTokens: usage.prompt_tokens, outputTokens: usage.completion_tokens, totalTokens: usage.total_tokens }, rawCall: { rawPrompt: args, rawSettings: {} }, warnings }; } async doStream(options) { const { args, warnings } = this.getArgs(options, true); const body = args; const { responseHeaders, value: response } = await (0, import_provider_utils2.postJsonToApi)({ url: this.config.baseURL, headers: (0, import_provider_utils2.combineHeaders)(this.config.headers(), options.headers), body, failedResponseHandler: sapAIFailedResponseHandler, successfulResponseHandler: (0, import_provider_utils2.createEventSourceResponseHandler)( sapAIStreamResponseSchema ), abortSignal: options.abortSignal, fetch: this.config.fetch }); let finishReason = "unknown"; const usage = { inputTokens: void 0, outputTokens: void 0, totalTokens: void 0 }; let isFirstChunk = true; let activeText = false; return { stream: response.pipeThrough( new TransformStream({ start(controller) { controller.enqueue({ type: "stream-start", warnings }); }, transform(chunk, controller) { if (!chunk.success) { controller.enqueue({ type: "error", error: chunk.error }); return; } const value = chunk.value; const llmResult = value.module_results.llm; if (!llmResult) { return; } if (isFirstChunk) { isFirstChunk = false; controller.enqueue({ type: "response-metadata", id: llmResult.id ?? void 0, modelId: llmResult.model ?? void 0, timestamp: llmResult.created ? new Date(llmResult.created * 1e3) : void 0 }); } if (llmResult.usage != null) { usage.inputTokens = llmResult.usage.prompt_tokens; usage.outputTokens = llmResult.usage.completion_tokens; usage.totalTokens = llmResult.usage.total_tokens; } const choice = llmResult.choices[0]; const delta = choice.delta; if (delta.content != null && delta.content.length > 0) { let textContent; try { const parsed = JSON.parse(delta.content); textContent = parsed.content || delta.content; } catch { textContent = delta.content; } if (!activeText) { controller.enqueue({ type: "text-start", id: "0" }); activeText = true; } controller.enqueue({ type: "text-delta", id: "0", delta: textContent }); } if (delta.tool_calls != null) { for (const toolCall of delta.tool_calls) { const toolCallId = toolCall.id; const toolName = toolCall.function.name; const input = toolCall.function.arguments; controller.enqueue({ type: "tool-input-start", id: toolCallId, toolName }); controller.enqueue({ type: "tool-input-delta", id: toolCallId, delta: input }); controller.enqueue({ type: "tool-input-end", id: toolCallId }); controller.enqueue({ type: "tool-call", toolCallId, toolName, input }); } } if (choice.finish_reason != null) { finishReason = choice.finish_reason; } }, flush(controller) { if (activeText) { controller.enqueue({ type: "text-end", id: "0" }); } controller.enqueue({ type: "finish", finishReason, usage }); } }) ), rawCall: { rawPrompt: body, rawSettings: {} } }; } }; // src/sap-ai-provider.ts async function getOAuthToken(serviceKey, customFetch) { const fetchFn = customFetch || fetch; const tokenUrl = `${serviceKey.url}/oauth/token`; const credentials = Buffer.from( `${serviceKey.clientid}:${serviceKey.clientsecret}` ).toString("base64"); const response = await fetchFn(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Authorization: `Basic ${credentials}` }, body: "grant_type=client_credentials" }); if (!response.ok) { const errorText = await response.text(); throw new Error( `Failed to get OAuth access token: ${response.status} ${response.statusText} ${errorText}` ); } const tokenData = await response.json(); return tokenData.access_token; } async function createSAPAIProvider(options = {}) { let baseURL; let authToken; let parsedServiceKey; if (options.serviceKey) { if (typeof options.serviceKey === "string") { try { parsedServiceKey = JSON.parse(options.serviceKey); } catch (error) { throw new Error("Invalid service key JSON format"); } } else { parsedServiceKey = options.serviceKey; } } if (parsedServiceKey) { baseURL = (0, import_provider_utils3.withoutTrailingSlash)(options.baseURL) ?? `${parsedServiceKey.serviceurls.AI_API_URL}/v2`; } else { baseURL = (0, import_provider_utils3.withoutTrailingSlash)(options.baseURL) ?? "https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com/v2"; } if (options.token) { authToken = options.token; } else if (parsedServiceKey) { authToken = await getOAuthToken(parsedServiceKey, options.fetch); } else { authToken = (0, import_provider_utils3.loadApiKey)({ apiKey: void 0, environmentVariableName: "SAP_AI_TOKEN", description: "SAP AI Core" }); } const deploymentId = options.deploymentId ?? "d65d81e7c077e583"; const resourceGroup = options.resourceGroup ?? "default"; const createModel = (modelId, settings = {}) => { return new SAPAIChatLanguageModel(modelId, settings, { provider: "sap-ai", baseURL: `${baseURL}/inference/deployments/${deploymentId}/completion`, headers: () => ({ Authorization: `Bearer ${authToken}`, "Content-Type": "application/json", "ai-resource-group": resourceGroup, ...options.headers }), fetch: options.fetch }); }; const provider = function(modelId, settings) { if (new.target) { throw new Error( "The SAP AI provider function cannot be called with the new keyword." ); } return createModel(modelId, settings); }; provider.chat = createModel; return provider; } function createSAPAIProviderSync(options) { const baseURL = (0, import_provider_utils3.withoutTrailingSlash)(options.baseURL) ?? "https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com/v2"; const deploymentId = options.deploymentId ?? "d65d81e7c077e583"; const resourceGroup = options.resourceGroup ?? "default"; const createModel = (modelId, settings = {}) => { return new SAPAIChatLanguageModel(modelId, settings, { provider: "sap-ai", baseURL: `${baseURL}/inference/deployments/${deploymentId}/completion`, headers: () => ({ Authorization: `Bearer ${options.token}`, "Content-Type": "application/json", "ai-resource-group": resourceGroup, ...options.headers }), fetch: options.fetch }); }; const provider = function(modelId, settings) { if (new.target) { throw new Error( "The SAP AI provider function cannot be called with the new keyword." ); } return createModel(modelId, settings); }; provider.chat = createModel; return provider; } function createDefaultSAPAI() { const createModel = (modelId, settings) => { const token = process.env.SAP_AI_TOKEN; if (!token) { throw new Error( 'SAP_AI_TOKEN environment variable is required for default instance. Either set SAP_AI_TOKEN or use createSAPAIProvider({ serviceKey: "..." }) instead.' ); } const provider2 = createSAPAIProviderSync({ token }); return provider2(modelId, settings); }; const provider = function(modelId, settings) { return createModel(modelId, settings); }; provider.chat = createModel; return provider; } var sapai = createDefaultSAPAI(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { SAPAIError, createSAPAIProvider, sapai }); //# sourceMappingURL=index.js.map