@just-every/ensemble 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.
Files changed (127) hide show
  1. package/README.md +245 -0
  2. package/dist/cost_tracker.d.ts +2 -0
  3. package/dist/cost_tracker.d.ts.map +1 -0
  4. package/dist/cost_tracker.js +2 -0
  5. package/dist/cost_tracker.js.map +1 -0
  6. package/dist/errors.d.ts +55 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +134 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/external_models.d.ts +10 -0
  11. package/dist/external_models.d.ts.map +1 -0
  12. package/dist/external_models.js +36 -0
  13. package/dist/external_models.js.map +1 -0
  14. package/dist/index.d.ts +31 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +47 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/model_data.d.ts +63 -0
  19. package/dist/model_data.d.ts.map +1 -0
  20. package/dist/model_data.js +1070 -0
  21. package/dist/model_data.js.map +1 -0
  22. package/dist/model_providers/base_provider.d.ts +37 -0
  23. package/dist/model_providers/base_provider.d.ts.map +1 -0
  24. package/dist/model_providers/base_provider.js +146 -0
  25. package/dist/model_providers/base_provider.js.map +1 -0
  26. package/dist/model_providers/claude.d.ts +11 -0
  27. package/dist/model_providers/claude.d.ts.map +1 -0
  28. package/dist/model_providers/claude.js +788 -0
  29. package/dist/model_providers/claude.js.map +1 -0
  30. package/dist/model_providers/deepseek.d.ts +8 -0
  31. package/dist/model_providers/deepseek.d.ts.map +1 -0
  32. package/dist/model_providers/deepseek.js +136 -0
  33. package/dist/model_providers/deepseek.js.map +1 -0
  34. package/dist/model_providers/gemini.d.ts +11 -0
  35. package/dist/model_providers/gemini.d.ts.map +1 -0
  36. package/dist/model_providers/gemini.js +711 -0
  37. package/dist/model_providers/gemini.js.map +1 -0
  38. package/dist/model_providers/grok.d.ts +8 -0
  39. package/dist/model_providers/grok.d.ts.map +1 -0
  40. package/dist/model_providers/grok.js +22 -0
  41. package/dist/model_providers/grok.js.map +1 -0
  42. package/dist/model_providers/model_provider.d.ts +11 -0
  43. package/dist/model_providers/model_provider.d.ts.map +1 -0
  44. package/dist/model_providers/model_provider.js +170 -0
  45. package/dist/model_providers/model_provider.js.map +1 -0
  46. package/dist/model_providers/openai.d.ts +13 -0
  47. package/dist/model_providers/openai.d.ts.map +1 -0
  48. package/dist/model_providers/openai.js +822 -0
  49. package/dist/model_providers/openai.js.map +1 -0
  50. package/dist/model_providers/openai_chat.d.ts +14 -0
  51. package/dist/model_providers/openai_chat.d.ts.map +1 -0
  52. package/dist/model_providers/openai_chat.js +719 -0
  53. package/dist/model_providers/openai_chat.js.map +1 -0
  54. package/dist/model_providers/openrouter.d.ts +6 -0
  55. package/dist/model_providers/openrouter.d.ts.map +1 -0
  56. package/dist/model_providers/openrouter.js +18 -0
  57. package/dist/model_providers/openrouter.js.map +1 -0
  58. package/dist/model_providers/refactored_openai.d.ts +22 -0
  59. package/dist/model_providers/refactored_openai.d.ts.map +1 -0
  60. package/dist/model_providers/refactored_openai.js +310 -0
  61. package/dist/model_providers/refactored_openai.js.map +1 -0
  62. package/dist/model_providers/test_provider.d.ts +27 -0
  63. package/dist/model_providers/test_provider.d.ts.map +1 -0
  64. package/dist/model_providers/test_provider.js +185 -0
  65. package/dist/model_providers/test_provider.js.map +1 -0
  66. package/dist/tsconfig.tsbuildinfo +1 -0
  67. package/dist/types/api_types.d.ts +249 -0
  68. package/dist/types/api_types.d.ts.map +1 -0
  69. package/dist/types/api_types.js +2 -0
  70. package/dist/types/api_types.js.map +1 -0
  71. package/dist/types/extended_types.d.ts +43 -0
  72. package/dist/types/extended_types.d.ts.map +1 -0
  73. package/dist/types/extended_types.js +2 -0
  74. package/dist/types/extended_types.js.map +1 -0
  75. package/dist/types.d.ts +301 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +2 -0
  78. package/dist/types.js.map +1 -0
  79. package/dist/utils/async_queue.d.ts +14 -0
  80. package/dist/utils/async_queue.d.ts.map +1 -0
  81. package/dist/utils/async_queue.js +68 -0
  82. package/dist/utils/async_queue.js.map +1 -0
  83. package/dist/utils/cache.d.ts +60 -0
  84. package/dist/utils/cache.d.ts.map +1 -0
  85. package/dist/utils/cache.js +205 -0
  86. package/dist/utils/cache.js.map +1 -0
  87. package/dist/utils/communication.d.ts +3 -0
  88. package/dist/utils/communication.d.ts.map +1 -0
  89. package/dist/utils/communication.js +8 -0
  90. package/dist/utils/communication.js.map +1 -0
  91. package/dist/utils/cost_tracker.d.ts +26 -0
  92. package/dist/utils/cost_tracker.d.ts.map +1 -0
  93. package/dist/utils/cost_tracker.js +177 -0
  94. package/dist/utils/cost_tracker.js.map +1 -0
  95. package/dist/utils/delta_buffer.d.ts +14 -0
  96. package/dist/utils/delta_buffer.d.ts.map +1 -0
  97. package/dist/utils/delta_buffer.js +60 -0
  98. package/dist/utils/delta_buffer.js.map +1 -0
  99. package/dist/utils/image_to_text.d.ts +3 -0
  100. package/dist/utils/image_to_text.d.ts.map +1 -0
  101. package/dist/utils/image_to_text.js +81 -0
  102. package/dist/utils/image_to_text.js.map +1 -0
  103. package/dist/utils/image_utils.d.ts +18 -0
  104. package/dist/utils/image_utils.d.ts.map +1 -0
  105. package/dist/utils/image_utils.js +132 -0
  106. package/dist/utils/image_utils.js.map +1 -0
  107. package/dist/utils/llm_logger.d.ts +8 -0
  108. package/dist/utils/llm_logger.d.ts.map +1 -0
  109. package/dist/utils/llm_logger.js +24 -0
  110. package/dist/utils/llm_logger.js.map +1 -0
  111. package/dist/utils/quota_tracker.d.ts +22 -0
  112. package/dist/utils/quota_tracker.d.ts.map +1 -0
  113. package/dist/utils/quota_tracker.js +338 -0
  114. package/dist/utils/quota_tracker.js.map +1 -0
  115. package/dist/utils/stream_converter.d.ts +19 -0
  116. package/dist/utils/stream_converter.d.ts.map +1 -0
  117. package/dist/utils/stream_converter.js +172 -0
  118. package/dist/utils/stream_converter.js.map +1 -0
  119. package/dist/validation.d.ts +1789 -0
  120. package/dist/validation.d.ts.map +1 -0
  121. package/dist/validation.js +289 -0
  122. package/dist/validation.js.map +1 -0
  123. package/dist/vitest.config.d.ts +3 -0
  124. package/dist/vitest.config.d.ts.map +1 -0
  125. package/dist/vitest.config.js +34 -0
  126. package/dist/vitest.config.js.map +1 -0
  127. package/package.json +86 -0
