@jerome-benoit/sap-ai-provider 3.0.0 → 4.0.0-rc.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/README.md +60 -24
- package/dist/index.cjs +169 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -43
- package/dist/index.d.ts +76 -43
- package/dist/index.js +169 -64
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@mymediset/sap-ai-provider)
|
|
4
4
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
5
5
|
[](https://sdk.vercel.ai/docs)
|
|
6
|
+
[](https://sdk.vercel.ai/docs/ai-sdk-core/provider-interfaces)
|
|
6
7
|
|
|
7
8
|
A community provider for SAP AI Core that integrates seamlessly with the Vercel AI SDK. Built on top of the official **@sap-ai-sdk/orchestration** package, this provider enables you to use SAP's enterprise-grade AI models through the familiar Vercel AI SDK interface.
|
|
8
9
|
|
|
@@ -12,15 +13,38 @@ A community provider for SAP AI Core that integrates seamlessly with the Vercel
|
|
|
12
13
|
- [Quick Start](#quick-start)
|
|
13
14
|
- [Quick Reference](#quick-reference)
|
|
14
15
|
- [Installation](#installation)
|
|
16
|
+
- [Provider Creation](#provider-creation)
|
|
17
|
+
- [Option 1: Factory Function (Recommended for Custom Configuration)](#option-1-factory-function-recommended-for-custom-configuration)
|
|
18
|
+
- [Option 2: Default Instance (Quick Start)](#option-2-default-instance-quick-start)
|
|
15
19
|
- [Authentication](#authentication)
|
|
16
20
|
- [Basic Usage](#basic-usage)
|
|
21
|
+
- [Text Generation](#text-generation)
|
|
22
|
+
- [Chat Conversations](#chat-conversations)
|
|
23
|
+
- [Streaming Responses](#streaming-responses)
|
|
24
|
+
- [Model Configuration](#model-configuration)
|
|
17
25
|
- [Supported Models](#supported-models)
|
|
18
26
|
- [Advanced Features](#advanced-features)
|
|
27
|
+
- [Tool Calling](#tool-calling)
|
|
28
|
+
- [Multi-modal Input (Images)](#multi-modal-input-images)
|
|
29
|
+
- [Data Masking (SAP DPI)](#data-masking-sap-dpi)
|
|
30
|
+
- [Content Filtering](#content-filtering)
|
|
19
31
|
- [Configuration Options](#configuration-options)
|
|
20
32
|
- [Error Handling](#error-handling)
|
|
33
|
+
- [Troubleshooting](#troubleshooting)
|
|
34
|
+
- [Performance](#performance)
|
|
35
|
+
- [Security](#security)
|
|
36
|
+
- [Debug Mode](#debug-mode)
|
|
21
37
|
- [Examples](#examples)
|
|
22
38
|
- [Migration Guides](#migration-guides)
|
|
39
|
+
- [Upgrading from v3.x to v4.x](#upgrading-from-v3x-to-v4x)
|
|
40
|
+
- [Upgrading from v2.x to v3.x](#upgrading-from-v2x-to-v3x)
|
|
41
|
+
- [Upgrading from v1.x to v2.x](#upgrading-from-v1x-to-v2x)
|
|
42
|
+
- [Important Note](#important-note)
|
|
23
43
|
- [Contributing](#contributing)
|
|
44
|
+
- [Resources](#resources)
|
|
45
|
+
- [Documentation](#documentation)
|
|
46
|
+
- [Community](#community)
|
|
47
|
+
- [Related Projects](#related-projects)
|
|
24
48
|
- [License](#license)
|
|
25
49
|
|
|
26
50
|
## Features
|
|
@@ -29,11 +53,12 @@ A community provider for SAP AI Core that integrates seamlessly with the Vercel
|
|
|
29
53
|
- 🎯 **Tool Calling Support** - Full tool/function calling capabilities
|
|
30
54
|
- 🧠 **Reasoning-Safe by Default** - Assistant reasoning parts are not forwarded unless enabled
|
|
31
55
|
- 🖼️ **Multi-modal Input** - Support for text and image inputs
|
|
32
|
-
- 📡 **Streaming Support** - Real-time text generation
|
|
56
|
+
- 📡 **Streaming Support** - Real-time text generation with structured V3 blocks
|
|
33
57
|
- 🔒 **Data Masking** - Built-in SAP DPI integration for privacy
|
|
34
58
|
- 🛡️ **Content Filtering** - Azure Content Safety and Llama Guard support
|
|
35
59
|
- 🔧 **TypeScript Support** - Full type safety and IntelliSense
|
|
36
60
|
- 🎨 **Multiple Models** - Support for GPT-4, Claude, Gemini, Nova, and more
|
|
61
|
+
- ⚡ **Language Model V3** - Latest Vercel AI SDK specification with enhanced streaming
|
|
37
62
|
|
|
38
63
|
## Quick Start
|
|
39
64
|
|
|
@@ -263,13 +288,13 @@ This provider supports all models available through SAP AI Core Orchestration se
|
|
|
263
288
|
**Popular models:**
|
|
264
289
|
|
|
265
290
|
- **OpenAI**: gpt-4o, gpt-4o-mini, gpt-4.1, o1, o3 (recommended for multi-tool apps)
|
|
266
|
-
- **Anthropic Claude**: claude-3.5-sonnet, claude-4-opus
|
|
291
|
+
- **Anthropic Claude**: anthropic--claude-3.5-sonnet, anthropic--claude-4-opus
|
|
267
292
|
- **Google Gemini**: gemini-2.5-pro, gemini-2.0-flash
|
|
268
293
|
|
|
269
294
|
⚠️ **Important:** Google Gemini models have a 1 tool limit per request.
|
|
270
295
|
|
|
271
|
-
- **Amazon Nova**: nova-pro, nova-lite
|
|
272
|
-
- **Open Source**: mistralai
|
|
296
|
+
- **Amazon Nova**: amazon--nova-pro, amazon--nova-lite
|
|
297
|
+
- **Open Source**: mistralai--mistral-large-instruct, meta--llama3.1-70b-instruct
|
|
273
298
|
|
|
274
299
|
> **Note:** Model availability depends on your SAP AI Core tenant configuration, region, and subscription.
|
|
275
300
|
|
|
@@ -496,6 +521,23 @@ npx tsx examples/example-generate-text.ts
|
|
|
496
521
|
|
|
497
522
|
## Migration Guides
|
|
498
523
|
|
|
524
|
+
### Upgrading from v3.x to v4.x
|
|
525
|
+
|
|
526
|
+
Version 4.0 migrates from **LanguageModelV2** to **LanguageModelV3** specification (AI SDK 6.0+). **See the [Migration Guide](./MIGRATION_GUIDE.md#version-3x-to-4x-breaking-changes) for complete upgrade instructions.**
|
|
527
|
+
|
|
528
|
+
**Key changes:**
|
|
529
|
+
|
|
530
|
+
- **Finish Reason**: Changed from string to object (`result.finishReason.unified`)
|
|
531
|
+
- **Usage Structure**: Nested format with detailed token breakdown (`result.usage.inputTokens.total`)
|
|
532
|
+
- **Stream Events**: Structured blocks (`text-start`, `text-delta`, `text-end`) instead of simple deltas
|
|
533
|
+
- **Warning Types**: Updated format with `feature` field for categorization
|
|
534
|
+
|
|
535
|
+
**Impact by user type:**
|
|
536
|
+
|
|
537
|
+
- High-level API users (`generateText`/`streamText`): ✅ Minimal impact (likely no changes)
|
|
538
|
+
- Direct provider users: ⚠️ Update type imports (`LanguageModelV2` → `LanguageModelV3`)
|
|
539
|
+
- Custom stream parsers: ⚠️ Update parsing logic for V3 structure
|
|
540
|
+
|
|
499
541
|
### Upgrading from v2.x to v3.x
|
|
500
542
|
|
|
501
543
|
Version 3.0 standardizes error handling to use Vercel AI SDK native error types. **See the [Migration Guide](./MIGRATION_GUIDE.md#v2x--v30) for complete upgrade instructions.**
|
|
@@ -526,34 +568,28 @@ Version 2.0 uses the official SAP AI SDK. **See the [Migration Guide](./MIGRATIO
|
|
|
526
568
|
|
|
527
569
|
We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) for details.
|
|
528
570
|
|
|
529
|
-
##
|
|
530
|
-
|
|
531
|
-
Apache License 2.0 - see [LICENSE](./LICENSE.md) for details.
|
|
532
|
-
|
|
533
|
-
## Support
|
|
534
|
-
|
|
535
|
-
- 📖 [Documentation](https://github.com/BITASIA/sap-ai-provider)
|
|
536
|
-
- 🐛 [Issue Tracker](https://github.com/BITASIA/sap-ai-provider/issues)
|
|
571
|
+
## Resources
|
|
537
572
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
### Guides
|
|
541
|
-
|
|
542
|
-
- [Environment Setup](./ENVIRONMENT_SETUP.md) - Authentication and configuration setup
|
|
543
|
-
- [Migration Guide](./MIGRATION_GUIDE.md) - Upgrading from v1.x to v2.x with step-by-step instructions
|
|
544
|
-
- [curl API Testing](./CURL_API_TESTING_GUIDE.md) - Direct API testing for debugging
|
|
545
|
-
|
|
546
|
-
### Reference
|
|
573
|
+
### Documentation
|
|
547
574
|
|
|
575
|
+
- [Migration Guide](./MIGRATION_GUIDE.md) - Version upgrade instructions (v1.x → v2.x → v3.x → v4.x)
|
|
548
576
|
- [API Reference](./API_REFERENCE.md) - Complete API documentation with all types and functions
|
|
577
|
+
- [Environment Setup](./ENVIRONMENT_SETUP.md) - Authentication and configuration setup
|
|
578
|
+
- [Troubleshooting](./TROUBLESHOOTING.md) - Common issues and solutions
|
|
549
579
|
- [Architecture](./ARCHITECTURE.md) - Internal architecture, design decisions, and request flows
|
|
580
|
+
- [curl API Testing](./CURL_API_TESTING_GUIDE.md) - Direct API testing for debugging
|
|
550
581
|
|
|
551
|
-
###
|
|
582
|
+
### Community
|
|
552
583
|
|
|
553
|
-
- [
|
|
584
|
+
- 🐛 [Issue Tracker](https://github.com/BITASIA/sap-ai-provider/issues) - Report bugs and request features
|
|
585
|
+
- 💬 [Discussions](https://github.com/BITASIA/sap-ai-provider/discussions) - Ask questions and share ideas
|
|
554
586
|
|
|
555
|
-
|
|
587
|
+
### Related Projects
|
|
556
588
|
|
|
557
589
|
- [Vercel AI SDK](https://sdk.vercel.ai/) - The AI SDK this provider extends
|
|
558
590
|
- [SAP AI SDK](https://sap.github.io/ai-sdk/) - Official SAP Cloud SDK for AI
|
|
559
591
|
- [SAP AI Core Documentation](https://help.sap.com/docs/ai-core) - Official SAP AI Core docs
|
|
592
|
+
|
|
593
|
+
## License
|
|
594
|
+
|
|
595
|
+
Apache License 2.0 - see [LICENSE](./LICENSE.md) for details.
|
package/dist/index.cjs
CHANGED
|
@@ -24766,7 +24766,7 @@ var require_form_data = __commonJS({
|
|
|
24766
24766
|
var parseUrl = require("url").parse;
|
|
24767
24767
|
var fs = require("fs");
|
|
24768
24768
|
var Stream = require("stream").Stream;
|
|
24769
|
-
var
|
|
24769
|
+
var crypto2 = require("crypto");
|
|
24770
24770
|
var mime = require_mime_types();
|
|
24771
24771
|
var asynckit = require_asynckit();
|
|
24772
24772
|
var setToStringTag = require_es_set_tostringtag();
|
|
@@ -24972,7 +24972,7 @@ var require_form_data = __commonJS({
|
|
|
24972
24972
|
return Buffer.concat([dataBuffer, Buffer.from(this._lastBoundary())]);
|
|
24973
24973
|
};
|
|
24974
24974
|
FormData2.prototype._generateBoundary = function() {
|
|
24975
|
-
this._boundary = "--------------------------" +
|
|
24975
|
+
this._boundary = "--------------------------" + crypto2.randomBytes(12).toString("hex");
|
|
24976
24976
|
};
|
|
24977
24977
|
FormData2.prototype.getLengthSync = function() {
|
|
24978
24978
|
var knownLength = this._overheadLength + this._valueLength;
|
|
@@ -26202,7 +26202,7 @@ var require_axios = __commonJS({
|
|
|
26202
26202
|
"node_modules/axios/dist/node/axios.cjs"(exports2, module2) {
|
|
26203
26203
|
"use strict";
|
|
26204
26204
|
var FormData$1 = require_form_data();
|
|
26205
|
-
var
|
|
26205
|
+
var crypto2 = require("crypto");
|
|
26206
26206
|
var url = require("url");
|
|
26207
26207
|
var proxyFromEnv = require_proxy_from_env();
|
|
26208
26208
|
var http = require("http");
|
|
@@ -26217,7 +26217,7 @@ var require_axios = __commonJS({
|
|
|
26217
26217
|
return e && typeof e === "object" && "default" in e ? e : { "default": e };
|
|
26218
26218
|
}
|
|
26219
26219
|
var FormData__default = /* @__PURE__ */ _interopDefaultLegacy(FormData$1);
|
|
26220
|
-
var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(
|
|
26220
|
+
var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(crypto2);
|
|
26221
26221
|
var url__default = /* @__PURE__ */ _interopDefaultLegacy(url);
|
|
26222
26222
|
var proxyFromEnv__default = /* @__PURE__ */ _interopDefaultLegacy(proxyFromEnv);
|
|
26223
26223
|
var http__default = /* @__PURE__ */ _interopDefaultLegacy(http);
|
|
@@ -29897,12 +29897,14 @@ function convertToSAPMessages(prompt, options = {}) {
|
|
|
29897
29897
|
}
|
|
29898
29898
|
case "tool": {
|
|
29899
29899
|
for (const part of message.content) {
|
|
29900
|
-
|
|
29901
|
-
|
|
29902
|
-
|
|
29903
|
-
|
|
29904
|
-
|
|
29905
|
-
|
|
29900
|
+
if (part.type === "tool-result") {
|
|
29901
|
+
const toolMessage = {
|
|
29902
|
+
role: "tool",
|
|
29903
|
+
tool_call_id: part.toolCallId,
|
|
29904
|
+
content: JSON.stringify(part.output)
|
|
29905
|
+
};
|
|
29906
|
+
messages.push(toolMessage);
|
|
29907
|
+
}
|
|
29906
29908
|
}
|
|
29907
29909
|
break;
|
|
29908
29910
|
}
|
|
@@ -30071,6 +30073,16 @@ See: https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-serv
|
|
|
30071
30073
|
}
|
|
30072
30074
|
|
|
30073
30075
|
// src/sap-ai-chat-language-model.ts
|
|
30076
|
+
var StreamIdGenerator = class {
|
|
30077
|
+
/**
|
|
30078
|
+
* Generates a unique ID for a text block.
|
|
30079
|
+
*
|
|
30080
|
+
* @returns RFC 4122-compliant UUID string
|
|
30081
|
+
*/
|
|
30082
|
+
generateTextBlockId() {
|
|
30083
|
+
return crypto.randomUUID();
|
|
30084
|
+
}
|
|
30085
|
+
};
|
|
30074
30086
|
function validateModelParameters(params, warnings) {
|
|
30075
30087
|
if (params.temperature !== void 0 && (params.temperature < 0 || params.temperature > 2)) {
|
|
30076
30088
|
warnings.push({
|
|
@@ -30162,7 +30174,7 @@ function buildSAPToolParameters(schema) {
|
|
|
30162
30174
|
};
|
|
30163
30175
|
}
|
|
30164
30176
|
var SAPAIChatLanguageModel = class {
|
|
30165
|
-
specificationVersion = "
|
|
30177
|
+
specificationVersion = "v3";
|
|
30166
30178
|
modelId;
|
|
30167
30179
|
config;
|
|
30168
30180
|
settings;
|
|
@@ -30185,10 +30197,14 @@ var SAPAIChatLanguageModel = class {
|
|
|
30185
30197
|
* Checks if a URL is supported for file/image uploads.
|
|
30186
30198
|
*
|
|
30187
30199
|
* @param url - The URL to check
|
|
30188
|
-
* @returns True if the URL protocol is HTTPS or data
|
|
30200
|
+
* @returns True if the URL protocol is HTTPS or data with valid image format
|
|
30189
30201
|
*/
|
|
30190
30202
|
supportsUrl(url) {
|
|
30191
|
-
|
|
30203
|
+
if (url.protocol === "https:") return true;
|
|
30204
|
+
if (url.protocol === "data:") {
|
|
30205
|
+
return /^data:image\//i.test(url.href);
|
|
30206
|
+
}
|
|
30207
|
+
return false;
|
|
30192
30208
|
}
|
|
30193
30209
|
/**
|
|
30194
30210
|
* Returns supported URL patterns for different content types.
|
|
@@ -30201,9 +30217,45 @@ var SAPAIChatLanguageModel = class {
|
|
|
30201
30217
|
};
|
|
30202
30218
|
}
|
|
30203
30219
|
/**
|
|
30204
|
-
*
|
|
30220
|
+
* Generates text completion using SAP AI Core's Orchestration API.
|
|
30221
|
+
*
|
|
30222
|
+
* This method implements the `LanguageModelV3.doGenerate` interface,
|
|
30223
|
+
* providing synchronous (non-streaming) text generation with support for:
|
|
30224
|
+
* - Multi-turn conversations with system/user/assistant messages
|
|
30225
|
+
* - Tool calling (function calling) with structured outputs
|
|
30226
|
+
* - Multi-modal inputs (text + images)
|
|
30227
|
+
* - Data masking via SAP DPI
|
|
30228
|
+
* - Content filtering via Azure Content Safety or Llama Guard
|
|
30229
|
+
*
|
|
30230
|
+
* **Return Structure:**
|
|
30231
|
+
* - Finish reason: `{ unified: string, raw?: string }`
|
|
30232
|
+
* - Usage: Nested structure with token breakdown `{ inputTokens: { total, ... }, outputTokens: { total, ... } }`
|
|
30233
|
+
* - Warnings: Array of warnings with `type` and optional `feature` field
|
|
30234
|
+
*
|
|
30235
|
+
* @param options - Generation options including prompt, tools, temperature, etc.
|
|
30236
|
+
* @returns Promise resolving to generation result with content, usage, and metadata
|
|
30237
|
+
*
|
|
30238
|
+
* @throws {InvalidPromptError} If prompt format is invalid
|
|
30239
|
+
* @throws {InvalidArgumentError} If arguments are malformed
|
|
30240
|
+
* @throws {APICallError} If the SAP AI Core API call fails
|
|
30241
|
+
*
|
|
30242
|
+
* @example
|
|
30243
|
+
* ```typescript
|
|
30244
|
+
* const result = await model.doGenerate({
|
|
30245
|
+
* prompt: [
|
|
30246
|
+
* { role: 'user', content: [{ type: 'text', text: 'Hello!' }] }
|
|
30247
|
+
* ],
|
|
30248
|
+
* temperature: 0.7,
|
|
30249
|
+
* maxTokens: 100
|
|
30250
|
+
* });
|
|
30251
|
+
*
|
|
30252
|
+
* console.log(result.content); // Array of V3 content parts
|
|
30253
|
+
* console.log(result.finishReason.unified); // "stop", "length", "tool-calls", etc.
|
|
30254
|
+
* console.log(result.usage.inputTokens.total); // Total input tokens
|
|
30255
|
+
* ```
|
|
30205
30256
|
*
|
|
30206
|
-
* @
|
|
30257
|
+
* @since 1.0.0
|
|
30258
|
+
* @since 4.0.0 Updated to LanguageModelV3 interface
|
|
30207
30259
|
*/
|
|
30208
30260
|
get provider() {
|
|
30209
30261
|
return this.config.provider;
|
|
@@ -30270,11 +30322,11 @@ var SAPAIChatLanguageModel = class {
|
|
|
30270
30322
|
const schemaRecord = jsonSchema;
|
|
30271
30323
|
delete schemaRecord.$schema;
|
|
30272
30324
|
parameters = buildSAPToolParameters(schemaRecord);
|
|
30273
|
-
} catch {
|
|
30325
|
+
} catch (error) {
|
|
30274
30326
|
warnings.push({
|
|
30275
|
-
type: "unsupported
|
|
30276
|
-
tool
|
|
30277
|
-
details:
|
|
30327
|
+
type: "unsupported",
|
|
30328
|
+
feature: `tool schema conversion for ${tool.name}`,
|
|
30329
|
+
details: `Failed to convert tool Zod schema: ${error instanceof Error ? error.message : String(error)}. Falling back to empty object schema.`
|
|
30278
30330
|
});
|
|
30279
30331
|
parameters = buildSAPToolParameters({});
|
|
30280
30332
|
}
|
|
@@ -30298,8 +30350,9 @@ var SAPAIChatLanguageModel = class {
|
|
|
30298
30350
|
};
|
|
30299
30351
|
} else {
|
|
30300
30352
|
warnings.push({
|
|
30301
|
-
type: "unsupported
|
|
30302
|
-
tool
|
|
30353
|
+
type: "unsupported",
|
|
30354
|
+
feature: `tool type for ${tool.name}`,
|
|
30355
|
+
details: "Only 'function' tool type is supported."
|
|
30303
30356
|
});
|
|
30304
30357
|
return null;
|
|
30305
30358
|
}
|
|
@@ -30351,8 +30404,8 @@ var SAPAIChatLanguageModel = class {
|
|
|
30351
30404
|
);
|
|
30352
30405
|
if (options.toolChoice && options.toolChoice.type !== "auto") {
|
|
30353
30406
|
warnings.push({
|
|
30354
|
-
type: "unsupported
|
|
30355
|
-
|
|
30407
|
+
type: "unsupported",
|
|
30408
|
+
feature: "toolChoice",
|
|
30356
30409
|
details: `SAP AI SDK does not support toolChoice '${options.toolChoice.type}'. Using default 'auto' behavior.`
|
|
30357
30410
|
});
|
|
30358
30411
|
}
|
|
@@ -30412,7 +30465,7 @@ var SAPAIChatLanguageModel = class {
|
|
|
30412
30465
|
/**
|
|
30413
30466
|
* Generates a single completion (non-streaming).
|
|
30414
30467
|
*
|
|
30415
|
-
* This method implements the `
|
|
30468
|
+
* This method implements the `LanguageModelV3.doGenerate` interface,
|
|
30416
30469
|
* sending a request to SAP AI Core and returning the complete response.
|
|
30417
30470
|
*
|
|
30418
30471
|
* **Features:**
|
|
@@ -30420,6 +30473,13 @@ var SAPAIChatLanguageModel = class {
|
|
|
30420
30473
|
* - Multi-modal input (text + images)
|
|
30421
30474
|
* - Data masking (if configured)
|
|
30422
30475
|
* - Content filtering (if configured)
|
|
30476
|
+
* - Abort signal support (via Promise.race)
|
|
30477
|
+
*
|
|
30478
|
+
* **Note on Abort Signal:**
|
|
30479
|
+
* The abort signal implementation uses Promise.race to reject the promise when
|
|
30480
|
+
* the signal is aborted. However, this does not cancel the underlying HTTP request
|
|
30481
|
+
* to SAP AI Core - the request continues executing on the server. This is a
|
|
30482
|
+
* limitation of the SAP AI SDK's chatCompletion API.
|
|
30423
30483
|
*
|
|
30424
30484
|
* @param options - Generation options including prompt, tools, and settings
|
|
30425
30485
|
* @returns Promise resolving to the generation result with content, usage, and metadata
|
|
@@ -30492,7 +30552,7 @@ var SAPAIChatLanguageModel = class {
|
|
|
30492
30552
|
Object.entries(responseHeadersRaw).flatMap(([key, value]) => {
|
|
30493
30553
|
if (typeof value === "string") return [[key, value]];
|
|
30494
30554
|
if (Array.isArray(value)) {
|
|
30495
|
-
const strings = value.filter((item) => typeof item === "string").join("
|
|
30555
|
+
const strings = value.filter((item) => typeof item === "string").join("; ");
|
|
30496
30556
|
return strings.length > 0 ? [[key, strings]] : [];
|
|
30497
30557
|
}
|
|
30498
30558
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
@@ -30533,9 +30593,17 @@ var SAPAIChatLanguageModel = class {
|
|
|
30533
30593
|
content,
|
|
30534
30594
|
finishReason,
|
|
30535
30595
|
usage: {
|
|
30536
|
-
inputTokens:
|
|
30537
|
-
|
|
30538
|
-
|
|
30596
|
+
inputTokens: {
|
|
30597
|
+
total: tokenUsage.prompt_tokens,
|
|
30598
|
+
noCache: tokenUsage.prompt_tokens,
|
|
30599
|
+
cacheRead: void 0,
|
|
30600
|
+
cacheWrite: void 0
|
|
30601
|
+
},
|
|
30602
|
+
outputTokens: {
|
|
30603
|
+
total: tokenUsage.completion_tokens,
|
|
30604
|
+
text: tokenUsage.completion_tokens,
|
|
30605
|
+
reasoning: void 0
|
|
30606
|
+
}
|
|
30539
30607
|
},
|
|
30540
30608
|
providerMetadata: {
|
|
30541
30609
|
"sap-ai": {
|
|
@@ -30566,19 +30634,28 @@ var SAPAIChatLanguageModel = class {
|
|
|
30566
30634
|
/**
|
|
30567
30635
|
* Generates a streaming completion.
|
|
30568
30636
|
*
|
|
30569
|
-
* This method implements the `
|
|
30637
|
+
* This method implements the `LanguageModelV3.doStream` interface,
|
|
30570
30638
|
* sending a streaming request to SAP AI Core and returning a stream of response parts.
|
|
30571
30639
|
*
|
|
30572
30640
|
* **Stream Events:**
|
|
30573
|
-
* - `stream-start` - Stream initialization
|
|
30641
|
+
* - `stream-start` - Stream initialization with warnings
|
|
30574
30642
|
* - `response-metadata` - Response metadata (model, timestamp)
|
|
30575
|
-
* - `text-start` - Text
|
|
30576
|
-
* - `text-delta` - Incremental text chunks
|
|
30577
|
-
* - `text-end` - Text
|
|
30578
|
-
* - `tool-
|
|
30643
|
+
* - `text-start` - Text block begins (with unique ID)
|
|
30644
|
+
* - `text-delta` - Incremental text chunks (with block ID)
|
|
30645
|
+
* - `text-end` - Text block completes (with accumulated text)
|
|
30646
|
+
* - `tool-input-start` - Tool input begins
|
|
30647
|
+
* - `tool-input-delta` - Tool input chunk
|
|
30648
|
+
* - `tool-input-end` - Tool input completes
|
|
30649
|
+
* - `tool-call` - Complete tool call
|
|
30579
30650
|
* - `finish` - Stream completes with usage and finish reason
|
|
30580
30651
|
* - `error` - Error occurred
|
|
30581
30652
|
*
|
|
30653
|
+
* **Stream Structure:**
|
|
30654
|
+
* - Text blocks have explicit lifecycle with unique IDs
|
|
30655
|
+
* - Finish reason format: `{ unified: string, raw?: string }`
|
|
30656
|
+
* - Usage format: `{ inputTokens: { total, ... }, outputTokens: { total, ... } }`
|
|
30657
|
+
* - Warnings only in `stream-start` event
|
|
30658
|
+
*
|
|
30582
30659
|
* @param options - Streaming options including prompt, tools, and settings
|
|
30583
30660
|
* @returns Promise resolving to stream and request metadata
|
|
30584
30661
|
*
|
|
@@ -30594,8 +30671,13 @@ var SAPAIChatLanguageModel = class {
|
|
|
30594
30671
|
* if (part.type === 'text-delta') {
|
|
30595
30672
|
* process.stdout.write(part.delta);
|
|
30596
30673
|
* }
|
|
30674
|
+
* if (part.type === 'text-end') {
|
|
30675
|
+
* console.log('Block complete:', part.id, part.text);
|
|
30676
|
+
* }
|
|
30597
30677
|
* }
|
|
30598
30678
|
* ```
|
|
30679
|
+
*
|
|
30680
|
+
* @since 4.0.0
|
|
30599
30681
|
*/
|
|
30600
30682
|
async doStream(options) {
|
|
30601
30683
|
try {
|
|
@@ -30623,12 +30705,25 @@ var SAPAIChatLanguageModel = class {
|
|
|
30623
30705
|
options.abortSignal,
|
|
30624
30706
|
{ promptTemplating: { include_usage: true } }
|
|
30625
30707
|
);
|
|
30708
|
+
const idGenerator = new StreamIdGenerator();
|
|
30709
|
+
let textBlockId = null;
|
|
30626
30710
|
const streamState = {
|
|
30627
|
-
finishReason:
|
|
30711
|
+
finishReason: {
|
|
30712
|
+
unified: "other",
|
|
30713
|
+
raw: void 0
|
|
30714
|
+
},
|
|
30628
30715
|
usage: {
|
|
30629
|
-
inputTokens:
|
|
30630
|
-
|
|
30631
|
-
|
|
30716
|
+
inputTokens: {
|
|
30717
|
+
total: void 0,
|
|
30718
|
+
noCache: void 0,
|
|
30719
|
+
cacheRead: void 0,
|
|
30720
|
+
cacheWrite: void 0
|
|
30721
|
+
},
|
|
30722
|
+
outputTokens: {
|
|
30723
|
+
total: void 0,
|
|
30724
|
+
text: void 0,
|
|
30725
|
+
reasoning: void 0
|
|
30726
|
+
}
|
|
30632
30727
|
},
|
|
30633
30728
|
isFirstChunk: true,
|
|
30634
30729
|
activeText: false
|
|
@@ -30656,19 +30751,25 @@ var SAPAIChatLanguageModel = class {
|
|
|
30656
30751
|
}
|
|
30657
30752
|
const deltaToolCalls = chunk.getDeltaToolCalls();
|
|
30658
30753
|
if (Array.isArray(deltaToolCalls) && deltaToolCalls.length > 0) {
|
|
30659
|
-
streamState.finishReason =
|
|
30754
|
+
streamState.finishReason = {
|
|
30755
|
+
unified: "tool-calls",
|
|
30756
|
+
raw: void 0
|
|
30757
|
+
};
|
|
30660
30758
|
}
|
|
30661
30759
|
const deltaContent = chunk.getDeltaContent();
|
|
30662
|
-
if (typeof deltaContent === "string" && deltaContent.length > 0 && streamState.finishReason !== "tool-calls") {
|
|
30760
|
+
if (typeof deltaContent === "string" && deltaContent.length > 0 && streamState.finishReason.unified !== "tool-calls") {
|
|
30663
30761
|
if (!streamState.activeText) {
|
|
30664
|
-
|
|
30762
|
+
textBlockId = idGenerator.generateTextBlockId();
|
|
30763
|
+
controller.enqueue({ type: "text-start", id: textBlockId });
|
|
30665
30764
|
streamState.activeText = true;
|
|
30666
30765
|
}
|
|
30667
|
-
|
|
30668
|
-
|
|
30669
|
-
|
|
30670
|
-
|
|
30671
|
-
|
|
30766
|
+
if (textBlockId) {
|
|
30767
|
+
controller.enqueue({
|
|
30768
|
+
type: "text-delta",
|
|
30769
|
+
id: textBlockId,
|
|
30770
|
+
delta: deltaContent
|
|
30771
|
+
});
|
|
30772
|
+
}
|
|
30672
30773
|
}
|
|
30673
30774
|
if (Array.isArray(deltaToolCalls) && deltaToolCalls.length > 0) {
|
|
30674
30775
|
for (const toolCallChunk of deltaToolCalls) {
|
|
@@ -30718,7 +30819,7 @@ var SAPAIChatLanguageModel = class {
|
|
|
30718
30819
|
const chunkFinishReason = chunk.getFinishReason();
|
|
30719
30820
|
if (chunkFinishReason) {
|
|
30720
30821
|
streamState.finishReason = mapFinishReason(chunkFinishReason);
|
|
30721
|
-
if (streamState.finishReason === "tool-calls") {
|
|
30822
|
+
if (streamState.finishReason.unified === "tool-calls") {
|
|
30722
30823
|
const toolCalls2 = Array.from(toolCallsInProgress.values());
|
|
30723
30824
|
for (const tc of toolCalls2) {
|
|
30724
30825
|
if (tc.didEmitCall) {
|
|
@@ -30747,8 +30848,8 @@ var SAPAIChatLanguageModel = class {
|
|
|
30747
30848
|
input: tc.arguments
|
|
30748
30849
|
});
|
|
30749
30850
|
}
|
|
30750
|
-
if (streamState.activeText) {
|
|
30751
|
-
controller.enqueue({ type: "text-end", id:
|
|
30851
|
+
if (streamState.activeText && textBlockId) {
|
|
30852
|
+
controller.enqueue({ type: "text-end", id: textBlockId });
|
|
30752
30853
|
streamState.activeText = false;
|
|
30753
30854
|
}
|
|
30754
30855
|
}
|
|
@@ -30784,20 +30885,24 @@ var SAPAIChatLanguageModel = class {
|
|
|
30784
30885
|
input: tc.arguments
|
|
30785
30886
|
});
|
|
30786
30887
|
}
|
|
30787
|
-
if (streamState.activeText) {
|
|
30788
|
-
controller.enqueue({ type: "text-end", id:
|
|
30888
|
+
if (streamState.activeText && textBlockId) {
|
|
30889
|
+
controller.enqueue({ type: "text-end", id: textBlockId });
|
|
30789
30890
|
}
|
|
30790
30891
|
const finalFinishReason = streamResponse.getFinishReason();
|
|
30791
30892
|
if (finalFinishReason) {
|
|
30792
30893
|
streamState.finishReason = mapFinishReason(finalFinishReason);
|
|
30793
30894
|
} else if (didEmitAnyToolCalls) {
|
|
30794
|
-
streamState.finishReason =
|
|
30895
|
+
streamState.finishReason = {
|
|
30896
|
+
unified: "tool-calls",
|
|
30897
|
+
raw: void 0
|
|
30898
|
+
};
|
|
30795
30899
|
}
|
|
30796
30900
|
const finalUsage = streamResponse.getTokenUsage();
|
|
30797
30901
|
if (finalUsage) {
|
|
30798
|
-
streamState.usage.inputTokens = finalUsage.prompt_tokens;
|
|
30799
|
-
streamState.usage.
|
|
30800
|
-
streamState.usage.
|
|
30902
|
+
streamState.usage.inputTokens.total = finalUsage.prompt_tokens;
|
|
30903
|
+
streamState.usage.inputTokens.noCache = finalUsage.prompt_tokens;
|
|
30904
|
+
streamState.usage.outputTokens.total = finalUsage.completion_tokens;
|
|
30905
|
+
streamState.usage.outputTokens.text = finalUsage.completion_tokens;
|
|
30801
30906
|
}
|
|
30802
30907
|
controller.enqueue({
|
|
30803
30908
|
type: "finish",
|
|
@@ -30828,8 +30933,7 @@ var SAPAIChatLanguageModel = class {
|
|
|
30828
30933
|
stream: transformedStream,
|
|
30829
30934
|
request: {
|
|
30830
30935
|
body: requestBody
|
|
30831
|
-
}
|
|
30832
|
-
warnings: warningsOut
|
|
30936
|
+
}
|
|
30833
30937
|
};
|
|
30834
30938
|
} catch (error) {
|
|
30835
30939
|
throw convertToAISDKError(error, {
|
|
@@ -30841,27 +30945,28 @@ var SAPAIChatLanguageModel = class {
|
|
|
30841
30945
|
}
|
|
30842
30946
|
};
|
|
30843
30947
|
function mapFinishReason(reason) {
|
|
30844
|
-
|
|
30948
|
+
const raw = reason;
|
|
30949
|
+
if (!reason) return { unified: "other", raw };
|
|
30845
30950
|
switch (reason.toLowerCase()) {
|
|
30846
30951
|
case "stop":
|
|
30847
30952
|
case "end_turn":
|
|
30848
30953
|
case "stop_sequence":
|
|
30849
30954
|
case "eos":
|
|
30850
|
-
return "stop";
|
|
30955
|
+
return { unified: "stop", raw };
|
|
30851
30956
|
case "length":
|
|
30852
30957
|
case "max_tokens":
|
|
30853
30958
|
case "max_tokens_reached":
|
|
30854
|
-
return "length";
|
|
30959
|
+
return { unified: "length", raw };
|
|
30855
30960
|
case "tool_calls":
|
|
30856
30961
|
case "tool_call":
|
|
30857
30962
|
case "function_call":
|
|
30858
|
-
return "tool-calls";
|
|
30963
|
+
return { unified: "tool-calls", raw };
|
|
30859
30964
|
case "content_filter":
|
|
30860
|
-
return "content-filter";
|
|
30965
|
+
return { unified: "content-filter", raw };
|
|
30861
30966
|
case "error":
|
|
30862
|
-
return "error";
|
|
30967
|
+
return { unified: "error", raw };
|
|
30863
30968
|
default:
|
|
30864
|
-
return "other";
|
|
30969
|
+
return { unified: "other", raw };
|
|
30865
30970
|
}
|
|
30866
30971
|
}
|
|
30867
30972
|
|