diff --git a/web/src/common/openai.test.ts b/web/src/common/openai.test.ts new file mode 100644 index 00000000..d289d917 --- /dev/null +++ b/web/src/common/openai.test.ts @@ -0,0 +1,81 @@ +// web/src/common/openai.test.ts +import { OpenAiChannel, MyMessage } from './openai'; // Adjust path as needed +import { describe, it, expect, beforeEach } from 'vitest'; + +describe('OpenAiChannel.messages_format', () => { + let channel: OpenAiChannel; + + beforeEach(() => { + // Minimal options needed for OpenAiChannel instantiation for this test + channel = new OpenAiChannel({ apiKey: 'test', baseURL: 'test' }); + }); + + it('should omit tool_calls if content_tool_calls is undefined', async () => { + const messages: MyMessage[] = [ + { role: 'user', content: 'Hello' }, + { + role: 'assistant', + content: 'Thinking...', + content_tool_calls: undefined + } + ]; + const formattedMessages = await channel.messages_format(messages); + const assistantMessage = formattedMessages.find(m => m.role === 'assistant'); + expect(assistantMessage).toBeDefined(); + expect(assistantMessage).not.toHaveProperty('tool_calls'); + }); + + it('should omit tool_calls if content_tool_calls is null', async () => { + const messages: MyMessage[] = [ + { role: 'user', content: 'Hello' }, + { + role: 'assistant', + content: 'Thinking...', + // @ts-ignore // To allow null assignment for testing, if MyMessage['content_tool_calls'] doesn't normally allow null + content_tool_calls: null + } + ]; + const formattedMessages = await channel.messages_format(messages); + const assistantMessage = formattedMessages.find(m => m.role === 'assistant'); + expect(assistantMessage).toBeDefined(); + expect(assistantMessage).not.toHaveProperty('tool_calls'); + }); + + it('should omit tool_calls if content_tool_calls is an empty array', async () => { + const messages: MyMessage[] = [ + { role: 'user', content: 'Hello' }, + { + role: 'assistant', + content: 'Thinking...', + content_tool_calls: [] + } + ]; + const formattedMessages = await channel.messages_format(messages); + const assistantMessage = formattedMessages.find(m => m.role === 'assistant'); + expect(assistantMessage).toBeDefined(); + expect(assistantMessage).not.toHaveProperty('tool_calls'); + }); + + // Optional: Add a test case for when content_tool_calls is valid and non-empty + it('should include tool_calls if content_tool_calls is valid and non-empty', async () => { + const messages: MyMessage[] = [ + { role: 'user', content: 'Hello' }, + { + role: 'assistant', + content: 'Thinking...', + content_tool_calls: [{ + id: 'call_123', + type: 'function', + function: { name: 'get_weather', arguments: '{"location": "Boston"}' } + }] + } + ]; + const formattedMessages = await channel.messages_format(messages); + const assistantMessage = formattedMessages.find(m => m.role === 'assistant'); + expect(assistantMessage).toBeDefined(); + expect(assistantMessage).toHaveProperty('tool_calls'); + expect(assistantMessage.tool_calls).toHaveLength(1); + // @ts-ignore + expect(assistantMessage.tool_calls[0].function.name).toBe('get_weather'); + }); +}); diff --git a/web/src/common/openai.ts b/web/src/common/openai.ts index 52a0965d..06d43865 100644 --- a/web/src/common/openai.ts +++ b/web/src/common/openai.ts @@ -754,13 +754,18 @@ export class OpenAiChannel { ...rest } = m; if (rest.role == "assistant") { - rest.tool_calls = content_tool_calls?.map((x: Tool_Call) => { - let { origin_name, restore_name, ...rest } = x; - let { argumentsOBJ, ...functionRest } = rest.function; - rest.function = functionRest as any; - return rest; - }) as any; - if (rest.tool_calls?.length == 0) { + // Check if content_tool_calls is valid and has items before mapping + if (content_tool_calls && content_tool_calls.length > 0) { + rest.tool_calls = content_tool_calls.map((x: Tool_Call) => { + // Using 'restCall' to avoid shadowing the outer 'rest' variable from message destructuring + let { origin_name, restore_name, ...restCall } = x; + let { argumentsOBJ, ...functionRest } = restCall.function; // argumentsOBJ is part of x.function + restCall.function = functionRest as any; + return restCall; // Return the modified 'restCall' object + }) as any; + } else { + // If content_tool_calls is null, undefined, or empty, + // ensure tool_calls is not part of the resulting object. delete rest.tool_calls; } }