@@ -0,0 +1,719 @@
1
+ import OpenAI, { APIError } from 'openai';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { costTracker } from '@just-every/ensemble/cost_tracker';
4
+ import { log_llm_error, log_llm_request, log_llm_response, } from '../utils/llm_logger.js';
5
+ import { extractBase64Image } from '../utils/image_utils.js';
6
+ import { convertImageToTextIfNeeded } from '../utils/image_to_text.js';
7
+ import { bufferDelta, flushBufferedDeltas, } from '../utils/delta_buffer.js';
8
+ const SIMULATED_TOOL_CALL_REGEX = /\n?\s*(?:```(?:json)?\s*)?\s*TOOL_CALLS:\s*(\[.*\])(?:\s*```)?/gs;
9
+ const TOOL_CALL_CLEANUP_REGEX = /\n?\s*(?:```(?:json)?\s*)?\s*TOOL_CALLS:\s*\[.*\](?:\s*```)?/gms;
10
+ const CLEANUP_PLACEHOLDER = '[Simulated Tool Calls Removed]';
11
+ function createCitationTracker() {
12
+ return {
13
+ citations: new Map(),
14
+ last: 0,
15
+ };
16
+ }
17
+ function formatCitation(tracker, citation) {
18
+ if (!tracker.citations.has(citation.url)) {
19
+ tracker.citations.set(citation.url, citation);
20
+ tracker.last++;
21
+ }
22
+ return ` [${Array.from(tracker.citations.keys()).indexOf(citation.url) + 1}]`;
23
+ }
24
+ function generateFootnotes(tracker) {
25
+ if (tracker.citations.size === 0)
26
+ return '';
27
+ const footnotes = Array.from(tracker.citations.values())
28
+ .map((citation, i) => `[${i + 1}] ${citation.title} – ${citation.url}`)
29
+ .join('\n');
30
+ return '\n\nReferences:\n' + footnotes;
31
+ }
32
+ function convertToOpenAITools(tools) {
33
+ return tools.map((tool) => ({
34
+ type: 'function',
35
+ function: {
36
+ name: tool.definition.function.name,
37
+ description: tool.definition.function.description,
38
+ parameters: { ...tool.definition.function.parameters },
39
+ },
40
+ }));
41
+ }
42
+ async function mapMessagesToOpenAI(messages, model) {
43
+ const result = [];
44
+ for (const message of messages) {
45
+ if (message.type === 'function_call_output') {
46
+ if (typeof message.output === 'string') {
47
+ const extracted = extractBase64Image(message.output);
48
+ if (extracted.found && extracted.image_id !== null) {
49
+ const imageId = extracted.image_id;
50
+ const imageData = extracted.images[imageId];
51
+ const processedImageData = await convertImageToTextIfNeeded(imageData, model);
52
+ if (!processedImageData.startsWith('data:image/')) {
53
+ const newContent = extracted.replaceContent.trim()
54
+ ? extracted.replaceContent.trim() +
55
+ ' ' +
56
+ processedImageData
57
+ : processedImageData;
58
+ result.push({
59
+ role: 'tool',
60
+ tool_call_id: message.call_id,
61
+ content: newContent,
62
+ });
63
+ }
64
+ else {
65
+ const image_id = imageId;
66
+ const placeholderOutput = extracted.replaceContent.trim()
67
+ ? `${extracted.replaceContent} [image #${image_id}]`
68
+ : `[image #${image_id}]`;
69
+ result.push({
70
+ role: 'tool',
71
+ tool_call_id: message.call_id,
72
+ content: placeholderOutput,
73
+ });
74
+ result.push({
75
+ role: 'system',
76
+ content: [
77
+ {
78
+ type: 'text',
79
+ text: `This is [image #${image_id}] from function call output`,
80
+ },
81
+ {
82
+ type: 'image_url',
83
+ image_url: {
84
+ url: processedImageData,
85
+ },
86
+ },
87
+ ],
88
+ });
89
+ }
90
+ }
91
+ else {
92
+ result.push({
93
+ role: 'tool',
94
+ tool_call_id: message.call_id,
95
+ content: message.output || '',
96
+ });
97
+ }
98
+ }
99
+ else {
100
+ result.push({
101
+ role: 'tool',
102
+ tool_call_id: message.call_id,
103
+ content: message.output || '',
104
+ });
105
+ }
106
+ }
107
+ else if (message.type === 'function_call') {
108
+ const functionCallMsg = message;
109
+ result.push({
110
+ role: 'assistant',
111
+ tool_calls: [
112
+ {
113
+ id: functionCallMsg.call_id,
114
+ type: 'function',
115
+ function: {
116
+ name: functionCallMsg.name || '',
117
+ arguments: functionCallMsg.arguments || '',
118
+ },
119
+ },
120
+ ],
121
+ });
122
+ }
123
+ else if (!message.type ||
124
+ message.type === 'message' ||
125
+ message.type === 'thinking') {
126
+ let content = '';
127
+ if ('content' in message) {
128
+ if (typeof message.content === 'string') {
129
+ const extracted = extractBase64Image(message.content);
130
+ if (extracted.found && extracted.image_id !== null) {
131
+ const imageId = extracted.image_id;
132
+ const imageData = extracted.images[imageId];
133
+ const processedImageData = await convertImageToTextIfNeeded(imageData, model);
134
+ let role = message.role || 'user';
135
+ if (role === 'developer')
136
+ role = 'system';
137
+ if (role !== 'system' &&
138
+ role !== 'user' &&
139
+ role !== 'assistant')
140
+ role = 'user';
141
+ if (!processedImageData.startsWith('data:image/')) {
142
+ const newContent = extracted.replaceContent.trim()
143
+ ? extracted.replaceContent.trim() +
144
+ ' ' +
145
+ processedImageData
146
+ : processedImageData;
147
+ result.push({
148
+ role: role,
149
+ content: newContent,
150
+ });
151
+ }
152
+ else {
153
+ const image_id = imageId;
154
+ const placeholderContent = extracted.replaceContent.trim()
155
+ ? `${extracted.replaceContent} [image #${image_id}]`
156
+ : `[image #${image_id}]`;
157
+ result.push({
158
+ role: role,
159
+ content: placeholderContent,
160
+ });
161
+ result.push({
162
+ role: 'system',
163
+ content: [
164
+ {
165
+ type: 'text',
166
+ text: `This is [image #${image_id}] from the ${role} message`,
167
+ },
168
+ {
169
+ type: 'image_url',
170
+ image_url: {
171
+ url: processedImageData,
172
+ },
173
+ },
174
+ ],
175
+ });
176
+ }
177
+ continue;
178
+ }
179
+ else {
180
+ content = message.content;
181
+ }
182
+ }
183
+ else if (message.content &&
184
+ typeof message.content === 'object' &&
185
+ 'text' in message.content &&
186
+ typeof message.content.text === 'string') {
187
+ content = message.content.text;
188
+ }
189
+ }
190
+ let role = message.role || 'user';
191
+ if (role === 'developer')
192
+ role = 'system';
193
+ if (role !== 'system' && role !== 'user' && role !== 'assistant')
194
+ role = 'user';
195
+ result.push({
196
+ role: role,
197
+ content: content,
198
+ });
199
+ }
200
+ }
201
+ return result.filter(Boolean);
202
+ }
203
+ export class OpenAIChat {
204
+ client;
205
+ provider;
206
+ baseURL;
207
+ commonParams = {};
208
+ constructor(provider, apiKey, baseURL, defaultHeaders, commonParams) {
209
+ this.provider = provider || 'openai';
210
+ this.baseURL = baseURL;
211
+ this.commonParams = commonParams || {};
212
+ this.client = new OpenAI({
213
+ apiKey: apiKey || process.env.OPENAI_API_KEY,
214
+ baseURL: this.baseURL,
215
+ defaultHeaders: defaultHeaders || {
216
+ 'User-Agent': 'magi',
217
+ },
218
+ });
219
+ if (!this.client.apiKey) {
220
+ throw new Error(`Failed to initialize OpenAI client for ${this.provider}. API key is missing.`);
221
+ }
222
+ }
223
+ prepareParameters(requestParams) {
224
+ return requestParams;
225
+ }
226
+ _parseAndPrepareSimulatedToolCalls(aggregatedContent, messageId) {
227
+ const matches = Array.from(aggregatedContent.matchAll(SIMULATED_TOOL_CALL_REGEX));
228
+ let jsonArrayString = null;
229
+ let matchIndex = -1;
230
+ if (matches.length > 0) {
231
+ const lastMatch = matches[matches.length - 1];
232
+ if (lastMatch && lastMatch[1]) {
233
+ jsonArrayString = lastMatch[1];
234
+ matchIndex = lastMatch.index ?? -1;
235
+ console.log(`(${this.provider}) Found ${matches.length} TOOL_CALLS patterns. Processing the last one.`);
236
+ }
237
+ }
238
+ else {
239
+ if (aggregatedContent.includes('TOOL_CALLS')) {
240
+ console.log(`(${this.provider}) TOOL_CALLS found but regex didn't match globally. Content snippet:`, aggregatedContent.substring(Math.max(0, aggregatedContent.indexOf('TOOL_CALLS') - 20), Math.min(aggregatedContent.length, aggregatedContent.indexOf('TOOL_CALLS') + 300)));
241
+ }
242
+ else {
243
+ console.log(`(${this.provider}) No TOOL_CALLS found in response.`);
244
+ }
245
+ console.debug(`(${this.provider}) Full response content:`, aggregatedContent);
246
+ }
247
+ if (jsonArrayString !== null && matchIndex !== -1) {
248
+ try {
249
+ console.log(`(${this.provider}) Processing last TOOL_CALLS JSON string:`, jsonArrayString);
250
+ let parsedToolCallArray;
251
+ try {
252
+ parsedToolCallArray = JSON.parse(jsonArrayString);
253
+ }
254
+ catch (initialParseError) {
255
+ console.error(`(${this.provider}) Failed initial parse. Error: ${initialParseError}. JSON String: ${jsonArrayString}`);
256
+ throw initialParseError;
257
+ }
258
+ if (!Array.isArray(parsedToolCallArray)) {
259
+ if (typeof parsedToolCallArray === 'object' &&
260
+ parsedToolCallArray !== null) {
261
+ console.log(`(${this.provider}) Parsed JSON is not an array but an object, wrapping in array`);
262
+ parsedToolCallArray = [parsedToolCallArray];
263
+ }
264
+ else {
265
+ throw new Error('Parsed JSON is not an array or object.');
266
+ }
267
+ }
268
+ const validSimulatedCalls = [];
269
+ for (const callData of parsedToolCallArray) {
270
+ console.log(`(${this.provider}) Processing tool call object:`, callData);
271
+ if (callData && typeof callData === 'object') {
272
+ const toolCall = {
273
+ id: callData.id || `sim_${uuidv4()}`,
274
+ type: 'function',
275
+ function: {
276
+ name: '',
277
+ arguments: '{}',
278
+ },
279
+ };
280
+ const funcDetails = callData.function;
281
+ if (typeof funcDetails === 'object' &&
282
+ funcDetails !== null) {
283
+ if (typeof funcDetails.name === 'string') {
284
+ toolCall.function.name = funcDetails.name;
285
+ }
286
+ if (funcDetails.arguments !== undefined) {
287
+ if (typeof funcDetails.arguments === 'string') {
288
+ try {
289
+ JSON.parse(funcDetails.arguments);
290
+ toolCall.function.arguments =
291
+ funcDetails.arguments;
292
+ }
293
+ catch (e) {
294
+ console.warn(`(${this.provider}) Argument string is not valid JSON, wrapping in quotes:`, funcDetails.arguments);
295
+ toolCall.function.arguments =
296
+ JSON.stringify(funcDetails.arguments);
297
+ }
298
+ }
299
+ else {
300
+ toolCall.function.arguments =
301
+ JSON.stringify(funcDetails.arguments);
302
+ }
303
+ }
304
+ }
305
+ else if (typeof callData.name === 'string') {
306
+ toolCall.function.name = callData.name;
307
+ if (callData.arguments !== undefined) {
308
+ if (typeof callData.arguments === 'string') {
309
+ try {
310
+ JSON.parse(callData.arguments);
311
+ toolCall.function.arguments =
312
+ callData.arguments;
313
+ }
314
+ catch (e) {
315
+ console.warn(`(${this.provider}) Argument string is not valid JSON, wrapping in quotes:`, callData.arguments);
316
+ toolCall.function.arguments =
317
+ JSON.stringify(callData.arguments);
318
+ }
319
+ }
320
+ else {
321
+ toolCall.function.arguments =
322
+ JSON.stringify(callData.arguments);
323
+ }
324
+ }
325
+ }
326
+ if (toolCall.function.name &&
327
+ toolCall.function.name.length > 0) {
328
+ validSimulatedCalls.push(toolCall);
329
+ }
330
+ else {
331
+ console.warn(`(${this.provider}) Invalid tool call object, missing name:`, callData);
332
+ }
333
+ }
334
+ else {
335
+ console.warn(`(${this.provider}) Skipping invalid item in tool call array:`, callData);
336
+ }
337
+ }
338
+ console.log(`(${this.provider}) Valid simulated calls extracted:`, validSimulatedCalls);
339
+ if (validSimulatedCalls.length > 0) {
340
+ let textBeforeToolCall = aggregatedContent
341
+ .substring(0, matchIndex)
342
+ .trim();
343
+ textBeforeToolCall = textBeforeToolCall.replaceAll(TOOL_CALL_CLEANUP_REGEX, CLEANUP_PLACEHOLDER);
344
+ const eventsToYield = [];
345
+ if (textBeforeToolCall) {
346
+ eventsToYield.push({
347
+ type: 'message_complete',
348
+ content: textBeforeToolCall,
349
+ message_id: messageId,
350
+ });
351
+ }
352
+ eventsToYield.push({
353
+ type: 'tool_start',
354
+ tool_calls: validSimulatedCalls,
355
+ });
356
+ return { handled: true, eventsToYield };
357
+ }
358
+ else {
359
+ console.warn(`(${this.provider}) Last TOOL_CALLS array found but contained no valid tool call objects after processing.`);
360
+ }
361
+ }
362
+ catch (parseError) {
363
+ console.error(`(${this.provider}) Found last TOOL_CALLS pattern, but failed during processing: ${parseError}. JSON String: ${jsonArrayString}`);
364
+ }
365
+ }
366
+ console.log(`(${this.provider}) No valid tool calls processed from TOOL_CALLS markers.`);
367
+ const cleanedContent = aggregatedContent.replaceAll(TOOL_CALL_CLEANUP_REGEX, CLEANUP_PLACEHOLDER);
368
+ return { handled: false, cleanedContent: cleanedContent };
369
+ }
370
+ async *createResponseStream(model, messages, agent) {
371
+ const toolsPromise = agent ? agent.getTools() : Promise.resolve([]);
372
+ const tools = await toolsPromise;
373
+ const settings = agent?.modelSettings;
374
+ let requestId;
375
+ try {
376
+ const chatMessages = await mapMessagesToOpenAI(messages, model);
377
+ let requestParams = { model, messages: chatMessages, stream: true };
378
+ if (settings?.temperature !== undefined)
379
+ requestParams.temperature = settings.temperature;
380
+ if (settings?.top_p !== undefined)
381
+ requestParams.top_p = settings.top_p;
382
+ if (settings?.max_tokens)
383
+ requestParams.max_tokens = settings.max_tokens;
384
+ if (settings?.tool_choice)
385
+ requestParams.tool_choice =
386
+ settings.tool_choice;
387
+ if (settings?.json_schema) {
388
+ requestParams.response_format = {
389
+ type: 'json_schema',
390
+ json_schema: settings.json_schema,
391
+ };
392
+ }
393
+ if (tools && tools.length > 0)
394
+ requestParams.tools = convertToOpenAITools(tools);
395
+ const overrideParams = { ...this.commonParams };
396
+ const REASONING_EFFORT_CONFIGS = [
397
+ 'low',
398
+ 'medium',
399
+ 'high',
400
+ ];
401
+ for (const effort of REASONING_EFFORT_CONFIGS) {
402
+ const suffix = `-${effort}`;
403
+ if (model.endsWith(suffix)) {
404
+ overrideParams.reasoning = {
405
+ effort: effort,
406
+ };
407
+ model = model.slice(0, -suffix.length);
408
+ requestParams.model = model;
409
+ break;
410
+ }
411
+ }
412
+ requestParams = {
413
+ ...requestParams,
414
+ ...overrideParams,
415
+ };
416
+ requestParams = this.prepareParameters(requestParams);
417
+ requestId = log_llm_request(agent.agent_id, this.provider, model, requestParams);
418
+ const stream = await this.client.chat.completions.create(requestParams);
419
+ let aggregatedContent = '';
420
+ let aggregatedThinking = '';
421
+ const messageId = uuidv4();
422
+ let messageIndex = 0;
423
+ const partialToolCallsByIndex = new Map();
424
+ let finishReason = null;
425
+ let usage = undefined;
426
+ const citationTracker = createCitationTracker();
427
+ const chunks = [];
428
+ try {
429
+ const deltaBuffers = new Map();
430
+ for await (const chunk of stream) {
431
+ chunks.push(chunk);
432
+ const choice = chunk.choices[0];
433
+ if (!choice?.delta)
434
+ continue;
435
+ const delta = choice.delta;
436
+ if (delta.content) {
437
+ aggregatedContent += delta.content;
438
+ for (const ev of bufferDelta(deltaBuffers, messageId, delta.content, content => ({
439
+ type: 'message_delta',
440
+ content,
441
+ message_id: messageId,
442
+ order: messageIndex++,
443
+ }))) {
444
+ yield ev;
445
+ }
446
+ }
447
+ const extendedDelta = delta;
448
+ if (extendedDelta.reasoning) {
449
+ aggregatedContent += extendedDelta.reasoning;
450
+ for (const ev of bufferDelta(deltaBuffers, messageId, extendedDelta.reasoning, content => ({
451
+ type: 'message_delta',
452
+ content,
453
+ message_id: messageId,
454
+ order: messageIndex++,
455
+ }))) {
456
+ yield ev;
457
+ }
458
+ }
459
+ if (Array.isArray(extendedDelta.annotations)) {
460
+ for (const ann of extendedDelta.annotations) {
461
+ if (ann.type === 'url_citation' &&
462
+ ann.url_citation?.url) {
463
+ const marker = formatCitation(citationTracker, {
464
+ title: ann.url_citation.title ||
465
+ ann.url_citation.url,
466
+ url: ann.url_citation.url,
467
+ });
468
+ aggregatedContent += marker;
469
+ yield {
470
+ type: 'message_delta',
471
+ content: marker,
472
+ message_id: messageId,
473
+ order: messageIndex++,
474
+ };
475
+ }
476
+ }
477
+ }
478
+ const extendedChunk = chunk;
479
+ if (Array.isArray(extendedChunk.citations) &&
480
+ extendedChunk.citations.length > 0) {
481
+ for (const url of extendedChunk.citations) {
482
+ if (typeof url === 'string' &&
483
+ !citationTracker.citations.has(url)) {
484
+ const title = url.split('/').pop() || url;
485
+ const marker = formatCitation(citationTracker, {
486
+ title,
487
+ url,
488
+ });
489
+ if (marker) {
490
+ aggregatedContent += marker;
491
+ yield {
492
+ type: 'message_delta',
493
+ content: marker,
494
+ message_id: messageId,
495
+ order: messageIndex++,
496
+ };
497
+ }
498
+ }
499
+ }
500
+ }
501
+ if ('reasoning_content' in delta) {
502
+ const thinking_content = delta.reasoning_content;
503
+ if (thinking_content) {
504
+ aggregatedThinking += thinking_content;
505
+ yield {
506
+ type: 'message_delta',
507
+ content: '',
508
+ message_id: messageId,
509
+ thinking_content,
510
+ order: messageIndex++,
511
+ };
512
+ }
513
+ }
514
+ if ('thinking_content' in delta) {
515
+ const thinking_content = delta.thinking_content;
516
+ if (thinking_content) {
517
+ aggregatedThinking += thinking_content;
518
+ yield {
519
+ type: 'message_delta',
520
+ content: '',
521
+ message_id: messageId,
522
+ thinking_content,
523
+ order: messageIndex++,
524
+ };
525
+ }
526
+ }
527
+ if (delta.tool_calls) {
528
+ for (const toolCallDelta of delta.tool_calls) {
529
+ const typedDelta = toolCallDelta;
530
+ const index = typedDelta.index;
531
+ if (typeof index !== 'number')
532
+ continue;
533
+ let partialCall = partialToolCallsByIndex.get(index);
534
+ if (!partialCall) {
535
+ partialCall = {
536
+ id: typedDelta.id || '',
537
+ type: 'function',
538
+ function: {
539
+ name: typedDelta.function?.name || '',
540
+ arguments: typedDelta.function?.arguments ||
541
+ '',
542
+ },
543
+ };
544
+ partialToolCallsByIndex.set(index, partialCall);
545
+ }
546
+ else {
547
+ if (typedDelta.id)
548
+ partialCall.id = typedDelta.id;
549
+ if (typedDelta.function?.name)
550
+ partialCall.function.name =
551
+ typedDelta.function.name;
552
+ if (typedDelta.function?.arguments)
553
+ partialCall.function.arguments +=
554
+ typedDelta.function.arguments;
555
+ }
556
+ }
557
+ }
558
+ if (choice.finish_reason)
559
+ finishReason = choice.finish_reason;
560
+ if (chunk.usage)
561
+ usage = chunk.usage;
562
+ }
563
+ if (citationTracker.citations.size > 0) {
564
+ const footnotes = generateFootnotes(citationTracker);
565
+ aggregatedContent += footnotes;
566
+ yield {
567
+ type: 'message_delta',
568
+ content: footnotes,
569
+ message_id: messageId,
570
+ order: messageIndex++,
571
+ };
572
+ }
573
+ if (usage) {
574
+ costTracker.addUsage({
575
+ model: model,
576
+ input_tokens: usage.prompt_tokens || 0,
577
+ output_tokens: usage.completion_tokens || 0,
578
+ cached_tokens: usage.prompt_tokens_details?.cached_tokens || 0,
579
+ metadata: {
580
+ total_tokens: usage.total_tokens || 0,
581
+ reasoning_tokens: usage.completion_tokens_details
582
+ ?.reasoning_tokens || 0,
583
+ },
584
+ });
585
+ }
586
+ else {
587
+ console.warn(`(${this.provider}) Usage info not found in stream for cost tracking.`);
588
+ }
589
+ for (const ev of flushBufferedDeltas(deltaBuffers, (id, content) => ({
590
+ type: 'message_delta',
591
+ content,
592
+ message_id: id,
593
+ order: messageIndex++,
594
+ }))) {
595
+ yield ev;
596
+ }
597
+ if (finishReason === 'stop') {
598
+ const parseResult = this._parseAndPrepareSimulatedToolCalls(aggregatedContent, messageId);
599
+ if (parseResult.handled && parseResult.eventsToYield) {
600
+ for (const event of parseResult.eventsToYield) {
601
+ yield event;
602
+ }
603
+ }
604
+ else {
605
+ yield {
606
+ type: 'message_complete',
607
+ content: parseResult.cleanedContent ?? '',
608
+ message_id: messageId,
609
+ thinking_content: aggregatedThinking,
610
+ };
611
+ }
612
+ }
613
+ else if (finishReason === 'tool_calls') {
614
+ const completedToolCalls = Array.from(partialToolCallsByIndex.values()).filter(call => call.id && call.function.name);
615
+ if (completedToolCalls.length > 0) {
616
+ yield {
617
+ type: 'tool_start',
618
+ tool_calls: completedToolCalls,
619
+ };
620
+ }
621
+ else {
622
+ log_llm_error(requestId, `Error (${this.provider}): Model indicated tool calls, but none were parsed correctly.`);
623
+ console.warn(`(${this.provider}) Finish reason 'tool_calls', but no complete native tool calls parsed.`);
624
+ yield {
625
+ type: 'error',
626
+ error: `Error (${this.provider}): Model indicated tool calls, but none were parsed correctly.`,
627
+ };
628
+ }
629
+ }
630
+ else if (finishReason === 'length') {
631
+ const cleanedPartialContent = aggregatedContent.replaceAll(TOOL_CALL_CLEANUP_REGEX, CLEANUP_PLACEHOLDER);
632
+ log_llm_error(requestId, `Error (${this.provider}): Response truncated (max_tokens). Partial: ${cleanedPartialContent.substring(0, 100)}...`);
633
+ yield {
634
+ type: 'error',
635
+ error: `Error (${this.provider}): Response truncated (max_tokens). Partial: ${cleanedPartialContent.substring(0, 100)}...`,
636
+ };
637
+ }
638
+ else if (finishReason) {
639
+ const cleanedReasonContent = aggregatedContent.replaceAll(TOOL_CALL_CLEANUP_REGEX, CLEANUP_PLACEHOLDER);
640
+ log_llm_error(requestId, `Error (${this.provider}): Response stopped due to: ${finishReason}. Content: ${cleanedReasonContent.substring(0, 100)}...`);
641
+ yield {
642
+ type: 'error',
643
+ error: `Error (${this.provider}): Response stopped due to: ${finishReason}. Content: ${cleanedReasonContent.substring(0, 100)}...`,
644
+ };
645
+ }
646
+ else {
647
+ if (aggregatedContent) {
648
+ console.warn(`(${this.provider}) Stream finished without finish_reason, yielding cleaned content.`);
649
+ const parseResult = this._parseAndPrepareSimulatedToolCalls(aggregatedContent, messageId);
650
+ if (parseResult.handled && parseResult.eventsToYield) {
651
+ for (const event of parseResult.eventsToYield) {
652
+ yield event;
653
+ }
654
+ }
655
+ else {
656
+ yield {
657
+ type: 'message_complete',
658
+ content: parseResult.cleanedContent ?? '',
659
+ message_id: messageId,
660
+ thinking_content: aggregatedThinking,
661
+ };
662
+ }
663
+ }
664
+ else if (partialToolCallsByIndex.size > 0) {
665
+ log_llm_error(requestId, `Error (${this.provider}): Stream ended unexpectedly during native tool call generation.`);
666
+ console.warn(`(${this.provider}) Stream finished without finish_reason during native tool call generation.`);
667
+ yield {
668
+ type: 'error',
669
+ error: `Error (${this.provider}): Stream ended unexpectedly during native tool call generation.`,
670
+ };
671
+ }
672
+ else {
673
+ log_llm_error(requestId, `Error (${this.provider}): Stream finished unexpectedly empty.`);
674
+ console.warn(`(${this.provider}) Stream finished empty without reason, content, or tool calls.`);
675
+ yield {
676
+ type: 'error',
677
+ error: `Error (${this.provider}): Stream finished unexpectedly empty.`,
678
+ };
679
+ }
680
+ }
681
+ }
682
+ catch (streamError) {
683
+ log_llm_error(requestId, streamError);
684
+ console.error(`(${this.provider}) Error processing chat completions stream:`, streamError);
685
+ yield {
686
+ type: 'error',
687
+ error: `Stream processing error (${this.provider} ${model}): ` +
688
+ (streamError instanceof OpenAI.APIError ||
689
+ streamError instanceof APIError
690
+ ? `${streamError.status} ${streamError.name} ${streamError.message} ${JSON.stringify(streamError.error)}`
691
+ : streamError instanceof Error
692
+ ? streamError.stack
693
+ : Object.getPrototypeOf(streamError) +
694
+ ' ' +
695
+ String(streamError)),
696
+ };
697
+ }
698
+ finally {
699
+ partialToolCallsByIndex.clear();
700
+ log_llm_response(requestId, chunks);
701
+ }
702
+ }
703
+ catch (error) {
704
+ log_llm_error(requestId, error);
705
+ console.error(`Error running ${this.provider} chat completions stream:`, error);
706
+ yield {
707
+ type: 'error',
708
+ error: `API Error (${this.provider} - ${model}): ` +
709
+ (error instanceof OpenAI.APIError ||
710
+ error instanceof APIError
711
+ ? `${error.status} ${error.name} ${error.message}`
712
+ : error instanceof Error
713
+ ? error.stack
714
+ : Object.getPrototypeOf(error) + ' ' + String(error)),
715
+ };
716
+ }
717
+ }
718
+ }
719
+ //# sourceMappingURL=openai_chat.js.map