Skip to content

Commit 6b074c7

Browse files
HahaBillroomotemrubensdaniel-lxs
authored andcommitted
Feat: Adding Gemini tools - URL Context and Grounding with Google Search (RooCodeInc#5959)
* feat: Adding more settings and control over Gemini - with topP, topK, maxOutputTokens - allow users to enable URL context and Grounding Research * feat: Adding parameter titles and descriptions + translation to all languages * feat: adding more translations * feat: adding `contextLimit` implementation from `maxContextWindow` PR + working with profile-specific thresholding * feat: max value for context limit to model's limit + converting description and titles to settings for translation purposes * feat: all languages translated * feat: changing profile-specific threshold in context management setting will also change in Gemini context management - sync between Context Management Settting <-> Gemini Context Management with regards to thresholding * feat: max value of maxOutputTokens is model's maxTokens + adding more tests * feat: improve unit tests and adding `data-testid` to slider and checkbox components * fix: small changes in geminiContextManagement descriptions + minor fix * fix: Switching from "Gemini Context Management" to "Token Management - better naming and correct purpose * fix: input field showed NaN -> annoying UX * fix: Removing redundant "tokens" after the "set context limit"'s checkbox + removing the lengthy description * fix: Changing the translation to be consistent with the english one * fix: more translations * fix: translations * fix: removing contextLimit and token management related code - due to the decision in: RooCodeInc#3717 * fix: removing `contextLimit` test and removing token management in translations * fix: changing from `Advanced Features` to `Tools` to be consistent with Gemini docs/AI studio * fix: adding `try-catch` block for `generateContentStream` * feat: Include citations + improved type safety * feat: adding citation for streams (generateContextStream) * fix: set default values for `topP`, `topK` and `maxOutputTokens` * fix: changing UI/UX according to the review/feedback from `daniel-lxs` * fix: updating the `Gemini.spec.tsx` unit test - testing when it is hidden - testing when users click on the collapsible trigger and model configuration appears * fix: more changes from the feedback/review from `daniel-lxs` * fix: adding sources at the end of the stream to preserve * fix: change the description for grounding with google search and url context * fix: adding translations * fix: removing redundant extra translations - a mistake made by the agent * fix: remove duplicate translation keys in geminiSections and geminiParameters - Fixed duplicate keys in 13 localization files (es, fr, hi, id, it, ja, ko, nl, pl, pt-BR, ru, tr, vi) - Removed second occurrence of geminiSections and geminiParameters keys - Kept first occurrence which contains more comprehensive descriptions - All JSON files validated for syntax correctness - Translation completeness verified with missing translations script Resolves duplicate key issue identified in PR RooCodeInc#4895 * fix: delete topK, topP and maxOutputTokens from Gemini * fix: deleting topK, topP and maxOutputTokens from translations/locales * fix: adjust spacing between labels and descriptions + sentence casing * fix: adding maxOutputTokens back and removing unknown type * fix: internalizing error Gemini error message * fix: updating tests in Gemini and Vertex to adjust to the new error logging * fix: address PR review feedback for Gemini tools feature - Fix Hindi translation grammatical error in settings.json - Internationalize 'Sources:' string and error messages in gemini.ts - Add comprehensive error scenario tests to gemini-handler.spec.ts - Remove unused currentModelId prop from Gemini component - Update all locale files with new translation keys --------- Co-authored-by: Roo Code <[email protected]> Co-authored-by: Matt Rubens <[email protected]> Co-authored-by: Daniel Riccio <[email protected]>
1 parent 7763c68 commit 6b074c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+707
-51
lines changed

packages/types/src/provider-settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ const lmStudioSchema = baseProviderSettingsSchema.extend({
171171
const geminiSchema = apiModelIdProviderModelSchema.extend({
172172
geminiApiKey: z.string().optional(),
173173
googleGeminiBaseUrl: z.string().optional(),
174+
enableUrlContext: z.boolean().optional(),
175+
enableGrounding: z.boolean().optional(),
174176
})
175177

176178
const geminiCliSchema = apiModelIdProviderModelSchema.extend({
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { describe, it, expect, vi } from "vitest"
2+
import { t } from "i18next"
3+
import { GeminiHandler } from "../gemini"
4+
import type { ApiHandlerOptions } from "../../../shared/api"
5+
6+
describe("GeminiHandler backend support", () => {
7+
it("passes tools for URL context and grounding in config", async () => {
8+
const options = {
9+
apiProvider: "gemini",
10+
enableUrlContext: true,
11+
enableGrounding: true,
12+
} as ApiHandlerOptions
13+
const handler = new GeminiHandler(options)
14+
const stub = vi.fn().mockReturnValue((async function* () {})())
15+
// @ts-ignore access private client
16+
handler["client"].models.generateContentStream = stub
17+
await handler.createMessage("instr", [] as any).next()
18+
const config = stub.mock.calls[0][0].config
19+
expect(config.tools).toEqual([{ urlContext: {} }, { googleSearch: {} }])
20+
})
21+
22+
it("completePrompt passes config overrides without tools when URL context and grounding disabled", async () => {
23+
const options = {
24+
apiProvider: "gemini",
25+
enableUrlContext: false,
26+
enableGrounding: false,
27+
} as ApiHandlerOptions
28+
const handler = new GeminiHandler(options)
29+
const stub = vi.fn().mockResolvedValue({ text: "ok" })
30+
// @ts-ignore access private client
31+
handler["client"].models.generateContent = stub
32+
const res = await handler.completePrompt("hi")
33+
expect(res).toBe("ok")
34+
const promptConfig = stub.mock.calls[0][0].config
35+
expect(promptConfig.tools).toBeUndefined()
36+
})
37+
38+
describe("error scenarios", () => {
39+
it("should handle grounding metadata extraction failure gracefully", async () => {
40+
const options = {
41+
apiProvider: "gemini",
42+
enableGrounding: true,
43+
} as ApiHandlerOptions
44+
const handler = new GeminiHandler(options)
45+
46+
const mockStream = async function* () {
47+
yield {
48+
candidates: [
49+
{
50+
groundingMetadata: {
51+
// Invalid structure - missing groundingChunks
52+
},
53+
content: { parts: [{ text: "test response" }] },
54+
},
55+
],
56+
usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5 },
57+
}
58+
}
59+
60+
const stub = vi.fn().mockReturnValue(mockStream())
61+
// @ts-ignore access private client
62+
handler["client"].models.generateContentStream = stub
63+
64+
const messages = []
65+
for await (const chunk of handler.createMessage("test", [] as any)) {
66+
messages.push(chunk)
67+
}
68+
69+
// Should still return the main content without sources
70+
expect(messages.some((m) => m.type === "text" && m.text === "test response")).toBe(true)
71+
expect(messages.some((m) => m.type === "text" && m.text?.includes("Sources:"))).toBe(false)
72+
})
73+
74+
it("should handle malformed grounding metadata", async () => {
75+
const options = {
76+
apiProvider: "gemini",
77+
enableGrounding: true,
78+
} as ApiHandlerOptions
79+
const handler = new GeminiHandler(options)
80+
81+
const mockStream = async function* () {
82+
yield {
83+
candidates: [
84+
{
85+
groundingMetadata: {
86+
groundingChunks: [
87+
{ web: null }, // Missing URI
88+
{ web: { uri: "https://example.com" } }, // Valid
89+
{}, // Missing web property entirely
90+
],
91+
},
92+
content: { parts: [{ text: "test response" }] },
93+
},
94+
],
95+
usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5 },
96+
}
97+
}
98+
99+
const stub = vi.fn().mockReturnValue(mockStream())
100+
// @ts-ignore access private client
101+
handler["client"].models.generateContentStream = stub
102+
103+
const messages = []
104+
for await (const chunk of handler.createMessage("test", [] as any)) {
105+
messages.push(chunk)
106+
}
107+
108+
// Should only include valid citations
109+
const sourceMessage = messages.find((m) => m.type === "text" && m.text?.includes("[2]"))
110+
expect(sourceMessage).toBeDefined()
111+
if (sourceMessage && "text" in sourceMessage) {
112+
expect(sourceMessage.text).toContain("https://example.com")
113+
expect(sourceMessage.text).not.toContain("[1]")
114+
expect(sourceMessage.text).not.toContain("[3]")
115+
}
116+
})
117+
118+
it("should handle API errors when tools are enabled", async () => {
119+
const options = {
120+
apiProvider: "gemini",
121+
enableUrlContext: true,
122+
enableGrounding: true,
123+
} as ApiHandlerOptions
124+
const handler = new GeminiHandler(options)
125+
126+
const mockError = new Error("API rate limit exceeded")
127+
const stub = vi.fn().mockRejectedValue(mockError)
128+
// @ts-ignore access private client
129+
handler["client"].models.generateContentStream = stub
130+
131+
await expect(async () => {
132+
const generator = handler.createMessage("test", [] as any)
133+
await generator.next()
134+
}).rejects.toThrow(t("common:errors.gemini.generate_stream", { error: "API rate limit exceeded" }))
135+
})
136+
})
137+
})

src/api/providers/__tests__/gemini.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
44

55
import { type ModelInfo, geminiDefaultModelId } from "@roo-code/types"
66

7+
import { t } from "i18next"
78
import { GeminiHandler } from "../gemini"
89

910
const GEMINI_20_FLASH_THINKING_NAME = "gemini-2.0-flash-thinking-exp-1219"
@@ -129,7 +130,7 @@ describe("GeminiHandler", () => {
129130
;(handler["client"].models.generateContent as any).mockRejectedValue(mockError)
130131

131132
await expect(handler.completePrompt("Test prompt")).rejects.toThrow(
132-
"Gemini completion error: Gemini API error",
133+
t("common:errors.gemini.generate_complete_prompt", { error: "Gemini API error" }),
133134
)
134135
})
135136

src/api/providers/__tests__/vertex.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
77

88
import { ApiStreamChunk } from "../../transform/stream"
99

10+
import { t } from "i18next"
1011
import { VertexHandler } from "../vertex"
1112

1213
describe("VertexHandler", () => {
@@ -105,7 +106,7 @@ describe("VertexHandler", () => {
105106
;(handler["client"].models.generateContent as any).mockRejectedValue(mockError)
106107

107108
await expect(handler.completePrompt("Test prompt")).rejects.toThrow(
108-
"Gemini completion error: Vertex API error",
109+
t("common:errors.gemini.generate_complete_prompt", { error: "Vertex API error" }),
109110
)
110111
})
111112

0 commit comments

Comments
 (0)