diff --git a/src/extension.ts b/src/extension.ts
index 700a86e..432dabb 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -109,16 +109,28 @@ export function activate(context: vscode.ExtensionContext) {
vscode.workspace.registerTextDocumentContentProvider("claude-dev-diff", diffContentProvider)
)
- // // URI Handler
- // const handleUri = async (uri: vscode.Uri) => {
- // const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
- // const token = query.get("token")
- // const email = query.get("email")
- // if (token) {
- // await sidebarProvider.saveKoduApiKey(token, email || undefined)
- // }
- // }
- // context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
+ // URI Handler
+ const handleUri = async (uri: vscode.Uri) => {
+ console.log("handleUri", uri)
+ const path = uri.path
+ const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
+ const visibleProvider = ClaudeDevProvider.getVisibleInstance()
+ if (!visibleProvider) {
+ return
+ }
+ switch (path) {
+ case "/openrouter": {
+ const code = query.get("code")
+ if (code) {
+ await visibleProvider.handleOpenRouterCallback(code)
+ }
+ break
+ }
+ default:
+ break
+ }
+ }
+ context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
}
// This method is called when your extension is deactivated
diff --git a/src/providers/ClaudeDevProvider.ts b/src/providers/ClaudeDevProvider.ts
index ca2c275..5b3c2a8 100644
--- a/src/providers/ClaudeDevProvider.ts
+++ b/src/providers/ClaudeDevProvider.ts
@@ -4,10 +4,11 @@ import { ClaudeDev } from "../ClaudeDev"
import { ApiModelId, ApiProvider } from "../shared/api"
import { ExtensionMessage } from "../shared/ExtensionMessage"
import { WebviewMessage } from "../shared/WebviewMessage"
-import { downloadTask, getNonce, getUri, selectImages } from "../utils"
+import { downloadTask, findLast, getNonce, getUri, selectImages } from "../utils"
import * as path from "path"
import fs from "fs/promises"
import { HistoryItem } from "../shared/HistoryItem"
+import axios from "axios"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -30,6 +31,7 @@ type GlobalStateKey =
export class ClaudeDevProvider implements vscode.WebviewViewProvider {
public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
public static readonly tabPanelId = "claude-dev.TabPanelProvider"
+ private static activeInstances: Set = new Set()
private disposables: vscode.Disposable[] = []
private view?: vscode.WebviewView | vscode.WebviewPanel
private claudeDev?: ClaudeDev
@@ -37,6 +39,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) {
this.outputChannel.appendLine("ClaudeDevProvider instantiated")
+ ClaudeDevProvider.activeInstances.add(this)
this.revertKodu()
}
@@ -81,6 +84,11 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
}
}
this.outputChannel.appendLine("Disposed all disposables")
+ ClaudeDevProvider.activeInstances.delete(this)
+ }
+
+ public static getVisibleInstance(): ClaudeDevProvider | undefined {
+ return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true)
}
resolveWebviewView(
@@ -373,6 +381,33 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
)
}
+ // OpenRouter
+
+ async handleOpenRouterCallback(code: string) {
+ console.log("handleOpenRouterCallback", code)
+ let apiKey: string
+ try {
+ const response = await axios.post("https://openrouter.ai/api/v1/auth/keys", { code })
+ console.log("OpenRouter API response:", response.data)
+
+ if (response.data && response.data.key) {
+ apiKey = response.data.key
+ } else {
+ throw new Error("Invalid response from OpenRouter API")
+ }
+ } catch (error) {
+ console.error("Error exchanging code for API key:", error)
+ throw error
+ }
+
+ const openrouter: ApiProvider = "openrouter"
+ await this.updateGlobalState("apiProvider", openrouter)
+ await this.storeSecret("openRouterApiKey", apiKey)
+ await this.postStateToWebview()
+ this.claudeDev?.updateApi({ apiProvider: openrouter, openRouterApiKey: apiKey })
+ await this.postMessageToWebview({ type: "action", action: "settingsButtonTapped" })
+ }
+
// Task history
async getTaskWithId(id: string): Promise<{
@@ -606,7 +641,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
if (apiKey) {
apiProvider = "anthropic"
} else {
- // New users should default to anthropic
+ // New users should default to anthropic for now, but will change to openrouter after fast edit mode
apiProvider = "anthropic"
}
}
diff --git a/webview-ui/src/components/Announcement.tsx b/webview-ui/src/components/Announcement.tsx
index 3f5b6cc..2d47aa8 100644
--- a/webview-ui/src/components/Announcement.tsx
+++ b/webview-ui/src/components/Announcement.tsx
@@ -1,5 +1,8 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { ApiConfiguration } from "../../../src/shared/api"
+// import VSCodeButtonLink from "./VSCodeButtonLink"
+// import { getOpenRouterAuthUrl } from "./ApiOptions"
+// import { vscode } from "../utils/vscode"
interface AnnouncementProps {
version: string
@@ -30,23 +33,55 @@ const Announcement = ({ version, hideAnnouncement, apiConfiguration, vscodeUriSc
🎉{" "}New in v{version}
+ {/* -
+ OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
+ so I recommend trying them out.
+
+ {!apiConfiguration?.openRouterApiKey && (
+
+ Get OpenRouter API Key
+
+ )}
+ {apiConfiguration?.openRouterApiKey && apiConfiguration?.apiProvider !== "openrouter" && (
+ {
+ vscode.postMessage({
+ type: "apiConfiguration",
+ apiConfiguration: { ...apiConfiguration, apiProvider: "openrouter" },
+ })
+ }}
+ style={{
+ transform: "scale(0.85)",
+ transformOrigin: "left center",
+ margin: "4px -30px 2px 0",
+ }}>
+ Switch to OpenRouter
+
+ )}
+ */}
+ -
+ Edit Claude's changes before accepting! When he creates or edits a file, you can modify his
+ changes directly in the right side of the diff view (+ hover over the 'Revert Block' arrow button in
+ the center to undo "
{"// rest of code here"}" shenanigans)
+
+ -
+ Adds new
search_files tool that lets Claude perform regex searches in your project,
+ making it easy for him to refactor code, address TODOs and FIXMEs, remove dead code, and more!
+
-
New terminal emulator! When Claude runs commands, you can now type directly in the terminal (+
support for Python environments)
- -
- You can now edit Claude's changes before accepting! When he edits or creates a file, you can
- modify his changes directly in the right side of the diff view (+ hover over the 'Revert Block'
- arrow button in the center to undo "
{"// rest of code here"}" shenanigans)
-
-
Adds support for reading .pdf and .docx files (try "turn my business_plan.docx into a company
website")
- -
- Adds new
search_files tool that lets Claude perform regex searches in your project,
- making it easy for him to refactor code, address TODOs and FIXMEs, remove dead code, and more!
-
Follow me for more updates!{" "}
diff --git a/webview-ui/src/components/ApiOptions.tsx b/webview-ui/src/components/ApiOptions.tsx
index 3a019e7..14bf84b 100644
--- a/webview-ui/src/components/ApiOptions.tsx
+++ b/webview-ui/src/components/ApiOptions.tsx
@@ -14,6 +14,7 @@ import {
vertexModels,
} from "../../../src/shared/api"
import { useExtensionState } from "../context/ExtensionStateContext"
+import VSCodeButtonLink from "./VSCodeButtonLink"
interface ApiOptionsProps {
showModelOptions: boolean
@@ -21,7 +22,7 @@ interface ApiOptionsProps {
}
const ApiOptions: React.FC = ({ showModelOptions, apiErrorMessage }) => {
- const { apiConfiguration, setApiConfiguration } = useExtensionState()
+ const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState()
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
setApiConfiguration({ ...apiConfiguration, [field]: event.target.value })
}
@@ -70,8 +71,8 @@ const ApiOptions: React.FC = ({ showModelOptions, apiErrorMessa
Anthropic
- AWS Bedrock
OpenRouter
+ AWS Bedrock
GCP Vertex AI
@@ -93,9 +94,11 @@ const ApiOptions: React.FC = ({ showModelOptions, apiErrorMessa
color: "var(--vscode-descriptionForeground)",
}}>
This key is stored locally and only used to make API requests from this extension.
-
- You can get an Anthropic API key by signing up here.
-
+ {!apiConfiguration?.apiKey && (
+
+ You can get an Anthropic API key by signing up here.
+
+ )}
)}
@@ -110,20 +113,24 @@ const ApiOptions: React.FC = ({ showModelOptions, apiErrorMessa
placeholder="Enter API Key...">
OpenRouter API Key
+ {!apiConfiguration?.openRouterApiKey && (
+
+ Get OpenRouter API Key
+
+ )}
- This key is stored locally and only used to make API requests from this extension.
-
- You can get an OpenRouter API key by signing up here.
- {" "}
-
- (Note: OpenRouter support is experimental and may
- not work well with large files.)
-
+ This key is stored locally and only used to make API requests from this extension.{" "}
+ {/* {!apiConfiguration?.openRouterApiKey && (
+
+ (Note: OpenRouter is recommended for high rate
+ limits, prompt caching, and wider selection of models.)
+
+ )} */}
)}
@@ -186,11 +193,13 @@ const ApiOptions: React.FC = ({ showModelOptions, apiErrorMessa
color: "var(--vscode-descriptionForeground)",
}}>
These credentials are stored locally and only used to make API requests from this extension.
-
- You can find your AWS access key and secret key here.
-
+ {!(apiConfiguration?.awsAccessKey && apiConfiguration?.awsSecretKey) && (
+
+ You can find your AWS access key and secret key here.
+
+ )}
)}
@@ -274,6 +283,10 @@ const ApiOptions: React.FC = ({ showModelOptions, apiErrorMessa
)
}
+export function getOpenRouterAuthUrl(uriScheme?: string) {
+ return `https://openrouter.ai/auth?callback_url=${uriScheme || "vscode"}://saoudrizwan.claude-dev/openrouter`
+}
+
export const formatPrice = (price: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",