@jerome-benoit/sap-ai-provider-v2 5.0.3 → 5.1.0
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 +44 -24
- package/dist/chunk-4ILJD6PI.js +333 -0
- package/dist/chunk-4ILJD6PI.js.map +1 -0
- package/dist/chunk-BHA5MIN6.js +224 -0
- package/dist/chunk-BHA5MIN6.js.map +1 -0
- package/dist/{chunk-4JLNWASF.js → chunk-DLGBK3C3.js} +91 -7
- package/dist/chunk-DLGBK3C3.js.map +1 -0
- package/dist/{chunk-E76ZDPIW.js → chunk-EUBI5ENK.js} +54 -27
- package/dist/chunk-EUBI5ENK.js.map +1 -0
- package/dist/{chunk-YP3AADWD.js → chunk-FKHOJ3ZC.js} +24 -3
- package/dist/chunk-FKHOJ3ZC.js.map +1 -0
- package/dist/{chunk-UIBQ5AFS.js → chunk-QFMHNGLB.js} +56 -218
- package/dist/chunk-QFMHNGLB.js.map +1 -0
- package/dist/{chunk-3H5C5U22.js → chunk-QQQYMQPU.js} +19 -10
- package/dist/chunk-QQQYMQPU.js.map +1 -0
- package/dist/{chunk-NF355OFT.js → chunk-ZOBD7PXJ.js} +116 -21
- package/dist/chunk-ZOBD7PXJ.js.map +1 -0
- package/dist/{foundation-models-embedding-model-strategy-SAVQFBF4.js → foundation-models-embedding-model-strategy-3WAKJXBY.js} +5 -4
- package/dist/{foundation-models-embedding-model-strategy-SAVQFBF4.js.map → foundation-models-embedding-model-strategy-3WAKJXBY.js.map} +1 -1
- package/dist/{foundation-models-language-model-strategy-ZDOWVYTA.js → foundation-models-language-model-strategy-PJ26VXEA.js} +21 -8
- package/dist/foundation-models-language-model-strategy-PJ26VXEA.js.map +1 -0
- package/dist/index.cjs +2008 -497
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +798 -185
- package/dist/index.d.ts +798 -185
- package/dist/index.js +148 -24
- package/dist/index.js.map +1 -1
- package/dist/{orchestration-embedding-model-strategy-GT27AFII.js → orchestration-embedding-model-strategy-AZ76XLMD.js} +6 -5
- package/dist/{orchestration-embedding-model-strategy-GT27AFII.js.map → orchestration-embedding-model-strategy-AZ76XLMD.js.map} +1 -1
- package/dist/{orchestration-language-model-strategy-7QTPQHCC.js → orchestration-language-model-strategy-CT6RHCYO.js} +48 -17
- package/dist/orchestration-language-model-strategy-CT6RHCYO.js.map +1 -0
- package/dist/responses-language-model-strategy-UYTV6TPM.js +738 -0
- package/dist/responses-language-model-strategy-UYTV6TPM.js.map +1 -0
- package/dist/sap-ai-error-UPKYWPSS.js +16 -0
- package/dist/sap-ai-error-UPKYWPSS.js.map +1 -0
- package/package.json +79 -39
- package/dist/chunk-3H5C5U22.js.map +0 -1
- package/dist/chunk-4JLNWASF.js.map +0 -1
- package/dist/chunk-E76ZDPIW.js.map +0 -1
- package/dist/chunk-NF355OFT.js.map +0 -1
- package/dist/chunk-UIBQ5AFS.js.map +0 -1
- package/dist/chunk-YP3AADWD.js.map +0 -1
- package/dist/foundation-models-language-model-strategy-ZDOWVYTA.js.map +0 -1
- package/dist/orchestration-language-model-strategy-7QTPQHCC.js.map +0 -1
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import {createRequire as __createRequire} from 'module';var require=__createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ERROR_BODY_READ_FAILURE,
|
|
4
|
+
destroyHttpResponse,
|
|
5
|
+
findFailedHttpRequest,
|
|
6
|
+
isDestroyableRequest,
|
|
7
|
+
preserveResponseBodyTimeout,
|
|
8
|
+
readHttpErrorBody
|
|
9
|
+
} from "./chunk-4ILJD6PI.js";
|
|
10
|
+
import {
|
|
11
|
+
getURLHref
|
|
12
|
+
} from "./chunk-FKHOJ3ZC.js";
|
|
13
|
+
import {
|
|
14
|
+
buildModelDeployment,
|
|
15
|
+
createAISDKRequestBodySummary,
|
|
16
|
+
createAbortError,
|
|
17
|
+
mergeRequestConfig,
|
|
18
|
+
throwIfAborted,
|
|
19
|
+
waitWithAbort
|
|
20
|
+
} from "./chunk-DLGBK3C3.js";
|
|
21
|
+
import {
|
|
22
|
+
VERSION,
|
|
23
|
+
getProviderName
|
|
24
|
+
} from "./chunk-BHA5MIN6.js";
|
|
25
|
+
import {
|
|
26
|
+
convertToAISDKError,
|
|
27
|
+
normalizeHeaders
|
|
28
|
+
} from "./chunk-QFMHNGLB.js";
|
|
29
|
+
|
|
30
|
+
// src/responses-language-model-strategy.ts
|
|
31
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
32
|
+
import { APICallError as APICallError2 } from "@ai-sdk/provider";
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
|
|
35
|
+
// src/responses-adapters-v3-to-v4.ts
|
|
36
|
+
import { UnsupportedFunctionalityError } from "@ai-sdk/provider";
|
|
37
|
+
import { getTopLevelMediaType } from "@ai-sdk/provider-utils";
|
|
38
|
+
function convertResponsesCallToV4(options) {
|
|
39
|
+
return {
|
|
40
|
+
...options,
|
|
41
|
+
prompt: options.prompt.map((message) => {
|
|
42
|
+
switch (message.role) {
|
|
43
|
+
case "assistant":
|
|
44
|
+
return { ...message, content: message.content.map(convertPromptPart) };
|
|
45
|
+
case "system":
|
|
46
|
+
return message;
|
|
47
|
+
case "tool":
|
|
48
|
+
return {
|
|
49
|
+
...message,
|
|
50
|
+
content: message.content.map(
|
|
51
|
+
(part) => part.type === "tool-result" ? convertToolResult(part) : part
|
|
52
|
+
)
|
|
53
|
+
};
|
|
54
|
+
case "user":
|
|
55
|
+
return {
|
|
56
|
+
...message,
|
|
57
|
+
content: message.content.map(
|
|
58
|
+
(part) => part.type === "file" ? convertFile(part) : part
|
|
59
|
+
)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function convertFile(part) {
|
|
66
|
+
const data = part.data;
|
|
67
|
+
const href = getURLHref(data);
|
|
68
|
+
if (href !== void 0) {
|
|
69
|
+
const url = data instanceof URL && data.toString === URL.prototype.toString ? data : new URL(href);
|
|
70
|
+
return { ...part, data: { type: "url", url } };
|
|
71
|
+
}
|
|
72
|
+
return { ...part, data: { data, type: "data" } };
|
|
73
|
+
}
|
|
74
|
+
function convertPromptPart(part) {
|
|
75
|
+
if (part.type === "file") {
|
|
76
|
+
throw new UnsupportedFunctionalityError({
|
|
77
|
+
functionality: "Responses assistant file history is not supported."
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (part.type === "tool-result") return convertToolResult(part);
|
|
81
|
+
return part;
|
|
82
|
+
}
|
|
83
|
+
function convertToolItem(item) {
|
|
84
|
+
switch (item.type) {
|
|
85
|
+
case "custom":
|
|
86
|
+
case "text":
|
|
87
|
+
return item;
|
|
88
|
+
case "file-data":
|
|
89
|
+
case "image-data": {
|
|
90
|
+
const delegateIsImage = getTopLevelMediaType(item.mediaType) === "image";
|
|
91
|
+
if (delegateIsImage !== (item.type === "image-data")) {
|
|
92
|
+
throw new UnsupportedFunctionalityError({
|
|
93
|
+
functionality: "Responses tool-result media whose legacy category disagrees with its media type."
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return { ...item, data: { data: item.data, type: "data" }, type: "file" };
|
|
97
|
+
}
|
|
98
|
+
case "file-id":
|
|
99
|
+
case "image-file-id":
|
|
100
|
+
return {
|
|
101
|
+
data: {
|
|
102
|
+
reference: typeof item.fileId === "string" ? { openai: item.fileId } : item.fileId,
|
|
103
|
+
type: "reference"
|
|
104
|
+
},
|
|
105
|
+
mediaType: item.type === "image-file-id" ? "image" : "application/octet-stream",
|
|
106
|
+
providerOptions: item.providerOptions,
|
|
107
|
+
type: "file"
|
|
108
|
+
};
|
|
109
|
+
case "file-url":
|
|
110
|
+
case "image-url":
|
|
111
|
+
return {
|
|
112
|
+
data: { type: "url", url: new URL(item.url) },
|
|
113
|
+
mediaType: item.type === "image-url" ? "image" : "application/octet-stream",
|
|
114
|
+
providerOptions: item.providerOptions,
|
|
115
|
+
type: "file"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function convertToolResult(part) {
|
|
120
|
+
return part.output.type === "content" ? { ...part, output: { ...part.output, value: part.output.value.map(convertToolItem) } } : { ...part, output: part.output };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/responses-adapters-v4-to-v3.ts
|
|
124
|
+
import { UnsupportedFunctionalityError as UnsupportedFunctionalityError2 } from "@ai-sdk/provider";
|
|
125
|
+
|
|
126
|
+
// src/adapter-metadata.ts
|
|
127
|
+
var ORIGINAL_V4_FINISH_REASON = /* @__PURE__ */ Symbol.for(
|
|
128
|
+
"@jerome-benoit/sap-ai-provider/adapter-metadata.originalV4FinishReason"
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// src/responses-adapters-v4-to-v3.ts
|
|
132
|
+
function convertResponsesResultToV3(result) {
|
|
133
|
+
return {
|
|
134
|
+
...result,
|
|
135
|
+
content: result.content.map(convertContent),
|
|
136
|
+
finishReason: convertFinishReason(result.finishReason),
|
|
137
|
+
warnings: result.warnings.map(convertWarning)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function convertResponsesStreamPartToV3(part) {
|
|
141
|
+
switch (part.type) {
|
|
142
|
+
case "custom":
|
|
143
|
+
case "reasoning-file":
|
|
144
|
+
throw new UnsupportedFunctionalityError2({
|
|
145
|
+
functionality: `Responses '${part.type}' output has no V3 equivalent.`
|
|
146
|
+
});
|
|
147
|
+
case "file":
|
|
148
|
+
return convertGeneratedFile(part);
|
|
149
|
+
case "finish":
|
|
150
|
+
return { ...part, finishReason: convertFinishReason(part.finishReason) };
|
|
151
|
+
case "stream-start":
|
|
152
|
+
return { ...part, warnings: part.warnings.map(convertWarning) };
|
|
153
|
+
default:
|
|
154
|
+
return part;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function convertContent(part) {
|
|
158
|
+
if (part.type === "custom" || part.type === "reasoning-file") {
|
|
159
|
+
throw new UnsupportedFunctionalityError2({
|
|
160
|
+
functionality: `Responses '${part.type}' output has no V3 equivalent.`
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return part.type === "file" ? convertGeneratedFile(part) : part;
|
|
164
|
+
}
|
|
165
|
+
function convertFinishReason(reason) {
|
|
166
|
+
if (reason.unified !== "interrupted") return reason;
|
|
167
|
+
const converted = { ...reason, unified: "other" };
|
|
168
|
+
return Object.defineProperty(converted, ORIGINAL_V4_FINISH_REASON, { value: reason });
|
|
169
|
+
}
|
|
170
|
+
function convertGeneratedFile(part) {
|
|
171
|
+
if (part.data.type !== "data") {
|
|
172
|
+
throw new UnsupportedFunctionalityError2({
|
|
173
|
+
functionality: "Responses generated file URLs have no V3 equivalent and are not fetched."
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return { ...part, data: part.data.data };
|
|
177
|
+
}
|
|
178
|
+
function convertWarning(warning) {
|
|
179
|
+
return warning.type === "deprecated" ? { message: `${warning.setting}: ${warning.message}`, type: "other" } : warning;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/responses-http-transport.ts
|
|
183
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
184
|
+
import { getFoundationModelDeploymentId, getResourceGroup } from "@sap-ai-sdk/ai-api/internal.js";
|
|
185
|
+
import { executeRequest, getAiCoreDestination } from "@sap-ai-sdk/core";
|
|
186
|
+
import { getTargetUrl } from "@sap-ai-sdk/core/internal.js";
|
|
187
|
+
async function createResponsesTransport(config, settings, options) {
|
|
188
|
+
throwIfAborted(options.abortSignal);
|
|
189
|
+
const deployment = buildModelDeployment(config, settings.modelVersion);
|
|
190
|
+
const [deploymentId, destination] = await waitWithAbort(
|
|
191
|
+
Promise.all([
|
|
192
|
+
getFoundationModelDeploymentId(deployment, "azure-openai", config.destination),
|
|
193
|
+
getAiCoreDestination(config.destination)
|
|
194
|
+
]),
|
|
195
|
+
options.abortSignal
|
|
196
|
+
);
|
|
197
|
+
throwIfAborted(options.abortSignal);
|
|
198
|
+
const deploymentPath = `/inference/deployments/${encodeURIComponent(deploymentId)}`;
|
|
199
|
+
const baseURL = getTargetUrl(destination.url, deploymentPath);
|
|
200
|
+
const endpoint = new URL(`${baseURL}/responses`);
|
|
201
|
+
const requestConfig = mergeRequestConfig(
|
|
202
|
+
config.requestConfig,
|
|
203
|
+
options.abortSignal,
|
|
204
|
+
options.headers
|
|
205
|
+
);
|
|
206
|
+
const activeRequests = /* @__PURE__ */ new Set();
|
|
207
|
+
let disposed = false;
|
|
208
|
+
let disposalReason;
|
|
209
|
+
const dispose = (reason) => {
|
|
210
|
+
if (disposed) return;
|
|
211
|
+
disposed = true;
|
|
212
|
+
disposalReason = reason;
|
|
213
|
+
for (const release of activeRequests) {
|
|
214
|
+
try {
|
|
215
|
+
release(reason);
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
activeRequests.clear();
|
|
220
|
+
};
|
|
221
|
+
const fetch = async (input, init) => {
|
|
222
|
+
if (disposed) throw createAbortError(disposalReason);
|
|
223
|
+
const url = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
|
|
224
|
+
if (url.origin !== endpoint.origin || url.pathname !== endpoint.pathname) {
|
|
225
|
+
throw new TypeError("SAP Responses requests must target the configured deployment.");
|
|
226
|
+
}
|
|
227
|
+
if (init?.method !== "POST" || typeof init.body !== "string") {
|
|
228
|
+
throw new TypeError("SAP Responses requires a POST request with a JSON body.");
|
|
229
|
+
}
|
|
230
|
+
const body = JSON.parse(init.body);
|
|
231
|
+
const operation = typeof body === "object" && body !== null && "stream" in body && body.stream === true ? "doStream" : "doGenerate";
|
|
232
|
+
const controller = new AbortController();
|
|
233
|
+
const signal = options.abortSignal ?? init.signal;
|
|
234
|
+
const bodyTimeoutCleanups = /* @__PURE__ */ new Set();
|
|
235
|
+
let abortFailedRequest;
|
|
236
|
+
let activeResponse;
|
|
237
|
+
const onAbort = () => {
|
|
238
|
+
controller.abort(signal?.reason);
|
|
239
|
+
};
|
|
240
|
+
const cleanup = () => {
|
|
241
|
+
activeRequests.delete(disposeRequest);
|
|
242
|
+
signal?.removeEventListener("abort", onAbort);
|
|
243
|
+
if (abortFailedRequest) controller.signal.removeEventListener("abort", abortFailedRequest);
|
|
244
|
+
for (const dispose2 of bodyTimeoutCleanups) dispose2();
|
|
245
|
+
activeResponse = void 0;
|
|
246
|
+
};
|
|
247
|
+
const disposeRequest = (reason) => {
|
|
248
|
+
try {
|
|
249
|
+
controller.abort(reason);
|
|
250
|
+
} finally {
|
|
251
|
+
try {
|
|
252
|
+
if (activeResponse) destroyHttpResponse(activeResponse);
|
|
253
|
+
} finally {
|
|
254
|
+
cleanup();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
throwIfAborted(signal ?? void 0);
|
|
259
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
260
|
+
activeRequests.add(disposeRequest);
|
|
261
|
+
const headers = new Headers(init.headers);
|
|
262
|
+
headers.delete("authorization");
|
|
263
|
+
const configuredHeaders = Object.fromEntries(
|
|
264
|
+
Object.entries(requestConfig?.headers ?? {}).map(([key, value]) => [
|
|
265
|
+
key.toLowerCase(),
|
|
266
|
+
value
|
|
267
|
+
])
|
|
268
|
+
);
|
|
269
|
+
let failedRequest;
|
|
270
|
+
let failedError;
|
|
271
|
+
const watchResponse = (response, request) => {
|
|
272
|
+
activeResponse = response;
|
|
273
|
+
if (controller.signal.aborted) destroyHttpResponse(response);
|
|
274
|
+
else preserveResponseBodyTimeout(response, request, bodyTimeoutCleanups);
|
|
275
|
+
return response;
|
|
276
|
+
};
|
|
277
|
+
const preserveBodyTimeout = ({ fn }) => async (request) => {
|
|
278
|
+
try {
|
|
279
|
+
return watchResponse(await fn(request), request);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const failed = findFailedHttpRequest(error);
|
|
282
|
+
if (failed) watchResponse(failed.response, request);
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const preserveErrorResponse = ({ fn }) => async (request) => {
|
|
287
|
+
try {
|
|
288
|
+
return await fn(request);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
const failed = findFailedHttpRequest(error);
|
|
291
|
+
if (!failed) throw error;
|
|
292
|
+
failedRequest = failed;
|
|
293
|
+
failedError = error;
|
|
294
|
+
const request2 = failed.response.request;
|
|
295
|
+
if (isDestroyableRequest(request2)) {
|
|
296
|
+
abortFailedRequest = () => {
|
|
297
|
+
request2.destroy();
|
|
298
|
+
};
|
|
299
|
+
if (controller.signal.aborted) abortFailedRequest();
|
|
300
|
+
else controller.signal.addEventListener("abort", abortFailedRequest, { once: true });
|
|
301
|
+
}
|
|
302
|
+
return failed.response;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
try {
|
|
306
|
+
const pendingResponse = executeRequest(
|
|
307
|
+
{
|
|
308
|
+
apiVersion: "2024-10-21",
|
|
309
|
+
resourceGroup: getResourceGroup(deployment),
|
|
310
|
+
url: `${deploymentPath}/responses`
|
|
311
|
+
},
|
|
312
|
+
body,
|
|
313
|
+
{
|
|
314
|
+
...requestConfig,
|
|
315
|
+
headers: { ...Object.fromEntries(headers), ...configuredHeaders },
|
|
316
|
+
middleware: [
|
|
317
|
+
preserveErrorResponse,
|
|
318
|
+
...requestConfig?.middleware ?? [],
|
|
319
|
+
preserveBodyTimeout
|
|
320
|
+
],
|
|
321
|
+
responseType: "stream",
|
|
322
|
+
signal: controller.signal
|
|
323
|
+
},
|
|
324
|
+
config.destination
|
|
325
|
+
);
|
|
326
|
+
const response = await waitWithAbort(pendingResponse, controller.signal, destroyHttpResponse);
|
|
327
|
+
activeResponse = response;
|
|
328
|
+
if (controller.signal.aborted) {
|
|
329
|
+
destroyHttpResponse(response);
|
|
330
|
+
throw createAbortError(controller.signal.reason);
|
|
331
|
+
}
|
|
332
|
+
const responseBody = response.data;
|
|
333
|
+
const responseStream = createByteStream(
|
|
334
|
+
responseBody,
|
|
335
|
+
controller,
|
|
336
|
+
cleanup,
|
|
337
|
+
config,
|
|
338
|
+
options,
|
|
339
|
+
operation
|
|
340
|
+
);
|
|
341
|
+
if (failedRequest) {
|
|
342
|
+
const readFailure = await readHttpErrorBody(
|
|
343
|
+
failedRequest.response,
|
|
344
|
+
responseStream,
|
|
345
|
+
signal ?? void 0
|
|
346
|
+
);
|
|
347
|
+
const failure = convertToAISDKError(failedError, {
|
|
348
|
+
modelId: config.modelId,
|
|
349
|
+
modelType: "languageModel",
|
|
350
|
+
operation,
|
|
351
|
+
requestBody: createAISDKRequestBodySummary(options),
|
|
352
|
+
url: "sap-ai:responses"
|
|
353
|
+
});
|
|
354
|
+
if (readFailure !== void 0 && APICallError.isInstance(failure)) {
|
|
355
|
+
Object.defineProperty(failure, ERROR_BODY_READ_FAILURE, { value: readFailure });
|
|
356
|
+
}
|
|
357
|
+
throw failure;
|
|
358
|
+
}
|
|
359
|
+
return new Response(responseStream, {
|
|
360
|
+
headers: normalizeHeaders(response.headers),
|
|
361
|
+
status: response.status
|
|
362
|
+
});
|
|
363
|
+
} catch (error) {
|
|
364
|
+
dispose(error);
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
return { baseURL, dispose, fetch };
|
|
369
|
+
}
|
|
370
|
+
function createByteStream(body, requestController, cleanup, config, options, operation) {
|
|
371
|
+
if (typeof body !== "object" || body === null || !(Symbol.asyncIterator in body) || typeof body[Symbol.asyncIterator] !== "function") {
|
|
372
|
+
requestController.abort();
|
|
373
|
+
cleanup();
|
|
374
|
+
throw new TypeError("SAP Responses did not return a readable response body.");
|
|
375
|
+
}
|
|
376
|
+
const source = body;
|
|
377
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
378
|
+
const encoder = new TextEncoder();
|
|
379
|
+
const onAbort = () => source.destroy?.();
|
|
380
|
+
const cleanupStream = () => {
|
|
381
|
+
requestController.signal.removeEventListener("abort", onAbort);
|
|
382
|
+
cleanup();
|
|
383
|
+
};
|
|
384
|
+
requestController.signal.addEventListener("abort", onAbort, { once: true });
|
|
385
|
+
if (requestController.signal.aborted) onAbort();
|
|
386
|
+
return new ReadableStream(
|
|
387
|
+
{
|
|
388
|
+
async cancel(reason) {
|
|
389
|
+
requestController.abort(reason);
|
|
390
|
+
cleanupStream();
|
|
391
|
+
if (source.destroy) {
|
|
392
|
+
source.destroy();
|
|
393
|
+
} else {
|
|
394
|
+
await iterator.return?.();
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
async pull(controller) {
|
|
398
|
+
try {
|
|
399
|
+
throwIfAborted(requestController.signal);
|
|
400
|
+
const chunk = await iterator.next();
|
|
401
|
+
throwIfAborted(requestController.signal);
|
|
402
|
+
if (chunk.done) {
|
|
403
|
+
cleanupStream();
|
|
404
|
+
controller.close();
|
|
405
|
+
} else if (typeof chunk.value === "string") {
|
|
406
|
+
controller.enqueue(encoder.encode(chunk.value));
|
|
407
|
+
} else if (chunk.value instanceof Uint8Array) {
|
|
408
|
+
controller.enqueue(chunk.value);
|
|
409
|
+
} else {
|
|
410
|
+
throw new TypeError("SAP Responses returned a non-byte response chunk.");
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
const failure = requestController.signal.aborted ? createAbortError(requestController.signal.reason) : error;
|
|
414
|
+
requestController.abort(error);
|
|
415
|
+
cleanupStream();
|
|
416
|
+
source.destroy?.();
|
|
417
|
+
controller.error(
|
|
418
|
+
convertToAISDKError(failure, {
|
|
419
|
+
modelId: config.modelId,
|
|
420
|
+
modelType: "languageModel",
|
|
421
|
+
operation,
|
|
422
|
+
requestBody: createAISDKRequestBodySummary(options),
|
|
423
|
+
url: "sap-ai:responses"
|
|
424
|
+
})
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
{ highWaterMark: 0 }
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/responses-language-model-strategy.ts
|
|
434
|
+
var MODEL_PARAMETERS = /* @__PURE__ */ new Set([
|
|
435
|
+
"frequencyPenalty",
|
|
436
|
+
"maxTokens",
|
|
437
|
+
"parallel_tool_calls",
|
|
438
|
+
"presencePenalty",
|
|
439
|
+
"temperature",
|
|
440
|
+
"topP"
|
|
441
|
+
]);
|
|
442
|
+
var OPENAI_STREAM_ERROR_SCHEMA = z.object({
|
|
443
|
+
data: z.object({ type: z.enum(["error", "response.failed"]) }).loose(),
|
|
444
|
+
isRetryable: z.boolean(),
|
|
445
|
+
message: z.string(),
|
|
446
|
+
statusCode: z.number().int().min(400).max(599)
|
|
447
|
+
});
|
|
448
|
+
var ResponsesLanguageModelStrategy = class {
|
|
449
|
+
async doGenerate(config, settings, options) {
|
|
450
|
+
let dispose;
|
|
451
|
+
try {
|
|
452
|
+
const prepared = await prepareModel(config, settings, options);
|
|
453
|
+
dispose = prepared.dispose;
|
|
454
|
+
const { input, model, providerName, warnings } = prepared;
|
|
455
|
+
const result = convertResponsesResultToV3(await model.doGenerate(input));
|
|
456
|
+
return {
|
|
457
|
+
...result,
|
|
458
|
+
content: result.content.map((part) => fromOpenAIContent(part, providerName)),
|
|
459
|
+
providerMetadata: responseMetadata(result.providerMetadata, providerName),
|
|
460
|
+
warnings: [...warnings, ...result.warnings]
|
|
461
|
+
};
|
|
462
|
+
} catch (error) {
|
|
463
|
+
const failure = convertError(error, config, options, "doGenerate");
|
|
464
|
+
dispose?.(failure);
|
|
465
|
+
throw failure;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async doStream(config, settings, options) {
|
|
469
|
+
let dispose;
|
|
470
|
+
try {
|
|
471
|
+
const prepared = await prepareModel(config, settings, options);
|
|
472
|
+
dispose = prepared.dispose;
|
|
473
|
+
const { input, model, providerName, warnings } = prepared;
|
|
474
|
+
const result = await model.doStream(input);
|
|
475
|
+
const reader = result.stream.getReader();
|
|
476
|
+
let cancelled = false;
|
|
477
|
+
const isCancelled = () => cancelled;
|
|
478
|
+
return {
|
|
479
|
+
...result,
|
|
480
|
+
stream: new ReadableStream(
|
|
481
|
+
{
|
|
482
|
+
async cancel(reason) {
|
|
483
|
+
cancelled = true;
|
|
484
|
+
dispose?.(reason);
|
|
485
|
+
try {
|
|
486
|
+
await reader.cancel(reason);
|
|
487
|
+
} finally {
|
|
488
|
+
reader.releaseLock();
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
async pull(controller) {
|
|
492
|
+
try {
|
|
493
|
+
if (options.abortSignal?.aborted)
|
|
494
|
+
throw createAbortError(options.abortSignal.reason);
|
|
495
|
+
const next = await reader.read();
|
|
496
|
+
if (isCancelled()) return;
|
|
497
|
+
if (options.abortSignal?.aborted)
|
|
498
|
+
throw createAbortError(options.abortSignal.reason);
|
|
499
|
+
if (next.done) {
|
|
500
|
+
reader.releaseLock();
|
|
501
|
+
controller.close();
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const part = convertResponsesStreamPartToV3(next.value);
|
|
505
|
+
if (part.type === "stream-start") {
|
|
506
|
+
controller.enqueue({ ...part, warnings: [...warnings, ...part.warnings] });
|
|
507
|
+
} else if (part.type === "finish") {
|
|
508
|
+
controller.enqueue({
|
|
509
|
+
...part,
|
|
510
|
+
providerMetadata: responseMetadata(part.providerMetadata, providerName)
|
|
511
|
+
});
|
|
512
|
+
} else if (part.type === "error") {
|
|
513
|
+
controller.enqueue({
|
|
514
|
+
...part,
|
|
515
|
+
error: convertError(part.error, config, options, "doStream")
|
|
516
|
+
});
|
|
517
|
+
} else if ("providerMetadata" in part || part.type === "text-end") {
|
|
518
|
+
controller.enqueue(fromOpenAIContent(part, providerName));
|
|
519
|
+
} else {
|
|
520
|
+
controller.enqueue(part);
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
if (isCancelled()) return;
|
|
524
|
+
const failure = convertError(error, config, options, "doStream");
|
|
525
|
+
dispose?.(failure);
|
|
526
|
+
await reader.cancel(error).catch(() => void 0);
|
|
527
|
+
if (isCancelled()) return;
|
|
528
|
+
reader.releaseLock();
|
|
529
|
+
controller.enqueue({
|
|
530
|
+
error: failure,
|
|
531
|
+
type: "error"
|
|
532
|
+
});
|
|
533
|
+
controller.close();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
{ highWaterMark: 0 }
|
|
538
|
+
)
|
|
539
|
+
};
|
|
540
|
+
} catch (error) {
|
|
541
|
+
const failure = convertError(error, config, options, "doStream");
|
|
542
|
+
dispose?.(failure);
|
|
543
|
+
throw failure;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
function convertError(error, config, options, operation) {
|
|
548
|
+
if (APICallError2.isInstance(error) && error.message === "Failed to process successful response" && error.statusCode !== void 0 && error.statusCode >= 200 && error.statusCode < 300 && APICallError2.isInstance(error.cause)) {
|
|
549
|
+
return error.cause;
|
|
550
|
+
}
|
|
551
|
+
if (operation === "doStream" && !APICallError2.isInstance(error)) {
|
|
552
|
+
const parsed = OPENAI_STREAM_ERROR_SCHEMA.safeParse(error);
|
|
553
|
+
if (parsed.success) {
|
|
554
|
+
return new APICallError2({
|
|
555
|
+
cause: error,
|
|
556
|
+
data: parsed.data.data,
|
|
557
|
+
isRetryable: parsed.data.isRetryable,
|
|
558
|
+
message: parsed.data.message,
|
|
559
|
+
requestBodyValues: createAISDKRequestBodySummary(options),
|
|
560
|
+
statusCode: parsed.data.statusCode,
|
|
561
|
+
url: "sap-ai:responses"
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return convertToAISDKError(error, {
|
|
566
|
+
modelId: config.modelId,
|
|
567
|
+
modelType: "languageModel",
|
|
568
|
+
operation,
|
|
569
|
+
requestBody: createAISDKRequestBodySummary(options),
|
|
570
|
+
url: "sap-ai:responses"
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
function fromOpenAI(metadata, providerName) {
|
|
574
|
+
if (providerName === "openai" || !metadata?.openai) return metadata;
|
|
575
|
+
const { openai, ...rest } = metadata;
|
|
576
|
+
return { ...rest, [providerName]: { ...rest[providerName], ...openai } };
|
|
577
|
+
}
|
|
578
|
+
function fromOpenAICitations(citations, providerName) {
|
|
579
|
+
if (providerName === "openai" || !citations) return citations;
|
|
580
|
+
let mapped;
|
|
581
|
+
let index = 0;
|
|
582
|
+
for (const citation of citations) {
|
|
583
|
+
const providerMetadata = fromOpenAI(citation.source.providerMetadata, providerName);
|
|
584
|
+
if (providerMetadata !== citation.source.providerMetadata) {
|
|
585
|
+
mapped ??= citations.slice();
|
|
586
|
+
mapped[index] = {
|
|
587
|
+
...citation,
|
|
588
|
+
source: { ...citation.source, providerMetadata }
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
index++;
|
|
592
|
+
}
|
|
593
|
+
return mapped ?? citations;
|
|
594
|
+
}
|
|
595
|
+
function fromOpenAIContent(part, providerName) {
|
|
596
|
+
const providerMetadata = fromOpenAI(part.providerMetadata, providerName);
|
|
597
|
+
const citations = fromOpenAICitations(part.citations, providerName);
|
|
598
|
+
if (providerMetadata === part.providerMetadata && citations === part.citations) return part;
|
|
599
|
+
if (providerMetadata === part.providerMetadata) return { ...part, citations };
|
|
600
|
+
return citations === part.citations ? { ...part, providerMetadata } : { ...part, citations, providerMetadata };
|
|
601
|
+
}
|
|
602
|
+
function modelResponseFormat(settings) {
|
|
603
|
+
const format = settings.responseFormat;
|
|
604
|
+
if (!format || format.type === "text") return format;
|
|
605
|
+
if (format.type === "json_object") return { type: "json" };
|
|
606
|
+
const schema = format.json_schema;
|
|
607
|
+
return {
|
|
608
|
+
description: schema.description,
|
|
609
|
+
name: schema.name,
|
|
610
|
+
schema: schema.schema === void 0 ? void 0 : z.record(z.string(), z.json()).parse(schema.schema),
|
|
611
|
+
type: "json"
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
async function prepareModel(config, settings, options) {
|
|
615
|
+
if (options.abortSignal?.aborted) {
|
|
616
|
+
throw createAbortError(options.abortSignal.reason);
|
|
617
|
+
}
|
|
618
|
+
const providerName = getProviderName(config.provider);
|
|
619
|
+
const sapOptions = config.parsedProviderOptions;
|
|
620
|
+
const parameters = { ...settings.modelParams, ...sapOptions?.modelParams };
|
|
621
|
+
const warnings = Object.keys(parameters).filter((key) => !MODEL_PARAMETERS.has(key)).map((key) => ({
|
|
622
|
+
details: "Use the responses option for Responses-specific parameters.",
|
|
623
|
+
feature: `modelParams.${key}`,
|
|
624
|
+
type: "unsupported"
|
|
625
|
+
}));
|
|
626
|
+
if (sapOptions?.includeReasoning ?? settings.includeReasoning) {
|
|
627
|
+
warnings.push({
|
|
628
|
+
details: "Responses preserves native reasoning history instead of converting it to text.",
|
|
629
|
+
feature: "includeReasoning",
|
|
630
|
+
type: "unsupported"
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
const responseOptions = {
|
|
634
|
+
...parameters.parallel_tool_calls !== void 0 ? { parallelToolCalls: parameters.parallel_tool_calls } : {},
|
|
635
|
+
...settings.responseFormat?.type === "json_schema" ? { strictJsonSchema: settings.responseFormat.json_schema.strict } : {},
|
|
636
|
+
store: false,
|
|
637
|
+
...settings.responses,
|
|
638
|
+
...sapOptions?.responses
|
|
639
|
+
};
|
|
640
|
+
const reasoning = "reasoning" in options ? options.reasoning : void 0;
|
|
641
|
+
if (typeof reasoning === "string" && reasoning !== "provider-default") {
|
|
642
|
+
responseOptions.reasoningEffort = reasoning;
|
|
643
|
+
}
|
|
644
|
+
const input = {
|
|
645
|
+
...options,
|
|
646
|
+
frequencyPenalty: options.frequencyPenalty ?? parameters.frequencyPenalty,
|
|
647
|
+
maxOutputTokens: options.maxOutputTokens ?? parameters.maxTokens,
|
|
648
|
+
presencePenalty: options.presencePenalty ?? parameters.presencePenalty,
|
|
649
|
+
prompt: toOpenAIPrompt(options.prompt, providerName),
|
|
650
|
+
providerOptions: {
|
|
651
|
+
...options.providerOptions,
|
|
652
|
+
openai: responseOptions
|
|
653
|
+
},
|
|
654
|
+
responseFormat: options.responseFormat ?? modelResponseFormat(settings),
|
|
655
|
+
temperature: options.temperature ?? parameters.temperature,
|
|
656
|
+
tools: options.tools?.map(
|
|
657
|
+
(tool) => tool.type === "function" ? {
|
|
658
|
+
...tool,
|
|
659
|
+
providerOptions: toOpenAI(tool.providerOptions, providerName),
|
|
660
|
+
strict: tool.strict ?? false
|
|
661
|
+
} : tool
|
|
662
|
+
),
|
|
663
|
+
topP: options.topP ?? parameters.topP
|
|
664
|
+
};
|
|
665
|
+
const convertedInput = convertResponsesCallToV4(input);
|
|
666
|
+
const transport = await createResponsesTransport(config, settings, options);
|
|
667
|
+
const model = createOpenAI({
|
|
668
|
+
// Authentication is supplied by SAP's transport, not an OpenAI API key.
|
|
669
|
+
apiKey: "sap-ai-core",
|
|
670
|
+
baseURL: transport.baseURL,
|
|
671
|
+
fetch: transport.fetch
|
|
672
|
+
}).responses(config.modelId);
|
|
673
|
+
return { dispose: transport.dispose, input: convertedInput, model, providerName, warnings };
|
|
674
|
+
}
|
|
675
|
+
function responseMetadata(metadata, providerName) {
|
|
676
|
+
const mapped = fromOpenAI(metadata, providerName);
|
|
677
|
+
return {
|
|
678
|
+
...mapped,
|
|
679
|
+
[providerName]: { ...mapped?.[providerName], api: "responses", version: VERSION }
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function toOpenAI(options, providerName) {
|
|
683
|
+
if (providerName === "openai" || !options?.[providerName]) return options;
|
|
684
|
+
return { ...options, openai: { ...options.openai, ...options[providerName] } };
|
|
685
|
+
}
|
|
686
|
+
function toOpenAIOutput(output, providerName) {
|
|
687
|
+
const mappedOutput = {
|
|
688
|
+
...output,
|
|
689
|
+
providerOptions: toOpenAI(output.providerOptions, providerName)
|
|
690
|
+
};
|
|
691
|
+
if (mappedOutput.type !== "content") return mappedOutput;
|
|
692
|
+
return {
|
|
693
|
+
...mappedOutput,
|
|
694
|
+
value: mappedOutput.value.map((part) => ({
|
|
695
|
+
...part,
|
|
696
|
+
...(part.type === "file-id" || part.type === "image-file-id") && typeof part.fileId !== "string" && part.fileId[providerName] ? { fileId: { ...part.fileId, openai: part.fileId[providerName] } } : {},
|
|
697
|
+
..."providerOptions" in part ? { providerOptions: toOpenAI(part.providerOptions, providerName) } : {}
|
|
698
|
+
}))
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
function toOpenAIPart(part, providerName) {
|
|
702
|
+
return {
|
|
703
|
+
...part,
|
|
704
|
+
...part.type === "tool-result" ? { output: toOpenAIOutput(part.output, providerName) } : {},
|
|
705
|
+
providerOptions: toOpenAI(part.providerOptions, providerName)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function toOpenAIPrompt(prompt, providerName) {
|
|
709
|
+
return prompt.map((message) => {
|
|
710
|
+
const providerOptions = toOpenAI(message.providerOptions, providerName);
|
|
711
|
+
switch (message.role) {
|
|
712
|
+
case "assistant":
|
|
713
|
+
return {
|
|
714
|
+
...message,
|
|
715
|
+
content: message.content.map((part) => toOpenAIPart(part, providerName)),
|
|
716
|
+
providerOptions
|
|
717
|
+
};
|
|
718
|
+
case "system":
|
|
719
|
+
return { ...message, providerOptions };
|
|
720
|
+
case "tool":
|
|
721
|
+
return {
|
|
722
|
+
...message,
|
|
723
|
+
content: message.content.map((part) => toOpenAIPart(part, providerName)),
|
|
724
|
+
providerOptions
|
|
725
|
+
};
|
|
726
|
+
case "user":
|
|
727
|
+
return {
|
|
728
|
+
...message,
|
|
729
|
+
content: message.content.map((part) => toOpenAIPart(part, providerName)),
|
|
730
|
+
providerOptions
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
export {
|
|
736
|
+
ResponsesLanguageModelStrategy
|
|
737
|
+
};
|
|
738
|
+
//# sourceMappingURL=responses-language-model-strategy-UYTV6TPM.js.map
|