@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/README.md +168 -113
- package/dist/index.cjs +713 -595
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +324 -223
- package/dist/index.d.ts +324 -223
- package/dist/index.js +713 -594
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
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
|
|
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 = "--------------------------" +
|
|
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
|
|
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(
|
|
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-
|
|
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
|
-
|
|
29761
|
-
|
|
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
|
-
|
|
29834
|
-
|
|
29893
|
+
content: contentParts[0].text ?? "",
|
|
29894
|
+
role: "user"
|
|
29835
29895
|
} : {
|
|
29836
|
-
|
|
29837
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30012
|
+
statusCode: 500,
|
|
30013
|
+
url: context?.url ?? ""
|
|
30061
30014
|
});
|
|
30062
30015
|
}
|
|
30063
|
-
|
|
30064
|
-
|
|
30065
|
-
|
|
30066
|
-
if (
|
|
30067
|
-
|
|
30068
|
-
|
|
30069
|
-
|
|
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
|
|
30123
|
-
|
|
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
|
|
30126
|
-
if (
|
|
30031
|
+
function isOrchestrationErrorResponse(error) {
|
|
30032
|
+
if (error === null || typeof error !== "object" || !("error" in error)) {
|
|
30127
30033
|
return false;
|
|
30128
30034
|
}
|
|
30129
|
-
const
|
|
30130
|
-
|
|
30131
|
-
|
|
30132
|
-
|
|
30133
|
-
|
|
30134
|
-
|
|
30135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30156
|
-
|
|
30157
|
-
|
|
30158
|
-
|
|
30159
|
-
|
|
30160
|
-
|
|
30161
|
-
|
|
30162
|
-
|
|
30163
|
-
|
|
30164
|
-
|
|
30165
|
-
|
|
30166
|
-
|
|
30167
|
-
|
|
30168
|
-
|
|
30169
|
-
|
|
30170
|
-
|
|
30171
|
-
|
|
30172
|
-
|
|
30173
|
-
|
|
30174
|
-
|
|
30175
|
-
|
|
30176
|
-
|
|
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
|
-
*
|
|
30069
|
+
* Generates a unique ID for a text block.
|
|
30196
30070
|
*
|
|
30197
|
-
* @returns
|
|
30071
|
+
* @returns RFC 4122-compliant UUID string
|
|
30198
30072
|
*/
|
|
30199
|
-
|
|
30200
|
-
return
|
|
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
|
-
*
|
|
30099
|
+
* Generates text completion using SAP AI Core's Orchestration API.
|
|
30222
30100
|
*
|
|
30223
|
-
*
|
|
30224
|
-
*
|
|
30225
|
-
*
|
|
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
|
-
|
|
30228
|
-
|
|
30229
|
-
|
|
30230
|
-
|
|
30231
|
-
|
|
30232
|
-
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
|
|
30237
|
-
|
|
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
|
|
30155
|
+
* Creates a new SAP AI Chat Language Model instance.
|
|
30391
30156
|
*
|
|
30392
|
-
* @param
|
|
30393
|
-
* @
|
|
30394
|
-
* @
|
|
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
|
-
|
|
30397
|
-
|
|
30398
|
-
|
|
30399
|
-
|
|
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 `
|
|
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 {
|
|
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
|
-
|
|
30500
|
-
|
|
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
|
-
|
|
30288
|
+
input: toolCall.function.arguments,
|
|
30508
30289
|
toolCallId: toolCall.id,
|
|
30509
30290
|
toolName: toolCall.function.name,
|
|
30510
|
-
|
|
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
|
-
|
|
30300
|
+
finishReason: finishReasonRaw,
|
|
30520
30301
|
tokenUsage,
|
|
30521
|
-
|
|
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
|
-
|
|
30543
|
-
modelId: this.modelId,
|
|
30318
|
+
body: rawResponseBody,
|
|
30544
30319
|
headers: responseHeaders,
|
|
30545
|
-
|
|
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
|
-
|
|
30553
|
-
|
|
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 `
|
|
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
|
|
30567
|
-
* - `text-delta` - Incremental text chunks
|
|
30568
|
-
* - `text-end` - Text
|
|
30569
|
-
* - `tool-
|
|
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 {
|
|
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
|
-
|
|
30619
|
-
|
|
30620
|
-
|
|
30621
|
-
|
|
30622
|
-
totalTokens: void 0
|
|
30425
|
+
activeText: false,
|
|
30426
|
+
finishReason: {
|
|
30427
|
+
raw: void 0,
|
|
30428
|
+
unified: "other"
|
|
30623
30429
|
},
|
|
30624
30430
|
isFirstChunk: true,
|
|
30625
|
-
|
|
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 =
|
|
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
|
-
|
|
30481
|
+
textBlockId = idGenerator.generateTextBlockId();
|
|
30482
|
+
controller.enqueue({ id: textBlockId, type: "text-start" });
|
|
30656
30483
|
streamState.activeText = true;
|
|
30657
30484
|
}
|
|
30658
|
-
|
|
30659
|
-
|
|
30660
|
-
|
|
30661
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30530
|
+
delta: argumentsDelta,
|
|
30702
30531
|
id: tc.id,
|
|
30703
|
-
|
|
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
|
-
|
|
30729
|
-
|
|
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"
|
|
30562
|
+
controller.enqueue({ id: tc.id, type: "tool-input-end" });
|
|
30734
30563
|
controller.enqueue({
|
|
30735
|
-
|
|
30564
|
+
input: tc.arguments,
|
|
30736
30565
|
toolCallId: tc.id,
|
|
30737
30566
|
toolName: tc.toolName ?? "",
|
|
30738
|
-
|
|
30567
|
+
type: "tool-call"
|
|
30739
30568
|
});
|
|
30740
30569
|
}
|
|
30741
|
-
if (streamState.activeText) {
|
|
30742
|
-
controller.enqueue({ type: "text-end"
|
|
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
|
-
|
|
30765
|
-
|
|
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"
|
|
30599
|
+
controller.enqueue({ id: tc.id, type: "tool-input-end" });
|
|
30771
30600
|
controller.enqueue({
|
|
30772
|
-
|
|
30601
|
+
input: tc.arguments,
|
|
30773
30602
|
toolCallId: tc.id,
|
|
30774
30603
|
toolName: tc.toolName ?? "",
|
|
30775
|
-
|
|
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
|
-
|
|
30779
|
-
|
|
30780
|
-
|
|
30781
|
-
|
|
30782
|
-
|
|
30783
|
-
|
|
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
|
-
|
|
30794
|
-
|
|
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
|
-
|
|
30819
|
-
|
|
30820
|
-
|
|
30821
|
-
|
|
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
|
-
|
|
30919
|
+
const raw = reason;
|
|
30920
|
+
if (!reason) return { raw, unified: "other" };
|
|
30836
30921
|
switch (reason.toLowerCase()) {
|
|
30837
|
-
case "
|
|
30922
|
+
case "content_filter":
|
|
30923
|
+
return { raw, unified: "content-filter" };
|
|
30838
30924
|
case "end_turn":
|
|
30839
|
-
case "stop_sequence":
|
|
30840
30925
|
case "eos":
|
|
30841
|
-
|
|
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
|
|
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-
|
|
31024
|
+
// src/sap-ai-settings.ts
|
|
30902
31025
|
import {
|
|
30903
|
-
buildDpiMaskingProvider,
|
|
30904
31026
|
buildAzureContentSafetyFilter,
|
|
30905
|
-
buildLlamaGuard38BFilter,
|
|
30906
31027
|
buildDocumentGroundingConfig,
|
|
30907
|
-
|
|
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
|
-
|
|
30917
|
-
|
|
31035
|
+
OrchestrationStreamChunkResponse,
|
|
31036
|
+
OrchestrationStreamResponse
|
|
30918
31037
|
} from "@sap-ai-sdk/orchestration";
|
|
30919
31038
|
|
|
30920
31039
|
// src/index.ts
|