• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lucasliet / llm-telegram-bot / 17309262078

28 Aug 2025 10:19PM UTC coverage: 33.696% (+2.2%) from 31.514%
17309262078

push

github

lucasliet
refactor: services and improve code consistency

- Updated import statements for consistency across services.
- Refactored image generation URL construction in PollinationsService.
- Cleaned up formatting and spacing in TelegramService for better readability.
- Streamlined function definitions and error handling in ToolService.
- Enhanced GeminiService and GithubCopilotService for improved readability and consistency.
- Removed unnecessary comments and improved documentation in ChatConfigUtil.
- Added detailed instructions for applying patches and prompts in resources.

47 of 82 branches covered (57.32%)

Branch coverage included in aggregate %.

24 of 302 new or added lines in 17 files covered. (7.95%)

9 existing lines in 6 files now uncovered.

1160 of 3500 relevant lines covered (33.14%)

1.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.32
/src/adapter/ToolUsageAdapter.ts
1
import OpenAi from 'npm:openai';
2
import { executeToolCalls, responseMap as OpenaiResponseMap } from '../service/openai/OpenAIService.ts';
3✔
3

4
export interface ToolOptions {
5
        tools?: OpenAi.Chat.Completions.ChatCompletionTool[];
6
        tool_choice?: OpenAi.Chat.Completions.ChatCompletionToolChoiceOption;
7
        functions?: OpenAi.ChatCompletionCreateParams.Function[];
8
        function_call?: 'auto' | 'none' | { name: string };
9
}
10

11
export type ToolCall = {
12
        index?: number;
13
        id?: string;
14
        type?: string;
15
        function?: { name?: string; arguments?: string };
16
};
17

18
interface ToolCallResult {
19
        choices: Array<{
20
                delta: {
21
                        content: string | null;
22
                        tool_calls?: ToolCall[];
23
                };
24
                finish_reason?: string | null;
25
        }>;
26
}
27

28
export interface OpenAiStreamResponse {
29
        choices: Array<{
30
                delta: {
31
                        content: string | null;
32
                        tool_calls?: ToolCall[];
33
                };
34
                finish_reason: string | null;
35
        }>;
36
}
37

38
const TEXT_DECODER = new TextDecoder();
3✔
39
const TEXT_ENCODER = new TextEncoder();
3✔
40
const FUNCTION_CALL_PATTERN = /```(?:function|json)?\s*\n([\s\S]*?)\n```/g;
3✔
41
const TOOL_CALL_ADAPTER_KEY = '__adapter_tool_calls';
3✔
42
const TOOL_BLOCK_START_REGEX = /```(function|json)?\s*\n?/;
3✔
43
const TOOL_BLOCK_END_REGEX = /\n```/;
3✔
44

45
/**
1✔
46
 * Adapts tool usage for text-only Chatbots, enabling function calling
47
 * by formatting instructions and parsing responses.
48
 */
