@jerome-benoit/sap-ai-provider 4.0.0 → 4.0.1
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/LICENSE.md +174 -188
- package/README.md +150 -69
- package/dist/index.cjs +140 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -147
- package/dist/index.d.ts +40 -147
- package/dist/index.js +140 -93
- package/dist/index.js.map +1 -1
- package/package.json +12 -6
package/dist/index.cjs
CHANGED
|
@@ -29979,18 +29979,29 @@ function convertToAISDKError(error, context) {
|
|
|
29979
29979
|
if (error instanceof import_provider2.APICallError || error instanceof import_provider2.LoadAPIKeyError) {
|
|
29980
29980
|
return error;
|
|
29981
29981
|
}
|
|
29982
|
-
|
|
29983
|
-
|
|
29982
|
+
const rootError = error instanceof Error && (0, import_util.isErrorWithCause)(error) ? error.rootCause : error;
|
|
29983
|
+
if (isOrchestrationErrorResponse(rootError)) {
|
|
29984
|
+
return convertSAPErrorToAPICallError(rootError, {
|
|
29984
29985
|
...context,
|
|
29985
29986
|
responseHeaders: context?.responseHeaders ?? getAxiosResponseHeaders(error)
|
|
29986
29987
|
});
|
|
29987
29988
|
}
|
|
29989
|
+
if (rootError instanceof Error) {
|
|
29990
|
+
const parsedError = tryExtractSAPErrorFromMessage(rootError.message);
|
|
29991
|
+
if (parsedError && isOrchestrationErrorResponse(parsedError)) {
|
|
29992
|
+
return convertSAPErrorToAPICallError(parsedError, {
|
|
29993
|
+
...context,
|
|
29994
|
+
responseHeaders: context?.responseHeaders ?? getAxiosResponseHeaders(error)
|
|
29995
|
+
});
|
|
29996
|
+
}
|
|
29997
|
+
}
|
|
29988
29998
|
const responseHeaders = context?.responseHeaders ?? getAxiosResponseHeaders(error);
|
|
29989
|
-
if (
|
|
29990
|
-
const errorMsg =
|
|
29991
|
-
|
|
29999
|
+
if (rootError instanceof Error) {
|
|
30000
|
+
const errorMsg = rootError.message.toLowerCase();
|
|
30001
|
+
const originalMsg = rootError.message;
|
|
30002
|
+
if (errorMsg.includes("authentication") || errorMsg.includes("unauthorized") || errorMsg.includes("aicore_service_key") || errorMsg.includes("invalid credentials") || errorMsg.includes("service credentials") || errorMsg.includes("service binding")) {
|
|
29992
30003
|
return new import_provider2.LoadAPIKeyError({
|
|
29993
|
-
message: `SAP AI Core authentication failed: ${
|
|
30004
|
+
message: `SAP AI Core authentication failed: ${originalMsg}
|
|
29994
30005
|
|
|
29995
30006
|
Make sure your AICORE_SERVICE_KEY environment variable is set correctly.
|
|
29996
30007
|
See: https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-service-key`
|
|
@@ -30000,15 +30011,79 @@ See: https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-serv
|
|
|
30000
30011
|
return new import_provider2.APICallError({
|
|
30001
30012
|
cause: error,
|
|
30002
30013
|
isRetryable: true,
|
|
30003
|
-
message: `Network error connecting to SAP AI Core: ${
|
|
30014
|
+
message: `Network error connecting to SAP AI Core: ${originalMsg}`,
|
|
30004
30015
|
requestBodyValues: context?.requestBody,
|
|
30005
30016
|
responseHeaders,
|
|
30006
30017
|
statusCode: 503,
|
|
30007
30018
|
url: context?.url ?? ""
|
|
30008
30019
|
});
|
|
30009
30020
|
}
|
|
30021
|
+
if (errorMsg.includes("could not resolve destination")) {
|
|
30022
|
+
return new import_provider2.APICallError({
|
|
30023
|
+
cause: error,
|
|
30024
|
+
isRetryable: false,
|
|
30025
|
+
message: `SAP AI Core destination error: ${originalMsg}
|
|
30026
|
+
|
|
30027
|
+
Check your destination configuration or provide a valid destinationName.`,
|
|
30028
|
+
requestBodyValues: context?.requestBody,
|
|
30029
|
+
responseHeaders,
|
|
30030
|
+
statusCode: 500,
|
|
30031
|
+
url: context?.url ?? ""
|
|
30032
|
+
});
|
|
30033
|
+
}
|
|
30034
|
+
if (errorMsg.includes("failed to resolve deployment") || errorMsg.includes("no deployment matched")) {
|
|
30035
|
+
return new import_provider2.APICallError({
|
|
30036
|
+
cause: error,
|
|
30037
|
+
isRetryable: false,
|
|
30038
|
+
message: `SAP AI Core deployment error: ${originalMsg}
|
|
30039
|
+
|
|
30040
|
+
Make sure you have a running orchestration deployment in your AI Core instance.
|
|
30041
|
+
See: https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-deployment-for-orchestration`,
|
|
30042
|
+
requestBodyValues: context?.requestBody,
|
|
30043
|
+
responseHeaders,
|
|
30044
|
+
statusCode: 404,
|
|
30045
|
+
url: context?.url ?? ""
|
|
30046
|
+
});
|
|
30047
|
+
}
|
|
30048
|
+
if (errorMsg.includes("filtered by the output filter")) {
|
|
30049
|
+
return new import_provider2.APICallError({
|
|
30050
|
+
cause: error,
|
|
30051
|
+
isRetryable: false,
|
|
30052
|
+
message: `Content was filtered: ${originalMsg}
|
|
30053
|
+
|
|
30054
|
+
The model's response was blocked by content safety filters. Try a different prompt.`,
|
|
30055
|
+
requestBodyValues: context?.requestBody,
|
|
30056
|
+
responseHeaders,
|
|
30057
|
+
statusCode: 400,
|
|
30058
|
+
url: context?.url ?? ""
|
|
30059
|
+
});
|
|
30060
|
+
}
|
|
30061
|
+
const statusMatch = /status code (\d+)/i.exec(originalMsg);
|
|
30062
|
+
if (statusMatch) {
|
|
30063
|
+
const extractedStatus = parseInt(statusMatch[1], 10);
|
|
30064
|
+
return new import_provider2.APICallError({
|
|
30065
|
+
cause: error,
|
|
30066
|
+
isRetryable: isRetryable(extractedStatus),
|
|
30067
|
+
message: `SAP AI Core request failed: ${originalMsg}`,
|
|
30068
|
+
requestBodyValues: context?.requestBody,
|
|
30069
|
+
responseHeaders,
|
|
30070
|
+
statusCode: extractedStatus,
|
|
30071
|
+
url: context?.url ?? ""
|
|
30072
|
+
});
|
|
30073
|
+
}
|
|
30074
|
+
if (errorMsg.includes("iterate over") || errorMsg.includes("consumed stream") || errorMsg.includes("parse message into json") || errorMsg.includes("no body")) {
|
|
30075
|
+
return new import_provider2.APICallError({
|
|
30076
|
+
cause: error,
|
|
30077
|
+
isRetryable: true,
|
|
30078
|
+
message: `SAP AI Core streaming error: ${originalMsg}`,
|
|
30079
|
+
requestBodyValues: context?.requestBody,
|
|
30080
|
+
responseHeaders,
|
|
30081
|
+
statusCode: 500,
|
|
30082
|
+
url: context?.url ?? ""
|
|
30083
|
+
});
|
|
30084
|
+
}
|
|
30010
30085
|
}
|
|
30011
|
-
const message =
|
|
30086
|
+
const message = rootError instanceof Error ? rootError.message : typeof rootError === "string" ? rootError : "Unknown error occurred";
|
|
30012
30087
|
const fullMessage = context?.operation ? `SAP AI Core ${context.operation} failed: ${message}` : `SAP AI Core error: ${message}`;
|
|
30013
30088
|
return new import_provider2.APICallError({
|
|
30014
30089
|
cause: error,
|
|
@@ -30069,13 +30144,35 @@ function normalizeHeaders(headers) {
|
|
|
30069
30144
|
if (entries.length === 0) return void 0;
|
|
30070
30145
|
return Object.fromEntries(entries);
|
|
30071
30146
|
}
|
|
30147
|
+
function tryExtractSAPErrorFromMessage(message) {
|
|
30148
|
+
const jsonMatch = /\{[\s\S]*\}/.exec(message);
|
|
30149
|
+
if (!jsonMatch) return null;
|
|
30150
|
+
try {
|
|
30151
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
30152
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
30153
|
+
return parsed;
|
|
30154
|
+
}
|
|
30155
|
+
if (parsed && typeof parsed === "object" && "message" in parsed) {
|
|
30156
|
+
return { error: parsed };
|
|
30157
|
+
}
|
|
30158
|
+
return null;
|
|
30159
|
+
} catch {
|
|
30160
|
+
return null;
|
|
30161
|
+
}
|
|
30162
|
+
}
|
|
30072
30163
|
|
|
30073
30164
|
// src/sap-ai-language-model.ts
|
|
30074
30165
|
var StreamIdGenerator = class {
|
|
30166
|
+
/**
|
|
30167
|
+
* Generates a unique ID for a response stream.
|
|
30168
|
+
* @returns RFC 4122-compliant UUID
|
|
30169
|
+
*/
|
|
30170
|
+
generateResponseId() {
|
|
30171
|
+
return crypto.randomUUID();
|
|
30172
|
+
}
|
|
30075
30173
|
/**
|
|
30076
30174
|
* Generates a unique ID for a text block.
|
|
30077
|
-
*
|
|
30078
|
-
* @returns RFC 4122-compliant UUID string
|
|
30175
|
+
* @returns RFC 4122-compliant UUID
|
|
30079
30176
|
*/
|
|
30080
30177
|
generateTextBlockId() {
|
|
30081
30178
|
return crypto.randomUUID();
|
|
@@ -30092,7 +30189,11 @@ var SAPAILanguageModel = class {
|
|
|
30092
30189
|
* SAP AI Core will fail the request at runtime.
|
|
30093
30190
|
*/
|
|
30094
30191
|
supportsImageUrls = true;
|
|
30095
|
-
/**
|
|
30192
|
+
/**
|
|
30193
|
+
* Multiple completions via the `n` parameter.
|
|
30194
|
+
* Note: Amazon and Anthropic models do not support this feature.
|
|
30195
|
+
* The provider silently omits the parameter for unsupported models.
|
|
30196
|
+
*/
|
|
30096
30197
|
supportsMultipleCompletions = true;
|
|
30097
30198
|
/** Parallel tool calls. */
|
|
30098
30199
|
supportsParallelToolCalls = true;
|
|
@@ -30103,52 +30204,14 @@ var SAPAILanguageModel = class {
|
|
|
30103
30204
|
/** Tool/function calling. */
|
|
30104
30205
|
supportsToolCalls = true;
|
|
30105
30206
|
/**
|
|
30106
|
-
*
|
|
30107
|
-
*
|
|
30108
|
-
* This method implements the `LanguageModelV3.doGenerate` interface,
|
|
30109
|
-
* providing synchronous (non-streaming) text generation with support for:
|
|
30110
|
-
* - Multi-turn conversations with system/user/assistant messages
|
|
30111
|
-
* - Tool calling (function calling) with structured outputs
|
|
30112
|
-
* - Multi-modal inputs (text + images)
|
|
30113
|
-
* - Data masking via SAP DPI
|
|
30114
|
-
* - Content filtering via Azure Content Safety or Llama Guard
|
|
30115
|
-
*
|
|
30116
|
-
* **Return Structure:**
|
|
30117
|
-
* - Finish reason: `{ unified: string, raw?: string }`
|
|
30118
|
-
* - Usage: Nested structure with token breakdown `{ inputTokens: { total, ... }, outputTokens: { total, ... } }`
|
|
30119
|
-
* - Warnings: Array of warnings with `type` and optional `feature` field
|
|
30120
|
-
*
|
|
30121
|
-
* @param options - Generation options including prompt, tools, temperature, etc.
|
|
30122
|
-
* @returns Promise resolving to generation result with content, usage, and metadata
|
|
30123
|
-
*
|
|
30124
|
-
* @throws {InvalidPromptError} If prompt format is invalid
|
|
30125
|
-
* @throws {InvalidArgumentError} If arguments are malformed
|
|
30126
|
-
* @throws {APICallError} If the SAP AI Core API call fails
|
|
30127
|
-
*
|
|
30128
|
-
* @example
|
|
30129
|
-
* ```typescript
|
|
30130
|
-
* const result = await model.doGenerate({
|
|
30131
|
-
* prompt: [
|
|
30132
|
-
* { role: 'user', content: [{ type: 'text', text: 'Hello!' }] }
|
|
30133
|
-
* ],
|
|
30134
|
-
* temperature: 0.7,
|
|
30135
|
-
* maxTokens: 100
|
|
30136
|
-
* });
|
|
30137
|
-
*
|
|
30138
|
-
* console.log(result.content); // Array of V3 content parts
|
|
30139
|
-
* console.log(result.finishReason.unified); // "stop", "length", "tool-calls", etc.
|
|
30140
|
-
* console.log(result.usage.inputTokens.total); // Total input tokens
|
|
30141
|
-
* ```
|
|
30142
|
-
*
|
|
30143
|
-
* @since 1.0.0
|
|
30144
|
-
* @since 4.0.0 Updated to LanguageModelV3 interface
|
|
30207
|
+
* Returns the provider identifier.
|
|
30208
|
+
* @returns The provider name
|
|
30145
30209
|
*/
|
|
30146
30210
|
get provider() {
|
|
30147
30211
|
return this.config.provider;
|
|
30148
30212
|
}
|
|
30149
30213
|
/**
|
|
30150
30214
|
* Returns supported URL patterns for different content types.
|
|
30151
|
-
*
|
|
30152
30215
|
* @returns Record of content types to regex patterns
|
|
30153
30216
|
*/
|
|
30154
30217
|
get supportedUrls() {
|
|
@@ -30160,13 +30223,10 @@ var SAPAILanguageModel = class {
|
|
|
30160
30223
|
settings;
|
|
30161
30224
|
/**
|
|
30162
30225
|
* Creates a new SAP AI Chat Language Model instance.
|
|
30163
|
-
*
|
|
30226
|
+
* @internal
|
|
30164
30227
|
* @param modelId - The model identifier
|
|
30165
30228
|
* @param settings - Model-specific configuration settings
|
|
30166
30229
|
* @param config - Internal configuration (deployment config, destination, etc.)
|
|
30167
|
-
*
|
|
30168
|
-
* @internal This constructor is not meant to be called directly.
|
|
30169
|
-
* Use the provider function instead.
|
|
30170
30230
|
*/
|
|
30171
30231
|
constructor(modelId, settings, config) {
|
|
30172
30232
|
this.settings = settings;
|
|
@@ -30188,15 +30248,12 @@ var SAPAILanguageModel = class {
|
|
|
30188
30248
|
*
|
|
30189
30249
|
* **Note on Abort Signal:**
|
|
30190
30250
|
* The abort signal implementation uses Promise.race to reject the promise when
|
|
30191
|
-
*
|
|
30192
|
-
*
|
|
30193
|
-
*
|
|
30194
|
-
*
|
|
30195
|
-
* @see https://github.com/SAP/ai-sdk-js/issues/1429 for tracking true cancellation support
|
|
30196
|
-
*
|
|
30251
|
+
* aborted. However, this does not cancel the underlying HTTP request to SAP AI Core -
|
|
30252
|
+
* the request continues executing on the server. This is a current limitation of the
|
|
30253
|
+
* SAP AI SDK's API. See https://github.com/SAP/ai-sdk-js/issues/1429
|
|
30197
30254
|
* @param options - Generation options including prompt, tools, and settings
|
|
30198
30255
|
* @returns Promise resolving to the generation result with content, usage, and metadata
|
|
30199
|
-
*
|
|
30256
|
+
* @since 1.0.0
|
|
30200
30257
|
* @example
|
|
30201
30258
|
* ```typescript
|
|
30202
30259
|
* const result = await model.doGenerate({
|
|
@@ -30355,40 +30412,29 @@ var SAPAILanguageModel = class {
|
|
|
30355
30412
|
/**
|
|
30356
30413
|
* Generates a streaming completion.
|
|
30357
30414
|
*
|
|
30358
|
-
*
|
|
30359
|
-
*
|
|
30415
|
+
* Implements `LanguageModelV3.doStream`, sending a streaming request to SAP AI Core
|
|
30416
|
+
* and returning a stream of response parts.
|
|
30360
30417
|
*
|
|
30361
30418
|
* **Stream Events:**
|
|
30362
|
-
* - `stream-start` -
|
|
30363
|
-
* - `response-metadata` -
|
|
30419
|
+
* - `stream-start` - Initialization with warnings
|
|
30420
|
+
* - `response-metadata` - Model, timestamp, response ID
|
|
30364
30421
|
* - `text-start` - Text block begins (with unique ID)
|
|
30365
|
-
* - `text-delta` - Incremental text chunks
|
|
30366
|
-
* - `text-end` - Text block completes
|
|
30367
|
-
* - `tool-input-start` - Tool input
|
|
30368
|
-
* - `tool-input-delta` - Tool input chunk
|
|
30369
|
-
* - `tool-input-end` - Tool input completes
|
|
30422
|
+
* - `text-delta` - Incremental text chunks
|
|
30423
|
+
* - `text-end` - Text block completes
|
|
30424
|
+
* - `tool-input-start/delta/end` - Tool input lifecycle
|
|
30370
30425
|
* - `tool-call` - Complete tool call
|
|
30371
30426
|
* - `finish` - Stream completes with usage and finish reason
|
|
30372
30427
|
* - `error` - Error occurred
|
|
30373
30428
|
*
|
|
30374
|
-
* **
|
|
30375
|
-
* -
|
|
30376
|
-
*
|
|
30377
|
-
* - Usage format: `{ inputTokens: { total, ... }, outputTokens: { total, ... } }`
|
|
30378
|
-
* - Warnings only in `stream-start` event
|
|
30429
|
+
* **Response ID:**
|
|
30430
|
+
* Client-generated UUID in `response-metadata.id` and `providerMetadata['sap-ai'].responseId`.
|
|
30431
|
+
* TODO: Use backend's `x-request-id` when `OrchestrationStreamResponse` exposes `rawResponse`.
|
|
30379
30432
|
*
|
|
30380
|
-
* **
|
|
30381
|
-
*
|
|
30382
|
-
* the signal is aborted. However, this does not cancel the underlying HTTP request
|
|
30383
|
-
* to SAP AI Core - the request continues executing on the server. This is a
|
|
30384
|
-
* limitation of the SAP AI SDK's chatCompletion API.
|
|
30385
|
-
*
|
|
30386
|
-
* @see https://github.com/SAP/ai-sdk-js/issues/1429 for tracking true cancellation support
|
|
30433
|
+
* **Abort Signal:**
|
|
30434
|
+
* Same limitation as `doGenerate` - see its documentation for details.
|
|
30387
30435
|
* @see {@link https://sdk.vercel.ai/docs/ai-sdk-core/streaming Vercel AI SDK Streaming}
|
|
30388
|
-
*
|
|
30389
30436
|
* @param options - Streaming options including prompt, tools, and settings
|
|
30390
30437
|
* @returns Promise resolving to stream and request metadata
|
|
30391
|
-
*
|
|
30392
30438
|
* @example
|
|
30393
30439
|
* ```typescript
|
|
30394
30440
|
* const { stream } = await model.doStream({
|
|
@@ -30401,13 +30447,9 @@ var SAPAILanguageModel = class {
|
|
|
30401
30447
|
* if (part.type === 'text-delta') {
|
|
30402
30448
|
* process.stdout.write(part.delta);
|
|
30403
30449
|
* }
|
|
30404
|
-
* if (part.type === 'text-end') {
|
|
30405
|
-
* console.log('Block complete:', part.id, part.text);
|
|
30406
|
-
* }
|
|
30407
30450
|
* }
|
|
30408
30451
|
* ```
|
|
30409
|
-
*
|
|
30410
|
-
* @since 4.0.0
|
|
30452
|
+
* @since 1.0.0
|
|
30411
30453
|
*/
|
|
30412
30454
|
async doStream(options) {
|
|
30413
30455
|
try {
|
|
@@ -30434,6 +30476,7 @@ var SAPAILanguageModel = class {
|
|
|
30434
30476
|
promptTemplating: { include_usage: true }
|
|
30435
30477
|
});
|
|
30436
30478
|
const idGenerator = new StreamIdGenerator();
|
|
30479
|
+
const responseId = idGenerator.generateResponseId();
|
|
30437
30480
|
let textBlockId = null;
|
|
30438
30481
|
const streamState = {
|
|
30439
30482
|
activeText: false,
|
|
@@ -30477,6 +30520,7 @@ var SAPAILanguageModel = class {
|
|
|
30477
30520
|
if (streamState.isFirstChunk) {
|
|
30478
30521
|
streamState.isFirstChunk = false;
|
|
30479
30522
|
controller.enqueue({
|
|
30523
|
+
id: responseId,
|
|
30480
30524
|
modelId,
|
|
30481
30525
|
timestamp: /* @__PURE__ */ new Date(),
|
|
30482
30526
|
type: "response-metadata"
|
|
@@ -30639,6 +30683,12 @@ var SAPAILanguageModel = class {
|
|
|
30639
30683
|
}
|
|
30640
30684
|
controller.enqueue({
|
|
30641
30685
|
finishReason: streamState.finishReason,
|
|
30686
|
+
providerMetadata: {
|
|
30687
|
+
"sap-ai": {
|
|
30688
|
+
finishReason: streamState.finishReason.raw,
|
|
30689
|
+
responseId
|
|
30690
|
+
}
|
|
30691
|
+
},
|
|
30642
30692
|
type: "finish",
|
|
30643
30693
|
usage: streamState.usage
|
|
30644
30694
|
});
|
|
@@ -30673,7 +30723,6 @@ var SAPAILanguageModel = class {
|
|
|
30673
30723
|
}
|
|
30674
30724
|
/**
|
|
30675
30725
|
* Checks if a URL is supported for file/image uploads.
|
|
30676
|
-
*
|
|
30677
30726
|
* @param url - The URL to check
|
|
30678
30727
|
* @returns True if the URL protocol is HTTPS or data with valid image format
|
|
30679
30728
|
*/
|
|
@@ -30686,7 +30735,6 @@ var SAPAILanguageModel = class {
|
|
|
30686
30735
|
}
|
|
30687
30736
|
/**
|
|
30688
30737
|
* Builds orchestration module config for SAP AI SDK.
|
|
30689
|
-
*
|
|
30690
30738
|
* @param options - Call options from the AI SDK
|
|
30691
30739
|
* @returns Object containing orchestration config, messages, and warnings
|
|
30692
30740
|
* @internal
|
|
@@ -30861,7 +30909,6 @@ var SAPAILanguageModel = class {
|
|
|
30861
30909
|
}
|
|
30862
30910
|
/**
|
|
30863
30911
|
* Creates an OrchestrationClient instance.
|
|
30864
|
-
*
|
|
30865
30912
|
* @param config - Orchestration module configuration
|
|
30866
30913
|
* @returns OrchestrationClient instance
|
|
30867
30914
|
* @internal
|