@jerome-benoit/sap-ai-provider 4.0.0-rc.1 → 4.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -29739,7 +29739,7 @@ var require_dist = __commonJS({
29739
29739
  }
29740
29740
  });
29741
29741
 
29742
- // src/sap-ai-chat-language-model.ts
29742
+ // src/sap-ai-language-model.ts
29743
29743
  import {
29744
29744
  OrchestrationClient
29745
29745
  } from "@sap-ai-sdk/orchestration";
@@ -29755,25 +29755,78 @@ function convertToSAPMessages(prompt, options = {}) {
29755
29755
  const includeReasoning = options.includeReasoning ?? false;
29756
29756
  for (const message of prompt) {
29757
29757
  switch (message.role) {
29758
+ case "assistant": {
29759
+ let text = "";
29760
+ const toolCalls = [];
29761
+ for (const part of message.content) {
29762
+ switch (part.type) {
29763
+ case "reasoning": {
29764
+ if (includeReasoning && part.text) {
29765
+ text += `<reasoning>${part.text}</reasoning>`;
29766
+ }
29767
+ break;
29768
+ }
29769
+ case "text": {
29770
+ text += part.text;
29771
+ break;
29772
+ }
29773
+ case "tool-call": {
29774
+ let argumentsJson;
29775
+ if (typeof part.input === "string") {
29776
+ try {
29777
+ JSON.parse(part.input);
29778
+ argumentsJson = part.input;
29779
+ } catch {
29780
+ argumentsJson = JSON.stringify(part.input);
29781
+ }
29782
+ } else {
29783
+ argumentsJson = JSON.stringify(part.input);
29784
+ }
29785
+ toolCalls.push({
29786
+ function: {
29787
+ arguments: argumentsJson,
29788
+ name: part.toolName
29789
+ },
29790
+ id: part.toolCallId,
29791
+ type: "function"
29792
+ });
29793
+ break;
29794
+ }
29795
+ }
29796
+ }
29797
+ const assistantMessage = {
29798
+ content: text || "",
29799
+ role: "assistant",
29800
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0
29801
+ };
29802
+ messages.push(assistantMessage);
29803
+ break;
29804
+ }
29758
29805
  case "system": {
29759
29806
  const systemMessage = {
29760
- role: "system",
29761
- content: message.content
29807
+ content: message.content,
29808
+ role: "system"
29762
29809
  };
29763
29810
  messages.push(systemMessage);
29764
29811
  break;
29765
29812
  }
29813
+ case "tool": {
29814
+ for (const part of message.content) {
29815
+ if (part.type === "tool-result") {
29816
+ const toolMessage = {
29817
+ content: JSON.stringify(part.output),
29818
+ role: "tool",
29819
+ tool_call_id: part.toolCallId
29820
+ };
29821
+ messages.push(toolMessage);
29822
+ }
29823
+ }
29824
+ break;
29825
+ }
29766
29826
  case "user": {
29767
29827
  const contentParts = [];
29768
29828
  for (const part of message.content) {
29769
29829
  switch (part.type) {
29770
- case "text": {
29771
- contentParts.push({
29772
- type: "text",
29773
- text: part.text
29774
- });
29775
- break;
29776
- }
29777
29830
  case "file": {
29778
29831
  if (!part.mediaType.startsWith("image/")) {
29779
29832
  throw new UnsupportedFunctionalityError({
@@ -29815,10 +29868,17 @@ function convertToSAPMessages(prompt, options = {}) {
29815
29868
  }
29816
29869
  }
29817
29870
  contentParts.push({
29818
- type: "image_url",
29819
29871
  image_url: {
29820
29872
  url: imageUrl
29821
- }
29873
+ },
29874
+ type: "image_url"
29875
+ });
29876
+ break;
29877
+ }
29878
+ case "text": {
29879
+ contentParts.push({
29880
+ text: part.text,
29881
+ type: "text"
29822
29882
  });
29823
29883
  break;
29824
29884
  }
@@ -29830,75 +29890,15 @@ function convertToSAPMessages(prompt, options = {}) {
29830
29890
  }
29831
29891
  }
29832
29892
  const userMessage = contentParts.length === 1 && contentParts[0].type === "text" ? {
29833
- role: "user",
29834
- content: contentParts[0].text ?? ""
29893
+ content: contentParts[0].text ?? "",
29894
+ role: "user"
29835
29895
  } : {
29836
- role: "user",
29837
- content: contentParts
29896
+ content: contentParts,
29897
+ role: "user"
29838
29898
  };
29839
29899
  messages.push(userMessage);
29840
29900
  break;
29841
29901
  }
29842
- case "assistant": {
29843
- let text = "";
29844
- const toolCalls = [];
29845
- for (const part of message.content) {
29846
- switch (part.type) {
29847
- case "text": {
29848
- text += part.text;
29849
- break;
29850
- }
29851
- case "reasoning": {
29852
- if (includeReasoning && part.text) {
29853
- text += `<reasoning>${part.text}</reasoning>`;
29854
- }
29855
- break;
29856
- }
29857
- case "tool-call": {
29858
- let argumentsJson;
29859
- if (typeof part.input === "string") {
29860
- try {
29861
- JSON.parse(part.input);
29862
- argumentsJson = part.input;
29863
- } catch {
29864
- argumentsJson = JSON.stringify(part.input);
29865
- }
29866
- } else {
29867
- argumentsJson = JSON.stringify(part.input);
29868
- }
29869
- toolCalls.push({
29870
- id: part.toolCallId,
29871
- type: "function",
29872
- function: {
29873
- name: part.toolName,
29874
- arguments: argumentsJson
29875
- }
29876
- });
29877
- break;
29878
- }
29879
- }
29880
- }
29881
- const assistantMessage = {
29882
- role: "assistant",
29883
- content: text || "",
29884
- tool_calls: toolCalls.length > 0 ? toolCalls : void 0
29885
- };
29886
- messages.push(assistantMessage);
29887
- break;
29888
- }
29889
- case "tool": {
29890
- for (const part of message.content) {
29891
- if (part.type === "tool-result") {
29892
- const toolMessage = {
29893
- role: "tool",
29894
- tool_call_id: part.toolCallId,
29895
- content: JSON.stringify(part.output)
29896
- };
29897
- messages.push(toolMessage);
29898
- }
29899
- }
29900
- break;
29901
- }
29902
29902
  default: {
29903
29903
  const _exhaustiveCheck = message;
29904
29904
  throw new Error(
@@ -29913,16 +29913,6 @@ function convertToSAPMessages(prompt, options = {}) {
29913
29913
  // src/sap-ai-error.ts
29914
29914
  var import_util = __toESM(require_dist(), 1);
29915
29915
  import { APICallError, LoadAPIKeyError } from "@ai-sdk/provider";
29916
- function getStatusCodeFromSAPError(code) {
29917
- if (!code) return 500;
29918
- if (code >= 100 && code < 600) {
29919
- return code;
29920
- }
29921
- return 500;
29922
- }
29923
- function isRetryable(statusCode) {
29924
- return statusCode === 429 || statusCode >= 500 && statusCode < 600;
29925
- }
29926
29916
  function convertSAPErrorToAPICallError(errorResponse, context) {
29927
29917
  const error = errorResponse.error;
29928
29918
  let message;
@@ -29944,9 +29934,9 @@ function convertSAPErrorToAPICallError(errorResponse, context) {
29944
29934
  const statusCode = getStatusCodeFromSAPError(code);
29945
29935
  const responseBody = JSON.stringify({
29946
29936
  error: {
29947
- message,
29948
29937
  code,
29949
29938
  location,
29939
+ message,
29950
29940
  request_id: requestId
29951
29941
  }
29952
29942
  });
@@ -29969,53 +29959,14 @@ Error location: ${location}`;
29969
29959
  Request ID: ${requestId}`;
29970
29960
  }
29971
29961
  return new APICallError({
29962
+ isRetryable: isRetryable(statusCode),
29972
29963
  message: enhancedMessage,
29973
- url: context?.url ?? "",
29974
29964
  requestBodyValues: context?.requestBody,
29975
- statusCode,
29976
- responseHeaders: context?.responseHeaders,
29977
29965
  responseBody,
29978
- isRetryable: isRetryable(statusCode)
29979
- });
29980
- }
29981
- function isOrchestrationErrorResponse(error) {
29982
- if (error === null || typeof error !== "object" || !("error" in error)) {
29983
- return false;
29984
- }
29985
- const errorEnvelope = error;
29986
- const innerError = errorEnvelope.error;
29987
- if (innerError === void 0) return false;
29988
- if (Array.isArray(innerError)) {
29989
- return innerError.every(
29990
- (entry) => entry !== null && typeof entry === "object" && "message" in entry && typeof entry.message === "string"
29991
- );
29992
- }
29993
- return typeof innerError === "object" && innerError !== null && "message" in innerError && typeof innerError.message === "string";
29994
- }
29995
- function normalizeHeaders(headers) {
29996
- if (!headers || typeof headers !== "object") return void 0;
29997
- const record = headers;
29998
- const entries = Object.entries(record).flatMap(([key, value]) => {
29999
- if (typeof value === "string") return [[key, value]];
30000
- if (Array.isArray(value)) {
30001
- const strings = value.filter((item) => typeof item === "string").join("; ");
30002
- return strings.length > 0 ? [[key, strings]] : [];
30003
- }
30004
- if (typeof value === "number" || typeof value === "boolean") {
30005
- return [[key, String(value)]];
30006
- }
30007
- return [];
29966
+ responseHeaders: context?.responseHeaders,
29967
+ statusCode,
29968
+ url: context?.url ?? ""
30008
29969
  });
30009
- if (entries.length === 0) return void 0;
30010
- return Object.fromEntries(entries);
30011
- }
30012
- function getAxiosResponseHeaders(error) {
30013
- if (!(error instanceof Error)) return void 0;
30014
- const rootCause = (0, import_util.isErrorWithCause)(error) ? error.rootCause : error;
30015
- if (typeof rootCause !== "object") return void 0;
30016
- const maybeAxios = rootCause;
30017
- if (maybeAxios.isAxiosError !== true) return void 0;
30018
- return normalizeHeaders(maybeAxios.response?.headers);
30019
29970
  }
30020
29971
  function convertToAISDKError(error, context) {
30021
29972
  if (error instanceof APICallError || error instanceof LoadAPIKeyError) {
@@ -30040,30 +29991,79 @@ See: https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-serv
30040
29991
  }
30041
29992
  if (errorMsg.includes("econnrefused") || errorMsg.includes("enotfound") || errorMsg.includes("network") || errorMsg.includes("timeout")) {
30042
29993
  return new APICallError({
29994
+ cause: error,
29995
+ isRetryable: true,
30043
29996
  message: `Network error connecting to SAP AI Core: ${error.message}`,
30044
- url: context?.url ?? "",
30045
29997
  requestBodyValues: context?.requestBody,
30046
- statusCode: 503,
30047
- isRetryable: true,
30048
29998
  responseHeaders,
30049
- cause: error
29999
+ statusCode: 503,
30000
+ url: context?.url ?? ""
30050
30001
  });
30051
30002
  }
30052
30003
  }
30053
30004
  const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
30054
30005
  const fullMessage = context?.operation ? `SAP AI Core ${context.operation} failed: ${message}` : `SAP AI Core error: ${message}`;
30055
30006
  return new APICallError({
30007
+ cause: error,
30008
+ isRetryable: false,
30056
30009
  message: fullMessage,
30057
- url: context?.url ?? "",
30058
30010
  requestBodyValues: context?.requestBody,
30059
- statusCode: 500,
30060
- isRetryable: false,
30061
30011
  responseHeaders,
30062
- cause: error
30012
+ statusCode: 500,
30013
+ url: context?.url ?? ""
30014
+ });
30015
+ }
30016
+ function getAxiosResponseHeaders(error) {
30017
+ if (!(error instanceof Error)) return void 0;
30018
+ const rootCause = (0, import_util.isErrorWithCause)(error) ? error.rootCause : error;
30019
+ if (typeof rootCause !== "object") return void 0;
30020
+ const maybeAxios = rootCause;
30021
+ if (maybeAxios.isAxiosError !== true) return void 0;
30022
+ return normalizeHeaders(maybeAxios.response?.headers);
30023
+ }
30024
+ function getStatusCodeFromSAPError(code) {
30025
+ if (!code) return 500;
30026
+ if (code >= 100 && code < 600) {
30027
+ return code;
30028
+ }
30029
+ return 500;
30030
+ }
30031
+ function isOrchestrationErrorResponse(error) {
30032
+ if (error === null || typeof error !== "object" || !("error" in error)) {
30033
+ return false;
30034
+ }
30035
+ const errorEnvelope = error;
30036
+ const innerError = errorEnvelope.error;
30037
+ if (innerError === void 0) return false;
30038
+ if (Array.isArray(innerError)) {
30039
+ return innerError.every(
30040
+ (entry) => entry !== null && typeof entry === "object" && "message" in entry && typeof entry.message === "string"
30041
+ );
30042
+ }
30043
+ return typeof innerError === "object" && innerError !== null && "message" in innerError && typeof innerError.message === "string";
30044
+ }
30045
+ function isRetryable(statusCode) {
30046
+ return statusCode === 429 || statusCode >= 500 && statusCode < 600;
30047
+ }
30048
+ function normalizeHeaders(headers) {
30049
+ if (!headers || typeof headers !== "object") return void 0;
30050
+ const record = headers;
30051
+ const entries = Object.entries(record).flatMap(([key, value]) => {
30052
+ if (typeof value === "string") return [[key, value]];
30053
+ if (Array.isArray(value)) {
30054
+ const strings = value.filter((item) => typeof item === "string").join("; ");
30055
+ return strings.length > 0 ? [[key, strings]] : [];
30056
+ }
30057
+ if (typeof value === "number" || typeof value === "boolean") {
30058
+ return [[key, String(value)]];
30059
+ }
30060
+ return [];
30063
30061
  });
30062
+ if (entries.length === 0) return void 0;
30063
+ return Object.fromEntries(entries);
30064
30064
  }
30065
30065
 
30066
- // src/sap-ai-chat-language-model.ts
30066
+ // src/sap-ai-language-model.ts
30067
30067
  var StreamIdGenerator = class {
30068
30068
  /**
30069
30069
  * Generates a unique ID for a text block.
@@ -30074,139 +30074,27 @@ var StreamIdGenerator = class {
30074
30074
  return crypto.randomUUID();
30075
30075
  }
30076
30076
  };
30077
- function validateModelParameters(params, warnings) {
30078
- if (params.temperature !== void 0 && (params.temperature < 0 || params.temperature > 2)) {
30079
- warnings.push({
30080
- type: "other",
30081
- message: `temperature=${String(params.temperature)} is outside typical range [0, 2]. The API may reject this value.`
30082
- });
30083
- }
30084
- if (params.topP !== void 0 && (params.topP < 0 || params.topP > 1)) {
30085
- warnings.push({
30086
- type: "other",
30087
- message: `topP=${String(params.topP)} is outside valid range [0, 1]. The API may reject this value.`
30088
- });
30089
- }
30090
- if (params.frequencyPenalty !== void 0 && (params.frequencyPenalty < -2 || params.frequencyPenalty > 2)) {
30091
- warnings.push({
30092
- type: "other",
30093
- message: `frequencyPenalty=${String(params.frequencyPenalty)} is outside typical range [-2, 2]. The API may reject this value.`
30094
- });
30095
- }
30096
- if (params.presencePenalty !== void 0 && (params.presencePenalty < -2 || params.presencePenalty > 2)) {
30097
- warnings.push({
30098
- type: "other",
30099
- message: `presencePenalty=${String(params.presencePenalty)} is outside typical range [-2, 2]. The API may reject this value.`
30100
- });
30101
- }
30102
- if (params.maxTokens !== void 0 && params.maxTokens <= 0) {
30103
- warnings.push({
30104
- type: "other",
30105
- message: `maxTokens=${String(params.maxTokens)} must be positive. The API will likely reject this value.`
30106
- });
30107
- }
30108
- if (params.n !== void 0 && params.n <= 0) {
30109
- warnings.push({
30110
- type: "other",
30111
- message: `n=${String(params.n)} must be positive. The API will likely reject this value.`
30112
- });
30113
- }
30114
- }
30115
- function createAISDKRequestBodySummary(options) {
30116
- return {
30117
- promptMessages: options.prompt.length,
30118
- hasImageParts: options.prompt.some(
30119
- (message) => message.role === "user" && message.content.some(
30120
- (part) => part.type === "file" && part.mediaType.startsWith("image/")
30121
- )
30122
- ),
30123
- tools: options.tools?.length ?? 0,
30124
- temperature: options.temperature,
30125
- topP: options.topP,
30126
- topK: options.topK,
30127
- maxOutputTokens: options.maxOutputTokens,
30128
- stopSequences: options.stopSequences?.length,
30129
- seed: options.seed,
30130
- toolChoiceType: options.toolChoice?.type,
30131
- responseFormatType: options.responseFormat?.type
30132
- };
30133
- }
30134
- function hasCallableParse(obj) {
30135
- return typeof obj.parse === "function";
30136
- }
30137
- function isZodSchema(obj) {
30138
- if (obj === null || typeof obj !== "object") {
30139
- return false;
30140
- }
30141
- const record = obj;
30142
- return "_def" in record && "parse" in record && hasCallableParse(record);
30143
- }
30144
- function buildSAPToolParameters(schema) {
30145
- const schemaType = schema.type;
30146
- if (schemaType !== void 0 && schemaType !== "object") {
30147
- return {
30148
- type: "object",
30149
- properties: {},
30150
- required: []
30151
- };
30152
- }
30153
- const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
30154
- const required = Array.isArray(schema.required) && schema.required.every((item) => typeof item === "string") ? schema.required : [];
30155
- const additionalFields = Object.fromEntries(
30156
- Object.entries(schema).filter(
30157
- ([key]) => key !== "type" && key !== "properties" && key !== "required"
30158
- )
30159
- );
30160
- return {
30161
- type: "object",
30162
- properties,
30163
- required,
30164
- ...additionalFields
30165
- };
30166
- }
30167
- var SAPAIChatLanguageModel = class {
30168
- specificationVersion = "v3";
30077
+ var SAPAILanguageModel = class {
30169
30078
  modelId;
30170
- config;
30171
- settings;
30172
- /**
30173
- * Creates a new SAP AI Chat Language Model instance.
30174
- *
30175
- * @param modelId - The model identifier
30176
- * @param settings - Model-specific configuration settings
30177
- * @param config - Internal configuration (deployment config, destination, etc.)
30178
- *
30179
- * @internal This constructor is not meant to be called directly.
30180
- * Use the provider function instead.
30181
- */
30182
- constructor(modelId, settings, config) {
30183
- this.settings = settings;
30184
- this.config = config;
30185
- this.modelId = modelId;
30186
- }
30187
- /**
30188
- * Checks if a URL is supported for file/image uploads.
30189
- *
30190
- * @param url - The URL to check
30191
- * @returns True if the URL protocol is HTTPS or data with valid image format
30192
- */
30193
- supportsUrl(url) {
30194
- if (url.protocol === "https:") return true;
30195
- if (url.protocol === "data:") {
30196
- return /^data:image\//i.test(url.href);
30197
- }
30198
- return false;
30199
- }
30079
+ specificationVersion = "v3";
30200
30080
  /**
30201
- * Returns supported URL patterns for different content types.
30081
+ * Model capabilities.
30202
30082
  *
30203
- * @returns Record of content types to regex patterns
30083
+ * These defaults assume “modern” model behavior to avoid maintaining a
30084
+ * per-model capability matrix. If a deployment doesn't support a feature,
30085
+ * SAP AI Core will fail the request at runtime.
30204
30086
  */
30205
- get supportedUrls() {
30206
- return {
30207
- "image/*": [/^https:\/\/.+$/i, /^data:image\/.*$/]
30208
- };
30209
- }
30087
+ supportsImageUrls = true;
30088
+ /** Multiple completions via the `n` parameter (provider-specific support). */
30089
+ supportsMultipleCompletions = true;
30090
+ /** Parallel tool calls. */
30091
+ supportsParallelToolCalls = true;
30092
+ /** Streaming responses. */
30093
+ supportsStreaming = true;
30094
+ /** Structured JSON outputs (json_schema response format). */
30095
+ supportsStructuredOutputs = true;
30096
+ /** Tool/function calling. */
30097
+ supportsToolCalls = true;
30210
30098
  /**
30211
30099
  * Generates text completion using SAP AI Core's Orchestration API.
30212
30100
  *
@@ -30252,206 +30140,31 @@ var SAPAIChatLanguageModel = class {
30252
30140
  return this.config.provider;
30253
30141
  }
30254
30142
  /**
30255
- * Model capabilities.
30256
- *
30257
- * These defaults assume “modern” model behavior to avoid maintaining a
30258
- * per-model capability matrix. If a deployment doesn't support a feature,
30259
- * SAP AI Core will fail the request at runtime.
30260
- */
30261
- supportsImageUrls = true;
30262
- /** Structured JSON outputs (json_schema response format). */
30263
- supportsStructuredOutputs = true;
30264
- /** Tool/function calling. */
30265
- supportsToolCalls = true;
30266
- /** Streaming responses. */
30267
- supportsStreaming = true;
30268
- /** Multiple completions via the `n` parameter (provider-specific support). */
30269
- supportsMultipleCompletions = true;
30270
- /** Parallel tool calls. */
30271
- supportsParallelToolCalls = true;
30272
- /**
30273
- * Builds orchestration module config for SAP AI SDK.
30143
+ * Returns supported URL patterns for different content types.
30274
30144
  *
30275
- * @param options - Call options from the AI SDK
30276
- * @returns Object containing orchestration config, messages, and warnings
30277
- * @internal
30145
+ * @returns Record of content types to regex patterns
30278
30146
  */
30279
- buildOrchestrationConfig(options) {
30280
- const providerOptions = options.providerOptions?.sap ?? {};
30281
- const warnings = [];
30282
- const messages = convertToSAPMessages(options.prompt, {
30283
- includeReasoning: providerOptions.includeReasoning ?? this.settings.includeReasoning ?? false
30284
- });
30285
- let tools;
30286
- const settingsTools = providerOptions.tools ?? this.settings.tools;
30287
- const optionsTools = options.tools;
30288
- const shouldUseSettingsTools = settingsTools && settingsTools.length > 0 && (!optionsTools || optionsTools.length === 0);
30289
- const shouldUseOptionsTools = !!(optionsTools && optionsTools.length > 0);
30290
- if (settingsTools && settingsTools.length > 0 && optionsTools && optionsTools.length > 0) {
30291
- warnings.push({
30292
- type: "other",
30293
- message: "Both settings.tools and call options.tools were provided; preferring call options.tools."
30294
- });
30295
- }
30296
- if (shouldUseSettingsTools) {
30297
- tools = settingsTools;
30298
- } else {
30299
- const availableTools = shouldUseOptionsTools ? optionsTools : void 0;
30300
- tools = availableTools?.map((tool) => {
30301
- if (tool.type === "function") {
30302
- const inputSchema = tool.inputSchema;
30303
- const toolWithParams = tool;
30304
- let parameters;
30305
- if (toolWithParams.parameters && isZodSchema(toolWithParams.parameters)) {
30306
- try {
30307
- const jsonSchema = zodToJsonSchema(
30308
- toolWithParams.parameters,
30309
- {
30310
- $refStrategy: "none"
30311
- }
30312
- );
30313
- const schemaRecord = jsonSchema;
30314
- delete schemaRecord.$schema;
30315
- parameters = buildSAPToolParameters(schemaRecord);
30316
- } catch (error) {
30317
- warnings.push({
30318
- type: "unsupported",
30319
- feature: `tool schema conversion for ${tool.name}`,
30320
- details: `Failed to convert tool Zod schema: ${error instanceof Error ? error.message : String(error)}. Falling back to empty object schema.`
30321
- });
30322
- parameters = buildSAPToolParameters({});
30323
- }
30324
- } else if (inputSchema && Object.keys(inputSchema).length > 0) {
30325
- const hasProperties = inputSchema.properties && typeof inputSchema.properties === "object" && Object.keys(inputSchema.properties).length > 0;
30326
- if (hasProperties) {
30327
- parameters = buildSAPToolParameters(inputSchema);
30328
- } else {
30329
- parameters = buildSAPToolParameters({});
30330
- }
30331
- } else {
30332
- parameters = buildSAPToolParameters({});
30333
- }
30334
- return {
30335
- type: "function",
30336
- function: {
30337
- name: tool.name,
30338
- description: tool.description,
30339
- parameters
30340
- }
30341
- };
30342
- } else {
30343
- warnings.push({
30344
- type: "unsupported",
30345
- feature: `tool type for ${tool.name}`,
30346
- details: "Only 'function' tool type is supported."
30347
- });
30348
- return null;
30349
- }
30350
- }).filter((t) => t !== null);
30351
- }
30352
- const supportsN = !this.modelId.startsWith("amazon--") && !this.modelId.startsWith("anthropic--");
30353
- const modelParams = {};
30354
- const maxTokens = options.maxOutputTokens ?? providerOptions.modelParams?.maxTokens ?? this.settings.modelParams?.maxTokens;
30355
- if (maxTokens !== void 0) modelParams.max_tokens = maxTokens;
30356
- const temperature = options.temperature ?? providerOptions.modelParams?.temperature ?? this.settings.modelParams?.temperature;
30357
- if (temperature !== void 0) modelParams.temperature = temperature;
30358
- const topP = options.topP ?? providerOptions.modelParams?.topP ?? this.settings.modelParams?.topP;
30359
- if (topP !== void 0) modelParams.top_p = topP;
30360
- if (options.topK !== void 0) modelParams.top_k = options.topK;
30361
- const frequencyPenalty = options.frequencyPenalty ?? providerOptions.modelParams?.frequencyPenalty ?? this.settings.modelParams?.frequencyPenalty;
30362
- if (frequencyPenalty !== void 0) {
30363
- modelParams.frequency_penalty = frequencyPenalty;
30364
- }
30365
- const presencePenalty = options.presencePenalty ?? providerOptions.modelParams?.presencePenalty ?? this.settings.modelParams?.presencePenalty;
30366
- if (presencePenalty !== void 0) {
30367
- modelParams.presence_penalty = presencePenalty;
30368
- }
30369
- if (supportsN) {
30370
- const nValue = providerOptions.modelParams?.n ?? this.settings.modelParams?.n;
30371
- if (nValue !== void 0) {
30372
- modelParams.n = nValue;
30373
- }
30374
- }
30375
- const parallelToolCalls = providerOptions.modelParams?.parallel_tool_calls ?? this.settings.modelParams?.parallel_tool_calls;
30376
- if (parallelToolCalls !== void 0) {
30377
- modelParams.parallel_tool_calls = parallelToolCalls;
30378
- }
30379
- if (options.stopSequences && options.stopSequences.length > 0) {
30380
- modelParams.stop = options.stopSequences;
30381
- }
30382
- if (options.seed !== void 0) {
30383
- modelParams.seed = options.seed;
30384
- }
30385
- validateModelParameters(
30386
- {
30387
- temperature,
30388
- topP,
30389
- frequencyPenalty,
30390
- presencePenalty,
30391
- maxTokens,
30392
- n: modelParams.n
30393
- },
30394
- warnings
30395
- );
30396
- if (options.toolChoice && options.toolChoice.type !== "auto") {
30397
- warnings.push({
30398
- type: "unsupported",
30399
- feature: "toolChoice",
30400
- details: `SAP AI SDK does not support toolChoice '${options.toolChoice.type}'. Using default 'auto' behavior.`
30401
- });
30402
- }
30403
- if (options.responseFormat?.type === "json") {
30404
- warnings.push({
30405
- type: "other",
30406
- message: "responseFormat JSON mode is forwarded to the underlying model; support and schema adherence depend on the model/deployment."
30407
- });
30408
- }
30409
- const responseFormat = options.responseFormat?.type === "json" ? options.responseFormat.schema ? {
30410
- type: "json_schema",
30411
- json_schema: {
30412
- name: options.responseFormat.name ?? "response",
30413
- description: options.responseFormat.description,
30414
- schema: options.responseFormat.schema,
30415
- strict: null
30416
- }
30417
- } : { type: "json_object" } : void 0;
30418
- const orchestrationConfig = {
30419
- promptTemplating: {
30420
- model: {
30421
- name: this.modelId,
30422
- version: providerOptions.modelVersion ?? this.settings.modelVersion ?? "latest",
30423
- params: modelParams
30424
- },
30425
- prompt: {
30426
- template: [],
30427
- tools: tools && tools.length > 0 ? tools : void 0,
30428
- ...responseFormat ? { response_format: responseFormat } : {}
30429
- }
30430
- },
30431
- ...(() => {
30432
- const masking = providerOptions.masking ?? this.settings.masking;
30433
- return masking && Object.keys(masking).length > 0 ? { masking } : {};
30434
- })(),
30435
- ...(() => {
30436
- const filtering = providerOptions.filtering ?? this.settings.filtering;
30437
- return filtering && Object.keys(filtering).length > 0 ? { filtering } : {};
30438
- })()
30147
+ get supportedUrls() {
30148
+ return {
30149
+ "image/*": [/^https:\/\/.+$/i, /^data:image\/.*$/]
30439
30150
  };
30440
- return { orchestrationConfig, messages, warnings };
30441
30151
  }
30152
+ config;
30153
+ settings;
30442
30154
  /**
30443
- * Creates an OrchestrationClient instance.
30155
+ * Creates a new SAP AI Chat Language Model instance.
30444
30156
  *
30445
- * @param config - Orchestration module configuration
30446
- * @returns OrchestrationClient instance
30447
- * @internal
30157
+ * @param modelId - The model identifier
30158
+ * @param settings - Model-specific configuration settings
30159
+ * @param config - Internal configuration (deployment config, destination, etc.)
30160
+ *
30161
+ * @internal This constructor is not meant to be called directly.
30162
+ * Use the provider function instead.
30448
30163
  */
30449
- createClient(config) {
30450
- return new OrchestrationClient(
30451
- config,
30452
- this.config.deploymentConfig,
30453
- this.config.destination
30454
- );
30164
+ constructor(modelId, settings, config) {
30165
+ this.settings = settings;
30166
+ this.config = config;
30167
+ this.modelId = modelId;
30455
30168
  }
30456
30169
  /**
30457
30170
  * Generates a single completion (non-streaming).
@@ -30489,7 +30202,7 @@ var SAPAIChatLanguageModel = class {
30489
30202
  */
30490
30203
  async doGenerate(options) {
30491
30204
  try {
30492
- const { orchestrationConfig, messages, warnings } = this.buildOrchestrationConfig(options);
30205
+ const { messages, orchestrationConfig, warnings } = this.buildOrchestrationConfig(options);
30493
30206
  const client = this.createClient(orchestrationConfig);
30494
30207
  const promptTemplating = orchestrationConfig.promptTemplating;
30495
30208
  const requestBody = {
@@ -30506,6 +30219,14 @@ var SAPAIChatLanguageModel = class {
30506
30219
  ...(() => {
30507
30220
  const filtering = orchestrationConfig.filtering;
30508
30221
  return filtering && Object.keys(filtering).length > 0 ? { filtering } : {};
30222
+ })(),
30223
+ ...(() => {
30224
+ const grounding = orchestrationConfig.grounding;
30225
+ return grounding && Object.keys(grounding).length > 0 ? { grounding } : {};
30226
+ })(),
30227
+ ...(() => {
30228
+ const translation = orchestrationConfig.translation;
30229
+ return translation && Object.keys(translation).length > 0 ? { translation } : {};
30509
30230
  })()
30510
30231
  };
30511
30232
  const response = await (async () => {
@@ -30556,18 +30277,18 @@ var SAPAIChatLanguageModel = class {
30556
30277
  const textContent = response.getContent();
30557
30278
  if (textContent) {
30558
30279
  content.push({
30559
- type: "text",
30560
- text: textContent
30280
+ text: textContent,
30281
+ type: "text"
30561
30282
  });
30562
30283
  }
30563
30284
  const toolCalls = response.getToolCalls();
30564
30285
  if (toolCalls) {
30565
30286
  for (const toolCall of toolCalls) {
30566
30287
  content.push({
30567
- type: "tool-call",
30288
+ input: toolCall.function.arguments,
30568
30289
  toolCallId: toolCall.id,
30569
30290
  toolName: toolCall.function.name,
30570
- input: toolCall.function.arguments
30291
+ type: "tool-call"
30571
30292
  });
30572
30293
  }
30573
30294
  }
@@ -30576,26 +30297,13 @@ var SAPAIChatLanguageModel = class {
30576
30297
  const finishReason = mapFinishReason(finishReasonRaw);
30577
30298
  const rawResponseBody = {
30578
30299
  content: textContent,
30579
- toolCalls,
30300
+ finishReason: finishReasonRaw,
30580
30301
  tokenUsage,
30581
- finishReason: finishReasonRaw
30302
+ toolCalls
30582
30303
  };
30583
30304
  return {
30584
30305
  content,
30585
30306
  finishReason,
30586
- usage: {
30587
- inputTokens: {
30588
- total: tokenUsage.prompt_tokens,
30589
- noCache: tokenUsage.prompt_tokens,
30590
- cacheRead: void 0,
30591
- cacheWrite: void 0
30592
- },
30593
- outputTokens: {
30594
- total: tokenUsage.completion_tokens,
30595
- text: tokenUsage.completion_tokens,
30596
- reasoning: void 0
30597
- }
30598
- },
30599
30307
  providerMetadata: {
30600
30308
  "sap-ai": {
30601
30309
  finishReason: finishReasonRaw ?? "unknown",
@@ -30607,18 +30315,31 @@ var SAPAIChatLanguageModel = class {
30607
30315
  body: requestBody
30608
30316
  },
30609
30317
  response: {
30610
- timestamp: /* @__PURE__ */ new Date(),
30611
- modelId: this.modelId,
30318
+ body: rawResponseBody,
30612
30319
  headers: responseHeaders,
30613
- body: rawResponseBody
30320
+ modelId: this.modelId,
30321
+ timestamp: /* @__PURE__ */ new Date()
30322
+ },
30323
+ usage: {
30324
+ inputTokens: {
30325
+ cacheRead: void 0,
30326
+ cacheWrite: void 0,
30327
+ noCache: tokenUsage.prompt_tokens,
30328
+ total: tokenUsage.prompt_tokens
30329
+ },
30330
+ outputTokens: {
30331
+ reasoning: void 0,
30332
+ text: tokenUsage.completion_tokens,
30333
+ total: tokenUsage.completion_tokens
30334
+ }
30614
30335
  },
30615
30336
  warnings
30616
30337
  };
30617
30338
  } catch (error) {
30618
30339
  throw convertToAISDKError(error, {
30619
30340
  operation: "doGenerate",
30620
- url: "sap-ai:orchestration",
30621
- requestBody: createAISDKRequestBodySummary(options)
30341
+ requestBody: createAISDKRequestBodySummary(options),
30342
+ url: "sap-ai:orchestration"
30622
30343
  });
30623
30344
  }
30624
30345
  }
@@ -30647,6 +30368,8 @@ var SAPAIChatLanguageModel = class {
30647
30368
  * - Usage format: `{ inputTokens: { total, ... }, outputTokens: { total, ... } }`
30648
30369
  * - Warnings only in `stream-start` event
30649
30370
  *
30371
+ * @see {@link https://sdk.vercel.ai/docs/ai-sdk-core/streaming Vercel AI SDK Streaming}
30372
+ *
30650
30373
  * @param options - Streaming options including prompt, tools, and settings
30651
30374
  * @returns Promise resolving to stream and request metadata
30652
30375
  *
@@ -30672,7 +30395,7 @@ var SAPAIChatLanguageModel = class {
30672
30395
  */
30673
30396
  async doStream(options) {
30674
30397
  try {
30675
- const { orchestrationConfig, messages, warnings } = this.buildOrchestrationConfig(options);
30398
+ const { messages, orchestrationConfig, warnings } = this.buildOrchestrationConfig(options);
30676
30399
  const client = this.createClient(orchestrationConfig);
30677
30400
  const promptTemplating = orchestrationConfig.promptTemplating;
30678
30401
  const requestBody = {
@@ -30699,25 +30422,25 @@ var SAPAIChatLanguageModel = class {
30699
30422
  const idGenerator = new StreamIdGenerator();
30700
30423
  let textBlockId = null;
30701
30424
  const streamState = {
30425
+ activeText: false,
30702
30426
  finishReason: {
30703
- unified: "other",
30704
- raw: void 0
30427
+ raw: void 0,
30428
+ unified: "other"
30705
30429
  },
30430
+ isFirstChunk: true,
30706
30431
  usage: {
30707
30432
  inputTokens: {
30708
- total: void 0,
30709
- noCache: void 0,
30710
30433
  cacheRead: void 0,
30711
- cacheWrite: void 0
30434
+ cacheWrite: void 0,
30435
+ noCache: void 0,
30436
+ total: void 0
30712
30437
  },
30713
30438
  outputTokens: {
30714
- total: void 0,
30439
+ reasoning: void 0,
30715
30440
  text: void 0,
30716
- reasoning: void 0
30441
+ total: void 0
30717
30442
  }
30718
- },
30719
- isFirstChunk: true,
30720
- activeText: false
30443
+ }
30721
30444
  };
30722
30445
  const toolCallsInProgress = /* @__PURE__ */ new Map();
30723
30446
  const sdkStream = streamResponse.stream;
@@ -30725,6 +30448,11 @@ var SAPAIChatLanguageModel = class {
30725
30448
  const warningsSnapshot = [...warnings];
30726
30449
  const warningsOut = [...warningsSnapshot];
30727
30450
  const transformedStream = new ReadableStream({
30451
+ cancel(reason) {
30452
+ if (reason) {
30453
+ console.debug("SAP AI stream cancelled:", reason);
30454
+ }
30455
+ },
30728
30456
  async start(controller) {
30729
30457
  controller.enqueue({
30730
30458
  type: "stream-start",
@@ -30735,30 +30463,30 @@ var SAPAIChatLanguageModel = class {
30735
30463
  if (streamState.isFirstChunk) {
30736
30464
  streamState.isFirstChunk = false;
30737
30465
  controller.enqueue({
30738
- type: "response-metadata",
30739
30466
  modelId,
30740
- timestamp: /* @__PURE__ */ new Date()
30467
+ timestamp: /* @__PURE__ */ new Date(),
30468
+ type: "response-metadata"
30741
30469
  });
30742
30470
  }
30743
30471
  const deltaToolCalls = chunk.getDeltaToolCalls();
30744
30472
  if (Array.isArray(deltaToolCalls) && deltaToolCalls.length > 0) {
30745
30473
  streamState.finishReason = {
30746
- unified: "tool-calls",
30747
- raw: void 0
30474
+ raw: void 0,
30475
+ unified: "tool-calls"
30748
30476
  };
30749
30477
  }
30750
30478
  const deltaContent = chunk.getDeltaContent();
30751
30479
  if (typeof deltaContent === "string" && deltaContent.length > 0 && streamState.finishReason.unified !== "tool-calls") {
30752
30480
  if (!streamState.activeText) {
30753
30481
  textBlockId = idGenerator.generateTextBlockId();
30754
- controller.enqueue({ type: "text-start", id: textBlockId });
30482
+ controller.enqueue({ id: textBlockId, type: "text-start" });
30755
30483
  streamState.activeText = true;
30756
30484
  }
30757
30485
  if (textBlockId) {
30758
30486
  controller.enqueue({
30759
- type: "text-delta",
30487
+ delta: deltaContent,
30760
30488
  id: textBlockId,
30761
- delta: deltaContent
30489
+ type: "text-delta"
30762
30490
  });
30763
30491
  }
30764
30492
  }
@@ -30770,11 +30498,11 @@ var SAPAIChatLanguageModel = class {
30770
30498
  }
30771
30499
  if (!toolCallsInProgress.has(index)) {
30772
30500
  toolCallsInProgress.set(index, {
30773
- id: toolCallChunk.id ?? `tool_${String(index)}`,
30774
- toolName: toolCallChunk.function?.name,
30775
30501
  arguments: "",
30502
+ didEmitCall: false,
30776
30503
  didEmitInputStart: false,
30777
- didEmitCall: false
30504
+ id: toolCallChunk.id ?? `tool_${String(index)}`,
30505
+ toolName: toolCallChunk.function?.name
30778
30506
  });
30779
30507
  }
30780
30508
  const tc = toolCallsInProgress.get(index);
@@ -30789,9 +30517,9 @@ var SAPAIChatLanguageModel = class {
30789
30517
  if (!tc.didEmitInputStart && tc.toolName) {
30790
30518
  tc.didEmitInputStart = true;
30791
30519
  controller.enqueue({
30792
- type: "tool-input-start",
30793
30520
  id: tc.id,
30794
- toolName: tc.toolName
30521
+ toolName: tc.toolName,
30522
+ type: "tool-input-start"
30795
30523
  });
30796
30524
  }
30797
30525
  const argumentsDelta = toolCallChunk.function?.arguments;
@@ -30799,9 +30527,9 @@ var SAPAIChatLanguageModel = class {
30799
30527
  tc.arguments += argumentsDelta;
30800
30528
  if (tc.didEmitInputStart) {
30801
30529
  controller.enqueue({
30802
- type: "tool-input-delta",
30530
+ delta: argumentsDelta,
30803
30531
  id: tc.id,
30804
- delta: argumentsDelta
30532
+ type: "tool-input-delta"
30805
30533
  });
30806
30534
  }
30807
30535
  }
@@ -30819,28 +30547,28 @@ var SAPAIChatLanguageModel = class {
30819
30547
  if (!tc.didEmitInputStart) {
30820
30548
  tc.didEmitInputStart = true;
30821
30549
  controller.enqueue({
30822
- type: "tool-input-start",
30823
30550
  id: tc.id,
30824
- toolName: tc.toolName ?? ""
30551
+ toolName: tc.toolName ?? "",
30552
+ type: "tool-input-start"
30825
30553
  });
30826
30554
  }
30827
30555
  if (!tc.toolName) {
30828
30556
  warningsOut.push({
30829
- type: "other",
30830
- message: "Received tool-call delta without a tool name. Emitting tool-call with an empty tool name."
30557
+ message: "Received tool-call delta without a tool name. Emitting tool-call with an empty tool name.",
30558
+ type: "other"
30831
30559
  });
30832
30560
  }
30833
30561
  tc.didEmitCall = true;
30834
- controller.enqueue({ type: "tool-input-end", id: tc.id });
30562
+ controller.enqueue({ id: tc.id, type: "tool-input-end" });
30835
30563
  controller.enqueue({
30836
- type: "tool-call",
30564
+ input: tc.arguments,
30837
30565
  toolCallId: tc.id,
30838
30566
  toolName: tc.toolName ?? "",
30839
- input: tc.arguments
30567
+ type: "tool-call"
30840
30568
  });
30841
30569
  }
30842
30570
  if (streamState.activeText && textBlockId) {
30843
- controller.enqueue({ type: "text-end", id: textBlockId });
30571
+ controller.enqueue({ id: textBlockId, type: "text-end" });
30844
30572
  streamState.activeText = false;
30845
30573
  }
30846
30574
  }
@@ -30855,109 +30583,399 @@ var SAPAIChatLanguageModel = class {
30855
30583
  if (!tc.didEmitInputStart) {
30856
30584
  tc.didEmitInputStart = true;
30857
30585
  controller.enqueue({
30858
- type: "tool-input-start",
30859
30586
  id: tc.id,
30860
- toolName: tc.toolName ?? ""
30587
+ toolName: tc.toolName ?? "",
30588
+ type: "tool-input-start"
30861
30589
  });
30862
30590
  }
30863
30591
  if (!tc.toolName) {
30864
30592
  warningsOut.push({
30865
- type: "other",
30866
- message: "Received tool-call delta without a tool name. Emitting tool-call with an empty tool name."
30593
+ message: "Received tool-call delta without a tool name. Emitting tool-call with an empty tool name.",
30594
+ type: "other"
30867
30595
  });
30868
30596
  }
30869
30597
  didEmitAnyToolCalls = true;
30870
30598
  tc.didEmitCall = true;
30871
- controller.enqueue({ type: "tool-input-end", id: tc.id });
30599
+ controller.enqueue({ id: tc.id, type: "tool-input-end" });
30872
30600
  controller.enqueue({
30873
- type: "tool-call",
30601
+ input: tc.arguments,
30874
30602
  toolCallId: tc.id,
30875
30603
  toolName: tc.toolName ?? "",
30876
- input: tc.arguments
30604
+ type: "tool-call"
30877
30605
  });
30878
30606
  }
30879
30607
  if (streamState.activeText && textBlockId) {
30880
- controller.enqueue({ type: "text-end", id: textBlockId });
30608
+ controller.enqueue({ id: textBlockId, type: "text-end" });
30881
30609
  }
30882
30610
  const finalFinishReason = streamResponse.getFinishReason();
30883
30611
  if (finalFinishReason) {
30884
30612
  streamState.finishReason = mapFinishReason(finalFinishReason);
30885
30613
  } else if (didEmitAnyToolCalls) {
30886
30614
  streamState.finishReason = {
30887
- unified: "tool-calls",
30888
- raw: void 0
30615
+ raw: void 0,
30616
+ unified: "tool-calls"
30889
30617
  };
30890
30618
  }
30891
- const finalUsage = streamResponse.getTokenUsage();
30892
- if (finalUsage) {
30893
- streamState.usage.inputTokens.total = finalUsage.prompt_tokens;
30894
- streamState.usage.inputTokens.noCache = finalUsage.prompt_tokens;
30895
- streamState.usage.outputTokens.total = finalUsage.completion_tokens;
30896
- streamState.usage.outputTokens.text = finalUsage.completion_tokens;
30619
+ const finalUsage = streamResponse.getTokenUsage();
30620
+ if (finalUsage) {
30621
+ streamState.usage.inputTokens.total = finalUsage.prompt_tokens;
30622
+ streamState.usage.inputTokens.noCache = finalUsage.prompt_tokens;
30623
+ streamState.usage.outputTokens.total = finalUsage.completion_tokens;
30624
+ streamState.usage.outputTokens.text = finalUsage.completion_tokens;
30625
+ }
30626
+ controller.enqueue({
30627
+ finishReason: streamState.finishReason,
30628
+ type: "finish",
30629
+ usage: streamState.usage
30630
+ });
30631
+ controller.close();
30632
+ } catch (error) {
30633
+ const aiError = convertToAISDKError(error, {
30634
+ operation: "doStream",
30635
+ requestBody: createAISDKRequestBodySummary(options),
30636
+ url: "sap-ai:orchestration"
30637
+ });
30638
+ controller.enqueue({
30639
+ error: aiError instanceof Error ? aiError : new Error(String(aiError)),
30640
+ type: "error"
30641
+ });
30642
+ controller.close();
30643
+ }
30644
+ }
30645
+ });
30646
+ return {
30647
+ request: {
30648
+ body: requestBody
30649
+ },
30650
+ stream: transformedStream
30651
+ };
30652
+ } catch (error) {
30653
+ throw convertToAISDKError(error, {
30654
+ operation: "doStream",
30655
+ requestBody: createAISDKRequestBodySummary(options),
30656
+ url: "sap-ai:orchestration"
30657
+ });
30658
+ }
30659
+ }
30660
+ /**
30661
+ * Checks if a URL is supported for file/image uploads.
30662
+ *
30663
+ * @param url - The URL to check
30664
+ * @returns True if the URL protocol is HTTPS or data with valid image format
30665
+ */
30666
+ supportsUrl(url) {
30667
+ if (url.protocol === "https:") return true;
30668
+ if (url.protocol === "data:") {
30669
+ return /^data:image\//i.test(url.href);
30670
+ }
30671
+ return false;
30672
+ }
30673
+ /**
30674
+ * Builds orchestration module config for SAP AI SDK.
30675
+ *
30676
+ * @param options - Call options from the AI SDK
30677
+ * @returns Object containing orchestration config, messages, and warnings
30678
+ * @internal
30679
+ */
30680
+ buildOrchestrationConfig(options) {
30681
+ const providerOptions = options.providerOptions?.sap ?? {};
30682
+ const warnings = [];
30683
+ const messages = convertToSAPMessages(options.prompt, {
30684
+ includeReasoning: providerOptions.includeReasoning ?? this.settings.includeReasoning ?? false
30685
+ });
30686
+ let tools;
30687
+ const settingsTools = providerOptions.tools ?? this.settings.tools;
30688
+ const optionsTools = options.tools;
30689
+ const shouldUseSettingsTools = settingsTools && settingsTools.length > 0 && (!optionsTools || optionsTools.length === 0);
30690
+ const shouldUseOptionsTools = !!(optionsTools && optionsTools.length > 0);
30691
+ if (settingsTools && settingsTools.length > 0 && optionsTools && optionsTools.length > 0) {
30692
+ warnings.push({
30693
+ message: "Both settings.tools and call options.tools were provided; preferring call options.tools.",
30694
+ type: "other"
30695
+ });
30696
+ }
30697
+ if (shouldUseSettingsTools) {
30698
+ tools = settingsTools;
30699
+ } else {
30700
+ const availableTools = shouldUseOptionsTools ? optionsTools : void 0;
30701
+ tools = availableTools?.map((tool) => {
30702
+ if (tool.type === "function") {
30703
+ const inputSchema = tool.inputSchema;
30704
+ const toolWithParams = tool;
30705
+ let parameters;
30706
+ if (toolWithParams.parameters && isZodSchema(toolWithParams.parameters)) {
30707
+ try {
30708
+ const jsonSchema = zodToJsonSchema(
30709
+ toolWithParams.parameters,
30710
+ {
30711
+ $refStrategy: "none"
30712
+ }
30713
+ );
30714
+ const schemaRecord = jsonSchema;
30715
+ delete schemaRecord.$schema;
30716
+ parameters = buildSAPToolParameters(schemaRecord);
30717
+ } catch (error) {
30718
+ warnings.push({
30719
+ details: `Failed to convert tool Zod schema: ${error instanceof Error ? error.message : String(error)}. Falling back to empty object schema.`,
30720
+ feature: `tool schema conversion for ${tool.name}`,
30721
+ type: "unsupported"
30722
+ });
30723
+ parameters = buildSAPToolParameters({});
30724
+ }
30725
+ } else if (inputSchema && Object.keys(inputSchema).length > 0) {
30726
+ const hasProperties = inputSchema.properties && typeof inputSchema.properties === "object" && Object.keys(inputSchema.properties).length > 0;
30727
+ if (hasProperties) {
30728
+ parameters = buildSAPToolParameters(inputSchema);
30729
+ } else {
30730
+ parameters = buildSAPToolParameters({});
30897
30731
  }
30898
- controller.enqueue({
30899
- type: "finish",
30900
- finishReason: streamState.finishReason,
30901
- usage: streamState.usage
30902
- });
30903
- controller.close();
30904
- } catch (error) {
30905
- const aiError = convertToAISDKError(error, {
30906
- operation: "doStream",
30907
- url: "sap-ai:orchestration",
30908
- requestBody: createAISDKRequestBodySummary(options)
30909
- });
30910
- controller.enqueue({
30911
- type: "error",
30912
- error: aiError instanceof Error ? aiError : new Error(String(aiError))
30913
- });
30914
- controller.close();
30915
- }
30916
- },
30917
- cancel(reason) {
30918
- if (reason) {
30919
- console.debug("SAP AI stream cancelled:", reason);
30732
+ } else {
30733
+ parameters = buildSAPToolParameters({});
30920
30734
  }
30735
+ return {
30736
+ function: {
30737
+ description: tool.description,
30738
+ name: tool.name,
30739
+ parameters
30740
+ },
30741
+ type: "function"
30742
+ };
30743
+ } else {
30744
+ warnings.push({
30745
+ details: "Only 'function' tool type is supported.",
30746
+ feature: `tool type for ${tool.name}`,
30747
+ type: "unsupported"
30748
+ });
30749
+ return null;
30921
30750
  }
30751
+ }).filter((t) => t !== null);
30752
+ }
30753
+ const supportsN = !this.modelId.startsWith("amazon--") && !this.modelId.startsWith("anthropic--");
30754
+ const modelParams = {};
30755
+ const maxTokens = options.maxOutputTokens ?? providerOptions.modelParams?.maxTokens ?? this.settings.modelParams?.maxTokens;
30756
+ if (maxTokens !== void 0) modelParams.max_tokens = maxTokens;
30757
+ const temperature = options.temperature ?? providerOptions.modelParams?.temperature ?? this.settings.modelParams?.temperature;
30758
+ if (temperature !== void 0) modelParams.temperature = temperature;
30759
+ const topP = options.topP ?? providerOptions.modelParams?.topP ?? this.settings.modelParams?.topP;
30760
+ if (topP !== void 0) modelParams.top_p = topP;
30761
+ if (options.topK !== void 0) modelParams.top_k = options.topK;
30762
+ const frequencyPenalty = options.frequencyPenalty ?? providerOptions.modelParams?.frequencyPenalty ?? this.settings.modelParams?.frequencyPenalty;
30763
+ if (frequencyPenalty !== void 0) {
30764
+ modelParams.frequency_penalty = frequencyPenalty;
30765
+ }
30766
+ const presencePenalty = options.presencePenalty ?? providerOptions.modelParams?.presencePenalty ?? this.settings.modelParams?.presencePenalty;
30767
+ if (presencePenalty !== void 0) {
30768
+ modelParams.presence_penalty = presencePenalty;
30769
+ }
30770
+ if (supportsN) {
30771
+ const nValue = providerOptions.modelParams?.n ?? this.settings.modelParams?.n;
30772
+ if (nValue !== void 0) {
30773
+ modelParams.n = nValue;
30774
+ }
30775
+ }
30776
+ const parallelToolCalls = providerOptions.modelParams?.parallel_tool_calls ?? this.settings.modelParams?.parallel_tool_calls;
30777
+ if (parallelToolCalls !== void 0) {
30778
+ modelParams.parallel_tool_calls = parallelToolCalls;
30779
+ }
30780
+ if (options.stopSequences && options.stopSequences.length > 0) {
30781
+ modelParams.stop = options.stopSequences;
30782
+ }
30783
+ if (options.seed !== void 0) {
30784
+ modelParams.seed = options.seed;
30785
+ }
30786
+ validateModelParameters(
30787
+ {
30788
+ frequencyPenalty,
30789
+ maxTokens,
30790
+ n: modelParams.n,
30791
+ presencePenalty,
30792
+ temperature,
30793
+ topP
30794
+ },
30795
+ warnings
30796
+ );
30797
+ if (options.toolChoice && options.toolChoice.type !== "auto") {
30798
+ warnings.push({
30799
+ details: `SAP AI SDK does not support toolChoice '${options.toolChoice.type}'. Using default 'auto' behavior.`,
30800
+ feature: "toolChoice",
30801
+ type: "unsupported"
30922
30802
  });
30923
- return {
30924
- stream: transformedStream,
30925
- request: {
30926
- body: requestBody
30927
- }
30928
- };
30929
- } catch (error) {
30930
- throw convertToAISDKError(error, {
30931
- operation: "doStream",
30932
- url: "sap-ai:orchestration",
30933
- requestBody: createAISDKRequestBodySummary(options)
30803
+ }
30804
+ if (options.responseFormat?.type === "json") {
30805
+ warnings.push({
30806
+ message: "responseFormat JSON mode is forwarded to the underlying model; support and schema adherence depend on the model/deployment.",
30807
+ type: "other"
30934
30808
  });
30935
30809
  }
30810
+ const responseFormat = options.responseFormat?.type === "json" ? options.responseFormat.schema ? {
30811
+ json_schema: {
30812
+ description: options.responseFormat.description,
30813
+ name: options.responseFormat.name ?? "response",
30814
+ schema: options.responseFormat.schema,
30815
+ strict: null
30816
+ },
30817
+ type: "json_schema"
30818
+ } : { type: "json_object" } : void 0;
30819
+ const orchestrationConfig = {
30820
+ promptTemplating: {
30821
+ model: {
30822
+ name: this.modelId,
30823
+ params: modelParams,
30824
+ version: providerOptions.modelVersion ?? this.settings.modelVersion ?? "latest"
30825
+ },
30826
+ prompt: {
30827
+ template: [],
30828
+ tools: tools && tools.length > 0 ? tools : void 0,
30829
+ ...responseFormat ? { response_format: responseFormat } : {}
30830
+ }
30831
+ },
30832
+ ...(() => {
30833
+ const masking = providerOptions.masking ?? this.settings.masking;
30834
+ return masking && Object.keys(masking).length > 0 ? { masking } : {};
30835
+ })(),
30836
+ ...(() => {
30837
+ const filtering = providerOptions.filtering ?? this.settings.filtering;
30838
+ return filtering && Object.keys(filtering).length > 0 ? { filtering } : {};
30839
+ })(),
30840
+ ...(() => {
30841
+ const grounding = providerOptions.grounding ?? this.settings.grounding;
30842
+ return grounding && Object.keys(grounding).length > 0 ? { grounding } : {};
30843
+ })(),
30844
+ ...(() => {
30845
+ const translation = providerOptions.translation ?? this.settings.translation;
30846
+ return translation && Object.keys(translation).length > 0 ? { translation } : {};
30847
+ })()
30848
+ };
30849
+ return { messages, orchestrationConfig, warnings };
30850
+ }
30851
+ /**
30852
+ * Creates an OrchestrationClient instance.
30853
+ *
30854
+ * @param config - Orchestration module configuration
30855
+ * @returns OrchestrationClient instance
30856
+ * @internal
30857
+ */
30858
+ createClient(config) {
30859
+ return new OrchestrationClient(
30860
+ config,
30861
+ this.config.deploymentConfig,
30862
+ this.config.destination
30863
+ );
30936
30864
  }
30937
30865
  };
30866
+ function buildSAPToolParameters(schema) {
30867
+ const schemaType = schema.type;
30868
+ if (schemaType !== void 0 && schemaType !== "object") {
30869
+ return {
30870
+ properties: {},
30871
+ required: [],
30872
+ type: "object"
30873
+ };
30874
+ }
30875
+ const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
30876
+ const required = Array.isArray(schema.required) && schema.required.every((item) => typeof item === "string") ? schema.required : [];
30877
+ const additionalFields = Object.fromEntries(
30878
+ Object.entries(schema).filter(
30879
+ ([key]) => key !== "type" && key !== "properties" && key !== "required"
30880
+ )
30881
+ );
30882
+ return {
30883
+ properties,
30884
+ required,
30885
+ type: "object",
30886
+ ...additionalFields
30887
+ };
30888
+ }
30889
+ function createAISDKRequestBodySummary(options) {
30890
+ return {
30891
+ hasImageParts: options.prompt.some(
30892
+ (message) => message.role === "user" && message.content.some(
30893
+ (part) => part.type === "file" && part.mediaType.startsWith("image/")
30894
+ )
30895
+ ),
30896
+ maxOutputTokens: options.maxOutputTokens,
30897
+ promptMessages: options.prompt.length,
30898
+ responseFormatType: options.responseFormat?.type,
30899
+ seed: options.seed,
30900
+ stopSequences: options.stopSequences?.length,
30901
+ temperature: options.temperature,
30902
+ toolChoiceType: options.toolChoice?.type,
30903
+ tools: options.tools?.length ?? 0,
30904
+ topK: options.topK,
30905
+ topP: options.topP
30906
+ };
30907
+ }
30908
+ function hasCallableParse(obj) {
30909
+ return typeof obj.parse === "function";
30910
+ }
30911
+ function isZodSchema(obj) {
30912
+ if (obj === null || typeof obj !== "object") {
30913
+ return false;
30914
+ }
30915
+ const record = obj;
30916
+ return "_def" in record && "parse" in record && hasCallableParse(record);
30917
+ }
30938
30918
  function mapFinishReason(reason) {
30939
30919
  const raw = reason;
30940
- if (!reason) return { unified: "other", raw };
30920
+ if (!reason) return { raw, unified: "other" };
30941
30921
  switch (reason.toLowerCase()) {
30942
- case "stop":
30922
+ case "content_filter":
30923
+ return { raw, unified: "content-filter" };
30943
30924
  case "end_turn":
30944
- case "stop_sequence":
30945
30925
  case "eos":
30946
- return { unified: "stop", raw };
30926
+ case "stop":
30927
+ case "stop_sequence":
30928
+ return { raw, unified: "stop" };
30929
+ case "error":
30930
+ return { raw, unified: "error" };
30931
+ case "function_call":
30932
+ case "tool_call":
30933
+ case "tool_calls":
30934
+ return { raw, unified: "tool-calls" };
30947
30935
  case "length":
30948
30936
  case "max_tokens":
30949
30937
  case "max_tokens_reached":
30950
- return { unified: "length", raw };
30951
- case "tool_calls":
30952
- case "tool_call":
30953
- case "function_call":
30954
- return { unified: "tool-calls", raw };
30955
- case "content_filter":
30956
- return { unified: "content-filter", raw };
30957
- case "error":
30958
- return { unified: "error", raw };
30938
+ return { raw, unified: "length" };
30959
30939
  default:
30960
- return { unified: "other", raw };
30940
+ return { raw, unified: "other" };
30941
+ }
30942
+ }
30943
+ function validateModelParameters(params, warnings) {
30944
+ if (params.temperature !== void 0 && (params.temperature < 0 || params.temperature > 2)) {
30945
+ warnings.push({
30946
+ message: `temperature=${String(params.temperature)} is outside typical range [0, 2]. The API may reject this value.`,
30947
+ type: "other"
30948
+ });
30949
+ }
30950
+ if (params.topP !== void 0 && (params.topP < 0 || params.topP > 1)) {
30951
+ warnings.push({
30952
+ message: `topP=${String(params.topP)} is outside valid range [0, 1]. The API may reject this value.`,
30953
+ type: "other"
30954
+ });
30955
+ }
30956
+ if (params.frequencyPenalty !== void 0 && (params.frequencyPenalty < -2 || params.frequencyPenalty > 2)) {
30957
+ warnings.push({
30958
+ message: `frequencyPenalty=${String(params.frequencyPenalty)} is outside typical range [-2, 2]. The API may reject this value.`,
30959
+ type: "other"
30960
+ });
30961
+ }
30962
+ if (params.presencePenalty !== void 0 && (params.presencePenalty < -2 || params.presencePenalty > 2)) {
30963
+ warnings.push({
30964
+ message: `presencePenalty=${String(params.presencePenalty)} is outside typical range [-2, 2]. The API may reject this value.`,
30965
+ type: "other"
30966
+ });
30967
+ }
30968
+ if (params.maxTokens !== void 0 && params.maxTokens <= 0) {
30969
+ warnings.push({
30970
+ message: `maxTokens=${String(params.maxTokens)} must be positive. The API will likely reject this value.`,
30971
+ type: "other"
30972
+ });
30973
+ }
30974
+ if (params.n !== void 0 && params.n <= 0) {
30975
+ warnings.push({
30976
+ message: `n=${String(params.n)} must be positive. The API will likely reject this value.`,
30977
+ type: "other"
30978
+ });
30961
30979
  }
30962
30980
  }
30963
30981
 
@@ -30975,19 +30993,19 @@ function createSAPAIProvider(options = {}) {
30975
30993
  const mergedSettings = {
30976
30994
  ...options.defaultSettings,
30977
30995
  ...settings,
30996
+ filtering: settings.filtering ?? options.defaultSettings?.filtering,
30997
+ // Complex objects: override, do not merge
30998
+ masking: settings.masking ?? options.defaultSettings?.masking,
30978
30999
  modelParams: {
30979
31000
  ...options.defaultSettings?.modelParams ?? {},
30980
31001
  ...settings.modelParams ?? {}
30981
31002
  },
30982
- // Complex objects: override, do not merge
30983
- masking: settings.masking ?? options.defaultSettings?.masking,
30984
- filtering: settings.filtering ?? options.defaultSettings?.filtering,
30985
31003
  tools: settings.tools ?? options.defaultSettings?.tools
30986
31004
  };
30987
- return new SAPAIChatLanguageModel(modelId, mergedSettings, {
30988
- provider: "sap-ai",
31005
+ return new SAPAILanguageModel(modelId, mergedSettings, {
30989
31006
  deploymentConfig,
30990
- destination: options.destination
31007
+ destination: options.destination,
31008
+ provider: "sap-ai"
30991
31009
  });
30992
31010
  };
30993
31011
  const provider = function(modelId, settings) {
@@ -31003,23 +31021,19 @@ function createSAPAIProvider(options = {}) {
31003
31021
  }
31004
31022
  var sapai = createSAPAIProvider();
31005
31023
 
31006
- // src/sap-ai-chat-settings.ts
31024
+ // src/sap-ai-settings.ts
31007
31025
  import {
31008
- buildDpiMaskingProvider,
31009
31026
  buildAzureContentSafetyFilter,
31010
- buildLlamaGuard38BFilter,
31011
31027
  buildDocumentGroundingConfig,
31012
- buildTranslationConfig
31028
+ buildDpiMaskingProvider,
31029
+ buildLlamaGuard38BFilter,
31030
+ buildTranslationConfig,
31031
+ isConfigReference
31013
31032
  } from "@sap-ai-sdk/orchestration";
31014
-
31015
- // src/types/completion-request.ts
31016
- import { isConfigReference } from "@sap-ai-sdk/orchestration";
31017
-
31018
- // src/types/completion-response.ts
31019
31033
  import {
31020
31034
  OrchestrationResponse,
31021
- OrchestrationStreamResponse,
31022
- OrchestrationStreamChunkResponse
31035
+ OrchestrationStreamChunkResponse,
31036
+ OrchestrationStreamResponse
31023
31037
  } from "@sap-ai-sdk/orchestration";
31024
31038
 
31025
31039
  // src/index.ts