@infinityi/engine-lib 1.0.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/LICENSE +21 -0
- package/README.md +488 -0
- package/dist/agent/agent-registry.d.ts +46 -0
- package/dist/agent/as-tool.d.ts +64 -0
- package/dist/agent/define.d.ts +35 -0
- package/dist/agent/handoff.d.ts +39 -0
- package/dist/agent/index.d.ts +20 -0
- package/dist/agent/index.js +38 -0
- package/dist/agent/registry.d.ts +27 -0
- package/dist/agent/types.d.ts +109 -0
- package/dist/context/index.d.ts +11 -0
- package/dist/context/index.js +21 -0
- package/dist/context/providers.d.ts +25 -0
- package/dist/context/types.d.ts +63 -0
- package/dist/context/window.d.ts +41 -0
- package/dist/errors.d.ts +93 -0
- package/dist/errors.js +24 -0
- package/dist/events/hub.d.ts +15 -0
- package/dist/events/index.d.ts +26 -0
- package/dist/events/index.js +24 -0
- package/dist/events/subscribers.d.ts +57 -0
- package/dist/events/telemetry.d.ts +61 -0
- package/dist/events/types.d.ts +39 -0
- package/dist/execution/index.d.ts +11 -0
- package/dist/execution/index.js +22 -0
- package/dist/execution/run.d.ts +35 -0
- package/dist/execution/types.d.ts +203 -0
- package/dist/execution/usage.d.ts +14 -0
- package/dist/index-02s1fjxr.js +226 -0
- package/dist/index-19pwq79t.js +0 -0
- package/dist/index-1p6mb2vz.js +32 -0
- package/dist/index-64tt9696.js +1796 -0
- package/dist/index-7690reng.js +96 -0
- package/dist/index-bqg01r42.js +354 -0
- package/dist/index-d4xz3abn.js +0 -0
- package/dist/index-dexgmwg6.js +148 -0
- package/dist/index-fkr3rcq9.js +97 -0
- package/dist/index-jg19te9v.js +0 -0
- package/dist/index-jp2b31xs.js +101 -0
- package/dist/index-jxgj4z08.js +68 -0
- package/dist/index-kte2h4k2.js +0 -0
- package/dist/index-pwr8179t.js +492 -0
- package/dist/index-rentvdpp.js +27 -0
- package/dist/index-vnby35rm.js +84 -0
- package/dist/index-w34cbktd.js +14 -0
- package/dist/index-xsv43c5j.js +39 -0
- package/dist/index-yrqrxwjt.js +148 -0
- package/dist/index-zfgr4xx3.js +90 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +117 -0
- package/dist/lifecycle/component.d.ts +74 -0
- package/dist/lifecycle/index.d.ts +12 -0
- package/dist/lifecycle/index.js +72 -0
- package/dist/messages/factory.d.ts +24 -0
- package/dist/messages/index.d.ts +8 -0
- package/dist/messages/index.js +17 -0
- package/dist/messages/types.d.ts +52 -0
- package/dist/providers/adapter.d.ts +42 -0
- package/dist/providers/anthropic/index.d.ts +31 -0
- package/dist/providers/anthropic/map.d.ts +12 -0
- package/dist/providers/anthropic/stream.d.ts +9 -0
- package/dist/providers/google/index.d.ts +29 -0
- package/dist/providers/google/map.d.ts +13 -0
- package/dist/providers/google/stream.d.ts +11 -0
- package/dist/providers/http.d.ts +61 -0
- package/dist/providers/index.d.ts +32 -0
- package/dist/providers/index.js +35 -0
- package/dist/providers/openai/index.d.ts +34 -0
- package/dist/providers/openai/map.d.ts +10 -0
- package/dist/providers/openai/stream.d.ts +9 -0
- package/dist/providers/openai-compatible/index.d.ts +37 -0
- package/dist/providers/openai-compatible/map.d.ts +13 -0
- package/dist/providers/openai-compatible/stream.d.ts +11 -0
- package/dist/providers/shared.d.ts +34 -0
- package/dist/providers/sse.d.ts +19 -0
- package/dist/providers/stream.d.ts +69 -0
- package/dist/providers/types.d.ts +137 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.js +11 -0
- package/dist/runtime/secret.d.ts +12 -0
- package/dist/runtime/types.d.ts +27 -0
- package/dist/schema/builder.d.ts +70 -0
- package/dist/schema/index.d.ts +13 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/json-schema.d.ts +19 -0
- package/dist/schema/types.d.ts +70 -0
- package/dist/schema/validate.d.ts +19 -0
- package/dist/session/index.d.ts +11 -0
- package/dist/session/index.js +8 -0
- package/dist/session/session.d.ts +31 -0
- package/dist/session/store.d.ts +20 -0
- package/dist/session/types.d.ts +55 -0
- package/dist/testing/conformance.d.ts +106 -0
- package/dist/testing/conformance.js +132 -0
- package/dist/testing/index.d.ts +84 -0
- package/dist/testing/index.js +31 -0
- package/dist/tools/define.d.ts +42 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/result.d.ts +36 -0
- package/dist/tools/types.d.ts +85 -0
- package/docs/README.md +36 -0
- package/examples/README.md +24 -0
- package/examples/incident-analysis.ts +100 -0
- package/examples/lifecycle.ts +53 -0
- package/examples/multi-agent.ts +93 -0
- package/examples/terminal-coder.ts +80 -0
- package/package.json +114 -0
|
@@ -0,0 +1,1796 @@
|
|
|
1
|
+
import {
|
|
2
|
+
combine,
|
|
3
|
+
currentContext,
|
|
4
|
+
exponentialBackoff,
|
|
5
|
+
retry,
|
|
6
|
+
timeout
|
|
7
|
+
} from "./index-bqg01r42.js";
|
|
8
|
+
import {
|
|
9
|
+
resolveSecret
|
|
10
|
+
} from "./index-xsv43c5j.js";
|
|
11
|
+
import {
|
|
12
|
+
ProviderError
|
|
13
|
+
} from "./index-7690reng.js";
|
|
14
|
+
// node_modules/@infinityi/forge/dist/index-r6ez01fw.js
|
|
15
|
+
var PROBLEM_CONTENT_TYPE = "application/problem+json";
|
|
16
|
+
var DEFAULT_PROBLEM_TYPE = "about:blank";
|
|
17
|
+
function normalizeProblem(input) {
|
|
18
|
+
const { type, title, status, detail, instance, ...extensions } = input;
|
|
19
|
+
const problem = {
|
|
20
|
+
...extensions,
|
|
21
|
+
type: type ?? DEFAULT_PROBLEM_TYPE,
|
|
22
|
+
title: title ?? statusText(status),
|
|
23
|
+
status
|
|
24
|
+
};
|
|
25
|
+
if (detail !== undefined)
|
|
26
|
+
problem.detail = detail;
|
|
27
|
+
if (instance !== undefined)
|
|
28
|
+
problem.instance = instance;
|
|
29
|
+
return problem;
|
|
30
|
+
}
|
|
31
|
+
function renderProblem(problem, init) {
|
|
32
|
+
const normalized = normalizeProblem(problem);
|
|
33
|
+
const headers = new Headers(init?.headers);
|
|
34
|
+
headers.set("content-type", PROBLEM_CONTENT_TYPE);
|
|
35
|
+
return new Response(JSON.stringify(normalized), {
|
|
36
|
+
status: normalized.status,
|
|
37
|
+
headers
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function statusText(status) {
|
|
41
|
+
return STATUS_TEXT[status] ?? (status >= 500 ? "Internal Server Error" : "Error");
|
|
42
|
+
}
|
|
43
|
+
var STATUS_TEXT = {
|
|
44
|
+
400: "Bad Request",
|
|
45
|
+
401: "Unauthorized",
|
|
46
|
+
403: "Forbidden",
|
|
47
|
+
404: "Not Found",
|
|
48
|
+
405: "Method Not Allowed",
|
|
49
|
+
409: "Conflict",
|
|
50
|
+
410: "Gone",
|
|
51
|
+
413: "Payload Too Large",
|
|
52
|
+
415: "Unsupported Media Type",
|
|
53
|
+
422: "Unprocessable Entity",
|
|
54
|
+
429: "Too Many Requests",
|
|
55
|
+
500: "Internal Server Error",
|
|
56
|
+
502: "Bad Gateway",
|
|
57
|
+
503: "Service Unavailable",
|
|
58
|
+
504: "Gateway Timeout"
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
class HttpError extends Error {
|
|
62
|
+
constructor(message, options) {
|
|
63
|
+
super(message, options);
|
|
64
|
+
this.name = "HttpError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class ProblemError extends HttpError {
|
|
69
|
+
problem;
|
|
70
|
+
constructor(problem, options) {
|
|
71
|
+
const normalized = normalizeProblem(problem);
|
|
72
|
+
super(normalized.detail ?? normalized.title, options);
|
|
73
|
+
this.name = "ProblemError";
|
|
74
|
+
this.problem = normalized;
|
|
75
|
+
}
|
|
76
|
+
get status() {
|
|
77
|
+
return this.problem.status;
|
|
78
|
+
}
|
|
79
|
+
toResponse() {
|
|
80
|
+
return renderProblem(this.problem);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class RequestError extends HttpError {
|
|
85
|
+
constructor(message, options) {
|
|
86
|
+
super(message, options);
|
|
87
|
+
this.name = "RequestError";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class ResponseError extends HttpError {
|
|
92
|
+
status;
|
|
93
|
+
response;
|
|
94
|
+
constructor(response, message, options) {
|
|
95
|
+
super(message ?? `HTTP ${response.status}`, options);
|
|
96
|
+
this.name = "ResponseError";
|
|
97
|
+
this.status = response.status;
|
|
98
|
+
this.response = response;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class TimeoutError extends HttpError {
|
|
103
|
+
timeoutMs;
|
|
104
|
+
constructor(timeoutMs, options) {
|
|
105
|
+
super(`request timed out after ${timeoutMs}ms`, options);
|
|
106
|
+
this.name = "TimeoutError";
|
|
107
|
+
this.timeoutMs = timeoutMs;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// node_modules/@infinityi/forge/dist/index-6rgawqtq.js
|
|
112
|
+
function formatTraceparent(ctx) {
|
|
113
|
+
const flags = (ctx.traceFlags & 255).toString(16).padStart(2, "0");
|
|
114
|
+
return `00-${ctx.traceId}-${ctx.spanId}-${flags}`;
|
|
115
|
+
}
|
|
116
|
+
function formatBaggage(baggage) {
|
|
117
|
+
const parts = [];
|
|
118
|
+
for (const [key, value] of Object.entries(baggage)) {
|
|
119
|
+
parts.push(`${encodeBaggageKey(key)}=${encodeURIComponent(value)}`);
|
|
120
|
+
}
|
|
121
|
+
return parts.join(",");
|
|
122
|
+
}
|
|
123
|
+
function encodeBaggageKey(key) {
|
|
124
|
+
return encodeURIComponent(key);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// node_modules/@infinityi/forge/dist/index-0xay9896.js
|
|
128
|
+
function tracedFetch(options) {
|
|
129
|
+
const tracer = options.tracer;
|
|
130
|
+
const inner = options.fetch ?? ((input, init) => fetch(input, init));
|
|
131
|
+
const buildName = options.spanName ?? ((input, init) => `HTTP ${requestMethod(input, init)}`);
|
|
132
|
+
const buildExtra = options.attributes;
|
|
133
|
+
const propagate = options.disablePropagation !== true;
|
|
134
|
+
return (input, init) => {
|
|
135
|
+
const method = requestMethod(input, init);
|
|
136
|
+
const url = requestUrl(input);
|
|
137
|
+
const attrs = {
|
|
138
|
+
"http.request.method": method
|
|
139
|
+
};
|
|
140
|
+
if (url) {
|
|
141
|
+
attrs["url.full"] = url;
|
|
142
|
+
const parsed = safeUrl(url);
|
|
143
|
+
if (parsed) {
|
|
144
|
+
attrs["server.address"] = parsed.hostname;
|
|
145
|
+
if (parsed.port)
|
|
146
|
+
attrs["server.port"] = Number(parsed.port);
|
|
147
|
+
attrs["url.scheme"] = parsed.protocol.replace(/:$/, "");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const extra = buildExtra ? buildExtra(input, init) : undefined;
|
|
151
|
+
if (extra) {
|
|
152
|
+
for (const k of Object.keys(extra))
|
|
153
|
+
attrs[k] = extra[k];
|
|
154
|
+
}
|
|
155
|
+
return tracer.withSpan(buildName(input, init), async (span) => {
|
|
156
|
+
span.setAttributes(attrs);
|
|
157
|
+
const nextInit = propagate ? injectHeaders(input, init, currentContext()) : init;
|
|
158
|
+
try {
|
|
159
|
+
const res = await inner(input, nextInit);
|
|
160
|
+
span.setAttribute("http.response.status_code", res.status);
|
|
161
|
+
if (res.status >= 500) {
|
|
162
|
+
span.setStatus({
|
|
163
|
+
code: "error",
|
|
164
|
+
message: `HTTP ${res.status}`
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return res;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
const errorType = err instanceof Error && err.name ? err.name : "fetch_error";
|
|
170
|
+
span.setAttribute("error.type", errorType);
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
}, { kind: "client" });
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function requestMethod(input, init) {
|
|
177
|
+
if (init && typeof init.method === "string")
|
|
178
|
+
return init.method.toUpperCase();
|
|
179
|
+
if (typeof input === "object" && input !== null && "method" in input) {
|
|
180
|
+
const m = input.method;
|
|
181
|
+
if (typeof m === "string")
|
|
182
|
+
return m.toUpperCase();
|
|
183
|
+
}
|
|
184
|
+
return "GET";
|
|
185
|
+
}
|
|
186
|
+
function requestUrl(input) {
|
|
187
|
+
if (typeof input === "string")
|
|
188
|
+
return input;
|
|
189
|
+
if (input instanceof URL)
|
|
190
|
+
return input.toString();
|
|
191
|
+
if (typeof input === "object" && input !== null && "url" in input) {
|
|
192
|
+
const u = input.url;
|
|
193
|
+
if (typeof u === "string")
|
|
194
|
+
return u;
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
function safeUrl(value) {
|
|
199
|
+
try {
|
|
200
|
+
return new URL(value);
|
|
201
|
+
} catch {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function injectHeaders(input, init, ctx) {
|
|
206
|
+
if (!ctx)
|
|
207
|
+
return init;
|
|
208
|
+
let base;
|
|
209
|
+
if (init?.headers !== undefined) {
|
|
210
|
+
base = init.headers;
|
|
211
|
+
} else if (isRequest(input)) {
|
|
212
|
+
base = input.headers;
|
|
213
|
+
}
|
|
214
|
+
const headers = new Headers(base ?? undefined);
|
|
215
|
+
headers.set("traceparent", formatTraceparent(ctx));
|
|
216
|
+
if (ctx.traceState && ctx.traceState.length > 0) {
|
|
217
|
+
headers.set("tracestate", ctx.traceState);
|
|
218
|
+
}
|
|
219
|
+
if (Object.keys(ctx.baggage).length > 0) {
|
|
220
|
+
headers.set("baggage", formatBaggage(ctx.baggage));
|
|
221
|
+
}
|
|
222
|
+
return { ...init ?? {}, headers };
|
|
223
|
+
}
|
|
224
|
+
function isRequest(input) {
|
|
225
|
+
return typeof Request !== "undefined" && typeof input === "object" && input !== null && input instanceof Request;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// node_modules/@infinityi/forge/dist/index-gw6f6j6v.js
|
|
229
|
+
var jsonCodec = {
|
|
230
|
+
contentType: "application/json",
|
|
231
|
+
encode(value) {
|
|
232
|
+
if (value === undefined || value === null)
|
|
233
|
+
return;
|
|
234
|
+
return JSON.stringify(value);
|
|
235
|
+
},
|
|
236
|
+
async decode(response) {
|
|
237
|
+
if (response.status === 204 || response.status === 205) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const text = await response.text();
|
|
241
|
+
if (text.length === 0)
|
|
242
|
+
return;
|
|
243
|
+
return JSON.parse(text);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
var passthroughFetch = (input, init) => fetch(input, init);
|
|
247
|
+
function createHttpClient(options = {}) {
|
|
248
|
+
const resolved = resolveOptions(options);
|
|
249
|
+
const durationHistogram = resolved.telemetry?.meter?.createHistogram("http.client.request.duration", { description: "Duration of outbound HTTP client requests.", unit: "s" });
|
|
250
|
+
const wrappedFetch = resolved.telemetry?.tracer ? tracedFetch({
|
|
251
|
+
tracer: resolved.telemetry.tracer,
|
|
252
|
+
fetch: resolved.fetch
|
|
253
|
+
}) : resolved.fetch;
|
|
254
|
+
async function request(req) {
|
|
255
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
256
|
+
const url = buildUrl(resolved, req.url, req.query);
|
|
257
|
+
const { headers, body } = buildBody(resolved, req, method);
|
|
258
|
+
const serverAddress = hostOf(url);
|
|
259
|
+
const timeoutMs = req.timeoutMs ?? resolved.timeoutMs;
|
|
260
|
+
const deadline = armTimeout(timeoutMs);
|
|
261
|
+
const startedAt = performance.now();
|
|
262
|
+
let statusLabel;
|
|
263
|
+
try {
|
|
264
|
+
const response = await runFetch(resolved, wrappedFetch, {
|
|
265
|
+
url,
|
|
266
|
+
method,
|
|
267
|
+
headers,
|
|
268
|
+
body,
|
|
269
|
+
callerSignal: req.signal,
|
|
270
|
+
deadline
|
|
271
|
+
});
|
|
272
|
+
statusLabel = String(response.status);
|
|
273
|
+
return await finalize(resolved, response);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
statusLabel = errorLabel(error);
|
|
276
|
+
throw error;
|
|
277
|
+
} finally {
|
|
278
|
+
deadline?.clear();
|
|
279
|
+
durationHistogram?.record((performance.now() - startedAt) / 1000, {
|
|
280
|
+
"http.request.method": method,
|
|
281
|
+
"http.response.status_code": statusLabel,
|
|
282
|
+
"server.address": serverAddress
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const client = {
|
|
287
|
+
request,
|
|
288
|
+
get: (url, init) => request({ method: "GET", url, ...init }),
|
|
289
|
+
post: (url, body, init) => request({ method: "POST", url, body, ...init }),
|
|
290
|
+
put: (url, body, init) => request({ method: "PUT", url, body, ...init }),
|
|
291
|
+
patch: (url, body, init) => request({ method: "PATCH", url, body, ...init }),
|
|
292
|
+
delete: (url, init) => request({ method: "DELETE", url, ...init }),
|
|
293
|
+
extend: (overrides) => createHttpClient({ ...options, ...overrides })
|
|
294
|
+
};
|
|
295
|
+
return client;
|
|
296
|
+
}
|
|
297
|
+
function resolveOptions(options) {
|
|
298
|
+
const allowedProtocols = options.allowedProtocols ?? ["http:", "https:"];
|
|
299
|
+
if (options.baseUrl !== undefined) {
|
|
300
|
+
let baseUrl;
|
|
301
|
+
try {
|
|
302
|
+
baseUrl = new URL(options.baseUrl);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
throw new RequestError(`invalid baseUrl: ${options.baseUrl}`, { cause: error });
|
|
305
|
+
}
|
|
306
|
+
if (!allowedProtocols.includes(baseUrl.protocol)) {
|
|
307
|
+
throw new RequestError(`baseUrl protocol ${baseUrl.protocol} is not in allowedProtocols`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
baseUrl: options.baseUrl,
|
|
312
|
+
allowAbsoluteUrls: options.allowAbsoluteUrls ?? false,
|
|
313
|
+
allowedProtocols,
|
|
314
|
+
allowedHosts: options.allowedHosts ?? [],
|
|
315
|
+
defaultHeaders: options.defaultHeaders ?? {},
|
|
316
|
+
timeoutMs: options.timeoutMs,
|
|
317
|
+
resilience: options.resilience,
|
|
318
|
+
parseProblem: options.parseProblem ?? true,
|
|
319
|
+
throwOnError: options.throwOnError ?? true,
|
|
320
|
+
codec: options.codec ?? jsonCodec,
|
|
321
|
+
fetch: options.fetch ?? passthroughFetch,
|
|
322
|
+
telemetry: options.telemetry
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
async function runFetch(resolved, doFetch, args) {
|
|
326
|
+
const attempt = async (pipelineSignal) => {
|
|
327
|
+
const signal = combineSignals([
|
|
328
|
+
args.callerSignal,
|
|
329
|
+
args.deadline?.signal,
|
|
330
|
+
pipelineSignal
|
|
331
|
+
]);
|
|
332
|
+
try {
|
|
333
|
+
return await doFetch(args.url, {
|
|
334
|
+
method: args.method,
|
|
335
|
+
headers: args.headers,
|
|
336
|
+
body: args.body,
|
|
337
|
+
signal
|
|
338
|
+
});
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (args.deadline?.timedOut) {
|
|
341
|
+
throw new TimeoutError(args.deadline.timeoutMs, { cause: error });
|
|
342
|
+
}
|
|
343
|
+
if (args.callerSignal?.aborted && isAbortError(error))
|
|
344
|
+
throw error;
|
|
345
|
+
if (error instanceof HttpError)
|
|
346
|
+
throw error;
|
|
347
|
+
throw new RequestError(messageOf(error), { cause: error });
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
if (resolved.resilience) {
|
|
351
|
+
return resolved.resilience.execute((ctx) => attempt(ctx.signal));
|
|
352
|
+
}
|
|
353
|
+
return attempt();
|
|
354
|
+
}
|
|
355
|
+
async function finalize(resolved, response) {
|
|
356
|
+
if (!response.ok) {
|
|
357
|
+
if (resolved.parseProblem && isProblemResponse(response)) {
|
|
358
|
+
throw await parseProblem(response);
|
|
359
|
+
}
|
|
360
|
+
if (resolved.throwOnError) {
|
|
361
|
+
throw new ResponseError(response);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const body = await resolved.codec.decode(response);
|
|
365
|
+
return { status: response.status, headers: response.headers, body, raw: response };
|
|
366
|
+
}
|
|
367
|
+
function buildBody(resolved, req, method) {
|
|
368
|
+
const headers = new Headers(resolved.defaultHeaders);
|
|
369
|
+
if (req.headers) {
|
|
370
|
+
const provided = new Headers(req.headers);
|
|
371
|
+
provided.forEach((value, key) => headers.set(key, value));
|
|
372
|
+
}
|
|
373
|
+
if (req.body === undefined || method === "GET" || method === "HEAD") {
|
|
374
|
+
return { headers, body: undefined };
|
|
375
|
+
}
|
|
376
|
+
if (isRawBody(req.body)) {
|
|
377
|
+
return { headers, body: req.body };
|
|
378
|
+
}
|
|
379
|
+
const encoded = resolved.codec.encode(req.body);
|
|
380
|
+
if (encoded !== undefined && !headers.has("content-type")) {
|
|
381
|
+
headers.set("content-type", resolved.codec.contentType);
|
|
382
|
+
}
|
|
383
|
+
return { headers, body: encoded };
|
|
384
|
+
}
|
|
385
|
+
function buildUrl(resolved, url, query) {
|
|
386
|
+
const { baseUrl } = resolved;
|
|
387
|
+
let resolvedUrl;
|
|
388
|
+
try {
|
|
389
|
+
resolvedUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
throw new RequestError(`invalid request url: ${url}`, { cause: error });
|
|
392
|
+
}
|
|
393
|
+
enforceUrlPolicy(resolved, resolvedUrl);
|
|
394
|
+
if (query) {
|
|
395
|
+
const params = query instanceof URLSearchParams ? query : toSearchParams(query);
|
|
396
|
+
params.forEach((value, key) => resolvedUrl.searchParams.append(key, value));
|
|
397
|
+
}
|
|
398
|
+
return resolvedUrl.toString();
|
|
399
|
+
}
|
|
400
|
+
function enforceUrlPolicy(resolved, url) {
|
|
401
|
+
if (!resolved.allowedProtocols.includes(url.protocol)) {
|
|
402
|
+
throw new RequestError(`unsupported request url protocol: ${url.protocol}`);
|
|
403
|
+
}
|
|
404
|
+
if (!resolved.baseUrl || resolved.allowAbsoluteUrls)
|
|
405
|
+
return;
|
|
406
|
+
const base = new URL(resolved.baseUrl);
|
|
407
|
+
if (url.origin !== base.origin && !resolved.allowedHosts.includes(url.hostname)) {
|
|
408
|
+
throw new RequestError(`request url must stay within baseUrl origin (${base.origin}): ${url.origin}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function toSearchParams(query) {
|
|
412
|
+
const params = new URLSearchParams;
|
|
413
|
+
for (const [key, value] of Object.entries(query)) {
|
|
414
|
+
if (value !== undefined)
|
|
415
|
+
params.set(key, String(value));
|
|
416
|
+
}
|
|
417
|
+
return params;
|
|
418
|
+
}
|
|
419
|
+
function armTimeout(timeoutMs) {
|
|
420
|
+
if (timeoutMs === undefined)
|
|
421
|
+
return;
|
|
422
|
+
const controller = new AbortController;
|
|
423
|
+
const deadline = {
|
|
424
|
+
signal: controller.signal,
|
|
425
|
+
timeoutMs,
|
|
426
|
+
timedOut: false,
|
|
427
|
+
clear() {
|
|
428
|
+
clearTimeout(timer);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const timer = setTimeout(() => {
|
|
432
|
+
deadline.timedOut = true;
|
|
433
|
+
controller.abort(new TimeoutError(timeoutMs));
|
|
434
|
+
}, timeoutMs);
|
|
435
|
+
return deadline;
|
|
436
|
+
}
|
|
437
|
+
function combineSignals(signals) {
|
|
438
|
+
const present = signals.filter((s) => s !== undefined);
|
|
439
|
+
if (present.length === 0)
|
|
440
|
+
return;
|
|
441
|
+
if (present.length === 1)
|
|
442
|
+
return present[0];
|
|
443
|
+
return AbortSignal.any(present);
|
|
444
|
+
}
|
|
445
|
+
async function parseProblem(response) {
|
|
446
|
+
try {
|
|
447
|
+
const data = await response.json();
|
|
448
|
+
return new ProblemError({ ...data, status: data.status ?? response.status });
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return new ProblemError({ status: response.status, detail: "malformed problem body" }, { cause: error });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function isProblemResponse(response) {
|
|
454
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
455
|
+
return contentType.toLowerCase().includes(PROBLEM_CONTENT_TYPE);
|
|
456
|
+
}
|
|
457
|
+
function isRawBody(body) {
|
|
458
|
+
return typeof body === "string" || body instanceof ArrayBuffer || body instanceof Blob || body instanceof FormData || body instanceof URLSearchParams || body instanceof ReadableStream || ArrayBuffer.isView(body);
|
|
459
|
+
}
|
|
460
|
+
function hostOf(url) {
|
|
461
|
+
try {
|
|
462
|
+
return new URL(url).hostname;
|
|
463
|
+
} catch {
|
|
464
|
+
return "";
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function isAbortError(error) {
|
|
468
|
+
return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
469
|
+
}
|
|
470
|
+
function messageOf(error) {
|
|
471
|
+
if (error instanceof Error)
|
|
472
|
+
return error.message;
|
|
473
|
+
return String(error);
|
|
474
|
+
}
|
|
475
|
+
function errorLabel(error) {
|
|
476
|
+
if (error instanceof ResponseError)
|
|
477
|
+
return String(error.status);
|
|
478
|
+
if (error instanceof ProblemError)
|
|
479
|
+
return String(error.status);
|
|
480
|
+
return "error";
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/providers/http.ts
|
|
484
|
+
var DEFAULT_TIMEOUT_MS = 60000;
|
|
485
|
+
function statusOf(error) {
|
|
486
|
+
if (typeof error === "object" && error !== null && "status" in error) {
|
|
487
|
+
const status = error.status;
|
|
488
|
+
if (typeof status === "number")
|
|
489
|
+
return status;
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
function messageOf2(error) {
|
|
494
|
+
if (error instanceof Error)
|
|
495
|
+
return error.message;
|
|
496
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
497
|
+
const message = error.message;
|
|
498
|
+
if (typeof message === "string")
|
|
499
|
+
return message;
|
|
500
|
+
}
|
|
501
|
+
return String(error);
|
|
502
|
+
}
|
|
503
|
+
function isTransient(error) {
|
|
504
|
+
if (error instanceof DOMException && error.name === "AbortError")
|
|
505
|
+
return false;
|
|
506
|
+
const status = statusOf(error);
|
|
507
|
+
if (status === undefined)
|
|
508
|
+
return true;
|
|
509
|
+
return status === 429 || status >= 500;
|
|
510
|
+
}
|
|
511
|
+
function defaultProviderResilience(timeoutMs) {
|
|
512
|
+
return combine(retry({
|
|
513
|
+
maxAttempts: 3,
|
|
514
|
+
backoff: exponentialBackoff({ initial: 250, max: 1e4 }),
|
|
515
|
+
shouldRetry: isTransient
|
|
516
|
+
}), timeout({ ms: timeoutMs }));
|
|
517
|
+
}
|
|
518
|
+
function createProviderHttp(opts, ctx) {
|
|
519
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
520
|
+
const telemetry = ctx?.telemetry;
|
|
521
|
+
return createHttpClient({
|
|
522
|
+
baseUrl: opts.baseUrl.endsWith("/") ? opts.baseUrl : `${opts.baseUrl}/`,
|
|
523
|
+
defaultHeaders: opts.headers,
|
|
524
|
+
timeoutMs: opts.resilience === undefined ? undefined : timeoutMs,
|
|
525
|
+
resilience: opts.resilience ?? defaultProviderResilience(timeoutMs),
|
|
526
|
+
telemetry: telemetry ? { meter: telemetry.meter, tracer: telemetry.tracer } : undefined,
|
|
527
|
+
logger: ctx?.logger ?? telemetry?.log,
|
|
528
|
+
fetch: opts.fetch,
|
|
529
|
+
allowAbsoluteUrls: opts.allowAbsoluteUrls,
|
|
530
|
+
throwOnError: true
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
function joinUrl(baseUrl, path) {
|
|
534
|
+
if (/^https?:\/\//.test(path))
|
|
535
|
+
return path;
|
|
536
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
537
|
+
}
|
|
538
|
+
function hostOf2(url) {
|
|
539
|
+
try {
|
|
540
|
+
return new URL(url).hostname;
|
|
541
|
+
} catch {
|
|
542
|
+
return "";
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function combineSignals2(...signals) {
|
|
546
|
+
const present = signals.filter((s) => s !== undefined);
|
|
547
|
+
if (present.length === 0)
|
|
548
|
+
return;
|
|
549
|
+
if (present.length === 1)
|
|
550
|
+
return present[0];
|
|
551
|
+
return AbortSignal.any(present);
|
|
552
|
+
}
|
|
553
|
+
async function openSseStream(provider, opts, req, ctx) {
|
|
554
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
555
|
+
const pipeline = opts.resilience ?? defaultProviderResilience(timeoutMs);
|
|
556
|
+
const telemetry = ctx?.telemetry;
|
|
557
|
+
const baseFetch = opts.fetch ?? ((input, init) => fetch(input, init));
|
|
558
|
+
const fetchImpl = telemetry?.tracer ? tracedFetch({ tracer: telemetry.tracer, fetch: baseFetch }) : baseFetch;
|
|
559
|
+
const durationHistogram = telemetry?.meter?.createHistogram("http.client.request.duration", { description: "Duration of outbound HTTP client requests.", unit: "s" });
|
|
560
|
+
const url = joinUrl(opts.baseUrl, req.path);
|
|
561
|
+
const serverAddress = hostOf2(url);
|
|
562
|
+
const headers = {
|
|
563
|
+
"content-type": "application/json",
|
|
564
|
+
accept: "text/event-stream",
|
|
565
|
+
...opts.headers
|
|
566
|
+
};
|
|
567
|
+
const startedAt = performance.now();
|
|
568
|
+
let statusLabel;
|
|
569
|
+
try {
|
|
570
|
+
const response = await pipeline.execute(async (pctx) => {
|
|
571
|
+
const response2 = await fetchImpl(url, {
|
|
572
|
+
method: "POST",
|
|
573
|
+
headers,
|
|
574
|
+
body: JSON.stringify(req.body),
|
|
575
|
+
signal: combineSignals2(pctx.signal, req.signal, ctx?.signal)
|
|
576
|
+
});
|
|
577
|
+
statusLabel = String(response2.status);
|
|
578
|
+
if (!response2.ok) {
|
|
579
|
+
const detail = await response2.text().catch(() => "");
|
|
580
|
+
throw Object.assign(new Error(detail || response2.statusText), {
|
|
581
|
+
status: response2.status
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return response2;
|
|
585
|
+
});
|
|
586
|
+
if (response.body === null) {
|
|
587
|
+
throw new ProviderError(`${provider} streaming response had no body`, {
|
|
588
|
+
provider
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
return response.body;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
statusLabel ??= statusOf(error)?.toString() ?? "error";
|
|
594
|
+
throw toProviderError(provider, error);
|
|
595
|
+
} finally {
|
|
596
|
+
const attributes = {
|
|
597
|
+
"http.request.method": "POST",
|
|
598
|
+
"server.address": serverAddress
|
|
599
|
+
};
|
|
600
|
+
if (statusLabel !== undefined) {
|
|
601
|
+
attributes["http.response.status_code"] = statusLabel;
|
|
602
|
+
}
|
|
603
|
+
durationHistogram?.record((performance.now() - startedAt) / 1000, attributes);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function toProviderError(provider, error) {
|
|
607
|
+
if (error instanceof ProviderError)
|
|
608
|
+
return error;
|
|
609
|
+
const status = statusOf(error);
|
|
610
|
+
const detail = messageOf2(error);
|
|
611
|
+
const message = status !== undefined ? `${provider} request failed (HTTP ${status}): ${detail}` : `${provider} request failed: ${detail}`;
|
|
612
|
+
return new ProviderError(message, { provider, cause: error });
|
|
613
|
+
}
|
|
614
|
+
// src/providers/sse.ts
|
|
615
|
+
async function* parseSse(stream, signal) {
|
|
616
|
+
const reader = stream.getReader();
|
|
617
|
+
const decoder = new TextDecoder;
|
|
618
|
+
let buffer = "";
|
|
619
|
+
let event;
|
|
620
|
+
let data = [];
|
|
621
|
+
let completed = false;
|
|
622
|
+
const flush = () => {
|
|
623
|
+
if (data.length === 0 && event === undefined)
|
|
624
|
+
return;
|
|
625
|
+
const message = event !== undefined ? { event, data: data.join(`
|
|
626
|
+
`) } : { data: data.join(`
|
|
627
|
+
`) };
|
|
628
|
+
event = undefined;
|
|
629
|
+
data = [];
|
|
630
|
+
return message;
|
|
631
|
+
};
|
|
632
|
+
const consumeLine = (line) => {
|
|
633
|
+
if (line === "")
|
|
634
|
+
return flush();
|
|
635
|
+
if (line.startsWith(":"))
|
|
636
|
+
return;
|
|
637
|
+
const colon = line.indexOf(":");
|
|
638
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
639
|
+
let rest = colon === -1 ? "" : line.slice(colon + 1);
|
|
640
|
+
if (rest.startsWith(" "))
|
|
641
|
+
rest = rest.slice(1);
|
|
642
|
+
if (field === "event")
|
|
643
|
+
event = rest;
|
|
644
|
+
else if (field === "data")
|
|
645
|
+
data.push(rest);
|
|
646
|
+
return;
|
|
647
|
+
};
|
|
648
|
+
function* drainLines() {
|
|
649
|
+
let newlineIndex;
|
|
650
|
+
while ((newlineIndex = buffer.indexOf(`
|
|
651
|
+
`)) !== -1) {
|
|
652
|
+
const line = buffer.slice(0, newlineIndex);
|
|
653
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
654
|
+
const message = consumeLine(line);
|
|
655
|
+
if (message !== undefined)
|
|
656
|
+
yield message;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
while (true) {
|
|
661
|
+
if (signal?.aborted)
|
|
662
|
+
return;
|
|
663
|
+
const { done, value } = await reader.read();
|
|
664
|
+
if (done) {
|
|
665
|
+
completed = true;
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
buffer += decoder.decode(value, { stream: true });
|
|
669
|
+
const trailingCr = buffer.endsWith("\r");
|
|
670
|
+
const head = trailingCr ? buffer.slice(0, -1) : buffer;
|
|
671
|
+
buffer = head.replace(/\r\n?/g, `
|
|
672
|
+
`) + (trailingCr ? "\r" : "");
|
|
673
|
+
yield* drainLines();
|
|
674
|
+
}
|
|
675
|
+
buffer += decoder.decode();
|
|
676
|
+
buffer = buffer.replace(/\r\n?/g, `
|
|
677
|
+
`);
|
|
678
|
+
yield* drainLines();
|
|
679
|
+
if (buffer.length > 0) {
|
|
680
|
+
const message = consumeLine(buffer);
|
|
681
|
+
if (message !== undefined)
|
|
682
|
+
yield message;
|
|
683
|
+
buffer = "";
|
|
684
|
+
}
|
|
685
|
+
const tail = flush();
|
|
686
|
+
if (tail !== undefined)
|
|
687
|
+
yield tail;
|
|
688
|
+
} finally {
|
|
689
|
+
if (!completed)
|
|
690
|
+
await reader.cancel().catch(() => {});
|
|
691
|
+
reader.releaseLock();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// src/providers/adapter.ts
|
|
695
|
+
function createProvider(spec) {
|
|
696
|
+
return {
|
|
697
|
+
name: spec.name,
|
|
698
|
+
defaultModel: spec.defaultModel,
|
|
699
|
+
capabilities: spec.capabilities,
|
|
700
|
+
async complete(req, ctx) {
|
|
701
|
+
const model = req.model ?? spec.defaultModel;
|
|
702
|
+
const http = createProviderHttp(spec.http, ctx);
|
|
703
|
+
const body = spec.buildBody(req, model, false);
|
|
704
|
+
try {
|
|
705
|
+
const res = await http.post(spec.completePath(model, req), body, {
|
|
706
|
+
signal: ctx?.signal
|
|
707
|
+
});
|
|
708
|
+
return spec.parseResponse(res.body, model);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
throw toProviderError(spec.name, error);
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
async* stream(req, ctx) {
|
|
714
|
+
const model = req.model ?? spec.defaultModel;
|
|
715
|
+
const body = spec.buildBody(req, model, true);
|
|
716
|
+
const stream = await openSseStream(spec.name, spec.http, { path: spec.streamPath(model, req), body, signal: ctx?.signal }, ctx);
|
|
717
|
+
try {
|
|
718
|
+
yield* spec.translateStream(parseSse(stream, ctx?.signal), model);
|
|
719
|
+
} catch (error) {
|
|
720
|
+
throw toProviderError(spec.name, error);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
// src/providers/shared.ts
|
|
726
|
+
function imageDataUrl(part) {
|
|
727
|
+
return /^https?:\/\//.test(part.data) ? part.data : `data:${part.mimeType};base64,${part.data}`;
|
|
728
|
+
}
|
|
729
|
+
function systemText(messages) {
|
|
730
|
+
const parts = [];
|
|
731
|
+
for (const message of messages) {
|
|
732
|
+
if (message.role !== "system")
|
|
733
|
+
continue;
|
|
734
|
+
for (const part of message.content) {
|
|
735
|
+
if (part.type === "text")
|
|
736
|
+
parts.push(part.text);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return parts.length > 0 ? parts.join(`
|
|
740
|
+
`) : undefined;
|
|
741
|
+
}
|
|
742
|
+
function withoutSystem(messages) {
|
|
743
|
+
return messages.filter((m) => m.role !== "system");
|
|
744
|
+
}
|
|
745
|
+
function toolResultText(part) {
|
|
746
|
+
return part.content.map((p) => p.text).join("");
|
|
747
|
+
}
|
|
748
|
+
var isText = (p) => p.type === "text";
|
|
749
|
+
var isToolCall = (p) => p.type === "tool_call";
|
|
750
|
+
var isToolResult = (p) => p.type === "tool_result";
|
|
751
|
+
function stringifyArguments(value) {
|
|
752
|
+
if (typeof value === "string")
|
|
753
|
+
return value;
|
|
754
|
+
if (value === undefined)
|
|
755
|
+
return "{}";
|
|
756
|
+
return JSON.stringify(value);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/providers/openai/map.ts
|
|
760
|
+
function toInputContent(part) {
|
|
761
|
+
if (part.type === "text")
|
|
762
|
+
return { type: "input_text", text: part.text };
|
|
763
|
+
if (part.type === "image") {
|
|
764
|
+
return { type: "input_image", image_url: imageDataUrl(part), detail: "auto" };
|
|
765
|
+
}
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
function toInputItems(messages) {
|
|
769
|
+
const items = [];
|
|
770
|
+
for (const message of messages) {
|
|
771
|
+
if (message.role === "tool") {
|
|
772
|
+
for (const part of message.content) {
|
|
773
|
+
if (isToolResult(part)) {
|
|
774
|
+
items.push({
|
|
775
|
+
type: "function_call_output",
|
|
776
|
+
call_id: part.toolCallId,
|
|
777
|
+
output: part.content.map((p) => p.text).join("")
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (message.role === "assistant") {
|
|
784
|
+
const textParts = message.content.filter(isText).map((p) => p.text).join("");
|
|
785
|
+
if (textParts !== "")
|
|
786
|
+
items.push({ role: "assistant", content: textParts });
|
|
787
|
+
for (const part of message.content) {
|
|
788
|
+
if (isToolCall(part)) {
|
|
789
|
+
items.push({
|
|
790
|
+
type: "function_call",
|
|
791
|
+
call_id: part.id,
|
|
792
|
+
name: part.name,
|
|
793
|
+
arguments: stringifyArguments(part.arguments)
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const content = message.content.map(toInputContent).filter((c) => c !== undefined);
|
|
800
|
+
items.push({ role: "user", content });
|
|
801
|
+
}
|
|
802
|
+
return items;
|
|
803
|
+
}
|
|
804
|
+
function toToolChoice(choice) {
|
|
805
|
+
if (typeof choice === "string")
|
|
806
|
+
return choice;
|
|
807
|
+
return { type: "function", name: choice.name };
|
|
808
|
+
}
|
|
809
|
+
function buildOpenAIBody(req, model, stream) {
|
|
810
|
+
const system = systemText(req.messages);
|
|
811
|
+
const body = {
|
|
812
|
+
model,
|
|
813
|
+
input: toInputItems(withoutSystem(req.messages)),
|
|
814
|
+
stream
|
|
815
|
+
};
|
|
816
|
+
if (system !== undefined)
|
|
817
|
+
body["instructions"] = system;
|
|
818
|
+
if (req.tools && req.tools.length > 0) {
|
|
819
|
+
body["tools"] = req.tools.map((t) => ({
|
|
820
|
+
type: "function",
|
|
821
|
+
name: t.name,
|
|
822
|
+
...t.description !== undefined ? { description: t.description } : {},
|
|
823
|
+
parameters: t.parameters,
|
|
824
|
+
strict: true
|
|
825
|
+
}));
|
|
826
|
+
}
|
|
827
|
+
if (req.toolChoice !== undefined)
|
|
828
|
+
body["tool_choice"] = toToolChoice(req.toolChoice);
|
|
829
|
+
if (req.responseSchema !== undefined) {
|
|
830
|
+
body["text"] = {
|
|
831
|
+
format: {
|
|
832
|
+
type: "json_schema",
|
|
833
|
+
name: req.responseSchema.name,
|
|
834
|
+
schema: req.responseSchema.schema,
|
|
835
|
+
strict: req.responseSchema.strict ?? true
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
if (req.maxOutputTokens !== undefined)
|
|
840
|
+
body["max_output_tokens"] = req.maxOutputTokens;
|
|
841
|
+
if (req.temperature !== undefined)
|
|
842
|
+
body["temperature"] = req.temperature;
|
|
843
|
+
if (req.topP !== undefined)
|
|
844
|
+
body["top_p"] = req.topP;
|
|
845
|
+
if (req.metadata !== undefined)
|
|
846
|
+
body["metadata"] = req.metadata;
|
|
847
|
+
if (req.providerOptions !== undefined)
|
|
848
|
+
Object.assign(body, req.providerOptions);
|
|
849
|
+
return body;
|
|
850
|
+
}
|
|
851
|
+
function toFinishReason(response, hadToolCalls, hadRefusal) {
|
|
852
|
+
if (response.status === "failed")
|
|
853
|
+
return "error";
|
|
854
|
+
if (hadRefusal)
|
|
855
|
+
return "content_filter";
|
|
856
|
+
if (response.status === "incomplete") {
|
|
857
|
+
return response.incomplete_details?.reason === "content_filter" ? "content_filter" : "length";
|
|
858
|
+
}
|
|
859
|
+
if (hadToolCalls)
|
|
860
|
+
return "tool_calls";
|
|
861
|
+
return "stop";
|
|
862
|
+
}
|
|
863
|
+
function parseOpenAIResponse(raw, model) {
|
|
864
|
+
const response = raw ?? {};
|
|
865
|
+
let text = "";
|
|
866
|
+
let hadRefusal = false;
|
|
867
|
+
const toolCalls = [];
|
|
868
|
+
for (const item of response.output ?? []) {
|
|
869
|
+
if (item.type === "message") {
|
|
870
|
+
for (const part of item.content ?? []) {
|
|
871
|
+
if (part.type === "output_text" && part.text !== undefined)
|
|
872
|
+
text += part.text;
|
|
873
|
+
else if (part.type === "refusal")
|
|
874
|
+
hadRefusal = true;
|
|
875
|
+
}
|
|
876
|
+
} else if (item.type === "function_call") {
|
|
877
|
+
const argumentsText = item.arguments ?? "";
|
|
878
|
+
toolCalls.push({
|
|
879
|
+
id: item.call_id ?? item.id ?? "",
|
|
880
|
+
name: item.name ?? "",
|
|
881
|
+
arguments: parseArguments(argumentsText),
|
|
882
|
+
argumentsText
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const content = [];
|
|
887
|
+
if (text !== "")
|
|
888
|
+
content.push({ type: "text", text });
|
|
889
|
+
for (const call of toolCalls) {
|
|
890
|
+
content.push({ type: "tool_call", id: call.id, name: call.name, arguments: call.arguments });
|
|
891
|
+
}
|
|
892
|
+
const usage = response.usage ? {
|
|
893
|
+
inputTokens: response.usage.input_tokens ?? 0,
|
|
894
|
+
outputTokens: response.usage.output_tokens ?? 0,
|
|
895
|
+
totalTokens: response.usage.total_tokens ?? (response.usage.input_tokens ?? 0) + (response.usage.output_tokens ?? 0),
|
|
896
|
+
...response.usage.output_tokens_details?.reasoning_tokens !== undefined ? { reasoningTokens: response.usage.output_tokens_details.reasoning_tokens } : {},
|
|
897
|
+
...response.usage.input_tokens_details?.cached_tokens !== undefined ? { cachedInputTokens: response.usage.input_tokens_details.cached_tokens } : {}
|
|
898
|
+
} : undefined;
|
|
899
|
+
return {
|
|
900
|
+
message: { role: "assistant", content },
|
|
901
|
+
toolCalls,
|
|
902
|
+
finishReason: toFinishReason(response, toolCalls.length > 0, hadRefusal),
|
|
903
|
+
...usage !== undefined ? { usage } : {},
|
|
904
|
+
model: response.model ?? model,
|
|
905
|
+
raw
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function parseArguments(text) {
|
|
909
|
+
if (text.trim() === "")
|
|
910
|
+
return;
|
|
911
|
+
try {
|
|
912
|
+
return JSON.parse(text);
|
|
913
|
+
} catch {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/providers/openai/stream.ts
|
|
919
|
+
async function* translateOpenAIStream(messages, model) {
|
|
920
|
+
let nextIndex = 0;
|
|
921
|
+
const indexByItem = new Map;
|
|
922
|
+
let started = false;
|
|
923
|
+
let hadToolCalls = false;
|
|
924
|
+
let finished = false;
|
|
925
|
+
const fallbackFinishReason = () => hadToolCalls ? "tool_calls" : "stop";
|
|
926
|
+
for await (const message of messages) {
|
|
927
|
+
if (message.data === "" || message.data === "[DONE]")
|
|
928
|
+
continue;
|
|
929
|
+
let event;
|
|
930
|
+
try {
|
|
931
|
+
event = JSON.parse(message.data);
|
|
932
|
+
} catch {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
switch (event.type) {
|
|
936
|
+
case "response.created":
|
|
937
|
+
if (!started) {
|
|
938
|
+
started = true;
|
|
939
|
+
yield { type: "message_start", model };
|
|
940
|
+
}
|
|
941
|
+
break;
|
|
942
|
+
case "response.output_item.added":
|
|
943
|
+
if (event.item?.type === "function_call") {
|
|
944
|
+
const index = nextIndex++;
|
|
945
|
+
hadToolCalls = true;
|
|
946
|
+
if (event.item.id !== undefined)
|
|
947
|
+
indexByItem.set(event.item.id, index);
|
|
948
|
+
yield {
|
|
949
|
+
type: "tool_call_start",
|
|
950
|
+
index,
|
|
951
|
+
id: event.item.call_id ?? event.item.id ?? "",
|
|
952
|
+
name: event.item.name ?? ""
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
break;
|
|
956
|
+
case "response.output_text.delta":
|
|
957
|
+
if (event.delta !== undefined)
|
|
958
|
+
yield { type: "text_delta", text: event.delta };
|
|
959
|
+
break;
|
|
960
|
+
case "response.function_call_arguments.delta": {
|
|
961
|
+
const index = event.item_id !== undefined ? indexByItem.get(event.item_id) : undefined;
|
|
962
|
+
if (index !== undefined && event.delta !== undefined) {
|
|
963
|
+
yield {
|
|
964
|
+
type: "tool_call_delta",
|
|
965
|
+
index,
|
|
966
|
+
argumentsTextDelta: event.delta
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
case "response.function_call_arguments.done": {
|
|
972
|
+
const index = event.item_id !== undefined ? indexByItem.get(event.item_id) : undefined;
|
|
973
|
+
if (index !== undefined)
|
|
974
|
+
yield { type: "tool_call_end", index };
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
case "response.completed":
|
|
978
|
+
case "response.incomplete":
|
|
979
|
+
case "response.failed": {
|
|
980
|
+
finished = true;
|
|
981
|
+
const result = parseOpenAIResponse(event.response, model);
|
|
982
|
+
yield {
|
|
983
|
+
type: "finish",
|
|
984
|
+
finishReason: result.finishReason,
|
|
985
|
+
...result.usage !== undefined ? { usage: result.usage } : {}
|
|
986
|
+
};
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
default:
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if (!finished) {
|
|
994
|
+
yield { type: "finish", finishReason: fallbackFinishReason() };
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/providers/openai/index.ts
|
|
999
|
+
var CAPABILITIES = {
|
|
1000
|
+
tools: true,
|
|
1001
|
+
streaming: true,
|
|
1002
|
+
multimodalInput: true,
|
|
1003
|
+
parallelToolCalls: true,
|
|
1004
|
+
structuredOutput: true
|
|
1005
|
+
};
|
|
1006
|
+
function createOpenAI(opts) {
|
|
1007
|
+
const apiKey = resolveSecret(opts.apiKey);
|
|
1008
|
+
return createProvider({
|
|
1009
|
+
name: "openai",
|
|
1010
|
+
defaultModel: opts.model ?? "gpt-5",
|
|
1011
|
+
capabilities: CAPABILITIES,
|
|
1012
|
+
http: {
|
|
1013
|
+
baseUrl: opts.baseUrl ?? "https://api.openai.com/v1",
|
|
1014
|
+
headers: {
|
|
1015
|
+
...opts.defaultHeaders,
|
|
1016
|
+
authorization: `Bearer ${apiKey}`
|
|
1017
|
+
},
|
|
1018
|
+
...opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {},
|
|
1019
|
+
...opts.resilience !== undefined ? { resilience: opts.resilience } : {},
|
|
1020
|
+
...opts.fetch !== undefined ? { fetch: opts.fetch } : {}
|
|
1021
|
+
},
|
|
1022
|
+
completePath: () => "responses",
|
|
1023
|
+
streamPath: () => "responses",
|
|
1024
|
+
buildBody: buildOpenAIBody,
|
|
1025
|
+
parseResponse: parseOpenAIResponse,
|
|
1026
|
+
translateStream: translateOpenAIStream
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
// src/providers/anthropic/map.ts
|
|
1030
|
+
var DEFAULT_MAX_TOKENS = 4096;
|
|
1031
|
+
function toImageBlock(part) {
|
|
1032
|
+
const isUrl = /^https?:\/\//.test(part.data);
|
|
1033
|
+
return {
|
|
1034
|
+
type: "image",
|
|
1035
|
+
source: isUrl ? { type: "url", url: part.data } : { type: "base64", media_type: part.mimeType, data: part.data }
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function toAnthropicMessages(messages) {
|
|
1039
|
+
const out = [];
|
|
1040
|
+
const pushUserBlocks = (blocks) => {
|
|
1041
|
+
const last = out[out.length - 1];
|
|
1042
|
+
if (last && last.role === "user")
|
|
1043
|
+
last.content.push(...blocks);
|
|
1044
|
+
else
|
|
1045
|
+
out.push({ role: "user", content: blocks });
|
|
1046
|
+
};
|
|
1047
|
+
for (const message of messages) {
|
|
1048
|
+
if (message.role === "tool") {
|
|
1049
|
+
const blocks2 = message.content.filter(isToolResult).map((part) => ({
|
|
1050
|
+
type: "tool_result",
|
|
1051
|
+
tool_use_id: part.toolCallId,
|
|
1052
|
+
content: toolResultText(part),
|
|
1053
|
+
...part.isError !== undefined ? { is_error: part.isError } : {}
|
|
1054
|
+
}));
|
|
1055
|
+
pushUserBlocks(blocks2);
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
if (message.role === "assistant") {
|
|
1059
|
+
const blocks2 = [];
|
|
1060
|
+
for (const part of message.content) {
|
|
1061
|
+
if (isText(part))
|
|
1062
|
+
blocks2.push({ type: "text", text: part.text });
|
|
1063
|
+
else if (isToolCall(part)) {
|
|
1064
|
+
blocks2.push({ type: "tool_use", id: part.id, name: part.name, input: part.arguments ?? {} });
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
out.push({ role: "assistant", content: blocks2 });
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const blocks = [];
|
|
1071
|
+
for (const part of message.content) {
|
|
1072
|
+
if (isText(part))
|
|
1073
|
+
blocks.push({ type: "text", text: part.text });
|
|
1074
|
+
else if (part.type === "image")
|
|
1075
|
+
blocks.push(toImageBlock(part));
|
|
1076
|
+
}
|
|
1077
|
+
pushUserBlocks(blocks);
|
|
1078
|
+
}
|
|
1079
|
+
return out;
|
|
1080
|
+
}
|
|
1081
|
+
function toToolChoice2(choice) {
|
|
1082
|
+
if (choice === "auto")
|
|
1083
|
+
return { type: "auto" };
|
|
1084
|
+
if (choice === "none")
|
|
1085
|
+
return { type: "none" };
|
|
1086
|
+
if (choice === "required")
|
|
1087
|
+
return { type: "any" };
|
|
1088
|
+
return { type: "tool", name: choice.name };
|
|
1089
|
+
}
|
|
1090
|
+
function buildAnthropicBody(req, model, stream) {
|
|
1091
|
+
const system = systemText(req.messages);
|
|
1092
|
+
const body = {
|
|
1093
|
+
model,
|
|
1094
|
+
max_tokens: req.maxOutputTokens ?? DEFAULT_MAX_TOKENS,
|
|
1095
|
+
messages: toAnthropicMessages(withoutSystem(req.messages)),
|
|
1096
|
+
stream
|
|
1097
|
+
};
|
|
1098
|
+
if (system !== undefined)
|
|
1099
|
+
body["system"] = system;
|
|
1100
|
+
if (req.tools && req.tools.length > 0) {
|
|
1101
|
+
body["tools"] = req.tools.map((t) => ({
|
|
1102
|
+
name: t.name,
|
|
1103
|
+
...t.description !== undefined ? { description: t.description } : {},
|
|
1104
|
+
input_schema: t.parameters,
|
|
1105
|
+
strict: true
|
|
1106
|
+
}));
|
|
1107
|
+
}
|
|
1108
|
+
if (req.toolChoice !== undefined)
|
|
1109
|
+
body["tool_choice"] = toToolChoice2(req.toolChoice);
|
|
1110
|
+
if (req.responseSchema !== undefined) {
|
|
1111
|
+
body["output_config"] = {
|
|
1112
|
+
format: { type: "json_schema", schema: req.responseSchema.schema }
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
if (req.temperature !== undefined)
|
|
1116
|
+
body["temperature"] = req.temperature;
|
|
1117
|
+
if (req.topP !== undefined)
|
|
1118
|
+
body["top_p"] = req.topP;
|
|
1119
|
+
if (req.stopSequences !== undefined)
|
|
1120
|
+
body["stop_sequences"] = req.stopSequences;
|
|
1121
|
+
if (req.metadata?.["userId"] !== undefined)
|
|
1122
|
+
body["metadata"] = { user_id: req.metadata["userId"] };
|
|
1123
|
+
if (req.providerOptions !== undefined)
|
|
1124
|
+
Object.assign(body, req.providerOptions);
|
|
1125
|
+
return body;
|
|
1126
|
+
}
|
|
1127
|
+
function mapStopReason(stopReason, hadToolCalls) {
|
|
1128
|
+
switch (stopReason) {
|
|
1129
|
+
case "end_turn":
|
|
1130
|
+
case "stop_sequence":
|
|
1131
|
+
return hadToolCalls ? "tool_calls" : "stop";
|
|
1132
|
+
case "tool_use":
|
|
1133
|
+
return "tool_calls";
|
|
1134
|
+
case "max_tokens":
|
|
1135
|
+
return "length";
|
|
1136
|
+
case "refusal":
|
|
1137
|
+
return "content_filter";
|
|
1138
|
+
default:
|
|
1139
|
+
return hadToolCalls ? "tool_calls" : "other";
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
function parseAnthropicResponse(raw, model) {
|
|
1143
|
+
const response = raw ?? {};
|
|
1144
|
+
let text = "";
|
|
1145
|
+
const toolCalls = [];
|
|
1146
|
+
for (const block of response.content ?? []) {
|
|
1147
|
+
if (block.type === "text" && block.text !== undefined)
|
|
1148
|
+
text += block.text;
|
|
1149
|
+
else if (block.type === "tool_use") {
|
|
1150
|
+
toolCalls.push({
|
|
1151
|
+
id: block.id ?? "",
|
|
1152
|
+
name: block.name ?? "",
|
|
1153
|
+
arguments: block.input,
|
|
1154
|
+
argumentsText: JSON.stringify(block.input ?? {})
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
const content = [];
|
|
1159
|
+
if (text !== "")
|
|
1160
|
+
content.push({ type: "text", text });
|
|
1161
|
+
for (const call of toolCalls) {
|
|
1162
|
+
content.push({ type: "tool_call", id: call.id, name: call.name, arguments: call.arguments });
|
|
1163
|
+
}
|
|
1164
|
+
const usage = response.usage ? {
|
|
1165
|
+
inputTokens: response.usage.input_tokens ?? 0,
|
|
1166
|
+
outputTokens: response.usage.output_tokens ?? 0,
|
|
1167
|
+
totalTokens: (response.usage.input_tokens ?? 0) + (response.usage.output_tokens ?? 0),
|
|
1168
|
+
...response.usage.cache_read_input_tokens !== undefined ? { cachedInputTokens: response.usage.cache_read_input_tokens } : {}
|
|
1169
|
+
} : undefined;
|
|
1170
|
+
return {
|
|
1171
|
+
message: { role: "assistant", content },
|
|
1172
|
+
toolCalls,
|
|
1173
|
+
finishReason: mapStopReason(response.stop_reason, toolCalls.length > 0),
|
|
1174
|
+
...usage !== undefined ? { usage } : {},
|
|
1175
|
+
model: response.model ?? model,
|
|
1176
|
+
raw
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/providers/anthropic/stream.ts
|
|
1181
|
+
async function* translateAnthropicStream(messages, model) {
|
|
1182
|
+
const toolIndexes = new Set;
|
|
1183
|
+
let inputTokens = 0;
|
|
1184
|
+
let outputTokens = 0;
|
|
1185
|
+
let stopReason;
|
|
1186
|
+
let hadToolCalls = false;
|
|
1187
|
+
let sawUsage = false;
|
|
1188
|
+
let finished = false;
|
|
1189
|
+
const finishEvent = (includeUsage = sawUsage) => {
|
|
1190
|
+
const usage = includeUsage ? {
|
|
1191
|
+
inputTokens,
|
|
1192
|
+
outputTokens,
|
|
1193
|
+
totalTokens: inputTokens + outputTokens
|
|
1194
|
+
} : undefined;
|
|
1195
|
+
return {
|
|
1196
|
+
type: "finish",
|
|
1197
|
+
finishReason: mapStopReason(stopReason, hadToolCalls),
|
|
1198
|
+
...usage !== undefined ? { usage } : {}
|
|
1199
|
+
};
|
|
1200
|
+
};
|
|
1201
|
+
for await (const message of messages) {
|
|
1202
|
+
if (message.data === "")
|
|
1203
|
+
continue;
|
|
1204
|
+
let event;
|
|
1205
|
+
try {
|
|
1206
|
+
event = JSON.parse(message.data);
|
|
1207
|
+
} catch {
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
switch (event.type) {
|
|
1211
|
+
case "message_start":
|
|
1212
|
+
if (event.message?.usage?.input_tokens !== undefined) {
|
|
1213
|
+
inputTokens = event.message.usage.input_tokens;
|
|
1214
|
+
sawUsage = true;
|
|
1215
|
+
}
|
|
1216
|
+
yield { type: "message_start", model: event.message?.model ?? model };
|
|
1217
|
+
break;
|
|
1218
|
+
case "content_block_start":
|
|
1219
|
+
if (event.content_block?.type === "tool_use" && event.index !== undefined) {
|
|
1220
|
+
toolIndexes.add(event.index);
|
|
1221
|
+
hadToolCalls = true;
|
|
1222
|
+
yield {
|
|
1223
|
+
type: "tool_call_start",
|
|
1224
|
+
index: event.index,
|
|
1225
|
+
id: event.content_block.id ?? "",
|
|
1226
|
+
name: event.content_block.name ?? ""
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
break;
|
|
1230
|
+
case "content_block_delta":
|
|
1231
|
+
if (event.delta?.type === "text_delta" && event.delta.text !== undefined) {
|
|
1232
|
+
yield { type: "text_delta", text: event.delta.text };
|
|
1233
|
+
} else if (event.delta?.type === "input_json_delta" && event.index !== undefined && event.delta.partial_json !== undefined) {
|
|
1234
|
+
yield {
|
|
1235
|
+
type: "tool_call_delta",
|
|
1236
|
+
index: event.index,
|
|
1237
|
+
argumentsTextDelta: event.delta.partial_json
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
break;
|
|
1241
|
+
case "content_block_stop":
|
|
1242
|
+
if (event.index !== undefined && toolIndexes.has(event.index)) {
|
|
1243
|
+
yield { type: "tool_call_end", index: event.index };
|
|
1244
|
+
}
|
|
1245
|
+
break;
|
|
1246
|
+
case "message_delta":
|
|
1247
|
+
if (event.delta?.stop_reason !== undefined)
|
|
1248
|
+
stopReason = event.delta.stop_reason;
|
|
1249
|
+
if (event.usage?.output_tokens !== undefined) {
|
|
1250
|
+
outputTokens = event.usage.output_tokens;
|
|
1251
|
+
sawUsage = true;
|
|
1252
|
+
}
|
|
1253
|
+
break;
|
|
1254
|
+
case "message_stop":
|
|
1255
|
+
finished = true;
|
|
1256
|
+
yield finishEvent(true);
|
|
1257
|
+
break;
|
|
1258
|
+
default:
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
if (!finished)
|
|
1263
|
+
yield finishEvent();
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// src/providers/anthropic/index.ts
|
|
1267
|
+
var DEFAULT_VERSION = "2023-06-01";
|
|
1268
|
+
var CAPABILITIES2 = {
|
|
1269
|
+
tools: true,
|
|
1270
|
+
streaming: true,
|
|
1271
|
+
multimodalInput: true,
|
|
1272
|
+
parallelToolCalls: true,
|
|
1273
|
+
structuredOutput: true
|
|
1274
|
+
};
|
|
1275
|
+
function createAnthropic(opts) {
|
|
1276
|
+
const apiKey = resolveSecret(opts.apiKey);
|
|
1277
|
+
return createProvider({
|
|
1278
|
+
name: "anthropic",
|
|
1279
|
+
defaultModel: opts.model ?? "claude-opus-4-7",
|
|
1280
|
+
capabilities: CAPABILITIES2,
|
|
1281
|
+
http: {
|
|
1282
|
+
baseUrl: opts.baseUrl ?? "https://api.anthropic.com/v1",
|
|
1283
|
+
headers: {
|
|
1284
|
+
...opts.defaultHeaders,
|
|
1285
|
+
"x-api-key": apiKey,
|
|
1286
|
+
"anthropic-version": opts.version ?? DEFAULT_VERSION
|
|
1287
|
+
},
|
|
1288
|
+
...opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {},
|
|
1289
|
+
...opts.resilience !== undefined ? { resilience: opts.resilience } : {},
|
|
1290
|
+
...opts.fetch !== undefined ? { fetch: opts.fetch } : {}
|
|
1291
|
+
},
|
|
1292
|
+
completePath: () => "messages",
|
|
1293
|
+
streamPath: () => "messages",
|
|
1294
|
+
buildBody: buildAnthropicBody,
|
|
1295
|
+
parseResponse: parseAnthropicResponse,
|
|
1296
|
+
translateStream: translateAnthropicStream
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
// src/providers/google/map.ts
|
|
1300
|
+
function toGeminiImage(part) {
|
|
1301
|
+
const isUrl = /^https?:\/\//.test(part.data);
|
|
1302
|
+
return isUrl ? { fileData: { mimeType: part.mimeType, fileUri: part.data } } : { inlineData: { mimeType: part.mimeType, data: part.data } };
|
|
1303
|
+
}
|
|
1304
|
+
function toolNameById(messages) {
|
|
1305
|
+
const names = new Map;
|
|
1306
|
+
for (const message of messages) {
|
|
1307
|
+
if (message.role !== "assistant")
|
|
1308
|
+
continue;
|
|
1309
|
+
for (const part of message.content) {
|
|
1310
|
+
if (isToolCall(part))
|
|
1311
|
+
names.set(part.id, part.name);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return names;
|
|
1315
|
+
}
|
|
1316
|
+
function toContents(messages) {
|
|
1317
|
+
const toolNames = toolNameById(messages);
|
|
1318
|
+
const contents = [];
|
|
1319
|
+
for (const message of messages) {
|
|
1320
|
+
if (message.role === "tool") {
|
|
1321
|
+
const parts2 = message.content.filter(isToolResult).map((part) => ({
|
|
1322
|
+
functionResponse: {
|
|
1323
|
+
id: part.toolCallId,
|
|
1324
|
+
name: toolNames.get(part.toolCallId) ?? part.toolCallId,
|
|
1325
|
+
response: { result: toolResultText(part) }
|
|
1326
|
+
}
|
|
1327
|
+
}));
|
|
1328
|
+
contents.push({ role: "user", parts: parts2 });
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
if (message.role === "assistant") {
|
|
1332
|
+
const parts2 = [];
|
|
1333
|
+
for (const part of message.content) {
|
|
1334
|
+
if (isText(part))
|
|
1335
|
+
parts2.push({ text: part.text });
|
|
1336
|
+
else if (isToolCall(part)) {
|
|
1337
|
+
parts2.push({ functionCall: { id: part.id, name: part.name, args: part.arguments ?? {} } });
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
contents.push({ role: "model", parts: parts2 });
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
const parts = [];
|
|
1344
|
+
for (const part of message.content) {
|
|
1345
|
+
if (isText(part))
|
|
1346
|
+
parts.push({ text: part.text });
|
|
1347
|
+
else if (part.type === "image")
|
|
1348
|
+
parts.push(toGeminiImage(part));
|
|
1349
|
+
}
|
|
1350
|
+
contents.push({ role: "user", parts });
|
|
1351
|
+
}
|
|
1352
|
+
return contents;
|
|
1353
|
+
}
|
|
1354
|
+
function toToolConfig(choice) {
|
|
1355
|
+
if (typeof choice === "object") {
|
|
1356
|
+
return { functionCallingConfig: { mode: "ANY", allowedFunctionNames: [choice.name] } };
|
|
1357
|
+
}
|
|
1358
|
+
const mode = choice === "none" ? "NONE" : choice === "required" ? "ANY" : "AUTO";
|
|
1359
|
+
return { functionCallingConfig: { mode } };
|
|
1360
|
+
}
|
|
1361
|
+
function buildGoogleBody(req, _model, _stream) {
|
|
1362
|
+
const system = systemText(req.messages);
|
|
1363
|
+
const body = {
|
|
1364
|
+
contents: toContents(withoutSystem(req.messages))
|
|
1365
|
+
};
|
|
1366
|
+
if (system !== undefined)
|
|
1367
|
+
body["systemInstruction"] = { parts: [{ text: system }] };
|
|
1368
|
+
if (req.tools && req.tools.length > 0) {
|
|
1369
|
+
body["tools"] = [
|
|
1370
|
+
{
|
|
1371
|
+
functionDeclarations: req.tools.map((t) => ({
|
|
1372
|
+
name: t.name,
|
|
1373
|
+
...t.description !== undefined ? { description: t.description } : {},
|
|
1374
|
+
parameters: t.parameters
|
|
1375
|
+
}))
|
|
1376
|
+
}
|
|
1377
|
+
];
|
|
1378
|
+
}
|
|
1379
|
+
if (req.toolChoice !== undefined)
|
|
1380
|
+
body["toolConfig"] = toToolConfig(req.toolChoice);
|
|
1381
|
+
const generationConfig = {};
|
|
1382
|
+
if (req.temperature !== undefined)
|
|
1383
|
+
generationConfig["temperature"] = req.temperature;
|
|
1384
|
+
if (req.topP !== undefined)
|
|
1385
|
+
generationConfig["topP"] = req.topP;
|
|
1386
|
+
if (req.maxOutputTokens !== undefined)
|
|
1387
|
+
generationConfig["maxOutputTokens"] = req.maxOutputTokens;
|
|
1388
|
+
if (req.stopSequences !== undefined)
|
|
1389
|
+
generationConfig["stopSequences"] = req.stopSequences;
|
|
1390
|
+
if (req.responseSchema !== undefined) {
|
|
1391
|
+
generationConfig["responseMimeType"] = "application/json";
|
|
1392
|
+
generationConfig["responseSchema"] = req.responseSchema.schema;
|
|
1393
|
+
}
|
|
1394
|
+
if (Object.keys(generationConfig).length > 0)
|
|
1395
|
+
body["generationConfig"] = generationConfig;
|
|
1396
|
+
if (req.providerOptions !== undefined)
|
|
1397
|
+
Object.assign(body, req.providerOptions);
|
|
1398
|
+
return body;
|
|
1399
|
+
}
|
|
1400
|
+
function mapGoogleFinish(reason, hadToolCalls) {
|
|
1401
|
+
if (hadToolCalls && (reason === undefined || reason === "STOP"))
|
|
1402
|
+
return "tool_calls";
|
|
1403
|
+
switch (reason) {
|
|
1404
|
+
case "STOP":
|
|
1405
|
+
return "stop";
|
|
1406
|
+
case "MAX_TOKENS":
|
|
1407
|
+
return "length";
|
|
1408
|
+
case "SAFETY":
|
|
1409
|
+
case "PROHIBITED_CONTENT":
|
|
1410
|
+
case "BLOCKLIST":
|
|
1411
|
+
case "SPII":
|
|
1412
|
+
case "IMAGE_SAFETY":
|
|
1413
|
+
case "IMAGE_PROHIBITED_CONTENT":
|
|
1414
|
+
return "content_filter";
|
|
1415
|
+
case undefined:
|
|
1416
|
+
return "stop";
|
|
1417
|
+
default:
|
|
1418
|
+
return "other";
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function parseGoogleResponse(raw, model) {
|
|
1422
|
+
const response = raw ?? {};
|
|
1423
|
+
const candidate = response.candidates?.[0];
|
|
1424
|
+
let text = "";
|
|
1425
|
+
const toolCalls = [];
|
|
1426
|
+
let index = 0;
|
|
1427
|
+
for (const part of candidate?.content?.parts ?? []) {
|
|
1428
|
+
if (part.text !== undefined)
|
|
1429
|
+
text += part.text;
|
|
1430
|
+
else if (part.functionCall !== undefined) {
|
|
1431
|
+
const name = part.functionCall.name ?? "";
|
|
1432
|
+
toolCalls.push({
|
|
1433
|
+
id: part.functionCall.id ?? `call_${name}_${index}`,
|
|
1434
|
+
name,
|
|
1435
|
+
arguments: part.functionCall.args,
|
|
1436
|
+
argumentsText: JSON.stringify(part.functionCall.args ?? {})
|
|
1437
|
+
});
|
|
1438
|
+
index += 1;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const content = [];
|
|
1442
|
+
if (text !== "")
|
|
1443
|
+
content.push({ type: "text", text });
|
|
1444
|
+
for (const call of toolCalls) {
|
|
1445
|
+
content.push({ type: "tool_call", id: call.id, name: call.name, arguments: call.arguments });
|
|
1446
|
+
}
|
|
1447
|
+
const meta = response.usageMetadata;
|
|
1448
|
+
const usage = meta ? {
|
|
1449
|
+
inputTokens: meta.promptTokenCount ?? 0,
|
|
1450
|
+
outputTokens: meta.candidatesTokenCount ?? 0,
|
|
1451
|
+
totalTokens: meta.totalTokenCount ?? (meta.promptTokenCount ?? 0) + (meta.candidatesTokenCount ?? 0),
|
|
1452
|
+
...meta.thoughtsTokenCount !== undefined ? { reasoningTokens: meta.thoughtsTokenCount } : {},
|
|
1453
|
+
...meta.cachedContentTokenCount !== undefined ? { cachedInputTokens: meta.cachedContentTokenCount } : {}
|
|
1454
|
+
} : undefined;
|
|
1455
|
+
return {
|
|
1456
|
+
message: { role: "assistant", content },
|
|
1457
|
+
toolCalls,
|
|
1458
|
+
finishReason: mapGoogleFinish(candidate?.finishReason, toolCalls.length > 0),
|
|
1459
|
+
...usage !== undefined ? { usage } : {},
|
|
1460
|
+
model: response.modelVersion ?? model,
|
|
1461
|
+
raw
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/providers/google/stream.ts
|
|
1466
|
+
async function* translateGoogleStream(messages, model) {
|
|
1467
|
+
let started = false;
|
|
1468
|
+
let toolIndex = 0;
|
|
1469
|
+
let hadToolCalls = false;
|
|
1470
|
+
let finishReason;
|
|
1471
|
+
let usage;
|
|
1472
|
+
for await (const message of messages) {
|
|
1473
|
+
if (message.data === "")
|
|
1474
|
+
continue;
|
|
1475
|
+
let chunk;
|
|
1476
|
+
try {
|
|
1477
|
+
chunk = JSON.parse(message.data);
|
|
1478
|
+
} catch {
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (!started) {
|
|
1482
|
+
started = true;
|
|
1483
|
+
yield { type: "message_start", model };
|
|
1484
|
+
}
|
|
1485
|
+
const candidate = chunk.candidates?.[0];
|
|
1486
|
+
for (const part of candidate?.content?.parts ?? []) {
|
|
1487
|
+
if (part.text !== undefined) {
|
|
1488
|
+
yield { type: "text_delta", text: part.text };
|
|
1489
|
+
} else if (part.functionCall !== undefined) {
|
|
1490
|
+
const index = toolIndex++;
|
|
1491
|
+
hadToolCalls = true;
|
|
1492
|
+
const name = part.functionCall.name ?? "";
|
|
1493
|
+
yield { type: "tool_call_start", index, id: part.functionCall.id ?? `call_${name}_${index}`, name };
|
|
1494
|
+
yield {
|
|
1495
|
+
type: "tool_call_delta",
|
|
1496
|
+
index,
|
|
1497
|
+
argumentsTextDelta: JSON.stringify(part.functionCall.args ?? {})
|
|
1498
|
+
};
|
|
1499
|
+
yield { type: "tool_call_end", index };
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (candidate?.finishReason !== undefined)
|
|
1503
|
+
finishReason = candidate.finishReason;
|
|
1504
|
+
if (chunk.usageMetadata) {
|
|
1505
|
+
const meta = chunk.usageMetadata;
|
|
1506
|
+
usage = {
|
|
1507
|
+
inputTokens: meta.promptTokenCount ?? 0,
|
|
1508
|
+
outputTokens: meta.candidatesTokenCount ?? 0,
|
|
1509
|
+
totalTokens: meta.totalTokenCount ?? (meta.promptTokenCount ?? 0) + (meta.candidatesTokenCount ?? 0),
|
|
1510
|
+
...meta.thoughtsTokenCount !== undefined ? { reasoningTokens: meta.thoughtsTokenCount } : {},
|
|
1511
|
+
...meta.cachedContentTokenCount !== undefined ? { cachedInputTokens: meta.cachedContentTokenCount } : {}
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
yield {
|
|
1516
|
+
type: "finish",
|
|
1517
|
+
finishReason: mapGoogleFinish(finishReason, hadToolCalls),
|
|
1518
|
+
...usage !== undefined ? { usage } : {}
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/providers/google/index.ts
|
|
1523
|
+
var CAPABILITIES3 = {
|
|
1524
|
+
tools: true,
|
|
1525
|
+
streaming: true,
|
|
1526
|
+
multimodalInput: true,
|
|
1527
|
+
parallelToolCalls: true,
|
|
1528
|
+
structuredOutput: true
|
|
1529
|
+
};
|
|
1530
|
+
function createGoogle(opts) {
|
|
1531
|
+
const apiKey = resolveSecret(opts.apiKey);
|
|
1532
|
+
return createProvider({
|
|
1533
|
+
name: "google",
|
|
1534
|
+
defaultModel: opts.model ?? "gemini-2.5-pro",
|
|
1535
|
+
capabilities: CAPABILITIES3,
|
|
1536
|
+
http: {
|
|
1537
|
+
baseUrl: opts.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta",
|
|
1538
|
+
headers: {
|
|
1539
|
+
...opts.defaultHeaders,
|
|
1540
|
+
"x-goog-api-key": apiKey
|
|
1541
|
+
},
|
|
1542
|
+
...opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {},
|
|
1543
|
+
...opts.resilience !== undefined ? { resilience: opts.resilience } : {},
|
|
1544
|
+
...opts.fetch !== undefined ? { fetch: opts.fetch } : {}
|
|
1545
|
+
},
|
|
1546
|
+
completePath: (model) => `models/${model}:generateContent`,
|
|
1547
|
+
streamPath: (model) => `models/${model}:streamGenerateContent?alt=sse`,
|
|
1548
|
+
buildBody: buildGoogleBody,
|
|
1549
|
+
parseResponse: parseGoogleResponse,
|
|
1550
|
+
translateStream: translateGoogleStream
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
// src/providers/openai-compatible/map.ts
|
|
1554
|
+
function toChatMessages(messages) {
|
|
1555
|
+
const out = [];
|
|
1556
|
+
for (const message of messages) {
|
|
1557
|
+
if (message.role === "system") {
|
|
1558
|
+
out.push({ role: "system", content: message.content.filter(isText).map((p) => p.text).join(`
|
|
1559
|
+
`) });
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (message.role === "tool") {
|
|
1563
|
+
for (const part of message.content) {
|
|
1564
|
+
if (isToolResult(part)) {
|
|
1565
|
+
out.push({ role: "tool", tool_call_id: part.toolCallId, content: toolResultText(part) });
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (message.role === "assistant") {
|
|
1571
|
+
const text = message.content.filter(isText).map((p) => p.text).join("");
|
|
1572
|
+
const toolCalls = message.content.filter(isToolCall).map((part) => ({
|
|
1573
|
+
id: part.id,
|
|
1574
|
+
type: "function",
|
|
1575
|
+
function: { name: part.name, arguments: stringifyArguments(part.arguments) }
|
|
1576
|
+
}));
|
|
1577
|
+
out.push({
|
|
1578
|
+
role: "assistant",
|
|
1579
|
+
content: text !== "" ? text : null,
|
|
1580
|
+
...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
|
|
1581
|
+
});
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
const hasImage = message.content.some((p) => p.type === "image");
|
|
1585
|
+
if (hasImage) {
|
|
1586
|
+
const content = [];
|
|
1587
|
+
for (const part of message.content) {
|
|
1588
|
+
if (part.type === "text")
|
|
1589
|
+
content.push({ type: "text", text: part.text });
|
|
1590
|
+
else if (part.type === "image")
|
|
1591
|
+
content.push({ type: "image_url", image_url: { url: imageDataUrl(part) } });
|
|
1592
|
+
}
|
|
1593
|
+
out.push({ role: "user", content });
|
|
1594
|
+
} else {
|
|
1595
|
+
out.push({ role: "user", content: message.content.filter(isText).map((p) => p.text).join("") });
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return out;
|
|
1599
|
+
}
|
|
1600
|
+
function toToolChoice3(choice) {
|
|
1601
|
+
if (typeof choice === "string")
|
|
1602
|
+
return choice;
|
|
1603
|
+
return { type: "function", function: { name: choice.name } };
|
|
1604
|
+
}
|
|
1605
|
+
function buildChatBody(req, model, stream) {
|
|
1606
|
+
const body = {
|
|
1607
|
+
model,
|
|
1608
|
+
messages: toChatMessages(req.messages),
|
|
1609
|
+
stream
|
|
1610
|
+
};
|
|
1611
|
+
if (stream)
|
|
1612
|
+
body["stream_options"] = { include_usage: true };
|
|
1613
|
+
if (req.tools && req.tools.length > 0) {
|
|
1614
|
+
body["tools"] = req.tools.map((t) => ({
|
|
1615
|
+
type: "function",
|
|
1616
|
+
function: {
|
|
1617
|
+
name: t.name,
|
|
1618
|
+
...t.description !== undefined ? { description: t.description } : {},
|
|
1619
|
+
parameters: t.parameters
|
|
1620
|
+
}
|
|
1621
|
+
}));
|
|
1622
|
+
}
|
|
1623
|
+
if (req.toolChoice !== undefined)
|
|
1624
|
+
body["tool_choice"] = toToolChoice3(req.toolChoice);
|
|
1625
|
+
if (req.responseSchema !== undefined) {
|
|
1626
|
+
body["response_format"] = {
|
|
1627
|
+
type: "json_schema",
|
|
1628
|
+
json_schema: {
|
|
1629
|
+
name: req.responseSchema.name,
|
|
1630
|
+
schema: req.responseSchema.schema,
|
|
1631
|
+
strict: req.responseSchema.strict ?? true
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
if (req.maxOutputTokens !== undefined)
|
|
1636
|
+
body["max_tokens"] = req.maxOutputTokens;
|
|
1637
|
+
if (req.temperature !== undefined)
|
|
1638
|
+
body["temperature"] = req.temperature;
|
|
1639
|
+
if (req.topP !== undefined)
|
|
1640
|
+
body["top_p"] = req.topP;
|
|
1641
|
+
if (req.stopSequences !== undefined)
|
|
1642
|
+
body["stop"] = req.stopSequences;
|
|
1643
|
+
if (req.metadata !== undefined)
|
|
1644
|
+
body["metadata"] = req.metadata;
|
|
1645
|
+
if (req.providerOptions !== undefined)
|
|
1646
|
+
Object.assign(body, req.providerOptions);
|
|
1647
|
+
return body;
|
|
1648
|
+
}
|
|
1649
|
+
function mapChatFinish(reason, hadToolCalls) {
|
|
1650
|
+
switch (reason) {
|
|
1651
|
+
case "stop":
|
|
1652
|
+
return hadToolCalls ? "tool_calls" : "stop";
|
|
1653
|
+
case "length":
|
|
1654
|
+
return "length";
|
|
1655
|
+
case "tool_calls":
|
|
1656
|
+
case "function_call":
|
|
1657
|
+
return "tool_calls";
|
|
1658
|
+
case "content_filter":
|
|
1659
|
+
return "content_filter";
|
|
1660
|
+
default:
|
|
1661
|
+
return hadToolCalls ? "tool_calls" : "stop";
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
function parseChatResponse(raw, model) {
|
|
1665
|
+
const response = raw ?? {};
|
|
1666
|
+
const choice = response.choices?.[0];
|
|
1667
|
+
const text = choice?.message?.content ?? "";
|
|
1668
|
+
const toolCalls = (choice?.message?.tool_calls ?? []).map((call) => {
|
|
1669
|
+
const argumentsText = call.function?.arguments ?? "";
|
|
1670
|
+
return {
|
|
1671
|
+
id: call.id ?? "",
|
|
1672
|
+
name: call.function?.name ?? "",
|
|
1673
|
+
arguments: parseArguments2(argumentsText),
|
|
1674
|
+
argumentsText
|
|
1675
|
+
};
|
|
1676
|
+
});
|
|
1677
|
+
const content = [];
|
|
1678
|
+
if (text !== "")
|
|
1679
|
+
content.push({ type: "text", text });
|
|
1680
|
+
for (const call of toolCalls) {
|
|
1681
|
+
content.push({ type: "tool_call", id: call.id, name: call.name, arguments: call.arguments });
|
|
1682
|
+
}
|
|
1683
|
+
const usage = response.usage ? {
|
|
1684
|
+
inputTokens: response.usage.prompt_tokens ?? 0,
|
|
1685
|
+
outputTokens: response.usage.completion_tokens ?? 0,
|
|
1686
|
+
totalTokens: response.usage.total_tokens ?? (response.usage.prompt_tokens ?? 0) + (response.usage.completion_tokens ?? 0),
|
|
1687
|
+
...response.usage.completion_tokens_details?.reasoning_tokens !== undefined ? { reasoningTokens: response.usage.completion_tokens_details.reasoning_tokens } : {},
|
|
1688
|
+
...response.usage.prompt_tokens_details?.cached_tokens !== undefined ? { cachedInputTokens: response.usage.prompt_tokens_details.cached_tokens } : {}
|
|
1689
|
+
} : undefined;
|
|
1690
|
+
return {
|
|
1691
|
+
message: { role: "assistant", content },
|
|
1692
|
+
toolCalls,
|
|
1693
|
+
finishReason: mapChatFinish(choice?.finish_reason, toolCalls.length > 0),
|
|
1694
|
+
...usage !== undefined ? { usage } : {},
|
|
1695
|
+
model: response.model ?? model,
|
|
1696
|
+
raw
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
function parseArguments2(text) {
|
|
1700
|
+
if (text.trim() === "")
|
|
1701
|
+
return;
|
|
1702
|
+
try {
|
|
1703
|
+
return JSON.parse(text);
|
|
1704
|
+
} catch {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/providers/openai-compatible/stream.ts
|
|
1710
|
+
async function* translateChatStream(messages, model) {
|
|
1711
|
+
let started = false;
|
|
1712
|
+
const openTools = new Set;
|
|
1713
|
+
let hadToolCalls = false;
|
|
1714
|
+
let finishReason;
|
|
1715
|
+
let usage;
|
|
1716
|
+
for await (const message of messages) {
|
|
1717
|
+
if (message.data === "" || message.data === "[DONE]")
|
|
1718
|
+
continue;
|
|
1719
|
+
let chunk;
|
|
1720
|
+
try {
|
|
1721
|
+
chunk = JSON.parse(message.data);
|
|
1722
|
+
} catch {
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
if (!started) {
|
|
1726
|
+
started = true;
|
|
1727
|
+
yield { type: "message_start", model: chunk.model ?? model };
|
|
1728
|
+
}
|
|
1729
|
+
const choice = chunk.choices?.[0];
|
|
1730
|
+
if (choice?.delta?.content !== undefined && choice.delta.content !== null) {
|
|
1731
|
+
yield { type: "text_delta", text: choice.delta.content };
|
|
1732
|
+
}
|
|
1733
|
+
for (const call of choice?.delta?.tool_calls ?? []) {
|
|
1734
|
+
const index = call.index ?? 0;
|
|
1735
|
+
if (!openTools.has(index)) {
|
|
1736
|
+
openTools.add(index);
|
|
1737
|
+
hadToolCalls = true;
|
|
1738
|
+
yield { type: "tool_call_start", index, id: call.id ?? "", name: call.function?.name ?? "" };
|
|
1739
|
+
}
|
|
1740
|
+
if (call.function?.arguments !== undefined && call.function.arguments !== "") {
|
|
1741
|
+
yield { type: "tool_call_delta", index, argumentsTextDelta: call.function.arguments };
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
if (choice?.finish_reason !== undefined && choice.finish_reason !== null) {
|
|
1745
|
+
finishReason = choice.finish_reason;
|
|
1746
|
+
}
|
|
1747
|
+
if (chunk.usage) {
|
|
1748
|
+
usage = {
|
|
1749
|
+
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
1750
|
+
outputTokens: chunk.usage.completion_tokens ?? 0,
|
|
1751
|
+
totalTokens: chunk.usage.total_tokens ?? (chunk.usage.prompt_tokens ?? 0) + (chunk.usage.completion_tokens ?? 0),
|
|
1752
|
+
...chunk.usage.completion_tokens_details?.reasoning_tokens !== undefined ? { reasoningTokens: chunk.usage.completion_tokens_details.reasoning_tokens } : {},
|
|
1753
|
+
...chunk.usage.prompt_tokens_details?.cached_tokens !== undefined ? { cachedInputTokens: chunk.usage.prompt_tokens_details.cached_tokens } : {}
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
for (const index of openTools)
|
|
1758
|
+
yield { type: "tool_call_end", index };
|
|
1759
|
+
yield {
|
|
1760
|
+
type: "finish",
|
|
1761
|
+
finishReason: mapChatFinish(finishReason, hadToolCalls),
|
|
1762
|
+
...usage !== undefined ? { usage } : {}
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// src/providers/openai-compatible/index.ts
|
|
1767
|
+
var DEFAULT_CAPABILITIES = {
|
|
1768
|
+
tools: true,
|
|
1769
|
+
streaming: true,
|
|
1770
|
+
multimodalInput: true,
|
|
1771
|
+
parallelToolCalls: true,
|
|
1772
|
+
structuredOutput: true
|
|
1773
|
+
};
|
|
1774
|
+
function createOpenAICompatible(opts) {
|
|
1775
|
+
const headers = { ...opts.defaultHeaders };
|
|
1776
|
+
if (opts.apiKey !== undefined)
|
|
1777
|
+
headers["authorization"] = `Bearer ${resolveSecret(opts.apiKey)}`;
|
|
1778
|
+
return createProvider({
|
|
1779
|
+
name: opts.name ?? "openai-compatible",
|
|
1780
|
+
defaultModel: opts.model,
|
|
1781
|
+
capabilities: { ...DEFAULT_CAPABILITIES, ...opts.capabilities },
|
|
1782
|
+
http: {
|
|
1783
|
+
baseUrl: opts.baseUrl,
|
|
1784
|
+
headers,
|
|
1785
|
+
...opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {},
|
|
1786
|
+
...opts.resilience !== undefined ? { resilience: opts.resilience } : {},
|
|
1787
|
+
...opts.fetch !== undefined ? { fetch: opts.fetch } : {}
|
|
1788
|
+
},
|
|
1789
|
+
completePath: () => "chat/completions",
|
|
1790
|
+
streamPath: () => "chat/completions",
|
|
1791
|
+
buildBody: buildChatBody,
|
|
1792
|
+
parseResponse: parseChatResponse,
|
|
1793
|
+
translateStream: translateChatStream
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
export { DEFAULT_TIMEOUT_MS, defaultProviderResilience, createProviderHttp, openSseStream, toProviderError, parseSse, createProvider, createOpenAI, createAnthropic, createGoogle, createOpenAICompatible };
|