From 2ee73479f98979b8583f347a1bd801b7efb7c5f0 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 8 Aug 2026 11:41:04 +0800 Subject: [PATCH] feat: add Gemini native tools and conversation context controls --- frontend/src/typings/entities/shareable.d.ts | 4 ++ .../src/views/presets/PresetFormModal.vue | 46 ++++++++++++++++++- src/adapters/impl/gemini/GeminiClient.ts | 41 ++++++++++++++++- src/channels/preset.ts | 11 ++++- src/types/adapter.ts | 5 ++ 5 files changed, 103 insertions(+), 4 deletions(-) diff --git a/frontend/src/typings/entities/shareable.d.ts b/frontend/src/typings/entities/shareable.d.ts index 1a48f56..7c32464 100644 --- a/frontend/src/typings/entities/shareable.d.ts +++ b/frontend/src/typings/entities/shareable.d.ts @@ -185,6 +185,8 @@ namespace Shareable { safetySettings?: SafetySetting[] toolCallLimit?: ToolCallLimitConfig + /** Gemini Generate Content built-in tools enabled by the preset. */ + geminiBuiltinTools?: Array<'googleSearch' | 'googleMaps' | 'codeExecution' | 'urlContext'> } interface SafetySetting { @@ -212,6 +214,8 @@ namespace Shareable { dynamicContextHistory?: 'use_system' | 'retain' | 'discard' /** Built-in tool categories enabled for this preset. */ builtinToolCategories?: Array<'mcp-discovery' | 'mcp-management' | 'skill-management'> + /** Whether to start each turn without previous user/assistant conversation context. */ + disableConversationContext?: boolean /** * 禁止系统prompt */ diff --git a/frontend/src/views/presets/PresetFormModal.vue b/frontend/src/views/presets/PresetFormModal.vue index ac9e6be..a1a2bac 100644 --- a/frontend/src/views/presets/PresetFormModal.vue +++ b/frontend/src/views/presets/PresetFormModal.vue @@ -44,7 +44,14 @@ const showModal = computed({ const groupContextOptions = [ { label: '禁用', value: 'disable' }, { label: '启用', value: 'enabled' }, - { label: '使用系统提示词', value: 'use_system' }, + { label: '使用全局配置', value: 'use_system' }, +] + +const geminiBuiltinToolOptions = [ + { label: 'Google Search', value: 'googleSearch' }, + { label: 'Google Maps', value: 'googleMaps' }, + { label: '代码执行', value: 'codeExecution' }, + { label: '网址上下文', value: 'urlContext' }, ] const dynamicContextHistoryOptions = [ @@ -124,6 +131,7 @@ const addPreset = ref>({ tools: [], } as Shareable.ToolChoice, toolGroupId: ['default_local'], + geminiBuiltinTools: [], responseModalities: undefined, safetySettings: undefined, } as Shareable.SendMessageOption, @@ -133,6 +141,7 @@ const addPreset = ref>({ dynamicContextHistory: 'use_system', builtinToolCategories: ['mcp-discovery', 'mcp-management', 'skill-management'], disableSystemInstructions: false, + disableConversationContext: false, }) // Initialize form when in edit mode @@ -153,6 +162,12 @@ watch(() => props.initialData, (newVal) => { if (!Array.isArray(data.builtinToolCategories)) { data.builtinToolCategories = ['mcp-discovery', 'mcp-management', 'skill-management'] } + if (!Array.isArray(data.sendMessageOption.geminiBuiltinTools)) { + data.sendMessageOption.geminiBuiltinTools = [] + } + if (data.disableConversationContext === undefined) { + data.disableConversationContext = false + } addPreset.value = data } @@ -214,6 +229,7 @@ watch(showModal, (val) => { tools: [], }, toolGroupId: ['default_local'], + geminiBuiltinTools: [], responseModalities: undefined, safetySettings: undefined, }, @@ -222,6 +238,8 @@ watch(showModal, (val) => { groupContext: 'use_system', dynamicContextHistory: 'use_system', builtinToolCategories: ['mcp-discovery', 'mcp-management', 'skill-management'], + disableSystemInstructions: false, + disableConversationContext: false, } } }) @@ -290,6 +308,9 @@ watch(showModal, (val) => { 禁用历史记录保存 + + 禁用对话上下文 + @@ -333,6 +354,29 @@ watch(showModal, (val) => { + + + + + + {{ option.label }} + + + + + + 仅 Gemini Generate Content API 生效;留空时不会改变原有请求。 + + + diff --git a/src/adapters/impl/gemini/GeminiClient.ts b/src/adapters/impl/gemini/GeminiClient.ts index 657e5b3..362ed46 100644 --- a/src/adapters/impl/gemini/GeminiClient.ts +++ b/src/adapters/impl/gemini/GeminiClient.ts @@ -56,6 +56,18 @@ export class GeminiClient extends AbstractClient { } messages.push(geminiContent) } + const normalizedBaseUrl = this.baseUrl.replace(/\/+$/, '') + const hasExplicitApiVersion = /\/v1(?:alpha|beta)?$/i.test(normalizedBaseUrl) + const isGoogleApi = /:\/\/(?:generativelanguage|[a-z0-9-]+-aiplatform)\.googleapis\.com(?:\/|$)/i.test(normalizedBaseUrl) + const hasBuiltinTools = (options.geminiBuiltinTools?.length || 0) > 0 + let apiVersion: string | undefined + if (hasBuiltinTools) { + if (hasExplicitApiVersion) { + apiVersion = '' + } else if (!isGoogleApi) { + apiVersion = 'v1' + } + } const ai = new GoogleGenAI({ apiKey, httpOptions: { @@ -63,13 +75,38 @@ export class GeminiClient extends AbstractClient { headers: { 'x-request-from': 'node-chaite/' + VERSION, }, + // GoogleGenAI defaults to v1beta. GCLI2API exposes its native Gemini + // endpoint under /v1; opt into that only for custom endpoints when a + // Gemini built-in tool is explicitly enabled, preserving the default + // request path when the new feature is disabled. + ...(apiVersion !== undefined ? { apiVersion } : {}), }, }) const functionDeclarations = this.tools.map(toolConverter) - const tools: Tool[] | undefined = functionDeclarations.length > 0 ? [{ + const functionTools: Tool[] = functionDeclarations.length > 0 ? [{ functionDeclarations, - }] : undefined + }] : [] + const builtinTools: Tool[] = [] + for (const builtinTool of options.geminiBuiltinTools || []) { + switch (builtinTool) { + case 'googleSearch': + builtinTools.push({ googleSearch: {} }) + break + case 'googleMaps': + builtinTools.push({ googleMaps: {} }) + break + case 'codeExecution': + builtinTools.push({ codeExecution: {} }) + break + case 'urlContext': + builtinTools.push({ urlContext: {} }) + break + } + } + const tools: Tool[] | undefined = functionTools.length > 0 || builtinTools.length > 0 + ? [...functionTools, ...builtinTools] + : undefined const modeMap = { 'none': FunctionCallingConfigMode.NONE, diff --git a/src/channels/preset.ts b/src/channels/preset.ts index a8103d7..e77ac13 100644 --- a/src/channels/preset.ts +++ b/src/channels/preset.ts @@ -17,6 +17,7 @@ export class ChatPreset extends AbstractShareable { this.dynamicContextHistory = params.dynamicContextHistory this.builtinToolCategories = params.builtinToolCategories this.disableSystemInstructions = params.disableSystemInstructions + this.disableConversationContext = params.disableConversationContext } } @@ -34,6 +35,8 @@ export class ChatPreset extends AbstractShareable { dynamicContextHistory?: 'use_system' | 'retain' | 'discard' /** Built-in tool categories enabled for this preset. Undefined enables all for compatibility. */ builtinToolCategories?: Array<'mcp-discovery' | 'mcp-management' | 'skill-management'> + /** Whether to start each turn from a detached history branch. */ + disableConversationContext?: boolean /** * 禁止系统prompt */ @@ -58,6 +61,7 @@ export class ChatPreset extends AbstractShareable { dynamicContextHistory: this.dynamicContextHistory, builtinToolCategories: this.builtinToolCategories, disableSystemInstructions: this.disableSystemInstructions, + disableConversationContext: this.disableConversationContext, }) } @@ -81,6 +85,7 @@ export class ChatPreset extends AbstractShareable { preset.dynamicContextHistory = raw.dynamicContextHistory preset.builtinToolCategories = raw.builtinToolCategories preset.disableSystemInstructions = raw.disableSystemInstructions + preset.disableConversationContext = raw.disableConversationContext if (raw.sendMessageOption) { preset.sendMessageOption = typeof raw.sendMessageOption === 'string' @@ -147,7 +152,11 @@ export class ChatPreset extends AbstractShareable { } else if (this.groupContext === 'enabled') { base += '\n携带群聊上下文' } else if (this.groupContext === 'use_system') { - base += '\n群聊上下文使用系统预设' + base += '\n群聊上下文使用全局配置' + } + + if (this.disableConversationContext) { + base += '\n禁止对话上下文' } if (this.disableSystemInstructions) { diff --git a/src/types/adapter.ts b/src/types/adapter.ts index 04f90b6..e4df066 100644 --- a/src/types/adapter.ts +++ b/src/types/adapter.ts @@ -48,6 +48,7 @@ export class SendMessageOption implements Serializable, DeSerializable): SendMessageOption { @@ -128,6 +129,9 @@ export class SendMessageOption implements Serializable, DeSerializable + /** Gemini Generate Content built-in tools enabled by the preset. */ + geminiBuiltinTools?: Array<'googleSearch' | 'googleMaps' | 'codeExecution' | 'urlContext'> + /** * 流模式的回调 * @param chunk @@ -167,6 +171,7 @@ export class SendMessageOption implements Serializable, DeSerializable