Skip to content

Commit c82e8c6

Browse files
committed
Merge remote-tracking branch 'origin/feature/gemini-cli-fixes' into features
2 parents d9315a2 + 1cd9b70 commit c82e8c6

File tree

9 files changed

+117
-85
lines changed

9 files changed

+117
-85
lines changed

src/api/providers/gemini-cli.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,10 @@ export class GeminiCliHandler extends BaseProvider implements SingleCompletionHa
405405
data: JSON.stringify(requestBody),
406406
})
407407

408-
// Extract text from response
409-
const responseData = response.data as any
408+
// Extract text from response, handling both direct and nested response structures
409+
const rawData = response.data as any
410+
const responseData = rawData.response || rawData
411+
410412
if (responseData.candidates && responseData.candidates.length > 0) {
411413
const candidate = responseData.candidates[0]
412414
if (candidate.content && candidate.content.parts) {

src/core/webview/webviewMessageHandler.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,18 @@ export const webviewMessageHandler = async (
923923
}
924924
break
925925
}
926+
case "addMcpServer": {
927+
if (message.text && message.source) {
928+
try {
929+
await provider.getMcpHub()?.addServer(message.text, message.source as "global" | "project")
930+
await provider.postStateToWebview()
931+
} catch (error) {
932+
const errorMessage = error instanceof Error ? error.message : String(error)
933+
provider.log(`Failed to add MCP server: ${errorMessage}`)
934+
}
935+
}
936+
break
937+
}
926938
case "restartMcpServer": {
927939
try {
928940
await provider.getMcpHub()?.restartConnection(message.text!, message.source as "global" | "project")

src/services/mcp/McpHub.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,36 @@ export class McpHub {
14821482
await fs.writeFile(configPath, JSON.stringify(updatedConfig, null, 2))
14831483
}
14841484

1485+
public async addServer(serverConfigText: string, source: "global" | "project"): Promise<void> {
1486+
try {
1487+
// Parse the server configuration from JSON text
1488+
let serverConfig: any
1489+
try {
1490+
serverConfig = JSON.parse(serverConfigText)
1491+
} catch (parseError) {
1492+
throw new Error(
1493+
`Invalid JSON configuration: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
1494+
)
1495+
}
1496+
1497+
// Validate that we have a server name
1498+
if (!serverConfig.name) {
1499+
throw new Error("Server configuration must include a 'name' field")
1500+
}
1501+
1502+
const serverName = serverConfig.name
1503+
1504+
// Remove the name from the config since it's used as a key
1505+
const { name, ...serverConfigWithoutName } = serverConfig
1506+
1507+
// Use updateServerConnections to add the new server
1508+
await this.updateServerConnections({ [serverName]: serverConfigWithoutName }, source)
1509+
} catch (error) {
1510+
this.showErrorMessage(`Failed to add MCP server`, error)
1511+
throw error
1512+
}
1513+
}
1514+
14851515
public async updateServerTimeout(
14861516
serverName: string,
14871517
timeout: number,

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export interface WebviewMessage {
112112
| "remoteBrowserHost"
113113
| "openMcpSettings"
114114
| "openProjectMcpSettings"
115+
| "addMcpServer"
115116
| "restartMcpServer"
116117
| "refreshAllMcpServers"
117118
| "toggleToolAlwaysAllow"
Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,49 @@
11
import type { ProviderSettings } from "@roo-code/types"
2-
import { buildApiHandler, SingleCompletionHandler, ApiHandler } from "../api" //kilocode_change
2+
import { buildApiHandler, SingleCompletionHandler, ApiHandler } from "../api"
33

44
/**
55
* Enhances a prompt using the configured API without creating a full Cline instance or task history.
66
* This is a lightweight alternative that only uses the API's completion functionality.
77
*/
88
export async function singleCompletionHandler(apiConfiguration: ProviderSettings, promptText: string): Promise<string> {
9-
if (!promptText) {
10-
throw new Error("No prompt text provided")
11-
}
12-
if (!apiConfiguration || !apiConfiguration.apiProvider) {
13-
throw new Error("No valid API configuration provided")
14-
}
9+
if (!promptText) {
10+
throw new Error("No prompt text provided")
11+
}
12+
if (!apiConfiguration || !apiConfiguration.apiProvider) {
13+
throw new Error("No valid API configuration provided")
14+
}
1515

16-
const handler = buildApiHandler(apiConfiguration)
16+
const handler = buildApiHandler(apiConfiguration)
1717

18-
// Check if handler supports single completions
19-
if (!("completePrompt" in handler)) {
20-
// kilocode_change start - stream responses for handlers without completePrompt
21-
// throw new Error("The selected API provider does not support prompt enhancement")
22-
return await streamResponseFromHandler(handler, promptText)
23-
// kilocode_change end
24-
}
18+
// kilocode_change start
19+
// Force gemini-cli to use completePrompt
20+
if (apiConfiguration.apiProvider === "gemini-cli") {
21+
if ("completePrompt" in handler) { // Add check for safety
22+
return (handler as SingleCompletionHandler).completePrompt(promptText)
23+
} else {
24+
throw new Error("Gemini-cli handler does not support completePrompt as expected.")
25+
}
26+
}
27+
// kilocode_change end
2528

26-
return (handler as SingleCompletionHandler).completePrompt(promptText)
29+
// Check if handler supports single completions
30+
if ("completePrompt" in handler) { // If completePrompt exists, use it
31+
return (handler as SingleCompletionHandler).completePrompt(promptText)
32+
} else { // Otherwise, stream responses
33+
return await streamResponseFromHandler(handler, promptText)
34+
}
2735
}
2836

2937
// kilocode_change start - Stream responses using createMessage
3038
async function streamResponseFromHandler(handler: ApiHandler, promptText: string): Promise<string> {
31-
const stream = handler.createMessage("", [{ role: "user", content: [{ type: "text", text: promptText }] }])
39+
const stream = handler.createMessage("", [{ role: "user", content: [{ type: "text", text: promptText }] }])
3240

33-
let response: string = ""
34-
for await (const chunk of stream) {
35-
if (chunk.type === "text") {
36-
response += chunk.text
37-
}
38-
}
39-
return response
41+
let response: string = ""
42+
for await (const chunk of stream) {
43+
if (chunk.type === "text") {
44+
response += chunk.text
45+
}
46+
}
47+
return response
4048
}
4149
// kilocode_change end - streamResponseFromHandler

webview-ui/src/components/common/CodeBlock.tsx

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ const CodeBlock = memo(
233233
const codeBlockRef = useRef<HTMLDivElement>(null)
234234
const preRef = useRef<HTMLDivElement>(null)
235235
const copyButtonWrapperRef = useRef<HTMLDivElement>(null)
236+
// Copy button ref no longer needed since we're using onClick
236237
const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard()
237238
const { t } = useAppTranslation()
238239
const isMountedRef = useRef(true)
@@ -647,38 +648,23 @@ const CodeBlock = memo(
647648
const [isSelecting, setIsSelecting] = useState(false)
648649

649650
useEffect(() => {
650-
if (!preRef.current) return
651-
652-
const handleMouseDown = (e: MouseEvent) => {
653-
// Only trigger if clicking the pre element directly
654-
if (e.currentTarget === preRef.current) {
655-
setIsSelecting(true)
656-
}
651+
const handleSelectionChange = () => {
652+
const selection = window.getSelection()
653+
const newIsSelecting = selection ? !selection.isCollapsed : false
654+
console.log("Selection changed, isSelecting:", newIsSelecting)
655+
setIsSelecting(newIsSelecting)
657656
}
658657

659-
const handleMouseUp = () => {
660-
setIsSelecting(false)
661-
}
662-
663-
const preElement = preRef.current
664-
preElement.addEventListener("mousedown", handleMouseDown)
665-
document.addEventListener("mouseup", handleMouseUp)
658+
document.addEventListener("selectionchange", handleSelectionChange)
666659

667660
return () => {
668-
preElement.removeEventListener("mousedown", handleMouseDown)
669-
document.removeEventListener("mouseup", handleMouseUp)
661+
document.removeEventListener("selectionchange", handleSelectionChange)
670662
}
671663
}, [])
672664

673665
const handleCopy = useCallback(
674666
(e: React.MouseEvent) => {
675667
e.stopPropagation()
676-
677-
// Check if code block is partially visible before allowing copy
678-
const codeBlock = codeBlockRef.current
679-
if (!codeBlock || codeBlock.getAttribute("data-partially-visible") !== "true") {
680-
return
681-
}
682668
const textToCopy = rawSource !== undefined ? rawSource : source || ""
683669
if (textToCopy) {
684670
copyWithFeedback(textToCopy, e)
@@ -691,6 +677,7 @@ const CodeBlock = memo(
691677
return null
692678
}
693679

680+
console.log("Rendering CodeBlock, isSelecting:", isSelecting)
694681
return (
695682
<CodeBlockContainer ref={codeBlockRef}>
696683
<MemoizedStyledPre
@@ -795,7 +782,7 @@ const CodeBlock = memo(
795782
</CodeBlockButton>
796783
</StandardTooltip>
797784
<StandardTooltip content={t("chat:codeblock.tooltips.copy_code")} side="top">
798-
<CodeBlockButton onClick={handleCopy}>
785+
<CodeBlockButton onClick={handleCopy} data-testid="codeblock-copy-button">
799786
{showCopyFeedback ? <Check size={16} /> : <Copy size={16} />}
800787
</CodeBlockButton>
801788
</StandardTooltip>

webview-ui/src/components/common/__tests__/CodeBlock.spec.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ vi.mock("../../../utils/highlighter", () => {
8484
})
8585

8686
// Mock clipboard utility
87+
const mockCopyWithFeedback = vi.fn()
8788
vi.mock("../../../utils/clipboard", () => ({
8889
useCopyToClipboard: () => ({
8990
showCopyFeedback: false,
90-
copyWithFeedback: vi.fn(),
91+
copyWithFeedback: mockCopyWithFeedback,
9192
}),
9293
}))
9394

@@ -199,23 +200,18 @@ describe("CodeBlock", () => {
199200

200201
it("handles copy functionality", async () => {
201202
const code = "const x = 1;"
202-
const { container } = render(<CodeBlock source={code} language="typescript" />)
203203

204-
// Simulate code block visibility
205-
const codeBlock = container.querySelector("[data-partially-visible]")
206-
if (codeBlock) {
207-
codeBlock.setAttribute("data-partially-visible", "true")
208-
}
204+
render(<CodeBlock source={code} language="typescript" />)
209205

210-
// Find the copy button by looking for the button containing the Copy icon
211-
const buttons = screen.getAllByRole("button")
212-
const copyButton = buttons.find((btn) => btn.querySelector("svg.lucide-copy"))
206+
// Wait for the highlighter to finish and render the code
207+
await screen.findByText(/\[dark-theme\]/, undefined, { timeout: 4000 })
213208

214-
expect(copyButton).toBeTruthy()
215-
if (copyButton) {
216-
await act(async () => {
217-
fireEvent.click(copyButton)
218-
})
219-
}
209+
const copyButton = screen.getByTestId("codeblock-copy-button")
210+
expect(copyButton).toBeInTheDocument()
211+
212+
fireEvent.click(copyButton)
213+
214+
// Verify the copyWithFeedback mock was called
215+
expect(mockCopyWithFeedback).toHaveBeenCalledWith(code, expect.anything())
220216
})
221217
})

webview-ui/src/components/mcp/McpView.tsx

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -160,26 +160,15 @@ const McpView = ({ onDone, hideHeader = false }: McpViewProps) => {
160160
<span className="codicon codicon-refresh" style={{ marginRight: "6px" }}></span>
161161
{t("mcp:refreshMCP")}
162162
</Button>
163-
{/* kilocode_change
164-
<StandardTooltip content={t("mcp:marketplace")}>
165-
<Button
166-
variant="secondary"
167-
style={{ width: "100%" }}
168-
onClick={() => {
169-
window.postMessage(
170-
{
171-
type: "action",
172-
action: "marketplaceButtonClicked",
173-
values: { marketplaceTab: "mcp" },
174-
},
175-
"*",
176-
)
177-
}}>
178-
<span className="codicon codicon-extensions" style={{ marginRight: "6px" }}></span>
179-
{t("mcp:marketplace")}
180-
</Button>
181-
</StandardTooltip>
182-
*/}
163+
<Button
164+
variant="secondary"
165+
style={{ width: "100%" }}
166+
onClick={() => {
167+
// TODO: Implement this
168+
}}>
169+
<span className="codicon codicon-add" style={{ marginRight: "6px" }}></span>
170+
{t("mcp:addUrlBasedMcp")}
171+
</Button>
183172
</div>
184173
{/* kilocode_change start */}
185174
<div className="mt-5">

webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ vi.mock("../providers/LiteLLM", () => ({
238238
),
239239
}))
240240

241+
vi.mock("@src/components/ui/hooks/useRouterModels", () => ({
242+
useRouterModels: vi.fn(() => ({
243+
data: {},
244+
refetch: vi.fn(),
245+
})),
246+
}))
247+
241248
vi.mock("@src/components/ui/hooks/useSelectedModel", () => ({
242249
useSelectedModel: vi.fn((apiConfiguration: ProviderSettings) => {
243250
if (apiConfiguration.apiModelId?.includes("thinking")) {

0 commit comments

Comments
 (0)