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

lucasliet / llm-telegram-bot / 14826161458

04 May 2025 11:19PM UTC coverage: 41.063% (+2.8%) from 38.295%
14826161458

push

github

lucasliet
refactor: streamline ToolUsageAdapter methods and improve tool call handling

49 of 88 branches covered (55.68%)

Branch coverage included in aggregate %.

91 of 251 new or added lines in 1 file covered. (36.25%)

113 existing lines in 5 files now uncovered.

1148 of 2827 relevant lines covered (40.61%)

2.09 hits per line

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

39.59
/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
         */
×
NEW
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
         */
×
NEW
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
         */
×
NEW
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
         */
×
NEW
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

NEW
105
                FUNCTION_CALL_PATTERN.lastIndex = 0;
×
106

NEW
107
                while ((match = FUNCTION_CALL_PATTERN.exec(content)) !== null) {
×
108
                        const functionData = match[1].trim();
×
109
                        try {
×
110
                                const functionObj = JSON.parse(functionData);
×
NEW
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], '');
×
NEW
121
                                } else {
×
NEW
122
                                        console.warn('Parsed function object is not valid:', functionObj);
×
123
                                }
×
124
                        } catch (error) {
×
NEW
125
                                console.warn(
×
NEW
126
                                        'Failed to parse potential function call JSON:',
×
NEW
127
                                        functionData.substring(0, 100),
×
NEW
128
                                        'Error:',
×
NEW
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.
NEW
143
         */
×
NEW
144
        private _convertToolRoleMessage(
×
NEW
145
                message: OpenAi.Chat.Completions.ChatCompletionToolMessageParam,
×
NEW
146
        ): OpenAi.Chat.Completions.ChatCompletionAssistantMessageParam {
×
NEW
147
                const toolCallId = message.tool_call_id;
×
NEW
148
                const toolResult = message.content;
×
NEW
149
                const formattedContent =
×
NEW
150
                        `This was the result of the tool call with ID ${toolCallId}, I will use it to formulate my next response: \`\`\`json\n${JSON.stringify(toolResult)
×
NEW
151
                        }\n\`\`\``;
×
NEW
152
                return {
×
NEW
153
                        role: 'assistant',
×
NEW
154
                        content: formattedContent,
×
NEW
155
                };
×
NEW
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✔
NEW
162
                if (!toolOptions || (!toolOptions.tools?.length && !toolOptions.functions?.length)) {
×
163
                        return '';
5✔
164
                }
5!
165

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

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

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

NEW
186
                toolsInfo += '\nTo call a tool, respond using this exact markdown format:\n';
×
NEW
187
                toolsInfo += '```function\n';
×
NEW
188
                toolsInfo += '{\n';
×
NEW
189
                toolsInfo += '  "name": "function_name",\n';
×
NEW
190
                toolsInfo += '  "arguments": {\n';
×
NEW
191
                toolsInfo += '    "param1": "value1",\n';
×
NEW
192
                toolsInfo += '    "param2": "value2"\n';
×
NEW
193
                toolsInfo += '  }\n';
×
NEW
194
                toolsInfo += '}\n';
×
NEW
195
                toolsInfo += '```\n\n';
×
NEW
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

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

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

NEW
215
                return toolsInfo;
×
NEW
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) =>
×
NEW
226
                        message.role === 'tool' ? this._convertToolRoleMessage(message) : message
×
227
                );
228

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

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

NEW
243
                                modifiedMessages[lastUserMessageIndex] = {
×
NEW
244
                                        ...lastUserMessage,
×
NEW
245
                                        content: `${originalContent}${toolsInfoString}`,
×
NEW
246
                                };
×
247
                        }
×
248
                }
5✔
249

250
                return modifiedMessages;
5✔
251
        }
5✔
252

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

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

286
                                                const rawChunkText = TEXT_DECODER.decode(value);
10✔
NEW
287
                                                const mappedText = responseChunkMapFn ? responseChunkMapFn(rawChunkText) : rawChunkText;
×
288

NEW
289
                                                if (!mappedText && !(formatAsOpenAIChunk && mappedText === '')) continue;
×
290

NEW
291
                                                const outputChunk = formatAsOpenAIChunk
×
NEW
292
                                                        ? ToolUsageAdapter._createOpenAIStreamChunk(mappedText)
✔
NEW
293
                                                        : mappedText;
×
294

295
                                                controller.enqueue(TEXT_ENCODER.encode(outputChunk));
8✔
296
                                        }
8✔
NEW
297
                                } catch (error) {
×
NEW
298
                                        console.error('Error in mapResponse stream:', error);
×
NEW
299
                                        controller.error(error);
×
300
                                } finally {
×
301
                                        controller.close();
5✔
302
                                        reader.releaseLock();
5✔
303
                                }
5✔
304
                        },
5✔
305
                }).getReader();
4✔
306
        }
4✔
307

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

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

NEW
334
                if (textChunk.includes(`"${TOOL_CALL_ADAPTER_KEY}"`)) {
×
335
                        try {
×
NEW
336
                                const data = JSON.parse(textChunk);
×
NEW
337
                                if (data[TOOL_CALL_ADAPTER_KEY]) {
×
338
                                        processed = true;
×
NEW
339
                                        const extractedToolCalls: ToolCall[] = data[TOOL_CALL_ADAPTER_KEY];
×
340

NEW
341
                                        toolCalls = extractedToolCalls.map((call, index) => ({
×
NEW
342
                                                index: index,
×
343
                                                id: call.id,
×
344
                                                type: call.type,
×
345
                                                function: call.function,
×
346
                                        }));
×
347

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

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

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

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

391
                                                const textChunk = TEXT_DECODER.decode(value);
8✔
392

393
                                                const toolProcessingResult = self._processAdapterToolCallChunk(textChunk);
8✔
394

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

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

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

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

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

NEW
445
                                                if (isInsideToolBlock) {
×
NEW
446
                                                        toolBlockBuffer += currentChunk;
×
NEW
447
                                                        const endMatchIndex = toolBlockBuffer.search(TOOL_BLOCK_END_REGEX);
×
448

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

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

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

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

NEW
472
                                                                if (textBeforeBlock) controller.enqueue(TEXT_ENCODER.encode(textBeforeBlock));
×
473

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

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

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

514
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