3✔
49
export class ToolUsageAdapter {
3✔
50
        /**
3✔
51
         * Formats a tool definition for inclusion in the prompt.
52
         */
×
53
        private _formatToolForMessage(tool: OpenAi.Chat.Completions.ChatCompletionTool) {
×
54
                if (tool.type === 'function' && tool.function) {
×
55
                        return {
×
56
                                name: tool.function.name,
×
57
                                description: tool.function.description || '',
×
58
                                parameters: tool.function.parameters,
×
59
                        };
×
60
                }
×
61
                return {
×
62
                        name: 'unknown_tool',
×
63
                        description: '',
×
64
                        parameters: undefined,
×
65
                };
×
66
        }
×
67

68
        /**
3✔
69
         * Formats a function definition for inclusion in the prompt.
70
         */
×
71
        private _formatFunctionForMessage(func: OpenAi.ChatCompletionCreateParams.Function) {
×
72
                return {
×
73
                        name: func.name,
×
74
                        description: func.description || '',
×
75
                        parameters: func.parameters,
×
76
                };
×
77
        }
×
78

79
        /**
3✔
80
         * Validates if an object represents a valid function call structure.
81
         */
×
82
        private _isValidFunctionCall(obj: any): obj is { name: string; arguments: object } {
×
83
                return (
×
84
                        typeof obj === 'object' &&
×
85
                        typeof obj.name === 'string' &&
×
86
                        obj.name.length > 0 &&
×
87
                        typeof obj.arguments === 'object' &&
×
88
                        obj.arguments !== null &&
×
89
                        !Array.isArray(obj.arguments)
×
90
                );
91
        }
×
92

93
        /**
3✔
94
         * Extracts structured tool calls from the model's text response.
95
         * Removes the tool call blocks from the original content.
96
         */
×
97
        private _extractToolCalls(content: string): {
×
98
                toolCalls: ToolCall[] | null;
99
                cleanedContent: string;
100
        } {
×
101
                const toolCalls: ToolCall[] = [];
×
102
                let cleanedContent = content;
×
103
                let match;
×
104

105
                FUNCTION_CALL_PATTERN.lastIndex = 0;
×
106

107
                while ((match = FUNCTION_CALL_PATTERN.exec(content)) !== null) {
×
108
                        const functionData = match[1].trim();
×
109
                        try {
×
110
                                const functionObj = JSON.parse(functionData);
×
111
                                if (this._isValidFunctionCall(functionObj)) {
×
112
                                        toolCalls.push({
×
113
                                                id: crypto.randomUUID(),
×
114
                                                type: 'function',
×
115
                                                function: {
×
116
                                                        name: functionObj.name,
×
117
                                                        arguments: JSON.stringify(functionObj.arguments),
×
118
                                                },
×
119
                                        });
×
120
                                        cleanedContent = cleanedContent.replace(match[0], '');
×
121
                                } else {
×
122
                                        console.warn('Parsed function object is not valid:', functionObj);
×
123
                                }
×
124
                        } catch (error) {
×
125
                                console.warn(
×
126
                                        'Failed to parse potential function call JSON:',
×
127
                                        functionData.substring(0, 100),
×
128
                                        'Error:',
×
129
                                        error,
×
130
                                );
131
                        }
×
132
                }
×
133

134
                return {
×
135
                        toolCalls: toolCalls.length > 0 ? toolCalls : null,
×
136
                        cleanedContent: cleanedContent.trim(),
×
137
                };
×
138
        }
×
139

140
        /**
3✔
141
         * Converts a message with the 'tool' role to an 'assistant' role message
142
         * containing the tool result, formatted for text-only models.
143
         */
×
144
        private _convertToolRoleMessage(
×
145
                message: OpenAi.Chat.Completions.ChatCompletionToolMessageParam,
×
146
        ): OpenAi.Chat.Completions.ChatCompletionAssistantMessageParam {
×
147
                const toolCallId = message.tool_call_id;
×
148
                const toolResult = message.content;
×
NEW
149
                const formattedContent = `This was the result of the tool call with ID ${toolCallId}, I will use it to formulate my next response: \`\`\`json\n${
×
NEW
150
                        JSON.stringify(toolResult)
×
NEW
151
                }\n\`\`\``;
×
152
                return {
×
153
                        role: 'assistant',
×
154
                        content: formattedContent,
×
155
                };
×
156
        }
×
157

158
        /**
3✔
159
         * Generates the informational text about available tools and how to use them.
160
         */
3✔
161
        private _generateToolsInfoString(toolOptions?: ToolOptions): string {
3✔
162
                if (!toolOptions || (!toolOptions.tools?.length && !toolOptions.functions?.length)) {
×
163
                        return '';
5✔
164
                }
5!
165

166
                let toolsInfo = '\n\nYou have access to the following tools:\n';
×
167

168
                if (toolOptions.tools?.length) {
×
169
                        toolsInfo += '\nTOOLS:\n';
×
170
                        toolOptions.tools.forEach((tool, index) => {
×
171
                                const formattedTool = this._formatToolForMessage(tool);
×
172
                                toolsInfo += `${index + 1}. ${formattedTool.name}: ${formattedTool.description}\n`;
×
173
                                toolsInfo += `   Parameters: ${JSON.stringify(formattedTool.parameters, null, 2)}\n\n`;
×
174
                        });
×
175
                }
×
176

177
                if (toolOptions.functions?.length) {
×
178
                        toolsInfo += '\nFUNCTIONS:\n';
×
179
                        toolOptions.functions.forEach((func, index) => {
×
180
                                const formattedFunc = this._formatFunctionForMessage(func);
×
181
                                toolsInfo += `${index + 1}. ${formattedFunc.name}: ${formattedFunc.description}\n`;
×
182
                                toolsInfo += `   Parameters: ${JSON.stringify(formattedFunc.parameters, null, 2)}\n\n`;
×
183
                        });
×
184
                }
×
185

186
                toolsInfo += '\nTo call a tool, respond using this exact markdown format:\n';
×
187
                toolsInfo += '```function\n';
×
188
                toolsInfo += '{\n';
×
189
                toolsInfo += '  "name": "function_name",\n';
×
190
                toolsInfo += '  "arguments": {\n';
×
191
                toolsInfo += '    "param1": "value1",\n';
×
192
                toolsInfo += '    "param2": "value2"\n';
×
193
                toolsInfo += '  }\n';
×
194
                toolsInfo += '}\n';
×
195
                toolsInfo += '```\n\n';
×
196
                toolsInfo += 'Before calling the tool, state what you are going to do. The very last part of your response must be the tool call block.\n';
×
197

198
                const choice = toolOptions?.tool_choice;
×
199
                const legacyFunctionCall = toolOptions?.function_call;
×
200

201
                if (choice) {
×
202
                        if (choice === 'none') {
×
203
                                toolsInfo += 'Do not use any tools unless absolutely necessary.\n\n';
×
204
                        } else if (typeof choice === 'object' && choice.type === 'function' && choice.function?.name) {
×
205
                                toolsInfo += `You must use the tool "${choice.function.name}" to answer this query.\n\n`;
×
206
                        }
×
207
                } else if (legacyFunctionCall) {
×
208
                        if (legacyFunctionCall === 'none') {
×
209
                                toolsInfo += 'Do not use any functions unless absolutely necessary.\n\n';
×
210
                        } else if (typeof legacyFunctionCall === 'object' && legacyFunctionCall.name) {
×
211
                                toolsInfo += `You must use the function "${legacyFunctionCall.name}" to answer this query.\n\n`;
×
212
                        }
×
213
                }
×
214

215
                return toolsInfo;
×
216
        }
×
217

218
        /**
3✔
219
         * Modifies the message history to include tool information and adapt tool messages.
220
         */
3✔
221
        modifyMessagesWithToolInfo(
3✔
222
                messages: OpenAi.Chat.Completions.ChatCompletionMessageParam[],
3✔
223
                toolOptions?: ToolOptions,
3✔
224
        ): OpenAi.Chat.Completions.ChatCompletionMessageParam[] {
3✔
NEW
225
                const modifiedMessages = messages.map((message) => message.role === 'tool' ? this._convertToolRoleMessage(message) : message);
×
226

227
                const lastUserMessageIndex = modifiedMessages.findLastIndex(
5✔
228
                        (message) => message.role === 'user',
5✔
229
                );
230

231
                if (lastUserMessageIndex >= 0) {
5✔
232
                        const toolsInfoString = this._generateToolsInfoString(toolOptions);
5✔
233
                        if (toolsInfoString) {
×
234
                                const lastUserMessage = modifiedMessages[lastUserMessageIndex];
×
235
                                const originalContent = typeof lastUserMessage.content === 'string'
×
236
                                        ? lastUserMessage.content
×
NEW
237
                                        : (Array.isArray(lastUserMessage.content) ? lastUserMessage.content.map((part) => part.type === 'text' ? part.text : '').join('') : '');
×
238

239
                                modifiedMessages[lastUserMessageIndex] = {
×
240
                                        ...lastUserMessage,
×
241
                                        content: `${originalContent}${toolsInfoString}`,
×
242
                                };
×
243
                        }
×
244
                }
5✔
245

246
                return modifiedMessages;
5✔
247
        }
5✔
248

249
        /**
3✔
250
         * Processes the model's response stream, extracts tool calls, executes them,
251
         * and returns a stream compatible with the OpenAI SDK format.
252
         */
3✔
253
        processModelResponse(
3✔
254
                generateTextFn: (messages: OpenAi.Chat.Completions.ChatCompletionMessageParam[], ...args: any[]) => Promise<ReadableStreamDefaultReader<Uint8Array>>,
3✔
255
                initialModelStreamReader: ReadableStreamDefaultReader<Uint8Array>,
3✔
256
                messages: OpenAi.Chat.ChatCompletionMessageParam[],
3✔
257
                responseChunkMapFn?: (responseBody: string) => string,
3✔
258
                ...generateTextArgs: any[]
3✔
259
        ): ReadableStreamDefaultReader<Uint8Array> {
3✔
260
                const toolCallExtractionReader = this._extractToolCallsFromStream(initialModelStreamReader, responseChunkMapFn);
4✔
261
                const openaiFormattedReader = this._formatStreamToOpenAIInterface(toolCallExtractionReader);
4✔
262
                const finalResponseReader = executeToolCalls(generateTextFn, openaiFormattedReader, messages, ...generateTextArgs);
4✔
263
                return this.mapResponse(finalResponseReader, false, OpenaiResponseMap);
4✔
264
        }
4✔
265

266
        /**
3✔
267
         * Maps the raw text chunks from a stream using an optional mapping function
268
         * and encodes them back to Uint8Array. Optionally formats as OpenAI stream chunks.
269
         */
3✔
270
        mapResponse(
3✔
271
                reader: ReadableStreamDefaultReader<Uint8Array>,
3✔
272
                formatAsOpenAIChunk: boolean = false,
3✔
273
                responseChunkMapFn?: (responseBody: string) => string,
3✔
274
        ): ReadableStreamDefaultReader<Uint8Array> {
3✔
275
                return new ReadableStream<Uint8Array>({
4✔
276
                        async start(controller) {
4✔
277
                                try {
5✔
278
                                        while (true) {
5✔
279
                                                const { done, value } = await reader.read();
8✔
280
                                                if (done) break;
8✔
281

282
                                                const rawChunkText = TEXT_DECODER.decode(value);
10✔
283
                                                const mappedText = responseChunkMapFn ? responseChunkMapFn(rawChunkText) : rawChunkText;
×
284

285
                                                if (!mappedText && !(formatAsOpenAIChunk && mappedText === '')) continue;
×
286

NEW
287
                                                const outputChunk = formatAsOpenAIChunk ? ToolUsageAdapter._createOpenAIStreamChunk(mappedText) : mappedText;
×
288

289
                                                controller.enqueue(TEXT_ENCODER.encode(outputChunk));
8✔
290
                                        }
8✔
291
                                } catch (error) {
×
292
                                        console.error('Error in mapResponse stream:', error);
×
293
                                        controller.error(error);
×
294
                                } finally {
×
295
                                        controller.close();
5✔
296
                                        reader.releaseLock();
5✔
297
                                }
5✔
298
                        },
5✔
299
                }).getReader();
4✔
300
        }
4✔
301

302
        /**
3✔
303
         * Creates a JSON string formatted as an OpenAI stream chunk.
304
         */
3✔
305
        private static _createOpenAIStreamChunk(text: string): string {
3✔
306
                const response: OpenAiStreamResponse = {
5✔
307
                        choices: [{
10✔
308
                                delta: { content: text },
15✔
309
                                finish_reason: text === '' ? 'stop' : null,
5✔
310
                        }],
10✔
311
                };
5✔
312
                return JSON.stringify(response);
5✔
313
        }
5✔
314

315
        /**
3✔
316
         * Processes a stream chunk containing the special adapter key for tool calls.
317
         * Parses the JSON and formats it into OpenAI SDK compatible tool call chunks.
318
         */
3✔
319
        private _processAdapterToolCallChunk(textChunk: string): {
3✔
320
                processed: boolean;
321
                toolCalls: Array<{ index: number; id?: string; type?: string; function?: { name?: string; arguments?: string } }> | null;
322
                formattedChunks: ToolCallResult[];
323
        } {
3✔
324
                const formattedChunks: ToolCallResult[] = [];
4✔
325
                let toolCalls: Array<{ index: number; id?: string; type?: string; function?: { name?: string; arguments?: string } }> = [];
4✔
326
                let processed = false;
4✔
327

328
                if (textChunk.includes(`"${TOOL_CALL_ADAPTER_KEY}"`) || textChunk.includes('function_call')) {
×
329
                        try {
×
330
                                const data = JSON.parse(textChunk);
×
331
                                if (
×
332
                                        data[TOOL_CALL_ADAPTER_KEY] ||
×
333
                                        (Array.isArray(data.output) && data.output.some((item: any) => item?.type === 'function_call'))
×
334
                                ) {
×
335
                                        processed = true;
×
NEW
336
                                        const extractedToolCalls: ToolCall[] = data[TOOL_CALL_ADAPTER_KEY] ||
×
337
                                                (Array.isArray(data.output) ? data.output.filter((item: any) => item?.type === 'function_call') : []);
×
338

339
                                        toolCalls = extractedToolCalls.map((call: any, index) => ({
×
340
                                                index: index,
×
341
                                                id: call.id,
×
342
                                                type: call.type,
×
NEW
343
                                                function: call.function ||
×
344
                                                        {
×
345
                                                                name: call.name,
×
NEW
346
                                                                arguments: typeof call.arguments === 'string' ? call.arguments : JSON.stringify(call.arguments ?? {}),
×
347
                                                        },
×
348
                                        }));
×
349

350
                                        for (const toolCall of toolCalls) {
×
351
                                                formattedChunks.push({
×
352
                                                        choices: [{
×
353
                                                                delta: { content: null, tool_calls: [toolCall] },
×
354
                                                                finish_reason: null,
×
355
                                                        }],
×
356
                                                });
×
357
                                        }
×
358

359
                                        formattedChunks.push({
×
360
                                                choices: [{
×
361
                                                        delta: { content: null },
×
362
                                                        finish_reason: 'tool_calls',
×
363
                                                }],
×
364
                                        });
×
365
                                }
×
366
                        } catch (error) {
×
367
                                console.error('Error processing adapter tool call chunk:', error, 'Chunk:', textChunk);
×
368
                                processed = false;
×
369
                                toolCalls = [];
×
370
                        }
×
371
                }
×
372

373
                return { processed, toolCalls: toolCalls.length > 0 ? toolCalls : null, formattedChunks };
16!
374
        }
4✔
375

376
        /**
3✔
377
         * Reads a stream, identifies chunks containing adapter-formatted tool calls,
378
         * processes them, and passes through other chunks formatted as OpenAI stream chunks.
379
         */
3✔
380
        private _formatStreamToOpenAIInterface(
3✔
381
                reader: ReadableStreamDefaultReader<Uint8Array>,
3✔
382
        ): ReadableStreamDefaultReader<Uint8Array> {
3✔
383
                // deno-lint-ignore no-this-alias
4✔
384
                const self = this;
4✔
385
                return new ReadableStream<Uint8Array>({
4✔
386
                        async start(controller) {
4✔
387
                                let toolCallsDetected = false;
5✔
388
                                try {
5✔
389
                                        while (true) {
5✔
390
                                                const { done, value } = await reader.read();
7✔
391
                                                if (done) break;
7✔
392

393
                                                const textChunk = TEXT_DECODER.decode(value);
8✔
394

395
                                                const toolProcessingResult = self._processAdapterToolCallChunk(textChunk);
8✔
396

397
                                                if (toolProcessingResult.processed) {
×
398
                                                        toolCallsDetected = true;
×
399
                                                        for (const result of toolProcessingResult.formattedChunks) {
×
400
                                                                controller.enqueue(TEXT_ENCODER.encode(JSON.stringify(result)));
×
401
                                                        }
×
402
                                                } else {
✔
403
                                                        controller.enqueue(TEXT_ENCODER.encode(ToolUsageAdapter._createOpenAIStreamChunk(textChunk)));
8✔
404
                                                }
8✔
405
                                        }
7✔
406

407
                                        if (!toolCallsDetected) {
5✔
408
                                                controller.enqueue(TEXT_ENCODER.encode(ToolUsageAdapter._createOpenAIStreamChunk('')));
5✔
409
                                        }
5✔
410
                                } catch (error) {
×
411
                                        console.error('Error in _formatStreamToOpenAIInterface:', error);
×
412
                                        controller.error(error);
×
413
                                } finally {
×
414
                                        controller.close();
5✔
415
                                        reader.releaseLock();
5✔
416
                                }
5✔
417
                        },
5✔
418
                }).getReader();
4✔
419
        }
4✔
420

421
        /**
3✔
422
         * Reads a stream from the model, identifies markdown blocks potentially containing
423
         * function calls, extracts them, and yields either regular text chunks or
424
         * a special JSON chunk containing the extracted tool calls.
425
         */
3✔
426
        private _extractToolCallsFromStream(
3✔
427
                modelStreamReader: ReadableStreamDefaultReader<Uint8Array>,
3✔
428
                responseChunkMapFn?: (responseBody: string) => string,
3✔
429
        ): ReadableStreamDefaultReader<Uint8Array> {
3✔
430
                // deno-lint-ignore no-this-alias
4✔
431
                const self = this;
4✔
432
                return new ReadableStream<Uint8Array>({
4✔
433
                        async start(controller) {
4✔
434
                                let chunkBuffer = '';
5✔
435
                                let toolBlockBuffer = '';
5✔
436
                                let isInsideToolBlock = false;
5✔
437
                                let streamFinished = false;
5✔
438

439
                                try {
5✔
440
                                        while (!streamFinished) {
5✔
441
                                                const { done, value } = await modelStreamReader.read();
7✔
442
                                                if (done) streamFinished = true;
7✔
443

444
                                                const rawChunk = value ? TEXT_DECODER.decode(value, { stream: true }) : '';
22✔
445
                                                const currentChunk = responseChunkMapFn ? responseChunkMapFn(rawChunk) : rawChunk;
×
446

447
                                                if (isInsideToolBlock) {
×
448
                                                        toolBlockBuffer += currentChunk;
×
449
                                                        const endMatchIndex = toolBlockBuffer.search(TOOL_BLOCK_END_REGEX);
×
450

451
                                                        if (endMatchIndex !== -1) {
×
452
                                                                const blockEndMarkerLength = toolBlockBuffer.match(TOOL_BLOCK_END_REGEX)![0].length;
×
453
                                                                const fullBlock = toolBlockBuffer.slice(0, endMatchIndex + blockEndMarkerLength);
×
454
                                                                const remainingChunkPart = toolBlockBuffer.slice(endMatchIndex + blockEndMarkerLength);
×
455

456
                                                                const { toolCalls, cleanedContent } = self._extractToolCalls(fullBlock);
×
457
                                                                if (cleanedContent) controller.enqueue(TEXT_ENCODER.encode(cleanedContent));
×
458
                                                                if (toolCalls) {
×
459
                                                                        controller.enqueue(TEXT_ENCODER.encode(JSON.stringify({ [TOOL_CALL_ADAPTER_KEY]: toolCalls })));
×
460
                                                                }
×
461

462
                                                                isInsideToolBlock = false;
×
463
                                                                toolBlockBuffer = '';
×
464
                                                                chunkBuffer = remainingChunkPart;
×
465
                                                        }
×
466
                                                } else {
×
467
                                                        chunkBuffer += currentChunk;
7✔
468
                                                        const startMatch = chunkBuffer.match(TOOL_BLOCK_START_REGEX);
7✔
469

470
                                                        if (startMatch) {
×
471
                                                                const blockStartIndex = chunkBuffer.indexOf(startMatch[0]);
×
472
                                                                const textBeforeBlock = chunkBuffer.slice(0, blockStartIndex);
×
473

474
                                                                if (textBeforeBlock) controller.enqueue(TEXT_ENCODER.encode(textBeforeBlock));
×
475

476
                                                                isInsideToolBlock = true;
×
477
                                                                toolBlockBuffer = chunkBuffer.slice(blockStartIndex);
×
478
                                                                chunkBuffer = '';
×
479
                                                        } else if (streamFinished) {
✔
480
                                                                if (chunkBuffer) controller.enqueue(TEXT_ENCODER.encode(chunkBuffer));
×
481
                                                                chunkBuffer = '';
8✔
482
                                                        } else {
7✔
483
                                                                const lastPotentialStart = chunkBuffer.lastIndexOf('```');
8✔
484
                                                                const safeEnqueueLength = lastPotentialStart === -1 ? chunkBuffer.length : lastPotentialStart;
×
485

486
                                                                if (safeEnqueueLength > 0) {
8✔
487
                                                                        controller.enqueue(TEXT_ENCODER.encode(chunkBuffer.slice(0, safeEnqueueLength)));
8✔
488
                                                                        chunkBuffer = chunkBuffer.slice(safeEnqueueLength);
8✔
489
                                                                }
8✔
490
                                                        }
8✔
491
                                                }
7✔
492
                                        }
7✔
493

494
                                        if (isInsideToolBlock && toolBlockBuffer) {
×
495
                                                console.warn('Stream ended while inside a tool block. Processing incomplete block.');
×
496
                                                const { toolCalls, cleanedContent } = self._extractToolCalls(toolBlockBuffer);
×
497
                                                if (cleanedContent) controller.enqueue(TEXT_ENCODER.encode(cleanedContent));
×
498
                                                if (toolCalls) {
×
499
                                                        controller.enqueue(TEXT_ENCODER.encode(JSON.stringify({ [TOOL_CALL_ADAPTER_KEY]: toolCalls })));
×
500
                                                }
×
501
                                        } else if (chunkBuffer) {
×
502
                                                controller.enqueue(TEXT_ENCODER.encode(chunkBuffer));
×
503
                                        }
×
504
                                } catch (error) {
×
505
                                        console.error('Error in _extractToolCallsFromStream:', error);
×
506
                                        controller.error(error);
×
507
                                } finally {
×
508
                                        controller.close();
5✔
509
                                        modelStreamReader.releaseLock();
5✔
510
                                }
5✔
511
                        },
5✔
512
                }).getReader();
4✔
513
        }
4✔
514
}
3✔
515

516
export default new ToolUsageAdapter();
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc