@jerome-benoit/sap-ai-provider 3.0.0 → 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
@@ -24771,7 +24771,7 @@ var require_form_data = __commonJS({
24771
24771
  var parseUrl = __require("url").parse;
24772
24772
  var fs = __require("fs");
24773
24773
  var Stream = __require("stream").Stream;
24774
- var crypto = __require("crypto");
24774
+ var crypto2 = __require("crypto");
24775
24775
  var mime = require_mime_types();
24776
24776
  var asynckit = require_asynckit();
24777
24777
  var setToStringTag = require_es_set_tostringtag();
@@ -24977,7 +24977,7 @@ var require_form_data = __commonJS({
24977
24977
  return Buffer.concat([dataBuffer, Buffer.from(this._lastBoundary())]);
24978
24978
  };
24979
24979
  FormData2.prototype._generateBoundary = function() {
24980
- this._boundary = "--------------------------" + crypto.randomBytes(12).toString("hex");
24980
+ this._boundary = "--------------------------" + crypto2.randomBytes(12).toString("hex");
24981
24981
  };
24982
24982
  FormData2.prototype.getLengthSync = function() {
24983
24983
  var knownLength = this._overheadLength + this._valueLength;
@@ -26207,7 +26207,7 @@ var require_axios = __commonJS({
26207
26207
  "node_modules/axios/dist/node/axios.cjs"(exports, module) {
26208
26208
  "use strict";
26209
26209
  var FormData$1 = require_form_data();
26210
- var crypto = __require("crypto");
26210
+ var crypto2 = __require("crypto");
26211
26211
  var url = __require("url");
26212
26212
  var proxyFromEnv = require_proxy_from_env();
26213
26213
  var http = __require("http");
@@ -26222,7 +26222,7 @@ var require_axios = __commonJS({
26222
26222
  return e && typeof e === "object" && "default" in e ? e : { "default": e };
26223
26223
  }
26224
26224
  var FormData__default = /* @__PURE__ */ _interopDefaultLegacy(FormData$1);
26225
- var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(crypto);
26225
+ var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(crypto2);
26226
26226
  var url__default = /* @__PURE__ */ _interopDefaultLegacy(url);
26227
26227
  var proxyFromEnv__default = /* @__PURE__ */ _interopDefaultLegacy(proxyFromEnv);
26228
26228
  var http__default = /* @__PURE__ */ _interopDefaultLegacy(http);
@@ -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,73 +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
- const toolMessage = {
29892
- role: "tool",
29893
- tool_call_id: part.toolCallId,
29894
- content: JSON.stringify(part.output)
29895
- };
29896
- messages.push(toolMessage);
29897
- }
29898
- break;
29899
- }
29900
29902
  default: {
29901
29903
  const _exhaustiveCheck = message;
29902
29904
  throw new Error(
@@ -29911,16 +29913,6 @@ function convertToSAPMessages(prompt, options = {}) {
29911
29913
  // src/sap-ai-error.ts
29912
29914
  var import_util = __toESM(require_dist(), 1);
29913
29915
  import { APICallError, LoadAPIKeyError } from "@ai-sdk/provider";
29914
- function getStatusCodeFromSAPError(code) {
29915
- if (!code) return 500;
29916
- if (code >= 100 && code < 600) {
29917
- return code;
29918
- }
29919
- return 500;
29920
- }
29921
- function isRetryable(statusCode) {
29922
- return statusCode === 429 || statusCode >= 500 && statusCode < 600;
29923
- }
29924
29916
  function convertSAPErrorToAPICallError(errorResponse, context) {
29925
29917
  const error = errorResponse.error;
29926
29918
  let message;
@@ -29942,9 +29934,9 @@ function convertSAPErrorToAPICallError(errorResponse, context) {
29942
29934
  const statusCode = getStatusCodeFromSAPError(code);
29943
29935
  const responseBody = JSON.stringify({
29944
29936
  error: {
29945
- message,
29946
29937
  code,
29947
29938
  location,
29939
+ message,
29948
29940
  request_id: requestId
29949
29941
  }
29950
29942
  });
@@ -29967,53 +29959,14 @@ Error location: ${location}`;
29967
29959
  Request ID: ${requestId}`;
29968
29960
  }
29969
29961
  return new APICallError({
29962
+ isRetryable: isRetryable(statusCode),
29970
29963
  message: enhancedMessage,
29971
- url: context?.url ?? "",
29972
29964
  requestBodyValues: context?.requestBody,
29973
- statusCode,
29974
- responseHeaders: context?.responseHeaders,
29975
29965
  responseBody,
29976
- isRetryable: isRetryable(statusCode)
29977
- });
29978
- }
29979
- function isOrchestrationErrorResponse(error) {
29980
- if (error === null || typeof error !== "object" || !("error" in error)) {
29981
- return false;
29982
- }
29983
- const errorEnvelope = error;
29984
- const innerError = errorEnvelope.error;
29985
- if (innerError === void 0) return false;
29986
- if (Array.isArray(innerError)) {
29987
- return innerError.every(
29988
- (entry) => entry !== null && typeof entry === "object" && "message" in entry && typeof entry.message === "string"
29989
- );
29990
- }
29991
- return typeof innerError === "object" && innerError !== null && "message" in innerError && typeof innerError.message === "string";
29992
- }
29993
- function normalizeHeaders(headers) {
29994
- if (!headers || typeof headers !== "object") return void 0;
29995
- const record = headers;
29996
- const entries = Object.entries(record).flatMap(([key, value]) => {
29997
- if (typeof value === "string") return [[key, value]];
29998
- if (Array.isArray(value)) {
29999
- const strings = value.filter((item) => typeof item === "string").join("; ");
30000
- return strings.length > 0 ? [[key, strings]] : [];
30001
- }
30002
- if (typeof value === "number" || typeof value === "boolean") {
30003
- return [[key, String(value)]];
30004
- }
30005
- return [];
29966
+ responseHeaders: context?.responseHeaders,
29967
+ statusCode,
29968
+ url: context?.url ?? ""
30006
29969
  });
30007
- if (entries.length === 0) return void 0;
30008
- return Object.fromEntries(entries);
30009
- }
30010
- function getAxiosResponseHeaders(error) {
30011
- if (!(error instanceof Error)) return void 0;
30012
- const rootCause = (0, import_util.isErrorWithCause)(error) ? error.rootCause : error;
30013
- if (typeof rootCause !== "object") return void 0;
30014
- const maybeAxios = rootCause;
30015
- if (maybeAxios.isAxiosError !== true) return void 0;
30016
- return normalizeHeaders(maybeAxios.response?.headers);
30017
29970
  }
30018
29971
  function convertToAISDKError(error, context) {
30019
29972
  if (error instanceof APICallError || error instanceof LoadAPIKeyError) {
@@ -30038,167 +29991,92 @@ See: https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-serv
30038
29991
  }
30039
29992
  if (errorMsg.includes("econnrefused") || errorMsg.includes("enotfound") || errorMsg.includes("network") || errorMsg.includes("timeout")) {
30040
29993
  return new APICallError({
29994
+ cause: error,
29995
+ isRetryable: true,
30041
29996
  message: `Network error connecting to SAP AI Core: ${error.message}`,
30042
- url: context?.url ?? "",
30043
29997
  requestBodyValues: context?.requestBody,
30044
- statusCode: 503,
30045
- isRetryable: true,
30046
29998
  responseHeaders,
30047
- cause: error
29999
+ statusCode: 503,
30000
+ url: context?.url ?? ""
30048
30001
  });
30049
30002
  }
30050
30003
  }
30051
30004
  const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
30052
30005
  const fullMessage = context?.operation ? `SAP AI Core ${context.operation} failed: ${message}` : `SAP AI Core error: ${message}`;
30053
30006
  return new APICallError({
30007
+ cause: error,
30008
+ isRetryable: false,
30054
30009
  message: fullMessage,
30055
- url: context?.url ?? "",
30056
30010
  requestBodyValues: context?.requestBody,
30057
- statusCode: 500,
30058
- isRetryable: false,
30059
30011
  responseHeaders,
30060
- cause: error
30012
+ statusCode: 500,
30013
+ url: context?.url ?? ""
30061
30014
  });
30062
30015
  }
30063
-
30064
- // src/sap-ai-chat-language-model.ts
30065
- function validateModelParameters(params, warnings) {
30066
- if (params.temperature !== void 0 && (params.temperature < 0 || params.temperature > 2)) {
30067
- warnings.push({
30068
- type: "other",
30069
- message: `temperature=${String(params.temperature)} is outside typical range [0, 2]. The API may reject this value.`
30070
- });
30071
- }
30072
- if (params.topP !== void 0 && (params.topP < 0 || params.topP > 1)) {
30073
- warnings.push({
30074
- type: "other",
30075
- message: `topP=${String(params.topP)} is outside valid range [0, 1]. The API may reject this value.`
30076
- });
30077
- }
30078
- if (params.frequencyPenalty !== void 0 && (params.frequencyPenalty < -2 || params.frequencyPenalty > 2)) {
30079
- warnings.push({
30080
- type: "other",
30081
- message: `frequencyPenalty=${String(params.frequencyPenalty)} is outside typical range [-2, 2]. The API may reject this value.`
30082
- });
30083
- }
30084
- if (params.presencePenalty !== void 0 && (params.presencePenalty < -2 || params.presencePenalty > 2)) {
30085
- warnings.push({
30086
- type: "other",
30087
- message: `presencePenalty=${String(params.presencePenalty)} is outside typical range [-2, 2]. The API may reject this value.`
30088
- });
30089
- }
30090
- if (params.maxTokens !== void 0 && params.maxTokens <= 0) {
30091
- warnings.push({
30092
- type: "other",
30093
- message: `maxTokens=${String(params.maxTokens)} must be positive. The API will likely reject this value.`
30094
- });
30095
- }
30096
- if (params.n !== void 0 && params.n <= 0) {
30097
- warnings.push({
30098
- type: "other",
30099
- message: `n=${String(params.n)} must be positive. The API will likely reject this value.`
30100
- });
30101
- }
30102
- }
30103
- function createAISDKRequestBodySummary(options) {
30104
- return {
30105
- promptMessages: options.prompt.length,
30106
- hasImageParts: options.prompt.some(
30107
- (message) => message.role === "user" && message.content.some(
30108
- (part) => part.type === "file" && part.mediaType.startsWith("image/")
30109
- )
30110
- ),
30111
- tools: options.tools?.length ?? 0,
30112
- temperature: options.temperature,
30113
- topP: options.topP,
30114
- topK: options.topK,
30115
- maxOutputTokens: options.maxOutputTokens,
30116
- stopSequences: options.stopSequences?.length,
30117
- seed: options.seed,
30118
- toolChoiceType: options.toolChoice?.type,
30119
- responseFormatType: options.responseFormat?.type
30120
- };
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);
30121
30023
  }
30122
- function hasCallableParse(obj) {
30123
- return typeof obj.parse === "function";
30024
+ function getStatusCodeFromSAPError(code) {
30025
+ if (!code) return 500;
30026
+ if (code >= 100 && code < 600) {
30027
+ return code;
30028
+ }
30029
+ return 500;
30124
30030
  }
30125
- function isZodSchema(obj) {
30126
- if (obj === null || typeof obj !== "object") {
30031
+ function isOrchestrationErrorResponse(error) {
30032
+ if (error === null || typeof error !== "object" || !("error" in error)) {
30127
30033
  return false;
30128
30034
  }
30129
- const record = obj;
30130
- return "_def" in record && "parse" in record && hasCallableParse(record);
30131
- }
30132
- function buildSAPToolParameters(schema) {
30133
- const schemaType = schema.type;
30134
- if (schemaType !== void 0 && schemaType !== "object") {
30135
- return {
30136
- type: "object",
30137
- properties: {},
30138
- required: []
30139
- };
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
+ );
30140
30042
  }
30141
- const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
30142
- const required = Array.isArray(schema.required) && schema.required.every((item) => typeof item === "string") ? schema.required : [];
30143
- const additionalFields = Object.fromEntries(
30144
- Object.entries(schema).filter(
30145
- ([key]) => key !== "type" && key !== "properties" && key !== "required"
30146
- )
30147
- );
30148
- return {
30149
- type: "object",
30150
- properties,
30151
- required,
30152
- ...additionalFields
30153
- };
30043
+ return typeof innerError === "object" && innerError !== null && "message" in innerError && typeof innerError.message === "string";
30154
30044
  }
30155
- var SAPAIChatLanguageModel = class {
30156
- specificationVersion = "v2";
30157
- modelId;
30158
- config;
30159
- settings;
30160
- /**
30161
- * Creates a new SAP AI Chat Language Model instance.
30162
- *
30163
- * @param modelId - The model identifier
30164
- * @param settings - Model-specific configuration settings
30165
- * @param config - Internal configuration (deployment config, destination, etc.)
30166
- *
30167
- * @internal This constructor is not meant to be called directly.
30168
- * Use the provider function instead.
30169
- */
30170
- constructor(modelId, settings, config) {
30171
- this.settings = settings;
30172
- this.config = config;
30173
- this.modelId = modelId;
30174
- }
30175
- /**
30176
- * Checks if a URL is supported for file/image uploads.
30177
- *
30178
- * @param url - The URL to check
30179
- * @returns True if the URL protocol is HTTPS or data (content-type rules are enforced via supportedUrls)
30180
- */
30181
- supportsUrl(url) {
30182
- return url.protocol === "https:" || url.protocol === "data:";
30183
- }
30184
- /**
30185
- * Returns supported URL patterns for different content types.
30186
- *
30187
- * @returns Record of content types to regex patterns
30188
- */
30189
- get supportedUrls() {
30190
- return {
30191
- "image/*": [/^https:\/\/.+$/i, /^data:image\/.*$/]
30192
- };
30193
- }
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 [];
30061
+ });
30062
+ if (entries.length === 0) return void 0;
30063
+ return Object.fromEntries(entries);
30064
+ }
30065
+
30066
+ // src/sap-ai-language-model.ts
30067
+ var StreamIdGenerator = class {
30194
30068
  /**
30195
- * Gets the provider identifier.
30069
+ * Generates a unique ID for a text block.
30196
30070
  *
30197
- * @returns The provider name ('sap-ai')
30071
+ * @returns RFC 4122-compliant UUID string
30198
30072
  */
30199
- get provider() {
30200
- return this.config.provider;
30073
+ generateTextBlockId() {
30074
+ return crypto.randomUUID();
30201
30075
  }
30076
+ };
30077
+ var SAPAILanguageModel = class {
30078
+ modelId;
30079
+ specificationVersion = "v3";
30202
30080
  /**
30203
30081
  * Model capabilities.
30204
30082
  *
@@ -30207,203 +30085,91 @@ var SAPAIChatLanguageModel = class {
30207
30085
  * SAP AI Core will fail the request at runtime.
30208
30086
  */
30209
30087
  supportsImageUrls = true;
30210
- /** Structured JSON outputs (json_schema response format). */
30211
- supportsStructuredOutputs = true;
30212
- /** Tool/function calling. */
30213
- supportsToolCalls = true;
30214
- /** Streaming responses. */
30215
- supportsStreaming = true;
30216
30088
  /** Multiple completions via the `n` parameter (provider-specific support). */
30217
30089
  supportsMultipleCompletions = true;
30218
30090
  /** Parallel tool calls. */
30219
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;
30220
30098
  /**
30221
- * Builds orchestration module config for SAP AI SDK.
30099
+ * Generates text completion using SAP AI Core's Orchestration API.
30222
30100
  *
30223
- * @param options - Call options from the AI SDK
30224
- * @returns Object containing orchestration config, messages, and warnings
30225
- * @internal
30101
+ * This method implements the `LanguageModelV3.doGenerate` interface,
30102
+ * providing synchronous (non-streaming) text generation with support for:
30103
+ * - Multi-turn conversations with system/user/assistant messages
30104
+ * - Tool calling (function calling) with structured outputs
30105
+ * - Multi-modal inputs (text + images)
30106
+ * - Data masking via SAP DPI
30107
+ * - Content filtering via Azure Content Safety or Llama Guard
30108
+ *
30109
+ * **Return Structure:**
30110
+ * - Finish reason: `{ unified: string, raw?: string }`
30111
+ * - Usage: Nested structure with token breakdown `{ inputTokens: { total, ... }, outputTokens: { total, ... } }`
30112
+ * - Warnings: Array of warnings with `type` and optional `feature` field
30113
+ *
30114
+ * @param options - Generation options including prompt, tools, temperature, etc.
30115
+ * @returns Promise resolving to generation result with content, usage, and metadata
30116
+ *
30117
+ * @throws {InvalidPromptError} If prompt format is invalid
30118
+ * @throws {InvalidArgumentError} If arguments are malformed
30119
+ * @throws {APICallError} If the SAP AI Core API call fails
30120
+ *
30121
+ * @example
30122
+ * ```typescript
30123
+ * const result = await model.doGenerate({
30124
+ * prompt: [
30125
+ * { role: 'user', content: [{ type: 'text', text: 'Hello!' }] }
30126
+ * ],
30127
+ * temperature: 0.7,
30128
+ * maxTokens: 100
30129
+ * });
30130
+ *
30131
+ * console.log(result.content); // Array of V3 content parts
30132
+ * console.log(result.finishReason.unified); // "stop", "length", "tool-calls", etc.
30133
+ * console.log(result.usage.inputTokens.total); // Total input tokens
30134
+ * ```
30135
+ *
30136
+ * @since 1.0.0
30137
+ * @since 4.0.0 Updated to LanguageModelV3 interface
30226
30138
  */
30227
- buildOrchestrationConfig(options) {
30228
- const providerOptions = options.providerOptions?.sap ?? {};
30229
- const warnings = [];
30230
- const messages = convertToSAPMessages(options.prompt, {
30231
- includeReasoning: providerOptions.includeReasoning ?? this.settings.includeReasoning ?? false
30232
- });
30233
- let tools;
30234
- const settingsTools = providerOptions.tools ?? this.settings.tools;
30235
- const optionsTools = options.tools;
30236
- const shouldUseSettingsTools = settingsTools && settingsTools.length > 0 && (!optionsTools || optionsTools.length === 0);
30237
- const shouldUseOptionsTools = !!(optionsTools && optionsTools.length > 0);
30238
- if (settingsTools && settingsTools.length > 0 && optionsTools && optionsTools.length > 0) {
30239
- warnings.push({
30240
- type: "other",
30241
- message: "Both settings.tools and call options.tools were provided; preferring call options.tools."
30242
- });
30243
- }
30244
- if (shouldUseSettingsTools) {
30245
- tools = settingsTools;
30246
- } else {
30247
- const availableTools = shouldUseOptionsTools ? optionsTools : void 0;
30248
- tools = availableTools?.map((tool) => {
30249
- if (tool.type === "function") {
30250
- const inputSchema = tool.inputSchema;
30251
- const toolWithParams = tool;
30252
- let parameters;
30253
- if (toolWithParams.parameters && isZodSchema(toolWithParams.parameters)) {
30254
- try {
30255
- const jsonSchema = zodToJsonSchema(
30256
- toolWithParams.parameters,
30257
- {
30258
- $refStrategy: "none"
30259
- }
30260
- );
30261
- const schemaRecord = jsonSchema;
30262
- delete schemaRecord.$schema;
30263
- parameters = buildSAPToolParameters(schemaRecord);
30264
- } catch {
30265
- warnings.push({
30266
- type: "unsupported-tool",
30267
- tool,
30268
- details: "Failed to convert tool Zod schema to JSON Schema. Falling back to empty object schema."
30269
- });
30270
- parameters = buildSAPToolParameters({});
30271
- }
30272
- } else if (inputSchema && Object.keys(inputSchema).length > 0) {
30273
- const hasProperties = inputSchema.properties && typeof inputSchema.properties === "object" && Object.keys(inputSchema.properties).length > 0;
30274
- if (hasProperties) {
30275
- parameters = buildSAPToolParameters(inputSchema);
30276
- } else {
30277
- parameters = buildSAPToolParameters({});
30278
- }
30279
- } else {
30280
- parameters = buildSAPToolParameters({});
30281
- }
30282
- return {
30283
- type: "function",
30284
- function: {
30285
- name: tool.name,
30286
- description: tool.description,
30287
- parameters
30288
- }
30289
- };
30290
- } else {
30291
- warnings.push({
30292
- type: "unsupported-tool",
30293
- tool
30294
- });
30295
- return null;
30296
- }
30297
- }).filter((t) => t !== null);
30298
- }
30299
- const supportsN = !this.modelId.startsWith("amazon--") && !this.modelId.startsWith("anthropic--");
30300
- const modelParams = {};
30301
- const maxTokens = options.maxOutputTokens ?? providerOptions.modelParams?.maxTokens ?? this.settings.modelParams?.maxTokens;
30302
- if (maxTokens !== void 0) modelParams.max_tokens = maxTokens;
30303
- const temperature = options.temperature ?? providerOptions.modelParams?.temperature ?? this.settings.modelParams?.temperature;
30304
- if (temperature !== void 0) modelParams.temperature = temperature;
30305
- const topP = options.topP ?? providerOptions.modelParams?.topP ?? this.settings.modelParams?.topP;
30306
- if (topP !== void 0) modelParams.top_p = topP;
30307
- if (options.topK !== void 0) modelParams.top_k = options.topK;
30308
- const frequencyPenalty = options.frequencyPenalty ?? providerOptions.modelParams?.frequencyPenalty ?? this.settings.modelParams?.frequencyPenalty;
30309
- if (frequencyPenalty !== void 0) {
30310
- modelParams.frequency_penalty = frequencyPenalty;
30311
- }
30312
- const presencePenalty = options.presencePenalty ?? providerOptions.modelParams?.presencePenalty ?? this.settings.modelParams?.presencePenalty;
30313
- if (presencePenalty !== void 0) {
30314
- modelParams.presence_penalty = presencePenalty;
30315
- }
30316
- if (supportsN) {
30317
- const nValue = providerOptions.modelParams?.n ?? this.settings.modelParams?.n;
30318
- if (nValue !== void 0) {
30319
- modelParams.n = nValue;
30320
- }
30321
- }
30322
- const parallelToolCalls = providerOptions.modelParams?.parallel_tool_calls ?? this.settings.modelParams?.parallel_tool_calls;
30323
- if (parallelToolCalls !== void 0) {
30324
- modelParams.parallel_tool_calls = parallelToolCalls;
30325
- }
30326
- if (options.stopSequences && options.stopSequences.length > 0) {
30327
- modelParams.stop = options.stopSequences;
30328
- }
30329
- if (options.seed !== void 0) {
30330
- modelParams.seed = options.seed;
30331
- }
30332
- validateModelParameters(
30333
- {
30334
- temperature,
30335
- topP,
30336
- frequencyPenalty,
30337
- presencePenalty,
30338
- maxTokens,
30339
- n: modelParams.n
30340
- },
30341
- warnings
30342
- );
30343
- if (options.toolChoice && options.toolChoice.type !== "auto") {
30344
- warnings.push({
30345
- type: "unsupported-setting",
30346
- setting: "toolChoice",
30347
- details: `SAP AI SDK does not support toolChoice '${options.toolChoice.type}'. Using default 'auto' behavior.`
30348
- });
30349
- }
30350
- if (options.responseFormat?.type === "json") {
30351
- warnings.push({
30352
- type: "other",
30353
- message: "responseFormat JSON mode is forwarded to the underlying model; support and schema adherence depend on the model/deployment."
30354
- });
30355
- }
30356
- const responseFormat = options.responseFormat?.type === "json" ? options.responseFormat.schema ? {
30357
- type: "json_schema",
30358
- json_schema: {
30359
- name: options.responseFormat.name ?? "response",
30360
- description: options.responseFormat.description,
30361
- schema: options.responseFormat.schema,
30362
- strict: null
30363
- }
30364
- } : { type: "json_object" } : void 0;
30365
- const orchestrationConfig = {
30366
- promptTemplating: {
30367
- model: {
30368
- name: this.modelId,
30369
- version: providerOptions.modelVersion ?? this.settings.modelVersion ?? "latest",
30370
- params: modelParams
30371
- },
30372
- prompt: {
30373
- template: [],
30374
- tools: tools && tools.length > 0 ? tools : void 0,
30375
- ...responseFormat ? { response_format: responseFormat } : {}
30376
- }
30377
- },
30378
- ...(() => {
30379
- const masking = providerOptions.masking ?? this.settings.masking;
30380
- return masking && Object.keys(masking).length > 0 ? { masking } : {};
30381
- })(),
30382
- ...(() => {
30383
- const filtering = providerOptions.filtering ?? this.settings.filtering;
30384
- return filtering && Object.keys(filtering).length > 0 ? { filtering } : {};
30385
- })()
30139
+ get provider() {
30140
+ return this.config.provider;
30141
+ }
30142
+ /**
30143
+ * Returns supported URL patterns for different content types.
30144
+ *
30145
+ * @returns Record of content types to regex patterns
30146
+ */
30147
+ get supportedUrls() {
30148
+ return {
30149
+ "image/*": [/^https:\/\/.+$/i, /^data:image\/.*$/]
30386
30150
  };
30387
- return { orchestrationConfig, messages, warnings };
30388
30151
  }
30152
+ config;
30153
+ settings;
30389
30154
  /**
30390
- * Creates an OrchestrationClient instance.
30155
+ * Creates a new SAP AI Chat Language Model instance.
30391
30156
  *
30392
- * @param config - Orchestration module configuration
30393
- * @returns OrchestrationClient instance
30394
- * @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.
30395
30163
  */
30396
- createClient(config) {
30397
- return new OrchestrationClient(
30398
- config,
30399
- this.config.deploymentConfig,
30400
- this.config.destination
30401
- );
30164
+ constructor(modelId, settings, config) {
30165
+ this.settings = settings;
30166
+ this.config = config;
30167
+ this.modelId = modelId;
30402
30168
  }
30403
30169
  /**
30404
30170
  * Generates a single completion (non-streaming).
30405
30171
  *
30406
- * This method implements the `LanguageModelV2.doGenerate` interface,
30172
+ * This method implements the `LanguageModelV3.doGenerate` interface,
30407
30173
  * sending a request to SAP AI Core and returning the complete response.
30408
30174
  *
30409
30175
  * **Features:**
@@ -30411,6 +30177,13 @@ var SAPAIChatLanguageModel = class {
30411
30177
  * - Multi-modal input (text + images)
30412
30178
  * - Data masking (if configured)
30413
30179
  * - Content filtering (if configured)
30180
+ * - Abort signal support (via Promise.race)
30181
+ *
30182
+ * **Note on Abort Signal:**
30183
+ * The abort signal implementation uses Promise.race to reject the promise when
30184
+ * the signal is aborted. However, this does not cancel the underlying HTTP request
30185
+ * to SAP AI Core - the request continues executing on the server. This is a
30186
+ * limitation of the SAP AI SDK's chatCompletion API.
30414
30187
  *
30415
30188
  * @param options - Generation options including prompt, tools, and settings
30416
30189
  * @returns Promise resolving to the generation result with content, usage, and metadata
@@ -30429,7 +30202,7 @@ var SAPAIChatLanguageModel = class {
30429
30202
  */
30430
30203
  async doGenerate(options) {
30431
30204
  try {
30432
- const { orchestrationConfig, messages, warnings } = this.buildOrchestrationConfig(options);
30205
+ const { messages, orchestrationConfig, warnings } = this.buildOrchestrationConfig(options);
30433
30206
  const client = this.createClient(orchestrationConfig);
30434
30207
  const promptTemplating = orchestrationConfig.promptTemplating;
30435
30208
  const requestBody = {
@@ -30446,6 +30219,14 @@ var SAPAIChatLanguageModel = class {
30446
30219
  ...(() => {
30447
30220
  const filtering = orchestrationConfig.filtering;
30448
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 } : {};
30449
30230
  })()
30450
30231
  };
30451
30232
  const response = await (async () => {
@@ -30483,7 +30264,7 @@ var SAPAIChatLanguageModel = class {
30483
30264
  Object.entries(responseHeadersRaw).flatMap(([key, value]) => {
30484
30265
  if (typeof value === "string") return [[key, value]];
30485
30266
  if (Array.isArray(value)) {
30486
- const strings = value.filter((item) => typeof item === "string").join(",");
30267
+ const strings = value.filter((item) => typeof item === "string").join("; ");
30487
30268
  return strings.length > 0 ? [[key, strings]] : [];
30488
30269
  }
30489
30270
  if (typeof value === "number" || typeof value === "boolean") {
@@ -30496,18 +30277,18 @@ var SAPAIChatLanguageModel = class {
30496
30277
  const textContent = response.getContent();
30497
30278
  if (textContent) {
30498
30279
  content.push({
30499
- type: "text",
30500
- text: textContent
30280
+ text: textContent,
30281
+ type: "text"
30501
30282
  });
30502
30283
  }
30503
30284
  const toolCalls = response.getToolCalls();
30504
30285
  if (toolCalls) {
30505
30286
  for (const toolCall of toolCalls) {
30506
30287
  content.push({
30507
- type: "tool-call",
30288
+ input: toolCall.function.arguments,
30508
30289
  toolCallId: toolCall.id,
30509
30290
  toolName: toolCall.function.name,
30510
- input: toolCall.function.arguments
30291
+ type: "tool-call"
30511
30292
  });
30512
30293
  }
30513
30294
  }
@@ -30516,18 +30297,13 @@ var SAPAIChatLanguageModel = class {
30516
30297
  const finishReason = mapFinishReason(finishReasonRaw);
30517
30298
  const rawResponseBody = {
30518
30299
  content: textContent,
30519
- toolCalls,
30300
+ finishReason: finishReasonRaw,
30520
30301
  tokenUsage,
30521
- finishReason: finishReasonRaw
30302
+ toolCalls
30522
30303
  };
30523
30304
  return {
30524
30305
  content,
30525
30306
  finishReason,
30526
- usage: {
30527
- inputTokens: tokenUsage.prompt_tokens,
30528
- outputTokens: tokenUsage.completion_tokens,
30529
- totalTokens: tokenUsage.total_tokens
30530
- },
30531
30307
  providerMetadata: {
30532
30308
  "sap-ai": {
30533
30309
  finishReason: finishReasonRaw ?? "unknown",
@@ -30539,37 +30315,61 @@ var SAPAIChatLanguageModel = class {
30539
30315
  body: requestBody
30540
30316
  },
30541
30317
  response: {
30542
- timestamp: /* @__PURE__ */ new Date(),
30543
- modelId: this.modelId,
30318
+ body: rawResponseBody,
30544
30319
  headers: responseHeaders,
30545
- 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
+ }
30546
30335
  },
30547
30336
  warnings
30548
30337
  };
30549
30338
  } catch (error) {
30550
30339
  throw convertToAISDKError(error, {
30551
30340
  operation: "doGenerate",
30552
- url: "sap-ai:orchestration",
30553
- requestBody: createAISDKRequestBodySummary(options)
30341
+ requestBody: createAISDKRequestBodySummary(options),
30342
+ url: "sap-ai:orchestration"
30554
30343
  });
30555
30344
  }
30556
30345
  }
30557
30346
  /**
30558
30347
  * Generates a streaming completion.
30559
30348
  *
30560
- * This method implements the `LanguageModelV2.doStream` interface,
30349
+ * This method implements the `LanguageModelV3.doStream` interface,
30561
30350
  * sending a streaming request to SAP AI Core and returning a stream of response parts.
30562
30351
  *
30563
30352
  * **Stream Events:**
30564
- * - `stream-start` - Stream initialization
30353
+ * - `stream-start` - Stream initialization with warnings
30565
30354
  * - `response-metadata` - Response metadata (model, timestamp)
30566
- * - `text-start` - Text generation starts
30567
- * - `text-delta` - Incremental text chunks
30568
- * - `text-end` - Text generation completes
30569
- * - `tool-call` - Tool call detected
30355
+ * - `text-start` - Text block begins (with unique ID)
30356
+ * - `text-delta` - Incremental text chunks (with block ID)
30357
+ * - `text-end` - Text block completes (with accumulated text)
30358
+ * - `tool-input-start` - Tool input begins
30359
+ * - `tool-input-delta` - Tool input chunk
30360
+ * - `tool-input-end` - Tool input completes
30361
+ * - `tool-call` - Complete tool call
30570
30362
  * - `finish` - Stream completes with usage and finish reason
30571
30363
  * - `error` - Error occurred
30572
30364
  *
30365
+ * **Stream Structure:**
30366
+ * - Text blocks have explicit lifecycle with unique IDs
30367
+ * - Finish reason format: `{ unified: string, raw?: string }`
30368
+ * - Usage format: `{ inputTokens: { total, ... }, outputTokens: { total, ... } }`
30369
+ * - Warnings only in `stream-start` event
30370
+ *
30371
+ * @see {@link https://sdk.vercel.ai/docs/ai-sdk-core/streaming Vercel AI SDK Streaming}
30372
+ *
30573
30373
  * @param options - Streaming options including prompt, tools, and settings
30574
30374
  * @returns Promise resolving to stream and request metadata
30575
30375
  *
@@ -30585,12 +30385,17 @@ var SAPAIChatLanguageModel = class {
30585
30385
  * if (part.type === 'text-delta') {
30586
30386
  * process.stdout.write(part.delta);
30587
30387
  * }
30388
+ * if (part.type === 'text-end') {
30389
+ * console.log('Block complete:', part.id, part.text);
30390
+ * }
30588
30391
  * }
30589
30392
  * ```
30393
+ *
30394
+ * @since 4.0.0
30590
30395
  */
30591
30396
  async doStream(options) {
30592
30397
  try {
30593
- const { orchestrationConfig, messages, warnings } = this.buildOrchestrationConfig(options);
30398
+ const { messages, orchestrationConfig, warnings } = this.buildOrchestrationConfig(options);
30594
30399
  const client = this.createClient(orchestrationConfig);
30595
30400
  const promptTemplating = orchestrationConfig.promptTemplating;
30596
30401
  const requestBody = {
@@ -30614,15 +30419,28 @@ var SAPAIChatLanguageModel = class {
30614
30419
  options.abortSignal,
30615
30420
  { promptTemplating: { include_usage: true } }
30616
30421
  );
30422
+ const idGenerator = new StreamIdGenerator();
30423
+ let textBlockId = null;
30617
30424
  const streamState = {
30618
- finishReason: "unknown",
30619
- usage: {
30620
- inputTokens: void 0,
30621
- outputTokens: void 0,
30622
- totalTokens: void 0
30425
+ activeText: false,
30426
+ finishReason: {
30427
+ raw: void 0,
30428
+ unified: "other"
30623
30429
  },
30624
30430
  isFirstChunk: true,
30625
- activeText: false
30431
+ usage: {
30432
+ inputTokens: {
30433
+ cacheRead: void 0,
30434
+ cacheWrite: void 0,
30435
+ noCache: void 0,
30436
+ total: void 0
30437
+ },
30438
+ outputTokens: {
30439
+ reasoning: void 0,
30440
+ text: void 0,
30441
+ total: void 0
30442
+ }
30443
+ }
30626
30444
  };
30627
30445
  const toolCallsInProgress = /* @__PURE__ */ new Map();
30628
30446
  const sdkStream = streamResponse.stream;
@@ -30630,6 +30448,11 @@ var SAPAIChatLanguageModel = class {
30630
30448
  const warningsSnapshot = [...warnings];
30631
30449
  const warningsOut = [...warningsSnapshot];
30632
30450
  const transformedStream = new ReadableStream({
30451
+ cancel(reason) {
30452
+ if (reason) {
30453
+ console.debug("SAP AI stream cancelled:", reason);
30454
+ }
30455
+ },
30633
30456
  async start(controller) {
30634
30457
  controller.enqueue({
30635
30458
  type: "stream-start",
@@ -30640,26 +30463,32 @@ var SAPAIChatLanguageModel = class {
30640
30463
  if (streamState.isFirstChunk) {
30641
30464
  streamState.isFirstChunk = false;
30642
30465
  controller.enqueue({
30643
- type: "response-metadata",
30644
30466
  modelId,
30645
- timestamp: /* @__PURE__ */ new Date()
30467
+ timestamp: /* @__PURE__ */ new Date(),
30468
+ type: "response-metadata"
30646
30469
  });
30647
30470
  }
30648
30471
  const deltaToolCalls = chunk.getDeltaToolCalls();
30649
30472
  if (Array.isArray(deltaToolCalls) && deltaToolCalls.length > 0) {
30650
- streamState.finishReason = "tool-calls";
30473
+ streamState.finishReason = {
30474
+ raw: void 0,
30475
+ unified: "tool-calls"
30476
+ };
30651
30477
  }
30652
30478
  const deltaContent = chunk.getDeltaContent();
30653
- if (typeof deltaContent === "string" && deltaContent.length > 0 && streamState.finishReason !== "tool-calls") {
30479
+ if (typeof deltaContent === "string" && deltaContent.length > 0 && streamState.finishReason.unified !== "tool-calls") {
30654
30480
  if (!streamState.activeText) {
30655
- controller.enqueue({ type: "text-start", id: "0" });
30481
+ textBlockId = idGenerator.generateTextBlockId();
30482
+ controller.enqueue({ id: textBlockId, type: "text-start" });
30656
30483
  streamState.activeText = true;
30657
30484
  }
30658
- controller.enqueue({
30659
- type: "text-delta",
30660
- id: "0",
30661
- delta: deltaContent
30662
- });
30485
+ if (textBlockId) {
30486
+ controller.enqueue({
30487
+ delta: deltaContent,
30488
+ id: textBlockId,
30489
+ type: "text-delta"
30490
+ });
30491
+ }
30663
30492
  }
30664
30493
  if (Array.isArray(deltaToolCalls) && deltaToolCalls.length > 0) {
30665
30494
  for (const toolCallChunk of deltaToolCalls) {
@@ -30669,11 +30498,11 @@ var SAPAIChatLanguageModel = class {
30669
30498
  }
30670
30499
  if (!toolCallsInProgress.has(index)) {
30671
30500
  toolCallsInProgress.set(index, {
30672
- id: toolCallChunk.id ?? `tool_${String(index)}`,
30673
- toolName: toolCallChunk.function?.name,
30674
30501
  arguments: "",
30502
+ didEmitCall: false,
30675
30503
  didEmitInputStart: false,
30676
- didEmitCall: false
30504
+ id: toolCallChunk.id ?? `tool_${String(index)}`,
30505
+ toolName: toolCallChunk.function?.name
30677
30506
  });
30678
30507
  }
30679
30508
  const tc = toolCallsInProgress.get(index);
@@ -30688,9 +30517,9 @@ var SAPAIChatLanguageModel = class {
30688
30517
  if (!tc.didEmitInputStart && tc.toolName) {
30689
30518
  tc.didEmitInputStart = true;
30690
30519
  controller.enqueue({
30691
- type: "tool-input-start",
30692
30520
  id: tc.id,
30693
- toolName: tc.toolName
30521
+ toolName: tc.toolName,
30522
+ type: "tool-input-start"
30694
30523
  });
30695
30524
  }
30696
30525
  const argumentsDelta = toolCallChunk.function?.arguments;
@@ -30698,9 +30527,9 @@ var SAPAIChatLanguageModel = class {
30698
30527
  tc.arguments += argumentsDelta;
30699
30528
  if (tc.didEmitInputStart) {
30700
30529
  controller.enqueue({
30701
- type: "tool-input-delta",
30530
+ delta: argumentsDelta,
30702
30531
  id: tc.id,
30703
- delta: argumentsDelta
30532
+ type: "tool-input-delta"
30704
30533
  });
30705
30534
  }
30706
30535
  }
@@ -30709,7 +30538,7 @@ var SAPAIChatLanguageModel = class {
30709
30538
  const chunkFinishReason = chunk.getFinishReason();
30710
30539
  if (chunkFinishReason) {
30711
30540
  streamState.finishReason = mapFinishReason(chunkFinishReason);
30712
- if (streamState.finishReason === "tool-calls") {
30541
+ if (streamState.finishReason.unified === "tool-calls") {
30713
30542
  const toolCalls2 = Array.from(toolCallsInProgress.values());
30714
30543
  for (const tc of toolCalls2) {
30715
30544
  if (tc.didEmitCall) {
@@ -30718,28 +30547,28 @@ var SAPAIChatLanguageModel = class {
30718
30547
  if (!tc.didEmitInputStart) {
30719
30548
  tc.didEmitInputStart = true;
30720
30549
  controller.enqueue({
30721
- type: "tool-input-start",
30722
30550
  id: tc.id,
30723
- toolName: tc.toolName ?? ""
30551
+ toolName: tc.toolName ?? "",
30552
+ type: "tool-input-start"
30724
30553
  });
30725
30554
  }
30726
30555
  if (!tc.toolName) {
30727
30556
  warningsOut.push({
30728
- type: "other",
30729
- 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"
30730
30559
  });
30731
30560
  }
30732
30561
  tc.didEmitCall = true;
30733
- controller.enqueue({ type: "tool-input-end", id: tc.id });
30562
+ controller.enqueue({ id: tc.id, type: "tool-input-end" });
30734
30563
  controller.enqueue({
30735
- type: "tool-call",
30564
+ input: tc.arguments,
30736
30565
  toolCallId: tc.id,
30737
30566
  toolName: tc.toolName ?? "",
30738
- input: tc.arguments
30567
+ type: "tool-call"
30739
30568
  });
30740
30569
  }
30741
- if (streamState.activeText) {
30742
- controller.enqueue({ type: "text-end", id: "0" });
30570
+ if (streamState.activeText && textBlockId) {
30571
+ controller.enqueue({ id: textBlockId, type: "text-end" });
30743
30572
  streamState.activeText = false;
30744
30573
  }
30745
30574
  }
@@ -30754,105 +30583,399 @@ var SAPAIChatLanguageModel = class {
30754
30583
  if (!tc.didEmitInputStart) {
30755
30584
  tc.didEmitInputStart = true;
30756
30585
  controller.enqueue({
30757
- type: "tool-input-start",
30758
30586
  id: tc.id,
30759
- toolName: tc.toolName ?? ""
30587
+ toolName: tc.toolName ?? "",
30588
+ type: "tool-input-start"
30760
30589
  });
30761
30590
  }
30762
30591
  if (!tc.toolName) {
30763
30592
  warningsOut.push({
30764
- type: "other",
30765
- 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"
30766
30595
  });
30767
30596
  }
30768
30597
  didEmitAnyToolCalls = true;
30769
30598
  tc.didEmitCall = true;
30770
- controller.enqueue({ type: "tool-input-end", id: tc.id });
30599
+ controller.enqueue({ id: tc.id, type: "tool-input-end" });
30771
30600
  controller.enqueue({
30772
- type: "tool-call",
30601
+ input: tc.arguments,
30773
30602
  toolCallId: tc.id,
30774
30603
  toolName: tc.toolName ?? "",
30775
- input: tc.arguments
30604
+ type: "tool-call"
30605
+ });
30606
+ }
30607
+ if (streamState.activeText && textBlockId) {
30608
+ controller.enqueue({ id: textBlockId, type: "text-end" });
30609
+ }
30610
+ const finalFinishReason = streamResponse.getFinishReason();
30611
+ if (finalFinishReason) {
30612
+ streamState.finishReason = mapFinishReason(finalFinishReason);
30613
+ } else if (didEmitAnyToolCalls) {
30614
+ streamState.finishReason = {
30615
+ raw: void 0,
30616
+ unified: "tool-calls"
30617
+ };
30618
+ }
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"
30776
30722
  });
30723
+ parameters = buildSAPToolParameters({});
30777
30724
  }
30778
- if (streamState.activeText) {
30779
- controller.enqueue({ type: "text-end", id: "0" });
30780
- }
30781
- const finalFinishReason = streamResponse.getFinishReason();
30782
- if (finalFinishReason) {
30783
- streamState.finishReason = mapFinishReason(finalFinishReason);
30784
- } else if (didEmitAnyToolCalls) {
30785
- streamState.finishReason = "tool-calls";
30786
- }
30787
- const finalUsage = streamResponse.getTokenUsage();
30788
- if (finalUsage) {
30789
- streamState.usage.inputTokens = finalUsage.prompt_tokens;
30790
- streamState.usage.outputTokens = finalUsage.completion_tokens;
30791
- streamState.usage.totalTokens = finalUsage.total_tokens;
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({});
30792
30731
  }
30793
- controller.enqueue({
30794
- type: "finish",
30795
- finishReason: streamState.finishReason,
30796
- usage: streamState.usage
30797
- });
30798
- controller.close();
30799
- } catch (error) {
30800
- const aiError = convertToAISDKError(error, {
30801
- operation: "doStream",
30802
- url: "sap-ai:orchestration",
30803
- requestBody: createAISDKRequestBodySummary(options)
30804
- });
30805
- controller.enqueue({
30806
- type: "error",
30807
- error: aiError instanceof Error ? aiError : new Error(String(aiError))
30808
- });
30809
- controller.close();
30810
- }
30811
- },
30812
- cancel(reason) {
30813
- if (reason) {
30814
- console.debug("SAP AI stream cancelled:", reason);
30732
+ } else {
30733
+ parameters = buildSAPToolParameters({});
30815
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;
30816
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"
30817
30802
  });
30818
- return {
30819
- stream: transformedStream,
30820
- request: {
30821
- body: requestBody
30822
- },
30823
- warnings: warningsOut
30824
- };
30825
- } catch (error) {
30826
- throw convertToAISDKError(error, {
30827
- operation: "doStream",
30828
- url: "sap-ai:orchestration",
30829
- 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"
30830
30808
  });
30831
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
+ );
30832
30864
  }
30833
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
+ }
30834
30918
  function mapFinishReason(reason) {
30835
- if (!reason) return "unknown";
30919
+ const raw = reason;
30920
+ if (!reason) return { raw, unified: "other" };
30836
30921
  switch (reason.toLowerCase()) {
30837
- case "stop":
30922
+ case "content_filter":
30923
+ return { raw, unified: "content-filter" };
30838
30924
  case "end_turn":
30839
- case "stop_sequence":
30840
30925
  case "eos":
30841
- return "stop";
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" };
30842
30935
  case "length":
30843
30936
  case "max_tokens":
30844
30937
  case "max_tokens_reached":
30845
- return "length";
30846
- case "tool_calls":
30847
- case "tool_call":
30848
- case "function_call":
30849
- return "tool-calls";
30850
- case "content_filter":
30851
- return "content-filter";
30852
- case "error":
30853
- return "error";
30938
+ return { raw, unified: "length" };
30854
30939
  default:
30855
- return "other";
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
+ });
30856
30979
  }
30857
30980
  }
30858
30981
 
@@ -30870,19 +30993,19 @@ function createSAPAIProvider(options = {}) {
30870
30993
  const mergedSettings = {
30871
30994
  ...options.defaultSettings,
30872
30995
  ...settings,
30996
+ filtering: settings.filtering ?? options.defaultSettings?.filtering,
30997
+ // Complex objects: override, do not merge
30998
+ masking: settings.masking ?? options.defaultSettings?.masking,
30873
30999
  modelParams: {
30874
31000
  ...options.defaultSettings?.modelParams ?? {},
30875
31001
  ...settings.modelParams ?? {}
30876
31002
  },
30877
- // Complex objects: override, do not merge
30878
- masking: settings.masking ?? options.defaultSettings?.masking,
30879
- filtering: settings.filtering ?? options.defaultSettings?.filtering,
30880
31003
  tools: settings.tools ?? options.defaultSettings?.tools
30881
31004
  };
30882
- return new SAPAIChatLanguageModel(modelId, mergedSettings, {
30883
- provider: "sap-ai",
31005
+ return new SAPAILanguageModel(modelId, mergedSettings, {
30884
31006
  deploymentConfig,
30885
- destination: options.destination
31007
+ destination: options.destination,
31008
+ provider: "sap-ai"
30886
31009
  });
30887
31010
  };
30888
31011
  const provider = function(modelId, settings) {
@@ -30898,23 +31021,19 @@ function createSAPAIProvider(options = {}) {
30898
31021
  }
30899
31022
  var sapai = createSAPAIProvider();
30900
31023
 
30901
- // src/sap-ai-chat-settings.ts
31024
+ // src/sap-ai-settings.ts
30902
31025
  import {
30903
- buildDpiMaskingProvider,
30904
31026
  buildAzureContentSafetyFilter,
30905
- buildLlamaGuard38BFilter,
30906
31027
  buildDocumentGroundingConfig,
30907
- buildTranslationConfig
31028
+ buildDpiMaskingProvider,
31029
+ buildLlamaGuard38BFilter,
31030
+ buildTranslationConfig,
31031
+ isConfigReference
30908
31032
  } from "@sap-ai-sdk/orchestration";
30909
-
30910
- // src/types/completion-request.ts
30911
- import { isConfigReference } from "@sap-ai-sdk/orchestration";
30912
-
30913
- // src/types/completion-response.ts
30914
31033
  import {
30915
31034
  OrchestrationResponse,
30916
- OrchestrationStreamResponse,
30917
- OrchestrationStreamChunkResponse
31035
+ OrchestrationStreamChunkResponse,
31036
+ OrchestrationStreamResponse
30918
31037
  } from "@sap-ai-sdk/orchestration";
30919
31038
 
30920
31039
  // src/index.ts