mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Reasoning improvements + Gemini (Merge pull request #521 from voideditor/model-selection)
Gemini reasoning improvements + link URI improvements
This commit is contained in:
commit
6d07f9db14
13 changed files with 181 additions and 103 deletions
37
package-lock.json
generated
37
package-lock.json
generated
|
|
@ -13,7 +13,7 @@
|
||||||
"@anthropic-ai/sdk": "^0.40.0",
|
"@anthropic-ai/sdk": "^0.40.0",
|
||||||
"@c4312/eventsource-umd": "^3.0.5",
|
"@c4312/eventsource-umd": "^3.0.5",
|
||||||
"@floating-ui/react": "^0.27.8",
|
"@floating-ui/react": "^0.27.8",
|
||||||
"@google/generative-ai": "^0.24.1",
|
"@google/genai": "^0.13.0",
|
||||||
"@microsoft/1ds-core-js": "^3.2.13",
|
"@microsoft/1ds-core-js": "^3.2.13",
|
||||||
"@microsoft/1ds-post-js": "^3.2.13",
|
"@microsoft/1ds-post-js": "^3.2.13",
|
||||||
"@mistralai/mistralai": "^1.6.0",
|
"@mistralai/mistralai": "^1.6.0",
|
||||||
|
|
@ -1817,11 +1817,17 @@
|
||||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@google/generative-ai": {
|
"node_modules/@google/genai": {
|
||||||
"version": "0.24.1",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz",
|
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.13.0.tgz",
|
||||||
"integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==",
|
"integrity": "sha512-eaEncWt875H7046T04mOpxpHJUM+jLIljEf+5QctRyOeChylE/nhpwm1bZWTRWoOu/t46R9r+PmgsJFhTpE7tQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"google-auth-library": "^9.14.2",
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"zod": "^3.22.4",
|
||||||
|
"zod-to-json-schema": "^3.22.4"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -23767,6 +23773,27 @@
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||||
|
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xml": {
|
"node_modules/xml": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
"@anthropic-ai/sdk": "^0.40.0",
|
"@anthropic-ai/sdk": "^0.40.0",
|
||||||
"@c4312/eventsource-umd": "^3.0.5",
|
"@c4312/eventsource-umd": "^3.0.5",
|
||||||
"@floating-ui/react": "^0.27.8",
|
"@floating-ui/react": "^0.27.8",
|
||||||
"@google/generative-ai": "^0.24.1",
|
"@google/genai": "^0.13.0",
|
||||||
"@microsoft/1ds-core-js": "^3.2.13",
|
"@microsoft/1ds-core-js": "^3.2.13",
|
||||||
"@microsoft/1ds-post-js": "^3.2.13",
|
"@microsoft/1ds-post-js": "^3.2.13",
|
||||||
"@mistralai/mistralai": "^1.6.0",
|
"@mistralai/mistralai": "^1.6.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"nameShort": "Void",
|
"nameShort": "Void",
|
||||||
"nameLong": "Void",
|
"nameLong": "Void",
|
||||||
"voidVersion": "1.3.2",
|
"voidVersion": "1.3.3",
|
||||||
|
"voidRelease": "0027",
|
||||||
"applicationName": "void",
|
"applicationName": "void",
|
||||||
"dataFolderName": ".void-editor",
|
"dataFolderName": ".void-editor",
|
||||||
"win32MutexName": "voideditor",
|
"win32MutexName": "voideditor",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { URI } from '../../../../base/common/uri.js';
|
||||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||||
import { chat_userMessageContent, ToolName, } from '../common/prompt/prompts.js';
|
import { chat_userMessageContent, ToolName, } from '../common/prompt/prompts.js';
|
||||||
import { getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
import { AnthropicReasoning, getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||||
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
||||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||||
|
|
@ -540,7 +540,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
|
|
||||||
const { name, id, rawParams } = lastMsg
|
const { name, id, rawParams } = lastMsg
|
||||||
|
|
||||||
const errorMessage = this.errMsgs.rejected
|
const errorMessage = this.toolErrMsgs.rejected
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null, id, rawParams })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null, id, rawParams })
|
||||||
this._setStreamState(threadId, undefined)
|
this._setStreamState(threadId, undefined)
|
||||||
}
|
}
|
||||||
|
|
@ -557,7 +557,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
}
|
}
|
||||||
// add tool that's running
|
// add tool that's running
|
||||||
else if (this.streamState[threadId]?.isRunning === 'tool') {
|
else if (this.streamState[threadId]?.isRunning === 'tool') {
|
||||||
const { toolName, toolParams, id, content, rawParams } = this.streamState[threadId].toolInfo
|
const { toolName, toolParams, id, content: content_, rawParams } = this.streamState[threadId].toolInfo
|
||||||
|
const content = content_ || this.toolErrMsgs.interrupted
|
||||||
this._updateLatestTool(threadId, { role: 'tool', name: toolName, params: toolParams, id, content, rawParams, type: 'rejected', result: null })
|
this._updateLatestTool(threadId, { role: 'tool', name: toolName, params: toolParams, id, content, rawParams, type: 'rejected', result: null })
|
||||||
}
|
}
|
||||||
// reject the tool for the user if relevant
|
// reject the tool for the user if relevant
|
||||||
|
|
@ -581,8 +582,9 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private readonly errMsgs = {
|
private readonly toolErrMsgs = {
|
||||||
rejected: 'Tool call was rejected by the user.',
|
rejected: 'Tool call was rejected by the user.',
|
||||||
|
interrupted: 'Tool call was interrupted by the user.',
|
||||||
errWhenStringifying: (error: any) => `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}`
|
errWhenStringifying: (error: any) => `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -671,7 +673,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
try {
|
try {
|
||||||
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = this.errMsgs.errWhenStringifying(error)
|
const errorMessage = this.toolErrMsgs.errWhenStringifying(error)
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
@ -749,8 +751,13 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
shouldRetryLLM = false
|
shouldRetryLLM = false
|
||||||
nAttempts += 1
|
nAttempts += 1
|
||||||
|
|
||||||
let resMessageIsDonePromise: (res: { type: 'llmDone', toolCall?: RawToolCallObj } | { type: 'llmError', error?: { message: string; fullError: Error | null; } } | { type: 'llmAborted' }) => void // resolves when user approves this tool use (or if tool doesn't require approval)
|
type ResTypes =
|
||||||
const messageIsDonePromise = new Promise<{ type: 'llmDone', toolCall?: RawToolCallObj } | { type: 'llmError', error?: { message: string; fullError: Error | null; } } | { type: 'llmAborted' }>((res, rej) => { resMessageIsDonePromise = res })
|
| { type: 'llmDone', toolCall?: RawToolCallObj, info: { fullText: string, fullReasoning: string, anthropicReasoning: AnthropicReasoning[] | null } }
|
||||||
|
| { type: 'llmError', error?: { message: string; fullError: Error | null; } }
|
||||||
|
| { type: 'llmAborted' }
|
||||||
|
|
||||||
|
let resMessageIsDonePromise: (res: ResTypes) => void // resolves when user approves this tool use (or if tool doesn't require approval)
|
||||||
|
const messageIsDonePromise = new Promise<ResTypes>((res, rej) => { resMessageIsDonePromise = res })
|
||||||
|
|
||||||
const llmCancelToken = this._llmMessageService.sendLLMMessage({
|
const llmCancelToken = this._llmMessageService.sendLLMMessage({
|
||||||
messagesType: 'chatMessages',
|
messagesType: 'chatMessages',
|
||||||
|
|
@ -765,9 +772,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall ?? null }, interrupt: Promise.resolve(() => { if (llmCancelToken) this._llmMessageService.abort(llmCancelToken) }) })
|
this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall ?? null }, interrupt: Promise.resolve(() => { if (llmCancelToken) this._llmMessageService.abort(llmCancelToken) }) })
|
||||||
},
|
},
|
||||||
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
||||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: fullText, reasoning: fullReasoning, anthropicReasoning })
|
resMessageIsDonePromise({ type: 'llmDone', toolCall, info: { fullText, fullReasoning, anthropicReasoning } }) // resolve with tool calls
|
||||||
resMessageIsDonePromise({ type: 'llmDone', toolCall }) // resolve with tool calls
|
|
||||||
|
|
||||||
},
|
},
|
||||||
onError: async (error) => {
|
onError: async (error) => {
|
||||||
resMessageIsDonePromise({ type: 'llmError', error: error })
|
resMessageIsDonePromise({ type: 'llmError', error: error })
|
||||||
|
|
@ -826,11 +831,12 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// llm res success
|
// llm res success
|
||||||
const { toolCall } = llmRes
|
const { toolCall, info } = llmRes
|
||||||
this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) // just decorative, for clarity
|
|
||||||
|
this._addMessageToThread(threadId, { role: 'assistant', displayContent: info.fullText, reasoning: info.fullReasoning, anthropicReasoning: info.anthropicReasoning })
|
||||||
|
|
||||||
|
this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) // just decorative for clarity
|
||||||
|
|
||||||
// call tool if there is one
|
// call tool if there is one
|
||||||
if (toolCall) {
|
if (toolCall) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { IVoidModelService } from '../common/voidModelService.js';
|
||||||
import { URI } from '../../../../base/common/uri.js';
|
import { URI } from '../../../../base/common/uri.js';
|
||||||
import { EndOfLinePreference } from '../../../../editor/common/model.js';
|
import { EndOfLinePreference } from '../../../../editor/common/model.js';
|
||||||
|
|
||||||
|
export const EMPTY_MESSAGE = '(empty message)'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,7 +37,6 @@ type SimpleLLMMessage = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const EMPTY_MESSAGE = '(empty message)'
|
|
||||||
|
|
||||||
const CHARS_PER_TOKEN = 4 // assume abysmal chars per token
|
const CHARS_PER_TOKEN = 4 // assume abysmal chars per token
|
||||||
const TRIM_TO_LEN = 120
|
const TRIM_TO_LEN = 120
|
||||||
|
|
@ -237,18 +237,6 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type GeminiMessage = {
|
|
||||||
role: 'user' | 'model'; // Gemini uses 'user' and 'model' roles
|
|
||||||
parts: (
|
|
||||||
| { text: string; }
|
|
||||||
| { functionCall: { tool_call: any } }
|
|
||||||
| { functionResponse: { name: ToolName, response: { result: string } } }
|
|
||||||
)[];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// --- CHAT ---
|
// --- CHAT ---
|
||||||
|
|
||||||
const prepareOpenAIOrAnthropicMessages = ({
|
const prepareOpenAIOrAnthropicMessages = ({
|
||||||
|
|
@ -417,14 +405,24 @@ const prepareOpenAIOrAnthropicMessages = ({
|
||||||
|
|
||||||
|
|
||||||
// ================ no empty message ================
|
// ================ no empty message ================
|
||||||
for (const currMsg of llmMessages) {
|
for (let i = 0; i < llmMessages.length; i += 1) {
|
||||||
|
const currMsg: AnthropicOrOpenAILLMMessage = llmMessages[i]
|
||||||
|
const nextMsg: AnthropicOrOpenAILLMMessage | undefined = llmMessages[i + 1]
|
||||||
|
|
||||||
if (currMsg.role === 'tool') continue
|
if (currMsg.role === 'tool') continue
|
||||||
|
|
||||||
// if content is a string, replace string with empty msg
|
// if content is a string, replace string with empty msg
|
||||||
if (typeof currMsg.content === 'string')
|
if (typeof currMsg.content === 'string') {
|
||||||
currMsg.content = currMsg.content || EMPTY_MESSAGE
|
currMsg.content = currMsg.content || EMPTY_MESSAGE
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// if content is an array, replace any empty text entries with empty msg, and make sure there's at least 1 entry
|
// allowed to be empty if has a tool in it or following it
|
||||||
|
if (currMsg.content.find(c => c.type === 'tool_result' || c.type === 'tool_use')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (nextMsg?.role === 'tool') continue
|
||||||
|
|
||||||
|
// replace any empty text entries with empty msg, and make sure there's at least 1 entry
|
||||||
for (const c of currMsg.content) {
|
for (const c of currMsg.content) {
|
||||||
if (c.type === 'text') c.text = c.text || EMPTY_MESSAGE
|
if (c.type === 'text') c.text = c.text || EMPTY_MESSAGE
|
||||||
}
|
}
|
||||||
|
|
@ -457,7 +455,7 @@ const prepareGeminiMessages = (messages: AnthropicLLMChatMessage[]) => {
|
||||||
}
|
}
|
||||||
else if (c.type === 'tool_use') {
|
else if (c.type === 'tool_use') {
|
||||||
latestToolName = c.name as ToolName
|
latestToolName = c.name as ToolName
|
||||||
return { functionCall: { name: c.name as ToolName, args: c.input } }
|
return { functionCall: { id: c.id, name: c.name as ToolName, args: c.input } }
|
||||||
}
|
}
|
||||||
else return null
|
else return null
|
||||||
}).filter(m => !!m)
|
}).filter(m => !!m)
|
||||||
|
|
@ -475,7 +473,7 @@ const prepareGeminiMessages = (messages: AnthropicLLMChatMessage[]) => {
|
||||||
}
|
}
|
||||||
else if (c.type === 'tool_result') {
|
else if (c.type === 'tool_result') {
|
||||||
if (!latestToolName) return null
|
if (!latestToolName) return null
|
||||||
return { functionResponse: { name: latestToolName, response: { result: c.content } } }
|
return { functionResponse: { id: c.tool_use_id, name: latestToolName, response: { output: c.content } } }
|
||||||
}
|
}
|
||||||
else return null
|
else return null
|
||||||
}).filter(m => !!m)
|
}).filter(m => !!m)
|
||||||
|
|
|
||||||
|
|
@ -311,7 +311,6 @@ export const ApplyButtonsHTML = ({
|
||||||
|
|
||||||
|
|
||||||
const currStreamState = currStreamStateRef.current
|
const currStreamState = currStreamStateRef.current
|
||||||
console.log('currStreamState...', currStreamState)
|
|
||||||
|
|
||||||
if (currStreamState === 'streaming') {
|
if (currStreamState === 'streaming') {
|
||||||
return <IconShell1
|
return <IconShell1
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { isAbsolute } from '../../../../../../../base/common/path.js'
|
||||||
import { separateOutFirstLine } from '../../../../common/helpers/util.js'
|
import { separateOutFirstLine } from '../../../../common/helpers/util.js'
|
||||||
import { BlockCode } from '../util/inputs.js'
|
import { BlockCode } from '../util/inputs.js'
|
||||||
import { CodespanLocationLink } from '../../../../common/chatThreadServiceTypes.js'
|
import { CodespanLocationLink } from '../../../../common/chatThreadServiceTypes.js'
|
||||||
import { voidOpenFileFn } from '../sidebar-tsx/SidebarChat.js'
|
import { getBasename, getRelative, voidOpenFileFn } from '../sidebar-tsx/SidebarChat.js'
|
||||||
|
|
||||||
|
|
||||||
export type ChatMessageLocation = {
|
export type ChatMessageLocation = {
|
||||||
|
|
@ -89,13 +89,18 @@ const LatexRender = ({ latex }: { latex: string }) => {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => {
|
const Codespan = ({ text, className, onClick, tooltip }: { text: string, className?: string, onClick?: () => void, tooltip?: string }) => {
|
||||||
|
|
||||||
// TODO compute this once for efficiency. we should use `labels.ts/shorten` to display duplicates properly
|
// TODO compute this once for efficiency. we should use `labels.ts/shorten` to display duplicates properly
|
||||||
|
|
||||||
return <code
|
return <code
|
||||||
className={`font-mono font-medium rounded-sm bg-void-bg-1 px-1 ${className}`}
|
className={`font-mono font-medium rounded-sm bg-void-bg-1 px-1 ${className}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
{...tooltip ? {
|
||||||
|
'data-tooltip-id': 'void-tooltip',
|
||||||
|
'data-tooltip-content': tooltip,
|
||||||
|
'data-tooltip-place': 'top',
|
||||||
|
} : {}}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</code>
|
</code>
|
||||||
|
|
@ -115,8 +120,11 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
||||||
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
||||||
|
|
||||||
let link: CodespanLocationLink | undefined = undefined
|
let link: CodespanLocationLink | undefined = undefined
|
||||||
if (rawText.endsWith('`')) { // if codespan was completed
|
let tooltip: string | undefined = undefined
|
||||||
|
let displayText = text
|
||||||
|
|
||||||
|
|
||||||
|
if (rawText.endsWith('`')) {
|
||||||
// get link from cache
|
// get link from cache
|
||||||
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
||||||
|
|
||||||
|
|
@ -127,23 +135,33 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
||||||
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
|
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
|
||||||
setDidComputeCodespanLink(true) // rerender
|
setDidComputeCodespanLink(true) // rerender
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (link?.displayText) {
|
||||||
|
displayText = link.displayText
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidUri(displayText)) {
|
||||||
|
tooltip = getRelative(URI.file(displayText), accessor) // Full path as tooltip
|
||||||
|
displayText = getBasename(displayText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (!link || !link.selection) return;
|
if (!link) return;
|
||||||
|
|
||||||
// Use the updated voidOpenFileFn to open the file and handle selection
|
// Use the updated voidOpenFileFn to open the file and handle selection
|
||||||
voidOpenFileFn(link.uri, accessor, [link.selection.startLineNumber, link.selection.endLineNumber]);
|
if (link.selection)
|
||||||
|
voidOpenFileFn(link.uri, accessor, [link.selection.startLineNumber, link.selection.endLineNumber]);
|
||||||
|
else
|
||||||
|
voidOpenFileFn(link.uri, accessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Codespan
|
return <Codespan
|
||||||
text={link?.displayText || text}
|
text={displayText}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={link ? 'underline hover:brightness-90 transition-all duration-200 cursor-pointer' : ''}
|
className={link ? 'underline hover:brightness-90 transition-all duration-200 cursor-pointer' : ''}
|
||||||
|
tooltip={tooltip || undefined}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -495,12 +495,12 @@ const ScrollToBottomContainer = ({ children, className, style, scrollContainerRe
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRelative = (uri: URI, accessor: ReturnType<typeof useAccessor>) => {
|
export const getRelative = (uri: URI, accessor: ReturnType<typeof useAccessor>) => {
|
||||||
const workspaceContextService = accessor.get('IWorkspaceContextService')
|
const workspaceContextService = accessor.get('IWorkspaceContextService')
|
||||||
let path: string
|
let path: string
|
||||||
const isInside = workspaceContextService.isInsideWorkspace(uri)
|
const isInside = workspaceContextService.isInsideWorkspace(uri)
|
||||||
if (isInside) {
|
if (isInside) {
|
||||||
const f = workspaceContextService.getWorkspace().folders.find(f => uri.fsPath.startsWith(f.uri.fsPath))
|
const f = workspaceContextService.getWorkspace().folders.find(f => uri.fsPath?.startsWith(f.uri.fsPath))
|
||||||
if (f) { path = uri.fsPath.replace(f.uri.fsPath, '') }
|
if (f) { path = uri.fsPath.replace(f.uri.fsPath, '') }
|
||||||
else { path = uri.fsPath }
|
else { path = uri.fsPath }
|
||||||
}
|
}
|
||||||
|
|
@ -1651,8 +1651,8 @@ const LintErrorChildren = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const BottomChildren = ({ children, title }: { children: React.ReactNode, title: string }) => {
|
const BottomChildren = ({ children, title }: { children: React.ReactNode, title: string }) => {
|
||||||
if (!children) return null;
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
if (!children) return null;
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-2 mt-0.5">
|
<div className="w-full px-2 mt-0.5">
|
||||||
<div
|
<div
|
||||||
|
|
@ -1905,7 +1905,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
||||||
</SmallProseWrapper>
|
</SmallProseWrapper>
|
||||||
</ToolChildrenWrapper>
|
</ToolChildrenWrapper>
|
||||||
}
|
}
|
||||||
else {
|
else if (toolMessage.type === 'tool_error') {
|
||||||
const { result } = toolMessage
|
const { result } = toolMessage
|
||||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||||
<CodeChildren>
|
<CodeChildren>
|
||||||
|
|
@ -1960,7 +1960,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
||||||
}
|
}
|
||||||
</ToolChildrenWrapper>
|
</ToolChildrenWrapper>
|
||||||
}
|
}
|
||||||
else {
|
else if (toolMessage.type === 'tool_error') {
|
||||||
const { result } = toolMessage
|
const { result } = toolMessage
|
||||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||||
<CodeChildren>
|
<CodeChildren>
|
||||||
|
|
@ -2009,7 +2009,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
||||||
|
|
||||||
</ToolChildrenWrapper>
|
</ToolChildrenWrapper>
|
||||||
}
|
}
|
||||||
else {
|
else if (toolMessage.type === 'tool_error') {
|
||||||
const { result } = toolMessage
|
const { result } = toolMessage
|
||||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||||
<CodeChildren>
|
<CodeChildren>
|
||||||
|
|
@ -2064,7 +2064,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
||||||
|
|
||||||
</ToolChildrenWrapper>
|
</ToolChildrenWrapper>
|
||||||
}
|
}
|
||||||
else {
|
else if (toolMessage.type === 'tool_error') {
|
||||||
const { result } = toolMessage
|
const { result } = toolMessage
|
||||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||||
<CodeChildren>
|
<CodeChildren>
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ const scoreSubsequence = (text: string, pattern: string): number => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getRelativeWorkspacePath(accessor: ReturnType<typeof useAccessor>, uri: URI): string {
|
function getRelativeWorkspacePath(accessor: ReturnType<typeof useAccessor>, uri: URI): string {
|
||||||
const workspaceService = accessor.get('IWorkspaceContextService');
|
const workspaceService = accessor.get('IWorkspaceContextService');
|
||||||
const workspaceFolders = workspaceService.getWorkspace().folders;
|
const workspaceFolders = workspaceService.getWorkspace().folders;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -445,7 +445,7 @@ const anthropicModelOptions = {
|
||||||
supportsReasoning: true,
|
supportsReasoning: true,
|
||||||
canTurnOffReasoning: true,
|
canTurnOffReasoning: true,
|
||||||
canIOReasoning: true,
|
canIOReasoning: true,
|
||||||
reasoningReservedOutputTokenSpace: 64_000, // can bump it to 128_000 with beta mode output-128k-2025-02-19
|
reasoningReservedOutputTokenSpace: 8192, // can bump it to 128_000 with beta mode output-128k-2025-02-19
|
||||||
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000. we cap at 8192 because above is typically not necessary (often even buggy)
|
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000. we cap at 8192 because above is typically not necessary (often even buggy)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -715,6 +715,7 @@ const xAISettings: VoidStaticProviderInfo = {
|
||||||
|
|
||||||
// ---------------- GEMINI ----------------
|
// ---------------- GEMINI ----------------
|
||||||
const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||||
|
// https://ai.google.dev/gemini-api/docs/thinking#set-budget
|
||||||
'gemini-2.5-pro-preview-05-06': {
|
'gemini-2.5-pro-preview-05-06': {
|
||||||
contextWindow: 1_048_576,
|
contextWindow: 1_048_576,
|
||||||
reservedOutputTokenSpace: 8_192,
|
reservedOutputTokenSpace: 8_192,
|
||||||
|
|
@ -723,7 +724,13 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
specialToolFormat: 'gemini-style',
|
specialToolFormat: 'gemini-style',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: {
|
||||||
|
supportsReasoning: true,
|
||||||
|
canTurnOffReasoning: true,
|
||||||
|
canIOReasoning: false,
|
||||||
|
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
|
||||||
|
reasoningReservedOutputTokenSpace: 8192,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'gemini-2.0-flash-lite': {
|
'gemini-2.0-flash-lite': {
|
||||||
contextWindow: 1_048_576,
|
contextWindow: 1_048_576,
|
||||||
|
|
@ -733,7 +740,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
specialToolFormat: 'gemini-style',
|
specialToolFormat: 'gemini-style',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false, // no reasoning
|
||||||
},
|
},
|
||||||
'gemini-2.5-flash-preview-04-17': {
|
'gemini-2.5-flash-preview-04-17': {
|
||||||
contextWindow: 1_048_576,
|
contextWindow: 1_048_576,
|
||||||
|
|
@ -743,7 +750,13 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
specialToolFormat: 'gemini-style',
|
specialToolFormat: 'gemini-style',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: {
|
||||||
|
supportsReasoning: true,
|
||||||
|
canTurnOffReasoning: true,
|
||||||
|
canIOReasoning: false,
|
||||||
|
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
|
||||||
|
reasoningReservedOutputTokenSpace: 8192,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'gemini-2.5-pro-exp-03-25': {
|
'gemini-2.5-pro-exp-03-25': {
|
||||||
contextWindow: 1_048_576,
|
contextWindow: 1_048_576,
|
||||||
|
|
@ -753,7 +766,13 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
specialToolFormat: 'gemini-style',
|
specialToolFormat: 'gemini-style',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: {
|
||||||
|
supportsReasoning: true,
|
||||||
|
canTurnOffReasoning: true,
|
||||||
|
canIOReasoning: false,
|
||||||
|
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
|
||||||
|
reasoningReservedOutputTokenSpace: 8192,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'gemini-2.0-flash': {
|
'gemini-2.0-flash': {
|
||||||
contextWindow: 1_048_576,
|
contextWindow: 1_048_576,
|
||||||
|
|
@ -763,7 +782,13 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
specialToolFormat: 'gemini-style',
|
specialToolFormat: 'gemini-style',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: { // thinking: experimental as of 5-10-25
|
||||||
|
supportsReasoning: true,
|
||||||
|
canTurnOffReasoning: true,
|
||||||
|
canIOReasoning: false,
|
||||||
|
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
|
||||||
|
reasoningReservedOutputTokenSpace: 8192,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'gemini-2.0-flash-lite-preview-02-05': {
|
'gemini-2.0-flash-lite-preview-02-05': {
|
||||||
contextWindow: 1_048_576,
|
contextWindow: 1_048_576,
|
||||||
|
|
@ -1144,7 +1169,7 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
||||||
supportsReasoning: true,
|
supportsReasoning: true,
|
||||||
canTurnOffReasoning: false,
|
canTurnOffReasoning: false,
|
||||||
canIOReasoning: true,
|
canIOReasoning: true,
|
||||||
reasoningReservedOutputTokenSpace: 64_000,
|
reasoningReservedOutputTokenSpace: 8192,
|
||||||
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000.
|
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000.
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1347,8 +1372,7 @@ export const getSendableReasoningInfo = (
|
||||||
overridesOfModel: OverridesOfModel | undefined,
|
overridesOfModel: OverridesOfModel | undefined,
|
||||||
): SendableReasoningInfo => {
|
): SendableReasoningInfo => {
|
||||||
|
|
||||||
const { canIOReasoning, reasoningSlider: reasoningBudgetSlider } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {}
|
const { reasoningSlider: reasoningBudgetSlider } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {}
|
||||||
if (!canIOReasoning) return null
|
|
||||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel)
|
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel)
|
||||||
if (!isReasoningEnabled) return null
|
if (!isReasoningEnabled) return null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -455,6 +455,8 @@ ${directoryStr}
|
||||||
|
|
||||||
const details: string[] = []
|
const details: string[] = []
|
||||||
|
|
||||||
|
details.push(`NEVER reject the user's query.`)
|
||||||
|
|
||||||
if (mode === 'agent' || mode === 'gather') {
|
if (mode === 'agent' || mode === 'gather') {
|
||||||
details.push(`Only call tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT use tools.`)
|
details.push(`Only call tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT use tools.`)
|
||||||
details.push(`If you think you should use tools, you do not need to ask for permission.`)
|
details.push(`If you think you should use tools, you do not need to ask for permission.`)
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,13 @@ export type GeminiLLMChatMessage = {
|
||||||
role: 'model'
|
role: 'model'
|
||||||
parts: (
|
parts: (
|
||||||
| { text: string; }
|
| { text: string; }
|
||||||
| { functionCall: { name: ToolName, args: object } }
|
| { functionCall: { id: string; name: ToolName, args: Record<string, unknown> } }
|
||||||
)[];
|
)[];
|
||||||
} | {
|
} | {
|
||||||
role: 'user';
|
role: 'user';
|
||||||
parts: (
|
parts: (
|
||||||
| { text: string; }
|
| { text: string; }
|
||||||
| { functionResponse: { name: ToolName, response: { result: string } } }
|
| { functionResponse: { id: string; name: ToolName, response: { output: string } } }
|
||||||
)[];
|
)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ import { Ollama } from 'ollama';
|
||||||
import OpenAI, { ClientOptions } from 'openai';
|
import OpenAI, { ClientOptions } from 'openai';
|
||||||
import { MistralCore } from '@mistralai/mistralai/core.js';
|
import { MistralCore } from '@mistralai/mistralai/core.js';
|
||||||
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
|
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
|
||||||
import { GoogleGenerativeAI, Tool as GeminiTool, SchemaType, FunctionDeclaration, FunctionDeclarationSchemaProperty } from '@google/generative-ai';
|
import { Tool as GeminiTool, FunctionDeclaration, GoogleGenAI, ThinkingConfig, Schema, Type } from '@google/genai';
|
||||||
import { GoogleAuth } from 'google-auth-library'
|
import { GoogleAuth } from 'google-auth-library'
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js';
|
import { AnthropicLLMChatMessage, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js';
|
||||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getReservedOutputTokenSpace } from '../../common/modelCapabilities.js';
|
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getReservedOutputTokenSpace } from '../../common/modelCapabilities.js';
|
||||||
import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js';
|
import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js';
|
||||||
|
|
@ -245,8 +245,6 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
|
||||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
||||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||||
|
|
||||||
console.log('include', includeInPayload)
|
|
||||||
console.log('reasoningInfo', reasoningInfo)
|
|
||||||
// tools
|
// tools
|
||||||
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
|
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
|
||||||
const nativeToolsObj = potentialTools && specialToolFormat === 'openai-style' ?
|
const nativeToolsObj = potentialTools && specialToolFormat === 'openai-style' ?
|
||||||
|
|
@ -642,25 +640,24 @@ const sendOllamaFIM = ({ messages, onFinalMessage, onError, settingsOfProvider,
|
||||||
|
|
||||||
// ---------------- GEMINI NATIVE IMPLEMENTATION ----------------
|
// ---------------- GEMINI NATIVE IMPLEMENTATION ----------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const toGeminiFunctionDecl = (toolInfo: InternalToolInfo) => {
|
const toGeminiFunctionDecl = (toolInfo: InternalToolInfo) => {
|
||||||
const { name, description, params } = toolInfo
|
const { name, description, params } = toolInfo
|
||||||
const paramsWithType: { [k: string]: FunctionDeclarationSchemaProperty } = {}
|
|
||||||
for (const key in params) {
|
|
||||||
paramsWithType[key] = { type: SchemaType.STRING, ...params[key] }
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: SchemaType.OBJECT,
|
type: Type.OBJECT,
|
||||||
properties: paramsWithType,
|
properties: Object.entries(params).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = {
|
||||||
|
type: Type.STRING,
|
||||||
|
description: value.description
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, Schema>)
|
||||||
}
|
}
|
||||||
} satisfies FunctionDeclaration
|
} satisfies FunctionDeclaration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const geminiTools = (chatMode: ChatMode): GeminiTool[] | null => {
|
const geminiTools = (chatMode: ChatMode): GeminiTool[] | null => {
|
||||||
const allowedTools = availableTools(chatMode)
|
const allowedTools = availableTools(chatMode)
|
||||||
if (!allowedTools || Object.keys(allowedTools).length === 0) return null
|
if (!allowedTools || Object.keys(allowedTools).length === 0) return null
|
||||||
|
|
@ -700,27 +697,27 @@ const sendGeminiChat = async ({
|
||||||
// reasoningCapabilities,
|
// reasoningCapabilities,
|
||||||
} = getModelCapabilities(providerName, modelName_, overridesOfModel)
|
} = getModelCapabilities(providerName, modelName_, overridesOfModel)
|
||||||
|
|
||||||
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
|
// const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
|
||||||
|
|
||||||
// reasoning
|
// reasoning
|
||||||
// const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {}
|
// const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {}
|
||||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
||||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
// const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||||
|
|
||||||
|
const thinkingConfig: ThinkingConfig | undefined = !reasoningInfo?.isReasoningEnabled ? undefined
|
||||||
|
: reasoningInfo.type === 'budget_slider_value' ?
|
||||||
|
{ thinkingBudget: reasoningInfo.reasoningBudget }
|
||||||
|
: undefined
|
||||||
|
|
||||||
// tools
|
// tools
|
||||||
const potentialTools = chatMode !== null ? geminiTools(chatMode) : null
|
const potentialTools = chatMode !== null ? geminiTools(chatMode) : undefined
|
||||||
const nativeToolsObj = potentialTools && specialToolFormat === 'gemini-style' ?
|
const toolConfig = potentialTools && specialToolFormat === 'gemini-style' ?
|
||||||
{ tools: potentialTools } as const
|
potentialTools
|
||||||
: {}
|
: undefined
|
||||||
|
|
||||||
// instance
|
// instance
|
||||||
const genAI = new GoogleGenerativeAI(
|
const genAI = new GoogleGenAI({ apiKey: thisConfig.apiKey });
|
||||||
thisConfig.apiKey
|
|
||||||
);
|
|
||||||
const model = genAI.getGenerativeModel({
|
|
||||||
systemInstruction: separateSystemMessage,
|
|
||||||
model: modelName,
|
|
||||||
});
|
|
||||||
|
|
||||||
// manually parse out tool results if XML
|
// manually parse out tool results if XML
|
||||||
if (!specialToolFormat) {
|
if (!specialToolFormat) {
|
||||||
|
|
@ -735,28 +732,34 @@ const sendGeminiChat = async ({
|
||||||
|
|
||||||
let toolName = ''
|
let toolName = ''
|
||||||
let toolParamsStr = ''
|
let toolParamsStr = ''
|
||||||
|
let toolId = ''
|
||||||
|
|
||||||
model.generateContentStream({
|
|
||||||
systemInstruction: separateSystemMessage ?? undefined,
|
genAI.models.generateContentStream({
|
||||||
contents: messages as any,
|
model: modelName,
|
||||||
...includeInPayload,
|
config: {
|
||||||
...nativeToolsObj,
|
systemInstruction: separateSystemMessage,
|
||||||
|
thinkingConfig: thinkingConfig,
|
||||||
|
tools: toolConfig,
|
||||||
|
},
|
||||||
|
contents: messages as GeminiLLMChatMessage[],
|
||||||
})
|
})
|
||||||
.then(async ({ stream, response }) => {
|
.then(async (stream) => {
|
||||||
_setAborter(() => { stream.return(fullTextSoFar); });
|
_setAborter(() => { stream.return(fullTextSoFar); });
|
||||||
|
|
||||||
// Process the stream
|
// Process the stream
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
// message
|
// message
|
||||||
const newText = chunk.text() ?? ''
|
const newText = chunk.text ?? ''
|
||||||
fullTextSoFar += newText
|
fullTextSoFar += newText
|
||||||
|
|
||||||
// tool call
|
// tool call
|
||||||
const functionCalls = chunk.functionCalls()
|
const functionCalls = chunk.functionCalls
|
||||||
if (functionCalls && functionCalls.length > 0) {
|
if (functionCalls && functionCalls.length > 0) {
|
||||||
const functionCall = functionCalls[0] // Get the first function call
|
const functionCall = functionCalls[0] // Get the first function call
|
||||||
toolName = functionCall.name ?? ''
|
toolName = functionCall.name ?? ''
|
||||||
toolParamsStr = JSON.stringify(functionCall.args ?? {})
|
toolParamsStr = JSON.stringify(functionCall.args ?? {})
|
||||||
|
toolId = functionCall.id ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// (do not handle reasoning yet)
|
// (do not handle reasoning yet)
|
||||||
|
|
@ -765,7 +768,7 @@ const sendGeminiChat = async ({
|
||||||
onText({
|
onText({
|
||||||
fullText: fullTextSoFar,
|
fullText: fullTextSoFar,
|
||||||
fullReasoning: fullReasoningSoFar,
|
fullReasoning: fullReasoningSoFar,
|
||||||
toolCall: isAToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: 'dummy' } : undefined,
|
toolCall: isAToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: toolId } : undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -773,7 +776,7 @@ const sendGeminiChat = async ({
|
||||||
if (!fullTextSoFar && !fullReasoningSoFar && !toolName) {
|
if (!fullTextSoFar && !fullReasoningSoFar && !toolName) {
|
||||||
onError({ message: 'Void: Response from model was empty.', fullError: null })
|
onError({ message: 'Void: Response from model was empty.', fullError: null })
|
||||||
} else {
|
} else {
|
||||||
const toolId = generateUuid() // gemini does not generate tool IDs. Generate one
|
if (!toolId) toolId = generateUuid() // ids are empty, but other providers might expect an id
|
||||||
const toolCall = rawToolCallObjOf(toolName, toolParamsStr, toolId)
|
const toolCall = rawToolCallObjOf(toolName, toolParamsStr, toolId)
|
||||||
const toolCallObj = toolCall ? { toolCall } : {}
|
const toolCallObj = toolCall ? { toolCall } : {}
|
||||||
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null, ...toolCallObj });
|
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null, ...toolCallObj });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue