@byfriends/kosong 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,801 @@
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 };