diff --git a/AIClasses/BaseAIClass.ts b/AIClasses/BaseAIClass.ts index 5f79d92..10e103f 100644 --- a/AIClasses/BaseAIClass.ts +++ b/AIClasses/BaseAIClass.ts @@ -88,7 +88,7 @@ export abstract class BaseAIClass implements IAIClass { public abstract streamRequest(conversation: Conversation): AsyncGenerator; - public abstract formatBinaryFiles(attachments: Attachment[]): string; + protected abstract formatBinaryFiles(attachments: Attachment[]): string; protected abstract parseStreamChunk(chunk: string): IStreamChunk; protected abstract extractContents(conversationContent: ConversationContent[]): unknown; diff --git a/AIClasses/Claude/Claude.ts b/AIClasses/Claude/Claude.ts index 5bf69a0..5657cc2 100644 --- a/AIClasses/Claude/Claude.ts +++ b/AIClasses/Claude/Claude.ts @@ -63,14 +63,7 @@ export class Claude extends BaseAIClass { const messages = this.addCacheControlToMessages( await this.extractContents(conversation.contents)); - const webSearchTool: WebSearchTool20250305 = { - type: "web_search_20250305", - name: "web_search", - max_uses: 5 - }; - - const tools: ToolUnion[] = this.addCacheControlToTools([ - webSearchTool, ...this.mapFunctionDefinitions(this.aiToolDefinitions)]); + const tools = this.getTools(); const requestBody = { model: this.model(), @@ -288,7 +281,7 @@ export class Claude extends BaseAIClass { })); } - public formatBinaryFiles(attachments: Attachment[]): string { + protected formatBinaryFiles(attachments: Attachment[]): string { const contentBlocks = attachments.flatMap(attachment => { const fileID = attachment.getFileID(this.provider); if (!fileID) { @@ -375,6 +368,20 @@ export class Claude extends BaseAIClass { return cachedMessages; } + private getTools(): ToolUnion[] { + if (this.settingsService.settings.enableWebSearch) { + const webSearchTool: WebSearchTool20250305 = { + type: "web_search_20250305", + name: "web_search", + max_uses: 5 + }; + + return this.addCacheControlToTools([ + webSearchTool, ...this.mapFunctionDefinitions(this.aiToolDefinitions)]); + } + return this.addCacheControlToTools(this.mapFunctionDefinitions(this.aiToolDefinitions)); + } + private buildClaudeToolChoice(): { type: string } { // If no tools defined, fall back to auto if (this.aiToolDefinitions.length === 0) { diff --git a/AIClasses/Gemini/Gemini.ts b/AIClasses/Gemini/Gemini.ts index 837866a..7f1de82 100644 --- a/AIClasses/Gemini/Gemini.ts +++ b/AIClasses/Gemini/Gemini.ts @@ -5,7 +5,7 @@ import type { Attachment } from "Conversations/Attachment"; import { Role } from "Enums/Role"; import { AIProvider, AIProviderURL } from "Enums/ApiProvider"; import { AIToolCall } from "AIClasses/AIToolCall"; -import { fromString as aiToolFromString } from "Enums/AITool"; +import { AITool, fromString as aiToolFromString } from "Enums/AITool"; import type { IAIToolDefinition } from "AIClasses/ToolDefinitions/IAIToolDefinition"; import type { ConversationContent } from "Conversations/ConversationContent"; import type { Candidate, Part, FunctionDeclaration } from "@google/genai"; @@ -22,7 +22,6 @@ import { Copy, replaceCopy } from "Enums/Copy"; export class Gemini extends BaseAIClass { - private readonly REQUEST_WEB_SEARCH: string = "request_web_search"; private readonly SUPPORTED_MIMETYPES = [ // Common Text @@ -83,7 +82,7 @@ export class Gemini extends BaseAIClass { public async* streamRequest(conversation: Conversation): AsyncGenerator { // next request should use web search only (gemini api doesn't support custom tooling and grounding at the same time) - const requestWebSearch = this.accumulatedFunctionName == this.REQUEST_WEB_SEARCH; + const requestWebSearch = this.accumulatedFunctionName == AITool.RequestWebSearch; this.accumulatedFunctionName = null; this.accumulatedFunctionArgs = {}; @@ -96,18 +95,7 @@ export class Gemini extends BaseAIClass { const contents = await this.extractContents(conversation.contents); - const tools = requestWebSearch ? { google_search: {} } : - { - functionDeclarations: [ - { - name: "request_web_search", - description: `Use this function when you need to search the web for current - information, recent events, news, or facts that may have changed. - After calling this, you will be able to perform web searches.`, - }, - ...this.mapFunctionDefinitions(this.aiToolDefinitions), - ] - } + const tools = requestWebSearch ? { google_search: {} } : this.getTools() const requestBody: Record = { system_instruction: { @@ -326,7 +314,7 @@ export class Gemini extends BaseAIClass { })); } - public formatBinaryFiles(attachments: Attachment[]): string { + protected formatBinaryFiles(attachments: Attachment[]): string { const parts: unknown[] = []; for (const attachment of attachments) { @@ -361,6 +349,23 @@ export class Gemini extends BaseAIClass { return JSON.stringify(parts); } + private getTools(): { functionDeclarations: FunctionDeclaration[] } { + if (this.settingsService.settings.enableWebSearch) { + return { + functionDeclarations: [ + { + name: AITool.RequestWebSearch, + description: `Use this function when you need to search the web for current + information, recent events, news, or facts that may have changed. + After calling this, you will be able to perform web searches.`, + }, + ...this.mapFunctionDefinitions(this.aiToolDefinitions), + ] + }; + } + return { functionDeclarations: this.mapFunctionDefinitions(this.aiToolDefinitions) }; + } + private buildGeminiToolConfig(): { function_calling_config: { mode: string } } { // If no tools defined, fall back to auto if (this.aiToolDefinitions.length === 0) { diff --git a/AIClasses/IAIClass.ts b/AIClasses/IAIClass.ts index 6c01dc6..52b8d60 100644 --- a/AIClasses/IAIClass.ts +++ b/AIClasses/IAIClass.ts @@ -1,6 +1,5 @@ import type { IStreamChunk } from "Services/StreamingService"; import type { Conversation } from "Conversations/Conversation"; -import type { Attachment } from "Conversations/Attachment"; import type { IAIToolDefinition } from "./ToolDefinitions/IAIToolDefinition"; import type { AIProvider } from "Enums/ApiProvider"; import type { AgentType } from "Enums/AgentType"; @@ -17,7 +16,6 @@ export interface IAIClass { set aiToolUsageMode(mode: AIToolUsageMode); streamRequest(conversation: Conversation): AsyncGenerator; - formatBinaryFiles(attachments: Attachment[]): string; // Optional provider-level tool call handler. Called before AIToolService. resolveToolCall?(toolCall: AIToolCall): Promise; diff --git a/AIClasses/Mistral/Mistral.ts b/AIClasses/Mistral/Mistral.ts index d658f73..6cc7474 100644 --- a/AIClasses/Mistral/Mistral.ts +++ b/AIClasses/Mistral/Mistral.ts @@ -46,24 +46,7 @@ export class Mistral extends BaseAIClass { public async* streamRequest(conversation: Conversation): AsyncGenerator { const messages = await this.buildMessages(conversation); - - const tools = [ - { - type: "function" as const, - function: { - name: AITool.RequestWebSearch, - description: `Use this function when you need to search the web for current information, recent events, news, or facts that may have changed.`, - parameters: { - type: "object" as const, - properties: { - query: { type: "string", description: "The search query to look up on the web." } - }, - required: ["query"] - } - } - }, - ...this.mapFunctionDefinitions(this.aiToolDefinitions) - ]; + const tools = this.getTools(); const requestBody: Record = { model: this.model(), @@ -357,7 +340,7 @@ export class Mistral extends BaseAIClass { })); } - public formatBinaryFiles(attachments: Attachment[]): string { + protected formatBinaryFiles(attachments: Attachment[]): string { const contentParts: MistralContentPart[] = []; const fileService = this.aiFileService as MistralFileService; @@ -420,6 +403,29 @@ export class Mistral extends BaseAIClass { return JSON.stringify(contentParts); } + private getTools(): MistralToolDefinition[] { + if (this.settingsService.settings.enableWebSearch) { + return [ + { + type: "function" as const, + function: { + name: AITool.RequestWebSearch, + description: `Use this function when you need to search the web for current information, recent events, news, or facts that may have changed.`, + parameters: { + type: "object" as const, + properties: { + query: { type: "string", description: "The search query to look up on the web." } + }, + required: ["query"] + } + } + }, + ...this.mapFunctionDefinitions(this.aiToolDefinitions) + ]; + } + return this.mapFunctionDefinitions(this.aiToolDefinitions); + } + private buildMistralToolChoice(): string { if (this.aiToolDefinitions.length === 0) { return "auto"; diff --git a/AIClasses/OpenAI/OpenAI.ts b/AIClasses/OpenAI/OpenAI.ts index 3995bda..e5352e0 100644 --- a/AIClasses/OpenAI/OpenAI.ts +++ b/AIClasses/OpenAI/OpenAI.ts @@ -42,9 +42,7 @@ export class OpenAI extends BaseAIClass { const input = await this.extractContents(conversation.contents); - const tools = [{ - type: "web_search" - }, ...this.mapFunctionDefinitions(this.aiToolDefinitions)]; + const tools = this.getTools(); const requestBody = { model: this.model(), @@ -329,7 +327,7 @@ export class OpenAI extends BaseAIClass { })); } - public formatBinaryFiles(attachments: Attachment[]): string { + protected formatBinaryFiles(attachments: Attachment[]): string { const contentBlocks: unknown[] = []; for (const attachment of attachments) { @@ -367,6 +365,15 @@ export class OpenAI extends BaseAIClass { }]); } + private getTools(): (OpenAIToolTool | { type: string })[] { + if (this.settingsService.settings.enableWebSearch) { + return [{ + type: "web_search" + }, ...this.mapFunctionDefinitions(this.aiToolDefinitions)]; + } + return this.mapFunctionDefinitions(this.aiToolDefinitions); + } + private buildOpenAIToolChoice(): string { // If no tools defined, fall back to auto if (this.aiToolDefinitions.length === 0) { diff --git a/Components/ChatInput.svelte b/Components/ChatInput.svelte index 9a62a21..d8a84f2 100644 --- a/Components/ChatInput.svelte +++ b/Components/ChatInput.svelte @@ -21,6 +21,7 @@ import { Copy, replaceCopy } from "Enums/Copy"; import { HelpModal } from "Modals/HelpModal"; import type { IPrompt } from "AIPrompts/IPrompt"; + import type { SettingsService } from "Services/SettingsService"; export let attachments: Attachment[] = []; @@ -32,6 +33,7 @@ export let onStop: () => void; const inputService: InputService = Resolve(Services.InputService); + const settingsService: SettingsService = Resolve(Services.SettingsService); const userInputService: UserInputService = Resolve(Services.UserInputService); const searchStateStore: SearchStateStore = Resolve(Services.SearchStateStore); const diffService: DiffService = Resolve(Services.DiffService); @@ -43,6 +45,7 @@ let inputDisplay: InputDisplay; let textareaElement: HTMLDivElement; let userInstructionButton: HTMLButtonElement; + let webSearchButton: HTMLButtonElement; let submitButton: HTMLButtonElement; let editModeButton: HTMLButtonElement; let planningModeButton: HTMLButtonElement; @@ -177,6 +180,10 @@ setIcon(userInstructionButton, "user-round-pen"); } + $: if (webSearchButton) { + setIcon(webSearchButton, "globe"); + } + $: userInstructionAreaActive, (() => { tick().then(async () => { userInstructionActive = (await aiPrompt.userInstruction()).trim() !== ""; @@ -278,6 +285,11 @@ searchStateStore.resetSearch(); } + function toggleWebSearch() { + settingsService.settings.enableWebSearch = !settingsService.settings.enableWebSearch; + settingsService.saveSettings(); + } + function toggleEditMode() { if (planningModeActive) { planningModeActive = false @@ -515,7 +527,15 @@ class:instruction-active={userInstructionActive} bind:this={userInstructionButton} on:click={toggleUserInstructionArea} - aria-label="User Instruction"> + aria-label={Copy.ButtonUserInstruction}> + + +
diff --git a/Enums/Copy.ts b/Enums/Copy.ts index cee2d37..9dc1898 100644 --- a/Enums/Copy.ts +++ b/Enums/Copy.ts @@ -107,6 +107,9 @@ export enum Copy { ButtonTurnOnAgentMode = "Turn on Agent Mode", ButtonTurnOffPlanningMode = "Turn off Planning Mode", ButtonTurnOnPlanningMode = "Turn on Planning Mode", + ButtonTurnOffWebSearch = "Turn off Web Search", + ButtonTurnOnWebSearch = "Turn on Web Search", + ButtonUserInstruction = "User Instruction", // Agent file message AttachedFile = `The user has attached the file {fileName}. The contents of the file are included below. diff --git a/Services/SettingsService.ts b/Services/SettingsService.ts index 390ff19..2063cb1 100644 --- a/Services/SettingsService.ts +++ b/Services/SettingsService.ts @@ -21,7 +21,9 @@ const DEFAULT_SETTINGS: IVaultkeeperAISettings = { snippetSizeLimit: 100, enableMemories: false, - allowUpdatingMemories: true + allowUpdatingMemories: true, + + enableWebSearch: true } export interface IVaultkeeperAISettings { @@ -43,6 +45,8 @@ export interface IVaultkeeperAISettings { enableMemories: boolean; allowUpdatingMemories: boolean; + + enableWebSearch: boolean; } export class SettingsService { diff --git a/__tests__/AIClasses/Claude.test.ts b/__tests__/AIClasses/Claude.test.ts index ff602bf..c3edd1d 100644 --- a/__tests__/AIClasses/Claude.test.ts +++ b/__tests__/AIClasses/Claude.test.ts @@ -1236,7 +1236,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1264,7 +1264,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1292,7 +1292,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1316,7 +1316,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1340,7 +1340,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1364,7 +1364,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1405,7 +1405,7 @@ describe('Claude', () => { } ]; - const result = claude.formatBinaryFiles(attachments as any); + const result = (claude as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(6); @@ -1472,7 +1472,7 @@ describe('Claude', () => { } ]; - const result = claude.formatBinaryFiles(attachments as any); + const result = (claude as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(5); @@ -1500,7 +1500,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); // Should return empty array when no file ID is available @@ -1529,7 +1529,7 @@ describe('Claude', () => { } ]; - const result = claude.formatBinaryFiles(attachments as any); + const result = (claude as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(4); @@ -1542,7 +1542,7 @@ describe('Claude', () => { it('should handle empty files array', () => { const attachments: any[] = []; - const result = claude.formatBinaryFiles(attachments); + const result = (claude as any).formatBinaryFiles(attachments); const parsed = JSON.parse(result); expect(parsed).toHaveLength(0); @@ -1559,7 +1559,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed[0].text).toBe(replaceCopy(Copy.AttachedFile, ["report (final) v2.pdf"])); @@ -1576,7 +1576,7 @@ describe('Claude', () => { deleteFileID: vi.fn() }; - const result = claude.formatBinaryFiles([attachment as any]); + const result = (claude as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed[1]).toEqual({ diff --git a/__tests__/AIClasses/Gemini.test.ts b/__tests__/AIClasses/Gemini.test.ts index d32e080..e82cca1 100644 --- a/__tests__/AIClasses/Gemini.test.ts +++ b/__tests__/AIClasses/Gemini.test.ts @@ -337,7 +337,35 @@ describe('Gemini', () => { }); describe('Web Search Toggle', () => { - it('should use custom tools by default', async () => { + it('should use custom tools by default (no request_web_search when disabled)', async () => { + mockSettingsService.settings.enableWebSearch = false; + + const conversation = new Conversation(); + conversation.contents.push(new ConversationContent({ role: Role.User, content: 'Test' })); + + mockStreamingService.streamRequest.mockImplementation(async function* () { + yield { content: 'done', isComplete: true }; + }); + + const generator = gemini.streamRequest(conversation); + for await (const chunk of generator) {} + + const callArgs = mockStreamingService.streamRequest.mock.calls[0]; + const requestBody = callArgs[1]; + + expect(requestBody.tools[0]).toHaveProperty('functionDeclarations'); + expect(requestBody.tools[0].functionDeclarations).toBeInstanceOf(Array); + + // Should NOT include request_web_search when web search is disabled + const hasWebSearchFunc = requestBody.tools[0].functionDeclarations.some( + (f: any) => f.name === 'request_web_search' + ); + expect(hasWebSearchFunc).toBe(false); + }); + + it('should include request_web_search function when web search is enabled', async () => { + mockSettingsService.settings.enableWebSearch = true; + const conversation = new Conversation(); conversation.contents.push(new ConversationContent({ role: Role.User, content: 'Test' })); @@ -355,7 +383,6 @@ describe('Gemini', () => { expect(requestBody.tools[0].functionDeclarations).toBeInstanceOf(Array); expect(requestBody.tools[0].functionDeclarations.length).toBeGreaterThan(0); - // Should include request_web_search function const hasWebSearchFunc = requestBody.tools[0].functionDeclarations.some( (f: any) => f.name === 'request_web_search' ); @@ -1131,7 +1158,7 @@ describe('Gemini', () => { deleteFileID: vi.fn() }; - const result = gemini.formatBinaryFiles([attachment as any]); + const result = (gemini as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1157,7 +1184,7 @@ describe('Gemini', () => { deleteFileID: vi.fn() }; - const result = gemini.formatBinaryFiles([attachment as any]); + const result = (gemini as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1183,7 +1210,7 @@ describe('Gemini', () => { deleteFileID: vi.fn() }; - const result = gemini.formatBinaryFiles([attachment as any]); + const result = (gemini as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -1206,7 +1233,7 @@ describe('Gemini', () => { deleteFileID: vi.fn() }; - const result = gemini.formatBinaryFiles([attachment as any]); + const result = (gemini as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1226,7 +1253,7 @@ describe('Gemini', () => { deleteFileID: vi.fn() }; - const result = gemini.formatBinaryFiles([attachment as any]); + const result = (gemini as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1266,7 +1293,7 @@ describe('Gemini', () => { } ]; - const result = gemini.formatBinaryFiles(attachments as any); + const result = (gemini as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(6); @@ -1330,7 +1357,7 @@ describe('Gemini', () => { } ]; - const result = gemini.formatBinaryFiles(attachments as any); + const result = (gemini as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(5); @@ -1366,7 +1393,7 @@ describe('Gemini', () => { } ]; - const result = gemini.formatBinaryFiles(attachments as any); + const result = (gemini as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); // Only successful upload @@ -1380,7 +1407,7 @@ describe('Gemini', () => { }); it('should handle empty attachments array', () => { - const result = gemini.formatBinaryFiles([]); + const result = (gemini as any).formatBinaryFiles([]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(0); @@ -1397,7 +1424,7 @@ describe('Gemini', () => { deleteFileID: vi.fn() }; - const result = gemini.formatBinaryFiles([attachment as any]); + const result = (gemini as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed[0].text).toBe(replaceCopy(Copy.AttachedFile, ["report (final) v2.pdf"])); @@ -1414,7 +1441,7 @@ describe('Gemini', () => { deleteFileID: vi.fn() }; - const result = gemini.formatBinaryFiles([attachment as any]); + const result = (gemini as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed[1]).toEqual({ diff --git a/__tests__/AIClasses/Mistral.test.ts b/__tests__/AIClasses/Mistral.test.ts index 7d35b8a..8874441 100644 --- a/__tests__/AIClasses/Mistral.test.ts +++ b/__tests__/AIClasses/Mistral.test.ts @@ -700,7 +700,7 @@ describe('Mistral', () => { deleteFileID: vi.fn() }; - const result = mistral.formatBinaryFiles([attachment as any]); + const result = (mistral as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -725,7 +725,7 @@ describe('Mistral', () => { deleteFileID: vi.fn() }; - const result = mistral.formatBinaryFiles([attachment as any]); + const result = (mistral as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -750,7 +750,7 @@ describe('Mistral', () => { deleteFileID: vi.fn() }; - const result = mistral.formatBinaryFiles([attachment as any]); + const result = (mistral as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); @@ -771,7 +771,7 @@ describe('Mistral', () => { deleteFileID: vi.fn() }; - const result = mistral.formatBinaryFiles([attachment as any]); + const result = (mistral as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -803,7 +803,7 @@ describe('Mistral', () => { } ]; - const result = mistral.formatBinaryFiles(attachments as any); + const result = (mistral as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(4); @@ -834,7 +834,7 @@ describe('Mistral', () => { deleteFileID: vi.fn() }; - const result = mistral.formatBinaryFiles([attachment as any]); + const result = (mistral as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); // Should return empty array when no file ID is available @@ -844,7 +844,7 @@ describe('Mistral', () => { it('should handle empty files array', () => { const attachments: any[] = []; - const result = mistral.formatBinaryFiles(attachments); + const result = (mistral as any).formatBinaryFiles(attachments); const parsed = JSON.parse(result); expect(parsed).toHaveLength(0); diff --git a/__tests__/AIClasses/OpenAI.test.ts b/__tests__/AIClasses/OpenAI.test.ts index ff682c0..c7ec113 100644 --- a/__tests__/AIClasses/OpenAI.test.ts +++ b/__tests__/AIClasses/OpenAI.test.ts @@ -976,7 +976,9 @@ describe('OpenAI', () => { ); }); - it('should include name field in web_search tool', async () => { + it('should include web_search tool when web search is enabled', async () => { + mockSettingsService.settings.enableWebSearch = true; + const conversation = new Conversation(); conversation.contents.push(new ConversationContent({ role: Role.User, content: 'Test message' })); @@ -993,7 +995,6 @@ describe('OpenAI', () => { expect(webSearchTool).toBeDefined(); expect(webSearchTool.type).toBe('web_search'); - expect(webSearchTool.name).toBe(undefined); }); }); @@ -1009,7 +1010,7 @@ describe('OpenAI', () => { deleteFileID: vi.fn() }; - const result = openai.formatBinaryFiles([attachment as any]); + const result = (openai as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1036,7 +1037,7 @@ describe('OpenAI', () => { deleteFileID: vi.fn() }; - const result = openai.formatBinaryFiles([attachment as any]); + const result = (openai as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1063,7 +1064,7 @@ describe('OpenAI', () => { deleteFileID: vi.fn() }; - const result = openai.formatBinaryFiles([attachment as any]); + const result = (openai as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1089,7 +1090,7 @@ describe('OpenAI', () => { deleteFileID: vi.fn() }; - const result = openai.formatBinaryFiles([attachment as any]); + const result = (openai as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1115,7 +1116,7 @@ describe('OpenAI', () => { deleteFileID: vi.fn() }; - const result = openai.formatBinaryFiles([attachment as any]); + const result = (openai as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1137,7 +1138,7 @@ describe('OpenAI', () => { deleteFileID: vi.fn() }; - const result = openai.formatBinaryFiles([attachment as any]); + const result = (openai as any).formatBinaryFiles([attachment as any]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1176,7 +1177,7 @@ describe('OpenAI', () => { } ]; - const result = openai.formatBinaryFiles(attachments as any); + const result = (openai as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1242,7 +1243,7 @@ describe('OpenAI', () => { } ]; - const result = openai.formatBinaryFiles(attachments as any); + const result = (openai as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1286,7 +1287,7 @@ describe('OpenAI', () => { } ]; - const result = openai.formatBinaryFiles(attachments as any); + const result = (openai as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1302,7 +1303,7 @@ describe('OpenAI', () => { }); it('should handle empty attachments array', () => { - const result = openai.formatBinaryFiles([]); + const result = (openai as any).formatBinaryFiles([]); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); @@ -1332,7 +1333,7 @@ describe('OpenAI', () => { } ]; - const result = openai.formatBinaryFiles(attachments as any); + const result = (openai as any).formatBinaryFiles(attachments as any); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1);