@byfriends/kosong 0.1.0 → 0.2.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/dist/{anthropic-Dm_GqFgS.d.mts → anthropic-0CVG5rBE.d.mts} +1 -2
- package/dist/{provider-DiJKWMsQ.d.mts → errors-wlT14tC4.d.mts} +227 -2
- package/dist/{google-genai-hX0X6CF3.d.mts → google-genai-xKK8lI_R.d.mts} +1 -2
- package/dist/index.d.mts +66 -12
- package/dist/index.mjs +945 -21
- package/dist/{openai-common-08qin3UI.mjs → openai-common-Dl42y_vn.mjs} +27 -2
- package/dist/{openai-common-B6cK2ig3.d.mts → openai-common-DwkxUSyI.d.mts} +7 -3
- package/dist/{openai-responses-BxOwxtd3.d.mts → openai-responses-DZ9mQ5RA.d.mts} +2 -2
- package/dist/providers/anthropic.d.mts +1 -1
- package/dist/providers/anthropic.mjs +56 -68
- package/dist/providers/google-genai.d.mts +1 -1
- package/dist/providers/google-genai.mjs +1 -2
- package/dist/providers/openai-common.d.mts +1 -1
- package/dist/providers/openai-common.mjs +1 -1
- package/dist/providers/openai-responses.d.mts +1 -1
- package/dist/providers/openai-responses.mjs +21 -4
- package/dist/request-auth-BMXt8jRu.mjs +341 -0
- package/package.json +1 -11
- package/dist/capability-registry-CMBuEYcf.mjs +0 -161
- package/dist/chat-completions-stream-BuMu_xr9.mjs +0 -62
- package/dist/errors-DweKbIOf.d.mts +0 -42
- package/dist/openai-compat-CMrIk-ib.d.mts +0 -132
- package/dist/openai-compat-CWbwO4b7.mjs +0 -801
- package/dist/openai-legacy-B6CVfLlr.d.mts +0 -71
- package/dist/providers/openai-compat.d.mts +0 -2
- package/dist/providers/openai-compat.mjs +0 -2
- package/dist/providers/openai-legacy.d.mts +0 -2
- package/dist/providers/openai-legacy.mjs +0 -248
- package/dist/request-auth-DCWSyCKI.mjs +0 -63
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
import { a as isFunctionToolCall, i as extractUsage, l as toolToOpenAI, n as convertOpenAIError, o as normalizeOpenAIFinishReason, s as reasoningEffortToThinkingEffort, t as convertContentPart } from "./openai-common-08qin3UI.mjs";
|
|
2
|
-
import { o as ChatProviderError } from "./errors-WFxxzL1B.mjs";
|
|
3
|
-
import { i as UNKNOWN_CAPABILITY, n as requireProviderApiKey, r as resolveAuthBackedClient, t as mergeRequestHeaders } from "./request-auth-DCWSyCKI.mjs";
|
|
4
|
-
import { t as convertChatCompletionStreamToolCall } from "./chat-completions-stream-BuMu_xr9.mjs";
|
|
5
|
-
import OpenAI from "openai";
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
|
-
import * as path from "node:path";
|
|
8
|
-
//#region src/providers/openai-compat-schema.ts
|
|
9
|
-
/**
|
|
10
|
-
* Dereference all `$ref` references in a JSON Schema by inlining definitions
|
|
11
|
-
* from local JSON pointers such as `$defs` and draft-7 `definitions`. Resolved
|
|
12
|
-
* top-level definition buckets are removed from the result.
|
|
13
|
-
*
|
|
14
|
-
* Circular references are detected and left as `$ref` to avoid infinite
|
|
15
|
-
* recursion; in that case the referenced definition bucket is preserved so the
|
|
16
|
-
* remaining local `$ref` pointers stay resolvable to a JSON Schema validator.
|
|
17
|
-
*/
|
|
18
|
-
function derefJsonSchema(schema) {
|
|
19
|
-
const result = resolveNode(schema, schema, /* @__PURE__ */ new Set());
|
|
20
|
-
if (!hasUnresolvedDefinitionRef(result, "$defs")) delete result["$defs"];
|
|
21
|
-
if (!hasUnresolvedDefinitionRef(result, "definitions")) delete result["definitions"];
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
const TYPE_COMPLETION_SKIP_KEYS = new Set([
|
|
25
|
-
"$ref",
|
|
26
|
-
"allOf",
|
|
27
|
-
"anyOf",
|
|
28
|
-
"else",
|
|
29
|
-
"if",
|
|
30
|
-
"not",
|
|
31
|
-
"oneOf",
|
|
32
|
-
"then"
|
|
33
|
-
]);
|
|
34
|
-
const CHILD_SCHEMA_SLOTS = [
|
|
35
|
-
{
|
|
36
|
-
key: "$defs",
|
|
37
|
-
kind: "map"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
key: "definitions",
|
|
41
|
-
kind: "map"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
key: "dependencies",
|
|
45
|
-
kind: "map",
|
|
46
|
-
parentType: "object"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
key: "dependentSchemas",
|
|
50
|
-
kind: "map",
|
|
51
|
-
parentType: "object"
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
key: "patternProperties",
|
|
55
|
-
kind: "map",
|
|
56
|
-
parentType: "object"
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
key: "properties",
|
|
60
|
-
kind: "map",
|
|
61
|
-
parentType: "object"
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
key: "additionalItems",
|
|
65
|
-
kind: "single",
|
|
66
|
-
parentType: "array"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
key: "additionalProperties",
|
|
70
|
-
kind: "single",
|
|
71
|
-
parentType: "object"
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
key: "contains",
|
|
75
|
-
kind: "single",
|
|
76
|
-
parentType: "array"
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
key: "contentSchema",
|
|
80
|
-
kind: "single",
|
|
81
|
-
parentType: "string"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
key: "else",
|
|
85
|
-
kind: "single"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
key: "if",
|
|
89
|
-
kind: "single"
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
key: "not",
|
|
93
|
-
kind: "single"
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
key: "propertyNames",
|
|
97
|
-
kind: "single",
|
|
98
|
-
parentType: "object"
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
key: "then",
|
|
102
|
-
kind: "single"
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
key: "unevaluatedItems",
|
|
106
|
-
kind: "single",
|
|
107
|
-
parentType: "array"
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
key: "unevaluatedProperties",
|
|
111
|
-
kind: "single",
|
|
112
|
-
parentType: "object"
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
key: "allOf",
|
|
116
|
-
kind: "array"
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
key: "anyOf",
|
|
120
|
-
kind: "array"
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
key: "oneOf",
|
|
124
|
-
kind: "array"
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
key: "prefixItems",
|
|
128
|
-
kind: "array",
|
|
129
|
-
parentType: "array"
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
key: "items",
|
|
133
|
-
kind: "schema-or-array",
|
|
134
|
-
parentType: "array"
|
|
135
|
-
}
|
|
136
|
-
];
|
|
137
|
-
const OBJECT_STRUCTURE_KEYS = new Set([
|
|
138
|
-
...childSchemaKeysForParentType("object"),
|
|
139
|
-
"dependentRequired",
|
|
140
|
-
"maxProperties",
|
|
141
|
-
"minProperties",
|
|
142
|
-
"required"
|
|
143
|
-
]);
|
|
144
|
-
const ARRAY_STRUCTURE_KEYS = new Set([
|
|
145
|
-
...childSchemaKeysForParentType("array"),
|
|
146
|
-
"maxContains",
|
|
147
|
-
"maxItems",
|
|
148
|
-
"minContains",
|
|
149
|
-
"minItems",
|
|
150
|
-
"uniqueItems"
|
|
151
|
-
]);
|
|
152
|
-
const STRING_STRUCTURE_KEYS = new Set([
|
|
153
|
-
...childSchemaKeysForParentType("string"),
|
|
154
|
-
"contentEncoding",
|
|
155
|
-
"contentMediaType",
|
|
156
|
-
"format",
|
|
157
|
-
"maxLength",
|
|
158
|
-
"minLength",
|
|
159
|
-
"pattern"
|
|
160
|
-
]);
|
|
161
|
-
const NUMERIC_STRUCTURE_KEYS = new Set([
|
|
162
|
-
"exclusiveMaximum",
|
|
163
|
-
"exclusiveMinimum",
|
|
164
|
-
"maximum",
|
|
165
|
-
"minimum",
|
|
166
|
-
"multipleOf"
|
|
167
|
-
]);
|
|
168
|
-
/**
|
|
169
|
-
* Return a deep-cloned JSON Schema with missing `type` fields filled in for
|
|
170
|
-
* OpenAI-compatible tool compatibility.
|
|
171
|
-
*
|
|
172
|
-
* The tool validator rejects some valid JSON Schema shapes when nested
|
|
173
|
-
* property schemas omit `type` (for example enum-only MCP properties). This is
|
|
174
|
-
* a provider-compatibility normalizer, not a complete JSON Schema compiler:
|
|
175
|
-
* it resolves local refs, preserves combinator nodes, infers obvious
|
|
176
|
-
* scalar/object/array types, and falls back to `string` only for nested
|
|
177
|
-
* typeless property schemas. The root schema object is treated as a container
|
|
178
|
-
* and is not itself normalized.
|
|
179
|
-
*/
|
|
180
|
-
function normalizeOpenAICompatToolSchema(schema) {
|
|
181
|
-
return ensureOpenAICompatPropertyTypes(derefJsonSchema(schema));
|
|
182
|
-
}
|
|
183
|
-
function ensureOpenAICompatPropertyTypes(schema) {
|
|
184
|
-
const normalized = cloneJsonValue(schema);
|
|
185
|
-
if (!isRecord(normalized)) throw new Error("JSON Schema root must normalize to an object.");
|
|
186
|
-
recurseSchema(normalized);
|
|
187
|
-
return normalized;
|
|
188
|
-
}
|
|
189
|
-
function hasUnresolvedDefinitionRef(node, bucketKey) {
|
|
190
|
-
if (Array.isArray(node)) return node.some((child) => hasUnresolvedDefinitionRef(child, bucketKey));
|
|
191
|
-
if (typeof node === "object" && node !== null) {
|
|
192
|
-
const obj = node;
|
|
193
|
-
const ref = obj["$ref"];
|
|
194
|
-
if (typeof ref === "string" && ref.startsWith(`#/${bucketKey}/`)) return true;
|
|
195
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
196
|
-
if (key === bucketKey) continue;
|
|
197
|
-
if (hasUnresolvedDefinitionRef(value, bucketKey)) return true;
|
|
198
|
-
}
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
function resolveNode(node, root, visited) {
|
|
204
|
-
if (Array.isArray(node)) return node.map((item) => resolveNode(item, root, visited));
|
|
205
|
-
if (typeof node === "object" && node !== null) {
|
|
206
|
-
const obj = node;
|
|
207
|
-
if (typeof obj["$ref"] === "string") {
|
|
208
|
-
const ref = obj["$ref"];
|
|
209
|
-
if (isLocalJsonPointerRef(ref)) {
|
|
210
|
-
if (visited.has(ref)) return obj;
|
|
211
|
-
const resolvedRef = resolveLocalJsonPointer(root, ref);
|
|
212
|
-
if (resolvedRef.found) {
|
|
213
|
-
visited.add(ref);
|
|
214
|
-
const resolved = resolveNode(resolvedRef.value, root, visited);
|
|
215
|
-
visited.delete(ref);
|
|
216
|
-
if (typeof resolved === "object" && resolved !== null && !Array.isArray(resolved)) {
|
|
217
|
-
const merged = { ...resolved };
|
|
218
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
219
|
-
if (key === "$ref") continue;
|
|
220
|
-
merged[key] = resolveNode(value, root, visited);
|
|
221
|
-
}
|
|
222
|
-
return merged;
|
|
223
|
-
}
|
|
224
|
-
return resolved;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return obj;
|
|
228
|
-
}
|
|
229
|
-
const resolved = {};
|
|
230
|
-
for (const [key, value] of Object.entries(obj)) resolved[key] = resolveNode(value, root, visited);
|
|
231
|
-
return resolved;
|
|
232
|
-
}
|
|
233
|
-
return node;
|
|
234
|
-
}
|
|
235
|
-
function isLocalJsonPointerRef(ref) {
|
|
236
|
-
return ref === "#" || ref.startsWith("#/");
|
|
237
|
-
}
|
|
238
|
-
function resolveLocalJsonPointer(root, ref) {
|
|
239
|
-
if (ref === "#") return {
|
|
240
|
-
found: true,
|
|
241
|
-
value: root
|
|
242
|
-
};
|
|
243
|
-
let current = root;
|
|
244
|
-
for (const rawPart of ref.slice(2).split("/")) {
|
|
245
|
-
const part = unescapeJsonPointerPart(rawPart);
|
|
246
|
-
if (isRecord(current)) {
|
|
247
|
-
if (!hasOwn(current, part)) return { found: false };
|
|
248
|
-
current = current[part];
|
|
249
|
-
} else if (Array.isArray(current)) {
|
|
250
|
-
const index = parseJsonPointerArrayIndex(part);
|
|
251
|
-
if (index === null || index >= current.length) return { found: false };
|
|
252
|
-
current = current[index];
|
|
253
|
-
} else return { found: false };
|
|
254
|
-
}
|
|
255
|
-
return {
|
|
256
|
-
found: true,
|
|
257
|
-
value: current
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
function unescapeJsonPointerPart(part) {
|
|
261
|
-
return part.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
262
|
-
}
|
|
263
|
-
function parseJsonPointerArrayIndex(part) {
|
|
264
|
-
if (!/^(0|[1-9]\d*)$/.test(part)) return null;
|
|
265
|
-
return Number(part);
|
|
266
|
-
}
|
|
267
|
-
function recurseSchema(node) {
|
|
268
|
-
if (!isRecord(node)) return;
|
|
269
|
-
visitChildSchemas(node, normalizeProperty);
|
|
270
|
-
}
|
|
271
|
-
function visitChildSchemas(node, visit) {
|
|
272
|
-
for (const { key, kind } of CHILD_SCHEMA_SLOTS) {
|
|
273
|
-
const value = node[key];
|
|
274
|
-
if (kind === "single") {
|
|
275
|
-
if (isRecord(value)) visit(value);
|
|
276
|
-
} else if (kind === "array") {
|
|
277
|
-
if (Array.isArray(value)) for (const item of value) visit(item);
|
|
278
|
-
} else if (kind === "map") {
|
|
279
|
-
if (isRecord(value)) for (const item of Object.values(value)) visit(item);
|
|
280
|
-
} else if (kind === "schema-or-array") {
|
|
281
|
-
if (isRecord(value)) visit(value);
|
|
282
|
-
else if (Array.isArray(value)) for (const item of value) visit(item);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
function childSchemaKeysForParentType(parentType) {
|
|
287
|
-
return CHILD_SCHEMA_SLOTS.flatMap((slot) => {
|
|
288
|
-
if (!("parentType" in slot) || slot.parentType !== parentType) return [];
|
|
289
|
-
return [slot.key];
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
function normalizeProperty(node) {
|
|
293
|
-
if (!isRecord(node)) return;
|
|
294
|
-
if (!hasOwn(node, "type") && !hasAnyKey(node, TYPE_COMPLETION_SKIP_KEYS)) {
|
|
295
|
-
const enumValues = node["enum"];
|
|
296
|
-
if (Array.isArray(enumValues) && enumValues.length > 0) node["type"] = inferTypeFromValues(enumValues);
|
|
297
|
-
else if (hasOwn(node, "const")) node["type"] = inferTypeFromValues([node["const"]]);
|
|
298
|
-
else node["type"] = inferTypeFromStructure(node);
|
|
299
|
-
}
|
|
300
|
-
recurseSchema(node);
|
|
301
|
-
}
|
|
302
|
-
function inferTypeFromStructure(schema) {
|
|
303
|
-
if (hasAnyKey(schema, OBJECT_STRUCTURE_KEYS)) return "object";
|
|
304
|
-
if (hasAnyKey(schema, ARRAY_STRUCTURE_KEYS)) return "array";
|
|
305
|
-
if (hasAnyKey(schema, STRING_STRUCTURE_KEYS)) return "string";
|
|
306
|
-
if (hasAnyKey(schema, NUMERIC_STRUCTURE_KEYS)) return "number";
|
|
307
|
-
return "string";
|
|
308
|
-
}
|
|
309
|
-
function inferTypeFromValues(values) {
|
|
310
|
-
const inferred = /* @__PURE__ */ new Set();
|
|
311
|
-
for (const value of values) {
|
|
312
|
-
const valueType = inferValueType(value);
|
|
313
|
-
if (valueType === void 0) throw new Error("Cannot infer JSON Schema type from non-JSON enum or const value.");
|
|
314
|
-
inferred.add(valueType);
|
|
315
|
-
}
|
|
316
|
-
const types = normalizeInferredTypes(inferred);
|
|
317
|
-
if (types.length === 1) {
|
|
318
|
-
const onlyType = types[0];
|
|
319
|
-
if (onlyType === void 0) throw new Error("Cannot infer JSON Schema type from an empty enum.");
|
|
320
|
-
return onlyType;
|
|
321
|
-
}
|
|
322
|
-
throw new Error("Mixed JSON Schema enum or const types are not supported by OpenAI-compatible tool schemas.");
|
|
323
|
-
}
|
|
324
|
-
function inferValueType(value) {
|
|
325
|
-
if (value === null) return "null";
|
|
326
|
-
if (Array.isArray(value)) return "array";
|
|
327
|
-
switch (typeof value) {
|
|
328
|
-
case "string": return "string";
|
|
329
|
-
case "number": return Number.isInteger(value) ? "integer" : "number";
|
|
330
|
-
case "boolean": return "boolean";
|
|
331
|
-
case "object": return "object";
|
|
332
|
-
case "bigint":
|
|
333
|
-
case "function":
|
|
334
|
-
case "symbol":
|
|
335
|
-
case "undefined": return;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
function normalizeInferredTypes(types) {
|
|
339
|
-
const normalized = new Set(types);
|
|
340
|
-
if (normalized.has("number")) normalized.delete("integer");
|
|
341
|
-
return [
|
|
342
|
-
"string",
|
|
343
|
-
"number",
|
|
344
|
-
"integer",
|
|
345
|
-
"boolean",
|
|
346
|
-
"object",
|
|
347
|
-
"array",
|
|
348
|
-
"null"
|
|
349
|
-
].filter((type) => normalized.has(type));
|
|
350
|
-
}
|
|
351
|
-
function hasAnyKey(obj, keys) {
|
|
352
|
-
for (const key of keys) if (hasOwn(obj, key)) return true;
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
function cloneJsonValue(value) {
|
|
356
|
-
if (Array.isArray(value)) return value.map((item) => cloneJsonValue(item));
|
|
357
|
-
if (isRecord(value)) {
|
|
358
|
-
const cloned = {};
|
|
359
|
-
for (const [key, child] of Object.entries(value)) cloned[key] = cloneJsonValue(child);
|
|
360
|
-
return cloned;
|
|
361
|
-
}
|
|
362
|
-
return value;
|
|
363
|
-
}
|
|
364
|
-
function isRecord(value) {
|
|
365
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
366
|
-
}
|
|
367
|
-
function hasOwn(obj, key) {
|
|
368
|
-
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
369
|
-
}
|
|
370
|
-
//#endregion
|
|
371
|
-
//#region src/providers/openai-compat-files.ts
|
|
372
|
-
/**
|
|
373
|
-
* OpenAI-compatible file upload client.
|
|
374
|
-
*
|
|
375
|
-
* Wraps the underlying OpenAI-compatible `files.create` API to upload videos
|
|
376
|
-
* to the file service and return them as {@link VideoURLPart} values
|
|
377
|
-
* suitable for use in chat messages.
|
|
378
|
-
*
|
|
379
|
-
* An `OpenAICompatFiles` instance is typically obtained from
|
|
380
|
-
* {@link OpenAICompatChatProvider.files}.
|
|
381
|
-
*/
|
|
382
|
-
var OpenAICompatFiles = class {
|
|
383
|
-
_apiKey;
|
|
384
|
-
_baseUrl;
|
|
385
|
-
_defaultHeaders;
|
|
386
|
-
_client;
|
|
387
|
-
_clientFactory;
|
|
388
|
-
constructor(options) {
|
|
389
|
-
this._apiKey = options.apiKey;
|
|
390
|
-
this._baseUrl = options.baseUrl;
|
|
391
|
-
this._defaultHeaders = options.defaultHeaders;
|
|
392
|
-
this._clientFactory = options.clientFactory;
|
|
393
|
-
this._client = options.apiKey === void 0 || options.apiKey.length === 0 ? void 0 : new OpenAI({
|
|
394
|
-
apiKey: options.apiKey,
|
|
395
|
-
baseURL: options.baseUrl,
|
|
396
|
-
defaultHeaders: options.defaultHeaders
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Upload a video file for use in chat messages.
|
|
401
|
-
*
|
|
402
|
-
* Accepts either a local filesystem path or an in-memory
|
|
403
|
-
* {@link VideoUploadInput}. Returns a {@link VideoURLPart} referencing the
|
|
404
|
-
* uploaded file by its file id.
|
|
405
|
-
*
|
|
406
|
-
* @param input - Local path string or `{ data, mimeType }` object.
|
|
407
|
-
* @returns A `VideoURLPart` whose `url` references the uploaded file
|
|
408
|
-
* by its file id (e.g. `ms://<file-id>`).
|
|
409
|
-
* @throws {ChatProviderError} if the input is not a video or the upload
|
|
410
|
-
* fails.
|
|
411
|
-
*/
|
|
412
|
-
async uploadVideo(input, options) {
|
|
413
|
-
let file;
|
|
414
|
-
if (typeof input === "string") {
|
|
415
|
-
if (!fs.existsSync(input)) throw new ChatProviderError(`Video file not found: ${input}`);
|
|
416
|
-
const filename = path.basename(input);
|
|
417
|
-
const mimeType = guessMimeTypeFromExt(filename);
|
|
418
|
-
if (mimeType === void 0 || !mimeType.startsWith("video/")) throw new ChatProviderError(`OpenAICompatFiles.uploadVideo: file extension does not indicate a video type: ${filename}`);
|
|
419
|
-
const data = await fs.promises.readFile(input);
|
|
420
|
-
const blob = new Blob([new Uint8Array(data)], { type: mimeType });
|
|
421
|
-
file = new File([blob], filename, { type: mimeType });
|
|
422
|
-
} else {
|
|
423
|
-
if (!input.mimeType.startsWith("video/")) throw new ChatProviderError(`Expected a video mime type, got ${input.mimeType}`);
|
|
424
|
-
const filename = input.filename ?? guessFilename(input.mimeType);
|
|
425
|
-
const bytes = input.data instanceof Uint8Array ? input.data : new Uint8Array(input.data);
|
|
426
|
-
const blob = new Blob([bytes], { type: input.mimeType });
|
|
427
|
-
file = new File([blob], filename, { type: input.mimeType });
|
|
428
|
-
}
|
|
429
|
-
let uploaded;
|
|
430
|
-
try {
|
|
431
|
-
uploaded = await this._createClient(options?.auth).files.create({
|
|
432
|
-
file,
|
|
433
|
-
purpose: "video"
|
|
434
|
-
}, options?.signal ? { signal: options.signal } : void 0);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
throw convertOpenAIError(error);
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
type: "video_url",
|
|
440
|
-
videoUrl: {
|
|
441
|
-
url: `ms://${uploaded.id}`,
|
|
442
|
-
id: uploaded.id
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
_createClient(auth) {
|
|
447
|
-
return resolveAuthBackedClient({
|
|
448
|
-
cachedClient: this._client,
|
|
449
|
-
clientFactory: this._clientFactory
|
|
450
|
-
}, auth, (a) => {
|
|
451
|
-
const defaultHeaders = mergeRequestHeaders(this._defaultHeaders, a?.headers);
|
|
452
|
-
return new OpenAI({
|
|
453
|
-
apiKey: requireProviderApiKey("OpenAICompatFiles.uploadVideo", a, this._apiKey),
|
|
454
|
-
baseURL: this._baseUrl,
|
|
455
|
-
defaultHeaders
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
/**
|
|
461
|
-
* Guess a filename for an upload from a video MIME type.
|
|
462
|
-
* Falls back to `upload.bin` for unknown types.
|
|
463
|
-
*/
|
|
464
|
-
function guessFilename(mimeType) {
|
|
465
|
-
return `upload.${MIME_TO_EXT[mimeType.toLowerCase()] ?? "bin"}`;
|
|
466
|
-
}
|
|
467
|
-
const MIME_TO_EXT = {
|
|
468
|
-
"video/mp4": "mp4",
|
|
469
|
-
"video/mpeg": "mpeg",
|
|
470
|
-
"video/quicktime": "mov",
|
|
471
|
-
"video/webm": "webm",
|
|
472
|
-
"video/x-matroska": "mkv",
|
|
473
|
-
"video/x-msvideo": "avi",
|
|
474
|
-
"video/x-flv": "flv",
|
|
475
|
-
"video/3gpp": "3gp"
|
|
476
|
-
};
|
|
477
|
-
const EXT_TO_MIME = Object.fromEntries(Object.entries(MIME_TO_EXT).map(([mime, ext]) => [ext, mime]));
|
|
478
|
-
/**
|
|
479
|
-
* Guess a MIME type from a filename extension. Only recognises the video
|
|
480
|
-
* types listed in {@link MIME_TO_EXT}; returns `undefined` otherwise.
|
|
481
|
-
*/
|
|
482
|
-
function guessMimeTypeFromExt(filename) {
|
|
483
|
-
const dot = filename.lastIndexOf(".");
|
|
484
|
-
if (dot < 0) return void 0;
|
|
485
|
-
return EXT_TO_MIME[filename.slice(dot + 1).toLowerCase()];
|
|
486
|
-
}
|
|
487
|
-
//#endregion
|
|
488
|
-
//#region src/providers/openai-compat.ts
|
|
489
|
-
function isEffectivelyEmptyContent(parts) {
|
|
490
|
-
for (const part of parts) {
|
|
491
|
-
if (part.type !== "text") return false;
|
|
492
|
-
if (part.text.trim() !== "") return false;
|
|
493
|
-
}
|
|
494
|
-
return true;
|
|
495
|
-
}
|
|
496
|
-
function convertMessage(message) {
|
|
497
|
-
let reasoningContent = "";
|
|
498
|
-
const nonThinkParts = [];
|
|
499
|
-
for (const part of message.content) if (part.type === "think") reasoningContent += part.think;
|
|
500
|
-
else nonThinkParts.push(part);
|
|
501
|
-
const result = { role: message.role };
|
|
502
|
-
const hasToolCalls = message.toolCalls.length > 0;
|
|
503
|
-
if (!(message.role === "assistant" && hasToolCalls && isEffectivelyEmptyContent(nonThinkParts))) {
|
|
504
|
-
const firstPart = nonThinkParts[0];
|
|
505
|
-
if (nonThinkParts.length === 1 && firstPart?.type === "text") result.content = firstPart.text;
|
|
506
|
-
else if (nonThinkParts.length > 0) result.content = nonThinkParts.map((p) => convertContentPart(p)).filter((p) => p !== null);
|
|
507
|
-
}
|
|
508
|
-
if (message.name !== void 0) result.name = message.name;
|
|
509
|
-
if (hasToolCalls) result.tool_calls = message.toolCalls.map((tc) => {
|
|
510
|
-
const mapped = {
|
|
511
|
-
type: tc.type,
|
|
512
|
-
id: tc.id,
|
|
513
|
-
function: {
|
|
514
|
-
name: tc.name,
|
|
515
|
-
arguments: tc.arguments
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
if (tc.extras !== void 0) mapped.extras = tc.extras;
|
|
519
|
-
return mapped;
|
|
520
|
-
});
|
|
521
|
-
if (message.toolCallId !== void 0) result.tool_call_id = message.toolCallId;
|
|
522
|
-
if (reasoningContent) result.reasoning_content = reasoningContent;
|
|
523
|
-
return result;
|
|
524
|
-
}
|
|
525
|
-
function convertTool(tool) {
|
|
526
|
-
if (tool.name.startsWith("$")) return {
|
|
527
|
-
type: "builtin_function",
|
|
528
|
-
function: { name: tool.name }
|
|
529
|
-
};
|
|
530
|
-
const converted = toolToOpenAI(tool);
|
|
531
|
-
return {
|
|
532
|
-
...converted,
|
|
533
|
-
function: {
|
|
534
|
-
...converted.function,
|
|
535
|
-
parameters: normalizeOpenAICompatToolSchema(tool.parameters)
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Extract usage from a streaming chunk. Some OpenAI-compatible providers may place usage in
|
|
541
|
-
* `choices[0].usage` in addition to the top-level `usage` field.
|
|
542
|
-
*/
|
|
543
|
-
function extractUsageFromChunk(chunk) {
|
|
544
|
-
if (chunk["usage"] !== null && chunk["usage"] !== void 0 && typeof chunk["usage"] === "object") return chunk["usage"];
|
|
545
|
-
const choices = chunk["choices"];
|
|
546
|
-
if (!Array.isArray(choices) || choices.length === 0) return null;
|
|
547
|
-
const firstChoice = choices[0];
|
|
548
|
-
if (firstChoice === void 0) return null;
|
|
549
|
-
const choiceUsage = firstChoice["usage"];
|
|
550
|
-
if (choiceUsage !== null && choiceUsage !== void 0 && typeof choiceUsage === "object") return choiceUsage;
|
|
551
|
-
return null;
|
|
552
|
-
}
|
|
553
|
-
var OpenAICompatStreamedMessage = class {
|
|
554
|
-
_id = null;
|
|
555
|
-
_usage = null;
|
|
556
|
-
_finishReason = null;
|
|
557
|
-
_rawFinishReason = null;
|
|
558
|
-
_iter;
|
|
559
|
-
constructor(response, isStream) {
|
|
560
|
-
if (isStream) this._iter = this._convertStreamResponse(response);
|
|
561
|
-
else this._iter = this._convertNonStreamResponse(response);
|
|
562
|
-
}
|
|
563
|
-
get id() {
|
|
564
|
-
return this._id;
|
|
565
|
-
}
|
|
566
|
-
get usage() {
|
|
567
|
-
return this._usage;
|
|
568
|
-
}
|
|
569
|
-
get finishReason() {
|
|
570
|
-
return this._finishReason;
|
|
571
|
-
}
|
|
572
|
-
get rawFinishReason() {
|
|
573
|
-
return this._rawFinishReason;
|
|
574
|
-
}
|
|
575
|
-
async *[Symbol.asyncIterator]() {
|
|
576
|
-
yield* this._iter;
|
|
577
|
-
}
|
|
578
|
-
_captureFinishReason(raw) {
|
|
579
|
-
const normalized = normalizeOpenAIFinishReason(raw);
|
|
580
|
-
this._finishReason = normalized.finishReason;
|
|
581
|
-
this._rawFinishReason = normalized.rawFinishReason;
|
|
582
|
-
}
|
|
583
|
-
async *_convertNonStreamResponse(response) {
|
|
584
|
-
this._id = response.id;
|
|
585
|
-
if (response.usage) this._usage = extractUsage(response.usage) ?? null;
|
|
586
|
-
this._captureFinishReason(response.choices[0]?.finish_reason ?? null);
|
|
587
|
-
const message = response.choices[0]?.message;
|
|
588
|
-
if (!message) return;
|
|
589
|
-
const rc = message["reasoning_content"];
|
|
590
|
-
if (typeof rc === "string" && rc) yield {
|
|
591
|
-
type: "think",
|
|
592
|
-
think: rc
|
|
593
|
-
};
|
|
594
|
-
if (message.content) yield {
|
|
595
|
-
type: "text",
|
|
596
|
-
text: message.content
|
|
597
|
-
};
|
|
598
|
-
if (message.tool_calls) for (const toolCall of message.tool_calls) {
|
|
599
|
-
if (!isFunctionToolCall(toolCall)) continue;
|
|
600
|
-
yield {
|
|
601
|
-
type: "function",
|
|
602
|
-
id: toolCall.id || crypto.randomUUID(),
|
|
603
|
-
name: toolCall.function.name,
|
|
604
|
-
arguments: toolCall.function.arguments
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
async *_convertStreamResponse(response) {
|
|
609
|
-
const bufferedToolCalls = /* @__PURE__ */ new Map();
|
|
610
|
-
try {
|
|
611
|
-
for await (const chunk of response) {
|
|
612
|
-
if (chunk.id) this._id = chunk.id;
|
|
613
|
-
const rawUsage = extractUsageFromChunk(chunk);
|
|
614
|
-
if (rawUsage) this._usage = extractUsage(rawUsage) ?? null;
|
|
615
|
-
if (!chunk.choices || chunk.choices.length === 0) continue;
|
|
616
|
-
const choice = chunk.choices[0];
|
|
617
|
-
if (!choice) continue;
|
|
618
|
-
if (choice.finish_reason !== null && choice.finish_reason !== void 0) this._captureFinishReason(choice.finish_reason);
|
|
619
|
-
const delta = choice.delta;
|
|
620
|
-
const rc = delta["reasoning_content"];
|
|
621
|
-
if (typeof rc === "string" && rc) yield {
|
|
622
|
-
type: "think",
|
|
623
|
-
think: rc
|
|
624
|
-
};
|
|
625
|
-
if (delta.content) yield {
|
|
626
|
-
type: "text",
|
|
627
|
-
text: delta.content
|
|
628
|
-
};
|
|
629
|
-
for (const toolCall of delta.tool_calls ?? []) for (const part of convertChatCompletionStreamToolCall(toolCall, bufferedToolCalls)) yield part;
|
|
630
|
-
}
|
|
631
|
-
} catch (error) {
|
|
632
|
-
throw convertOpenAIError(error);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
var OpenAICompatChatProvider = class {
|
|
637
|
-
name = "openai-compat";
|
|
638
|
-
_model;
|
|
639
|
-
_stream;
|
|
640
|
-
_apiKey;
|
|
641
|
-
_baseUrl;
|
|
642
|
-
_defaultHeaders;
|
|
643
|
-
_generationKwargs;
|
|
644
|
-
_thinkingEffortKey;
|
|
645
|
-
_client;
|
|
646
|
-
_clientFactory;
|
|
647
|
-
_files;
|
|
648
|
-
constructor(options) {
|
|
649
|
-
const apiKey = options.apiKey;
|
|
650
|
-
this._apiKey = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
|
|
651
|
-
this._baseUrl = options.baseUrl ?? process.env["BYF_BASE_URL"] ?? "";
|
|
652
|
-
this._defaultHeaders = options.defaultHeaders;
|
|
653
|
-
this._clientFactory = options.clientFactory;
|
|
654
|
-
this._model = options.model;
|
|
655
|
-
this._stream = options.stream ?? true;
|
|
656
|
-
const normalizedThinkingEffortKey = options.thinkingEffortKey?.trim();
|
|
657
|
-
this._thinkingEffortKey = normalizedThinkingEffortKey !== void 0 && normalizedThinkingEffortKey.length > 0 ? normalizedThinkingEffortKey : "reasoning_effort";
|
|
658
|
-
this._generationKwargs = { ...options.generationKwargs };
|
|
659
|
-
this._client = this._apiKey === void 0 ? void 0 : new OpenAI({
|
|
660
|
-
apiKey: this._apiKey,
|
|
661
|
-
baseURL: this._baseUrl,
|
|
662
|
-
defaultHeaders: this._defaultHeaders
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
get modelName() {
|
|
666
|
-
return this._model;
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* File upload client for an OpenAI-compatible service.
|
|
670
|
-
*
|
|
671
|
-
* Use this to upload videos (and other media in the future) to the file
|
|
672
|
-
* service and receive a content part that can be embedded in chat
|
|
673
|
-
* messages.
|
|
674
|
-
*/
|
|
675
|
-
get files() {
|
|
676
|
-
this._files ??= new OpenAICompatFiles({
|
|
677
|
-
apiKey: this._apiKey,
|
|
678
|
-
baseUrl: this._baseUrl,
|
|
679
|
-
defaultHeaders: this._defaultHeaders,
|
|
680
|
-
clientFactory: this._clientFactory
|
|
681
|
-
});
|
|
682
|
-
return this._files;
|
|
683
|
-
}
|
|
684
|
-
uploadVideo(input, options) {
|
|
685
|
-
return this.files.uploadVideo(input, options);
|
|
686
|
-
}
|
|
687
|
-
get thinkingEffort() {
|
|
688
|
-
const customValue = this._generationKwargs[this._thinkingEffortKey];
|
|
689
|
-
if (typeof customValue === "string") return reasoningEffortToThinkingEffort(customValue);
|
|
690
|
-
const defaultValue = this._generationKwargs.reasoning_effort;
|
|
691
|
-
return reasoningEffortToThinkingEffort(defaultValue);
|
|
692
|
-
}
|
|
693
|
-
get modelParameters() {
|
|
694
|
-
return {
|
|
695
|
-
model: this._model,
|
|
696
|
-
baseUrl: this._baseUrl,
|
|
697
|
-
...this._generationKwargs
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
async generate(systemPrompt, tools, history, options) {
|
|
701
|
-
const messages = [];
|
|
702
|
-
if (systemPrompt) messages.push({
|
|
703
|
-
role: "system",
|
|
704
|
-
content: systemPrompt
|
|
705
|
-
});
|
|
706
|
-
for (const msg of history) messages.push(convertMessage(msg));
|
|
707
|
-
const kwargs = { ...this._generationKwargs };
|
|
708
|
-
for (const key of Object.keys(kwargs)) if (kwargs[key] === void 0) delete kwargs[key];
|
|
709
|
-
if (kwargs["max_completion_tokens"] === void 0 && kwargs["max_tokens"] !== void 0) kwargs["max_completion_tokens"] = kwargs["max_tokens"];
|
|
710
|
-
delete kwargs["max_tokens"];
|
|
711
|
-
const { extra_body: extraBody, ...requestKwargs } = kwargs;
|
|
712
|
-
const createParams = {
|
|
713
|
-
model: this._model,
|
|
714
|
-
messages,
|
|
715
|
-
stream: this._stream,
|
|
716
|
-
...requestKwargs,
|
|
717
|
-
...extraBody
|
|
718
|
-
};
|
|
719
|
-
if (tools.length > 0) createParams["tools"] = tools.map((t) => convertTool(t));
|
|
720
|
-
if (this._stream) createParams["stream_options"] = { include_usage: true };
|
|
721
|
-
try {
|
|
722
|
-
return new OpenAICompatStreamedMessage(await this._createClient(options?.auth).chat.completions.create(createParams, options?.signal ? { signal: options.signal } : void 0), this._stream);
|
|
723
|
-
} catch (error) {
|
|
724
|
-
throw convertOpenAIError(error);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
getCapability(_model) {
|
|
728
|
-
return UNKNOWN_CAPABILITY;
|
|
729
|
-
}
|
|
730
|
-
withThinking(effort) {
|
|
731
|
-
const thinking = { type: effort === "off" ? "disabled" : "enabled" };
|
|
732
|
-
let reasoningEffort;
|
|
733
|
-
switch (effort) {
|
|
734
|
-
case "off":
|
|
735
|
-
reasoningEffort = void 0;
|
|
736
|
-
break;
|
|
737
|
-
case "low":
|
|
738
|
-
reasoningEffort = "low";
|
|
739
|
-
break;
|
|
740
|
-
case "medium":
|
|
741
|
-
reasoningEffort = "medium";
|
|
742
|
-
break;
|
|
743
|
-
case "high":
|
|
744
|
-
case "xhigh":
|
|
745
|
-
case "max":
|
|
746
|
-
reasoningEffort = "high";
|
|
747
|
-
break;
|
|
748
|
-
}
|
|
749
|
-
const nextEffort = { [this._thinkingEffortKey]: reasoningEffort };
|
|
750
|
-
return this._withGenerationKwargs(nextEffort).withExtraBody({ thinking });
|
|
751
|
-
}
|
|
752
|
-
withGenerationKwargs(kwargs) {
|
|
753
|
-
return this._withGenerationKwargs(kwargs);
|
|
754
|
-
}
|
|
755
|
-
withMaxCompletionTokens(maxCompletionTokens) {
|
|
756
|
-
return this._withGenerationKwargs({ max_completion_tokens: maxCompletionTokens });
|
|
757
|
-
}
|
|
758
|
-
withExtraBody(extraBody) {
|
|
759
|
-
const oldExtra = this._generationKwargs.extra_body ?? {};
|
|
760
|
-
const merged = {
|
|
761
|
-
...oldExtra,
|
|
762
|
-
...extraBody
|
|
763
|
-
};
|
|
764
|
-
const oldThinking = oldExtra.thinking;
|
|
765
|
-
const newThinking = extraBody.thinking;
|
|
766
|
-
if (oldThinking !== void 0 && newThinking !== void 0) merged.thinking = {
|
|
767
|
-
...oldThinking,
|
|
768
|
-
...newThinking
|
|
769
|
-
};
|
|
770
|
-
return this._withGenerationKwargs({ extra_body: merged });
|
|
771
|
-
}
|
|
772
|
-
_createClient(auth) {
|
|
773
|
-
return resolveAuthBackedClient({
|
|
774
|
-
cachedClient: this._client,
|
|
775
|
-
clientFactory: this._clientFactory
|
|
776
|
-
}, auth, (a) => {
|
|
777
|
-
const defaultHeaders = mergeRequestHeaders(this._defaultHeaders, a?.headers);
|
|
778
|
-
return new OpenAI({
|
|
779
|
-
apiKey: requireProviderApiKey("OpenAICompatChatProvider", a, this._apiKey),
|
|
780
|
-
baseURL: this._baseUrl,
|
|
781
|
-
defaultHeaders
|
|
782
|
-
});
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
_withGenerationKwargs(kwargs) {
|
|
786
|
-
const clone = this._clone();
|
|
787
|
-
clone._generationKwargs = {
|
|
788
|
-
...clone._generationKwargs,
|
|
789
|
-
...kwargs
|
|
790
|
-
};
|
|
791
|
-
return clone;
|
|
792
|
-
}
|
|
793
|
-
_clone() {
|
|
794
|
-
const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
|
795
|
-
clone._generationKwargs = { ...this._generationKwargs };
|
|
796
|
-
clone._files = void 0;
|
|
797
|
-
return clone;
|
|
798
|
-
}
|
|
799
|
-
};
|
|
800
|
-
//#endregion
|
|
801
|
-
export { extractUsageFromChunk as n, OpenAICompatChatProvider as t };
|