@jerome-benoit/sap-ai-provider 3.0.0-rc.2 → 3.0.0-rc.4

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.mjs DELETED
@@ -1,687 +0,0 @@
1
- // src/sap-ai-chat-language-model.ts
2
- import {
3
- OrchestrationClient
4
- } from "@sap-ai-sdk/orchestration";
5
- import { zodToJsonSchema } from "zod-to-json-schema";
6
-
7
- // src/convert-to-sap-messages.ts
8
- import {
9
- UnsupportedFunctionalityError
10
- } from "@ai-sdk/provider";
11
- function convertToSAPMessages(prompt) {
12
- const messages = [];
13
- for (const message of prompt) {
14
- switch (message.role) {
15
- case "system": {
16
- const systemMessage = {
17
- role: "system",
18
- content: message.content
19
- };
20
- messages.push(systemMessage);
21
- break;
22
- }
23
- case "user": {
24
- const contentParts = [];
25
- for (const part of message.content) {
26
- switch (part.type) {
27
- case "text": {
28
- contentParts.push({
29
- type: "text",
30
- text: part.text
31
- });
32
- break;
33
- }
34
- case "file": {
35
- if (!part.mediaType.startsWith("image/")) {
36
- throw new UnsupportedFunctionalityError({
37
- functionality: "Only image files are supported"
38
- });
39
- }
40
- const imageUrl = part.data instanceof URL ? part.data.toString() : `data:${part.mediaType};base64,${String(part.data)}`;
41
- contentParts.push({
42
- type: "image_url",
43
- image_url: {
44
- url: imageUrl
45
- }
46
- });
47
- break;
48
- }
49
- default: {
50
- throw new UnsupportedFunctionalityError({
51
- functionality: `Content type ${part.type}`
52
- });
53
- }
54
- }
55
- }
56
- const userMessage = contentParts.length === 1 && contentParts[0].type === "text" ? {
57
- role: "user",
58
- content: contentParts[0].text ?? ""
59
- } : {
60
- role: "user",
61
- content: contentParts
62
- };
63
- messages.push(userMessage);
64
- break;
65
- }
66
- case "assistant": {
67
- let text = "";
68
- const toolCalls = [];
69
- for (const part of message.content) {
70
- switch (part.type) {
71
- case "text": {
72
- text += part.text;
73
- break;
74
- }
75
- case "tool-call": {
76
- toolCalls.push({
77
- id: part.toolCallId,
78
- type: "function",
79
- function: {
80
- name: part.toolName,
81
- arguments: JSON.stringify(part.input)
82
- }
83
- });
84
- break;
85
- }
86
- }
87
- }
88
- const assistantMessage = {
89
- role: "assistant",
90
- content: text || "",
91
- tool_calls: toolCalls.length > 0 ? toolCalls : void 0
92
- };
93
- messages.push(assistantMessage);
94
- break;
95
- }
96
- case "tool": {
97
- for (const part of message.content) {
98
- const toolMessage = {
99
- role: "tool",
100
- tool_call_id: part.toolCallId,
101
- content: JSON.stringify(part.output)
102
- };
103
- messages.push(toolMessage);
104
- }
105
- break;
106
- }
107
- default: {
108
- const _exhaustiveCheck = message;
109
- throw new Error(
110
- `Unsupported role: ${_exhaustiveCheck.role}`
111
- );
112
- }
113
- }
114
- }
115
- return messages;
116
- }
117
-
118
- // src/sap-ai-chat-language-model.ts
119
- function isZodSchema(obj) {
120
- return obj !== null && typeof obj === "object" && "_def" in obj && "parse" in obj && typeof obj.parse === "function";
121
- }
122
- var SAPAIChatLanguageModel = class {
123
- /**
124
- * Creates a new SAP AI Chat Language Model instance.
125
- *
126
- * @param modelId - The model identifier
127
- * @param settings - Model-specific configuration settings
128
- * @param config - Internal configuration (deployment config, destination, etc.)
129
- *
130
- * @internal This constructor is not meant to be called directly.
131
- * Use the provider function instead.
132
- */
133
- constructor(modelId, settings, config) {
134
- /** AI SDK specification version */
135
- this.specificationVersion = "v2";
136
- /** Default object generation mode */
137
- this.defaultObjectGenerationMode = "json";
138
- /** Whether the model supports image URLs */
139
- this.supportsImageUrls = true;
140
- /** Whether the model supports structured outputs */
141
- this.supportsStructuredOutputs = true;
142
- this.settings = settings;
143
- this.config = config;
144
- this.modelId = modelId;
145
- }
146
- /**
147
- * Checks if a URL is supported for file/image uploads.
148
- *
149
- * @param url - The URL to check
150
- * @returns True if the URL protocol is HTTPS
151
- */
152
- supportsUrl(url) {
153
- return url.protocol === "https:";
154
- }
155
- /**
156
- * Returns supported URL patterns for different content types.
157
- *
158
- * @returns Record of content types to regex patterns
159
- */
160
- get supportedUrls() {
161
- return {
162
- "image/*": [
163
- /^https:\/\/.*\.(?:png|jpg|jpeg|gif|webp)$/i,
164
- /^data:image\/.*$/
165
- ]
166
- };
167
- }
168
- /**
169
- * Gets the provider identifier.
170
- *
171
- * @returns The provider name ('sap-ai')
172
- */
173
- get provider() {
174
- return this.config.provider;
175
- }
176
- /**
177
- * Builds orchestration module config for SAP AI SDK.
178
- *
179
- * @param options - Call options from the AI SDK
180
- * @returns Object containing orchestration config and warnings
181
- *
182
- * @internal
183
- */
184
- buildOrchestrationConfig(options) {
185
- const warnings = [];
186
- const messages = convertToSAPMessages(options.prompt);
187
- let tools;
188
- if (this.settings.tools && this.settings.tools.length > 0) {
189
- tools = this.settings.tools;
190
- } else {
191
- const availableTools = options.tools;
192
- tools = availableTools?.map((tool) => {
193
- if (tool.type === "function") {
194
- const inputSchema = tool.inputSchema;
195
- const toolWithParams = tool;
196
- let parameters;
197
- if (toolWithParams.parameters && isZodSchema(toolWithParams.parameters)) {
198
- const jsonSchema = zodToJsonSchema(toolWithParams.parameters, {
199
- $refStrategy: "none"
200
- });
201
- delete jsonSchema.$schema;
202
- parameters = {
203
- type: "object",
204
- ...jsonSchema
205
- };
206
- } else if (inputSchema && Object.keys(inputSchema).length > 0) {
207
- const hasProperties = inputSchema.properties && typeof inputSchema.properties === "object" && Object.keys(inputSchema.properties).length > 0;
208
- if (hasProperties) {
209
- parameters = {
210
- type: "object",
211
- ...inputSchema
212
- };
213
- } else {
214
- parameters = {
215
- type: "object",
216
- properties: {},
217
- required: []
218
- };
219
- }
220
- } else {
221
- parameters = {
222
- type: "object",
223
- properties: {},
224
- required: []
225
- };
226
- }
227
- return {
228
- type: "function",
229
- function: {
230
- name: tool.name,
231
- description: tool.description,
232
- parameters
233
- }
234
- };
235
- } else {
236
- warnings.push({
237
- type: "unsupported-tool",
238
- tool
239
- });
240
- return null;
241
- }
242
- }).filter((t) => t !== null);
243
- }
244
- const supportsN = !this.modelId.startsWith("amazon--") && !this.modelId.startsWith("anthropic--");
245
- const orchestrationConfig = {
246
- promptTemplating: {
247
- model: {
248
- name: this.modelId,
249
- version: this.settings.modelVersion ?? "latest",
250
- params: {
251
- max_tokens: this.settings.modelParams?.maxTokens,
252
- temperature: this.settings.modelParams?.temperature,
253
- top_p: this.settings.modelParams?.topP,
254
- frequency_penalty: this.settings.modelParams?.frequencyPenalty,
255
- presence_penalty: this.settings.modelParams?.presencePenalty,
256
- n: supportsN ? this.settings.modelParams?.n ?? 1 : void 0
257
- }
258
- },
259
- prompt: {
260
- template: [],
261
- tools: tools && tools.length > 0 ? tools : void 0
262
- }
263
- },
264
- // Include masking module if provided
265
- ...this.settings.masking ? { masking: this.settings.masking } : {},
266
- // Include filtering module if provided
267
- ...this.settings.filtering ? { filtering: this.settings.filtering } : {}
268
- };
269
- return { orchestrationConfig, messages, warnings };
270
- }
271
- /**
272
- * Creates an OrchestrationClient instance.
273
- *
274
- * @param config - Orchestration module configuration
275
- * @returns OrchestrationClient instance
276
- *
277
- * @internal
278
- */
279
- createClient(config) {
280
- return new OrchestrationClient(
281
- config,
282
- this.config.deploymentConfig,
283
- this.config.destination
284
- );
285
- }
286
- /**
287
- * Generates a single completion (non-streaming).
288
- *
289
- * This method implements the `LanguageModelV2.doGenerate` interface,
290
- * sending a request to SAP AI Core and returning the complete response.
291
- *
292
- * **Features:**
293
- * - Tool calling support
294
- * - Multi-modal input (text + images)
295
- * - Data masking (if configured)
296
- * - Content filtering (if configured)
297
- *
298
- * @param options - Generation options including prompt, tools, and settings
299
- * @returns Promise resolving to the generation result with content, usage, and metadata
300
- *
301
- * @example
302
- * ```typescript
303
- * const result = await model.doGenerate({
304
- * prompt: [
305
- * { role: 'user', content: [{ type: 'text', text: 'Hello!' }] }
306
- * ]
307
- * });
308
- *
309
- * console.log(result.content); // Generated content
310
- * console.log(result.usage); // Token usage
311
- * ```
312
- */
313
- async doGenerate(options) {
314
- const { orchestrationConfig, messages, warnings } = this.buildOrchestrationConfig(options);
315
- const client = this.createClient(orchestrationConfig);
316
- const response = await client.chatCompletion({
317
- messages
318
- });
319
- const content = [];
320
- const textContent = response.getContent();
321
- if (textContent) {
322
- content.push({
323
- type: "text",
324
- text: textContent
325
- });
326
- }
327
- const toolCalls = response.getToolCalls();
328
- if (toolCalls) {
329
- for (const toolCall of toolCalls) {
330
- content.push({
331
- type: "tool-call",
332
- toolCallId: toolCall.id,
333
- toolName: toolCall.function.name,
334
- // AI SDK expects input as a JSON string, which it parses internally
335
- input: toolCall.function.arguments
336
- });
337
- }
338
- }
339
- const tokenUsage = response.getTokenUsage();
340
- const finishReasonRaw = response.getFinishReason();
341
- const finishReason = mapFinishReason(finishReasonRaw);
342
- return {
343
- content,
344
- finishReason,
345
- usage: {
346
- inputTokens: tokenUsage.prompt_tokens,
347
- outputTokens: tokenUsage.completion_tokens,
348
- totalTokens: tokenUsage.total_tokens
349
- },
350
- rawCall: {
351
- rawPrompt: { config: orchestrationConfig, messages },
352
- rawSettings: {}
353
- },
354
- warnings
355
- };
356
- }
357
- /**
358
- * Generates a streaming completion.
359
- *
360
- * This method implements the `LanguageModelV2.doStream` interface,
361
- * sending a streaming request to SAP AI Core and returning a stream of response parts.
362
- *
363
- * **Stream Events:**
364
- * - `stream-start` - Stream initialization
365
- * - `response-metadata` - Response metadata (model, timestamp)
366
- * - `text-start` - Text generation starts
367
- * - `text-delta` - Incremental text chunks
368
- * - `text-end` - Text generation completes
369
- * - `tool-call` - Tool call detected
370
- * - `finish` - Stream completes with usage and finish reason
371
- * - `error` - Error occurred
372
- *
373
- * @param options - Streaming options including prompt, tools, and settings
374
- * @returns Promise resolving to stream and raw call metadata
375
- *
376
- * @example
377
- * ```typescript
378
- * const { stream } = await model.doStream({
379
- * prompt: [
380
- * { role: 'user', content: [{ type: 'text', text: 'Write a story' }] }
381
- * ]
382
- * });
383
- *
384
- * for await (const part of stream) {
385
- * if (part.type === 'text-delta') {
386
- * process.stdout.write(part.delta);
387
- * }
388
- * }
389
- * ```
390
- */
391
- async doStream(options) {
392
- const { orchestrationConfig, messages, warnings } = this.buildOrchestrationConfig(options);
393
- const client = this.createClient(orchestrationConfig);
394
- const streamResponse = await client.stream(
395
- { messages },
396
- options.abortSignal,
397
- { promptTemplating: { include_usage: true } }
398
- );
399
- let finishReason = "unknown";
400
- const usage = {
401
- inputTokens: void 0,
402
- outputTokens: void 0,
403
- totalTokens: void 0
404
- };
405
- let isFirstChunk = true;
406
- let activeText = false;
407
- const toolCallsInProgress = /* @__PURE__ */ new Map();
408
- const sdkStream = streamResponse.stream;
409
- const transformedStream = new ReadableStream({
410
- async start(controller) {
411
- controller.enqueue({ type: "stream-start", warnings });
412
- try {
413
- for await (const chunk of sdkStream) {
414
- if (isFirstChunk) {
415
- isFirstChunk = false;
416
- controller.enqueue({
417
- type: "response-metadata",
418
- id: void 0,
419
- modelId: void 0,
420
- timestamp: /* @__PURE__ */ new Date()
421
- });
422
- }
423
- const deltaContent = chunk.getDeltaContent();
424
- if (deltaContent) {
425
- if (!activeText) {
426
- controller.enqueue({ type: "text-start", id: "0" });
427
- activeText = true;
428
- }
429
- controller.enqueue({
430
- type: "text-delta",
431
- id: "0",
432
- delta: deltaContent
433
- });
434
- }
435
- const deltaToolCalls = chunk.getDeltaToolCalls();
436
- if (deltaToolCalls) {
437
- for (const toolCallChunk of deltaToolCalls) {
438
- const index = toolCallChunk.index;
439
- if (!toolCallsInProgress.has(index)) {
440
- toolCallsInProgress.set(index, {
441
- id: toolCallChunk.id ?? `tool_${String(index)}`,
442
- name: toolCallChunk.function?.name ?? "",
443
- arguments: ""
444
- });
445
- const tc2 = toolCallsInProgress.get(index);
446
- if (!tc2) continue;
447
- if (toolCallChunk.function?.name) {
448
- controller.enqueue({
449
- type: "tool-input-start",
450
- id: tc2.id,
451
- toolName: tc2.name
452
- });
453
- }
454
- }
455
- const tc = toolCallsInProgress.get(index);
456
- if (!tc) continue;
457
- if (toolCallChunk.id) {
458
- tc.id = toolCallChunk.id;
459
- }
460
- if (toolCallChunk.function?.name) {
461
- tc.name = toolCallChunk.function.name;
462
- }
463
- if (toolCallChunk.function?.arguments) {
464
- tc.arguments += toolCallChunk.function.arguments;
465
- controller.enqueue({
466
- type: "tool-input-delta",
467
- id: tc.id,
468
- delta: toolCallChunk.function.arguments
469
- });
470
- }
471
- }
472
- }
473
- const chunkFinishReason = chunk.getFinishReason();
474
- if (chunkFinishReason) {
475
- finishReason = mapFinishReason(chunkFinishReason);
476
- }
477
- const chunkUsage = chunk.getTokenUsage();
478
- if (chunkUsage) {
479
- usage.inputTokens = chunkUsage.prompt_tokens;
480
- usage.outputTokens = chunkUsage.completion_tokens;
481
- usage.totalTokens = chunkUsage.total_tokens;
482
- }
483
- }
484
- const toolCalls = Array.from(toolCallsInProgress.values());
485
- for (const tc of toolCalls) {
486
- controller.enqueue({
487
- type: "tool-input-end",
488
- id: tc.id
489
- });
490
- controller.enqueue({
491
- type: "tool-call",
492
- toolCallId: tc.id,
493
- toolName: tc.name,
494
- input: tc.arguments
495
- });
496
- }
497
- if (activeText) {
498
- controller.enqueue({ type: "text-end", id: "0" });
499
- }
500
- const finalUsage = streamResponse.getTokenUsage();
501
- if (finalUsage) {
502
- usage.inputTokens = finalUsage.prompt_tokens;
503
- usage.outputTokens = finalUsage.completion_tokens;
504
- usage.totalTokens = finalUsage.total_tokens;
505
- }
506
- const finalFinishReason = streamResponse.getFinishReason();
507
- if (finalFinishReason) {
508
- finishReason = mapFinishReason(finalFinishReason);
509
- }
510
- controller.enqueue({
511
- type: "finish",
512
- finishReason,
513
- usage
514
- });
515
- controller.close();
516
- } catch (error) {
517
- controller.enqueue({
518
- type: "error",
519
- error: error instanceof Error ? error : new Error(String(error))
520
- });
521
- controller.close();
522
- }
523
- }
524
- });
525
- return {
526
- stream: transformedStream,
527
- rawCall: {
528
- rawPrompt: { config: orchestrationConfig, messages },
529
- rawSettings: {}
530
- }
531
- };
532
- }
533
- };
534
- function mapFinishReason(reason) {
535
- if (!reason) return "unknown";
536
- switch (reason.toLowerCase()) {
537
- case "stop":
538
- return "stop";
539
- case "length":
540
- return "length";
541
- case "tool_calls":
542
- case "function_call":
543
- return "tool-calls";
544
- case "content_filter":
545
- return "content-filter";
546
- default:
547
- return "unknown";
548
- }
549
- }
550
-
551
- // src/sap-ai-provider.ts
552
- function createSAPAIProvider(options = {}) {
553
- const resourceGroup = options.resourceGroup ?? "default";
554
- const deploymentConfig = options.deploymentId ? { deploymentId: options.deploymentId } : { resourceGroup };
555
- const createModel = (modelId, settings = {}) => {
556
- const mergedSettings = {
557
- ...options.defaultSettings,
558
- ...settings,
559
- modelParams: {
560
- ...options.defaultSettings?.modelParams ?? {},
561
- ...settings.modelParams ?? {}
562
- }
563
- };
564
- return new SAPAIChatLanguageModel(modelId, mergedSettings, {
565
- provider: "sap-ai",
566
- deploymentConfig,
567
- destination: options.destination
568
- });
569
- };
570
- const provider = function(modelId, settings) {
571
- if (new.target) {
572
- throw new Error(
573
- "The SAP AI provider function cannot be called with the new keyword."
574
- );
575
- }
576
- return createModel(modelId, settings);
577
- };
578
- provider.chat = createModel;
579
- return provider;
580
- }
581
- var sapai = createSAPAIProvider();
582
-
583
- // src/sap-ai-chat-settings.ts
584
- import {
585
- buildDpiMaskingProvider,
586
- buildAzureContentSafetyFilter,
587
- buildLlamaGuard38BFilter,
588
- buildDocumentGroundingConfig,
589
- buildTranslationConfig
590
- } from "@sap-ai-sdk/orchestration";
591
-
592
- // src/sap-ai-error.ts
593
- var SAPAIError = class _SAPAIError extends Error {
594
- constructor(message, options) {
595
- super(message);
596
- this.name = "SAPAIError";
597
- this.code = options?.code;
598
- this.location = options?.location;
599
- this.requestId = options?.requestId;
600
- this.details = options?.details;
601
- this.cause = options?.cause;
602
- }
603
- /**
604
- * Creates a SAPAIError from an OrchestrationErrorResponse.
605
- *
606
- * @param errorResponse - The error response from SAP AI SDK
607
- * @returns A new SAPAIError instance
608
- */
609
- static fromOrchestrationError(errorResponse) {
610
- const error = errorResponse.error;
611
- if (Array.isArray(error)) {
612
- const firstError = error[0];
613
- return new _SAPAIError(
614
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
615
- firstError?.message ?? "Unknown orchestration error",
616
- {
617
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
618
- code: firstError?.code,
619
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
620
- location: firstError?.location,
621
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
622
- requestId: firstError?.request_id
623
- }
624
- );
625
- } else {
626
- return new _SAPAIError(error.message ?? "Unknown orchestration error", {
627
- code: error.code,
628
- location: error.location,
629
- requestId: error.request_id
630
- });
631
- }
632
- }
633
- /**
634
- * Creates a SAPAIError from a generic error.
635
- *
636
- * @param error - The original error
637
- * @param context - Optional context about where the error occurred
638
- * @returns A new SAPAIError instance
639
- */
640
- static fromError(error, context) {
641
- if (error instanceof _SAPAIError) {
642
- return error;
643
- }
644
- let message;
645
- if (error instanceof Error) {
646
- message = error.message;
647
- } else if (error == null) {
648
- message = "Unknown error";
649
- } else if (typeof error === "string" || typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") {
650
- message = String(error);
651
- } else {
652
- try {
653
- message = JSON.stringify(error);
654
- } catch {
655
- message = "[Unstringifiable Value]";
656
- }
657
- }
658
- return new _SAPAIError(context ? `${context}: ${message}` : message, {
659
- cause: error
660
- });
661
- }
662
- };
663
-
664
- // src/types/completion-response.ts
665
- import {
666
- OrchestrationResponse,
667
- OrchestrationStreamResponse,
668
- OrchestrationStreamChunkResponse
669
- } from "@sap-ai-sdk/orchestration";
670
-
671
- // src/index.ts
672
- import { OrchestrationClient as OrchestrationClient2 } from "@sap-ai-sdk/orchestration";
673
- export {
674
- OrchestrationClient2 as OrchestrationClient,
675
- OrchestrationResponse,
676
- OrchestrationStreamChunkResponse,
677
- OrchestrationStreamResponse,
678
- SAPAIError,
679
- buildAzureContentSafetyFilter,
680
- buildDocumentGroundingConfig,
681
- buildDpiMaskingProvider,
682
- buildLlamaGuard38BFilter,
683
- buildTranslationConfig,
684
- createSAPAIProvider,
685
- sapai
686
- };
687
- //# sourceMappingURL=index.mjs.map