@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/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
- if (isOrchestrationErrorResponse(error)) {
29983
- return convertSAPErrorToAPICallError(error, {
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 (error instanceof Error) {
29990
- const errorMsg = error.message.toLowerCase();
29991
- if (errorMsg.includes("authentication") || errorMsg.includes("unauthorized") || errorMsg.includes("aicore_service_key") || errorMsg.includes("invalid credentials")) {
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: ${error.message}
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: ${error.message}`,
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 = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
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
- /** Multiple completions via the `n` parameter (provider-specific support). */
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
- * Generates text completion using SAP AI Core's Orchestration API.
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
- * the signal is aborted. However, this does not cancel the underlying HTTP request
30192
- * to SAP AI Core - the request continues executing on the server. This is a
30193
- * limitation of the SAP AI SDK's chatCompletion API.
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
- * This method implements the `LanguageModelV3.doStream` interface,
30359
- * sending a streaming request to SAP AI Core and returning a stream of response parts.
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` - Stream initialization with warnings
30363
- * - `response-metadata` - Response metadata (model, timestamp)
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 (with block ID)
30366
- * - `text-end` - Text block completes (with accumulated text)
30367
- * - `tool-input-start` - Tool input begins
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
- * **Stream Structure:**
30375
- * - Text blocks have explicit lifecycle with unique IDs
30376
- * - Finish reason format: `{ unified: string, raw?: string }`
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
- * **Note on Abort Signal:**
30381
- * The abort signal implementation uses Promise.race to reject the promise when
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