Skip to content

Commit bdc9bac

Browse files
committed
Add Google Search option for Gemini
1 parent 25be233 commit bdc9bac

File tree

28 files changed

+7656
-7
lines changed

28 files changed

+7656
-7
lines changed

package-lock.json

Lines changed: 7496 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/types/src/provider-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ const lmStudioSchema = baseProviderSettingsSchema.extend({
157157
const geminiSchema = apiModelIdProviderModelSchema.extend({
158158
geminiApiKey: z.string().optional(),
159159
googleGeminiBaseUrl: z.string().optional(),
160+
geminiEnableGoogleSearch: z.boolean().optional(),
160161
})
161162

162163
const geminiCliSchema = apiModelIdProviderModelSchema.extend({

src/api/providers/gemini.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
6666
const { id: model, info, reasoning: thinkingConfig, maxTokens } = this.getModel()
6767

6868
const contents = messages.map(convertAnthropicMessageToGemini)
69-
7069
const config: GenerateContentConfig = {
7170
systemInstruction,
71+
...(this.options.geminiEnableGoogleSearch && { tools: [{ googleSearch: {} }] }),
7272
httpOptions: this.options.googleGeminiBaseUrl ? { baseUrl: this.options.googleGeminiBaseUrl } : undefined,
7373
thinkingConfig,
7474
maxOutputTokens: this.options.modelMaxTokens ?? maxTokens ?? undefined,
@@ -144,20 +144,52 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
144144

145145
async completePrompt(prompt: string): Promise<string> {
146146
try {
147-
const { id: model } = this.getModel()
148-
147+
const { id: model, reasoning: thinkingConfig } = this.getModel()
149148
const result = await this.client.models.generateContent({
150149
model,
151150
contents: [{ role: "user", parts: [{ text: prompt }] }],
152151
config: {
152+
thinkingConfig,
153+
...(this.options.geminiEnableGoogleSearch && { tools: [{ googleSearch: {} }] }),
153154
httpOptions: this.options.googleGeminiBaseUrl
154155
? { baseUrl: this.options.googleGeminiBaseUrl }
155156
: undefined,
156157
temperature: this.options.modelTemperature ?? 0,
157158
},
158159
})
159160

160-
return result.text ?? ""
161+
let text = result.text ?? ""
162+
const candidate = result.candidates?.[0]
163+
const supports = candidate?.groundingMetadata?.groundingSupports
164+
const chunks = candidate?.groundingMetadata?.groundingChunks
165+
166+
if (text && supports && chunks) {
167+
// Sort supports by end index in descending order to avoid shifting issues when inserting.
168+
const sortedSupports = [...supports].sort(
169+
(a, b) => (b.segment?.endIndex ?? 0) - (a.segment?.endIndex ?? 0),
170+
)
171+
172+
for (const support of sortedSupports) {
173+
const endIndex = support.segment?.endIndex
174+
if (endIndex === undefined || !support.groundingChunkIndices?.length) {
175+
continue
176+
}
177+
178+
const citationLinks = support.groundingChunkIndices
179+
.map((i: number) => {
180+
const uri = chunks[i]?.web?.uri
181+
return uri ? `[${i + 1}](${uri})` : null
182+
})
183+
.filter(Boolean)
184+
185+
if (citationLinks.length > 0) {
186+
const citationString = " " + citationLinks.join(" ")
187+
text = text.slice(0, endIndex) + citationString + text.slice(endIndex)
188+
}
189+
}
190+
}
191+
192+
return text
161193
} catch (error) {
162194
if (error instanceof Error) {
163195
throw new Error(`Gemini completion error: ${error.message}`)

src/api/transform/reasoning.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@ export const getGeminiReasoning = ({
5858
reasoningBudget,
5959
settings,
6060
}: GetModelReasoningOptions): GeminiReasoningParams | undefined =>
61-
shouldUseReasoningBudget({ model, settings })
61+
shouldUseReasoningBudget({ model, settings }) && !settings.geminiEnableGoogleSearch
6262
? { thinkingBudget: reasoningBudget!, includeThoughts: true }
6363
: undefined

src/core/prompts/system.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,29 @@ export const SYSTEM_PROMPT = async (
166166
{ language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions },
167167
)
168168

169-
// For file-based prompts, don't include the tool sections
169+
const effectiveDiffStrategy = diffEnabled ? diffStrategy : undefined
170+
const codeIndexManager = CodeIndexManager.getInstance(context)
171+
const toolDescriptions = getToolDescriptionsForMode(
172+
mode,
173+
cwd,
174+
supportsComputerUse,
175+
codeIndexManager,
176+
effectiveDiffStrategy,
177+
browserViewportSize,
178+
mcpHub,
179+
customModes,
180+
experiments,
181+
partialReadsEnabled,
182+
settings,
183+
)
184+
185+
// For file-based prompts, include the tool sections
170186
return `${roleDefinition}
171187
172188
${fileCustomSystemPrompt}
173189
190+
${toolDescriptions}
191+
174192
${customInstructions}`
175193
}
176194

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ToolArgs } from "./types"
2+
3+
export function getGoogleSearchDescription(args: ToolArgs): string {
4+
return `
5+
# google_search
6+
Description: When you need to answer questions about current events or things that have happened since your knowledge cutoff, you can use this tool to get information from the web.
7+
`
8+
}

src/core/prompts/tools/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getListFilesDescription } from "./list-files"
1414
import { getInsertContentDescription } from "./insert-content"
1515
import { getSearchAndReplaceDescription } from "./search-and-replace"
1616
import { getListCodeDefinitionNamesDescription } from "./list-code-definition-names"
17+
import { getGoogleSearchDescription } from "./google-search"
1718
import { getBrowserActionDescription } from "./browser-action"
1819
import { getAskFollowupQuestionDescription } from "./ask-followup-question"
1920
import { getAttemptCompletionDescription } from "./attempt-completion"
@@ -43,6 +44,7 @@ const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined>
4344
new_task: (args) => getNewTaskDescription(args),
4445
insert_content: (args) => getInsertContentDescription(args),
4546
search_and_replace: (args) => getSearchAndReplaceDescription(args),
47+
google_search: (args) => getGoogleSearchDescription(args),
4648
apply_diff: (args) =>
4749
args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "",
4850
}
@@ -107,6 +109,10 @@ export function getToolDescriptionsForMode(
107109
tools.delete("codebase_search")
108110
}
109111

112+
if (settings?.geminiEnableGoogleSearch) {
113+
tools.add("google_search")
114+
}
115+
110116
// Map tool descriptions for allowed tools
111117
const descriptions = Array.from(tools).map((toolName) => {
112118
const descriptionFn = toolDescriptionMap[toolName]

src/core/task/Task.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,7 @@ export class Task extends EventEmitter<ClineEvents> {
16361636
maxReadFileLine !== -1,
16371637
{
16381638
maxConcurrentFileReads,
1639+
...this.apiConfiguration,
16391640
},
16401641
)
16411642
})()
@@ -1716,7 +1717,9 @@ export class Task extends EventEmitter<ClineEvents> {
17161717

17171718
const contextWindow = modelInfo.contextWindow
17181719

1719-
const currentProfileId = state?.listApiConfigMeta.find((profile) => profile.name === state?.currentApiConfigName)?.id ?? "default";
1720+
const currentProfileId =
1721+
state?.listApiConfigMeta.find((profile) => profile.name === state?.currentApiConfigName)?.id ??
1722+
"default"
17201723

17211724
const truncateResult = await truncateConversationIfNeeded({
17221725
messages: this.apiConversationHistory,

src/core/webview/generateSystemPrompt.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
8282
maxReadFileLine !== -1,
8383
{
8484
maxConcurrentFileReads,
85+
...apiConfiguration,
8586
},
8687
)
8788

webview-ui/src/components/settings/providers/Gemini.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ export const Gemini = ({ apiConfiguration, setApiConfigurationField }: GeminiPro
5050
{t("settings:providers.getGeminiApiKey")}
5151
</VSCodeButtonLink>
5252
)}
53+
<Checkbox
54+
checked={!!apiConfiguration.geminiEnableGoogleSearch}
55+
onChange={(checked: boolean) => {
56+
setApiConfigurationField("geminiEnableGoogleSearch", checked)
57+
}}>
58+
<div className="flex flex-col">
59+
<span className="font-medium">{t("settings:providers.geminiEnableGoogleSearch.label")}</span>
60+
<span className="text-sm text-vscode-descriptionForeground">
61+
{t("settings:providers.geminiEnableGoogleSearch.description")}
62+
</span>
63+
</div>
64+
</Checkbox>
5365
<div>
5466
<Checkbox
5567
checked={googleGeminiBaseUrlSelected}

0 commit comments

Comments
 (0)