Add oauth button to openrouter

This commit is contained in:
Saoud Rizwan
2024-09-03 04:49:44 -04:00
parent 43e30d3f76
commit ce71ed7cba
4 changed files with 134 additions and 39 deletions

View File

@@ -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

View File

@@ -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<ClaudeDevProvider> = 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"
}
}

View File

@@ -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}
</h3>
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
{/* <li>
OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
so I recommend trying them out.
<br />
{!apiConfiguration?.openRouterApiKey && (
<VSCodeButtonLink
href={getOpenRouterAuthUrl(vscodeUriScheme)}
style={{
transform: "scale(0.85)",
transformOrigin: "left center",
margin: "4px -30px 2px 0",
}}>
Get OpenRouter API Key
</VSCodeButtonLink>
)}
{apiConfiguration?.openRouterApiKey && apiConfiguration?.apiProvider !== "openrouter" && (
<VSCodeButton
onClick={() => {
vscode.postMessage({
type: "apiConfiguration",
apiConfiguration: { ...apiConfiguration, apiProvider: "openrouter" },
})
}}
style={{
transform: "scale(0.85)",
transformOrigin: "left center",
margin: "4px -30px 2px 0",
}}>
Switch to OpenRouter
</VSCodeButton>
)}
</li> */}
<li>
<b>Edit Claude's changes before accepting!</b> 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 "<code>{"// rest of code here"}</code>" shenanigans)
</li>
<li>
Adds new <code>search_files</code> 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!
</li>
<li>
New terminal emulator! When Claude runs commands, you can now type directly in the terminal (+
support for Python environments)
</li>
<li>
<b>You can now edit Claude's changes before accepting!</b> 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 "<code>{"// rest of code here"}</code>" shenanigans)
</li>
<li>
Adds support for reading .pdf and .docx files (try "turn my business_plan.docx into a company
website")
</li>
<li>
Adds new <code>search_files</code> 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!
</li>
</ul>
<p style={{ margin: "0" }}>
Follow me for more updates!{" "}

View File

@@ -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<ApiOptionsProps> = ({ 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<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
</label>
<VSCodeDropdown id="api-provider" value={selectedProvider} onChange={handleInputChange("apiProvider")}>
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
<VSCodeOption value="vertex">GCP Vertex AI</VSCodeOption>
</VSCodeDropdown>
</div>
@@ -93,9 +94,11 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
color: "var(--vscode-descriptionForeground)",
}}>
This key is stored locally and only used to make API requests from this extension.
{!apiConfiguration?.apiKey && (
<VSCodeLink href="https://console.anthropic.com/" style={{ display: "inline" }}>
You can get an Anthropic API key by signing up here.
</VSCodeLink>
)}
</p>
</div>
)}
@@ -110,20 +113,24 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
placeholder="Enter API Key...">
<span style={{ fontWeight: 500 }}>OpenRouter API Key</span>
</VSCodeTextField>
{!apiConfiguration?.openRouterApiKey && (
<VSCodeButtonLink href={getOpenRouterAuthUrl(uriScheme)} style={{ margin: "5px 0 0 0" }}>
Get OpenRouter API Key
</VSCodeButtonLink>
)}
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
This key is stored locally and only used to make API requests from this extension.
<VSCodeLink href="https://openrouter.ai/" style={{ display: "inline" }}>
You can get an OpenRouter API key by signing up here.
</VSCodeLink>{" "}
<span style={{ color: "var(--vscode-errorForeground)" }}>
(<span style={{ fontWeight: 500 }}>Note:</span> 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 && (
<span style={{ color: "var(--vscode-charts-green)" }}>
(<span style={{ fontWeight: 500 }}>Note:</span> OpenRouter is recommended for high rate
limits, prompt caching, and wider selection of models.)
</span>
)} */}
</p>
</div>
)}
@@ -186,11 +193,13 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
color: "var(--vscode-descriptionForeground)",
}}>
These credentials are stored locally and only used to make API requests from this extension.
{!(apiConfiguration?.awsAccessKey && apiConfiguration?.awsSecretKey) && (
<VSCodeLink
href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
style={{ display: "inline" }}>
You can find your AWS access key and secret key here.
</VSCodeLink>
)}
</p>
</div>
)}
@@ -274,6 +283,10 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ 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",