mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add oauth button to openrouter
This commit is contained in:
@@ -109,16 +109,28 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.workspace.registerTextDocumentContentProvider("claude-dev-diff", diffContentProvider)
|
vscode.workspace.registerTextDocumentContentProvider("claude-dev-diff", diffContentProvider)
|
||||||
)
|
)
|
||||||
|
|
||||||
// // URI Handler
|
// URI Handler
|
||||||
// const handleUri = async (uri: vscode.Uri) => {
|
const handleUri = async (uri: vscode.Uri) => {
|
||||||
// const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
|
console.log("handleUri", uri)
|
||||||
// const token = query.get("token")
|
const path = uri.path
|
||||||
// const email = query.get("email")
|
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
|
||||||
// if (token) {
|
const visibleProvider = ClaudeDevProvider.getVisibleInstance()
|
||||||
// await sidebarProvider.saveKoduApiKey(token, email || undefined)
|
if (!visibleProvider) {
|
||||||
// }
|
return
|
||||||
// }
|
}
|
||||||
// context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
|
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
|
// This method is called when your extension is deactivated
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import { ClaudeDev } from "../ClaudeDev"
|
|||||||
import { ApiModelId, ApiProvider } from "../shared/api"
|
import { ApiModelId, ApiProvider } from "../shared/api"
|
||||||
import { ExtensionMessage } from "../shared/ExtensionMessage"
|
import { ExtensionMessage } from "../shared/ExtensionMessage"
|
||||||
import { WebviewMessage } from "../shared/WebviewMessage"
|
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 * as path from "path"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import { HistoryItem } from "../shared/HistoryItem"
|
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
|
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 {
|
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 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"
|
public static readonly tabPanelId = "claude-dev.TabPanelProvider"
|
||||||
|
private static activeInstances: Set<ClaudeDevProvider> = new Set()
|
||||||
private disposables: vscode.Disposable[] = []
|
private disposables: vscode.Disposable[] = []
|
||||||
private view?: vscode.WebviewView | vscode.WebviewPanel
|
private view?: vscode.WebviewView | vscode.WebviewPanel
|
||||||
private claudeDev?: ClaudeDev
|
private claudeDev?: ClaudeDev
|
||||||
@@ -37,6 +39,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) {
|
constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) {
|
||||||
this.outputChannel.appendLine("ClaudeDevProvider instantiated")
|
this.outputChannel.appendLine("ClaudeDevProvider instantiated")
|
||||||
|
ClaudeDevProvider.activeInstances.add(this)
|
||||||
this.revertKodu()
|
this.revertKodu()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +84,11 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.outputChannel.appendLine("Disposed all disposables")
|
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(
|
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
|
// Task history
|
||||||
|
|
||||||
async getTaskWithId(id: string): Promise<{
|
async getTaskWithId(id: string): Promise<{
|
||||||
@@ -606,7 +641,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
|||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
apiProvider = "anthropic"
|
apiProvider = "anthropic"
|
||||||
} else {
|
} 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"
|
apiProvider = "anthropic"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||||
import { ApiConfiguration } from "../../../src/shared/api"
|
import { ApiConfiguration } from "../../../src/shared/api"
|
||||||
|
// import VSCodeButtonLink from "./VSCodeButtonLink"
|
||||||
|
// import { getOpenRouterAuthUrl } from "./ApiOptions"
|
||||||
|
// import { vscode } from "../utils/vscode"
|
||||||
|
|
||||||
interface AnnouncementProps {
|
interface AnnouncementProps {
|
||||||
version: string
|
version: string
|
||||||
@@ -30,23 +33,55 @@ const Announcement = ({ version, hideAnnouncement, apiConfiguration, vscodeUriSc
|
|||||||
🎉{" "}New in v{version}
|
🎉{" "}New in v{version}
|
||||||
</h3>
|
</h3>
|
||||||
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
|
<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>
|
<li>
|
||||||
New terminal emulator! When Claude runs commands, you can now type directly in the terminal (+
|
New terminal emulator! When Claude runs commands, you can now type directly in the terminal (+
|
||||||
support for Python environments)
|
support for Python environments)
|
||||||
</li>
|
</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>
|
<li>
|
||||||
Adds support for reading .pdf and .docx files (try "turn my business_plan.docx into a company
|
Adds support for reading .pdf and .docx files (try "turn my business_plan.docx into a company
|
||||||
website")
|
website")
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
<p style={{ margin: "0" }}>
|
<p style={{ margin: "0" }}>
|
||||||
Follow me for more updates!{" "}
|
Follow me for more updates!{" "}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
vertexModels,
|
vertexModels,
|
||||||
} from "../../../src/shared/api"
|
} from "../../../src/shared/api"
|
||||||
import { useExtensionState } from "../context/ExtensionStateContext"
|
import { useExtensionState } from "../context/ExtensionStateContext"
|
||||||
|
import VSCodeButtonLink from "./VSCodeButtonLink"
|
||||||
|
|
||||||
interface ApiOptionsProps {
|
interface ApiOptionsProps {
|
||||||
showModelOptions: boolean
|
showModelOptions: boolean
|
||||||
@@ -21,7 +22,7 @@ interface ApiOptionsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessage }) => {
|
const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessage }) => {
|
||||||
const { apiConfiguration, setApiConfiguration } = useExtensionState()
|
const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState()
|
||||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||||
setApiConfiguration({ ...apiConfiguration, [field]: event.target.value })
|
setApiConfiguration({ ...apiConfiguration, [field]: event.target.value })
|
||||||
}
|
}
|
||||||
@@ -70,8 +71,8 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
|
|||||||
</label>
|
</label>
|
||||||
<VSCodeDropdown id="api-provider" value={selectedProvider} onChange={handleInputChange("apiProvider")}>
|
<VSCodeDropdown id="api-provider" value={selectedProvider} onChange={handleInputChange("apiProvider")}>
|
||||||
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
||||||
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
|
||||||
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
|
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
|
||||||
|
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
||||||
<VSCodeOption value="vertex">GCP Vertex AI</VSCodeOption>
|
<VSCodeOption value="vertex">GCP Vertex AI</VSCodeOption>
|
||||||
</VSCodeDropdown>
|
</VSCodeDropdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,9 +94,11 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
|
|||||||
color: "var(--vscode-descriptionForeground)",
|
color: "var(--vscode-descriptionForeground)",
|
||||||
}}>
|
}}>
|
||||||
This key is stored locally and only used to make API requests from this extension.
|
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" }}>
|
<VSCodeLink href="https://console.anthropic.com/" style={{ display: "inline" }}>
|
||||||
You can get an Anthropic API key by signing up here.
|
You can get an Anthropic API key by signing up here.
|
||||||
</VSCodeLink>
|
</VSCodeLink>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -110,20 +113,24 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
|
|||||||
placeholder="Enter API Key...">
|
placeholder="Enter API Key...">
|
||||||
<span style={{ fontWeight: 500 }}>OpenRouter API Key</span>
|
<span style={{ fontWeight: 500 }}>OpenRouter API Key</span>
|
||||||
</VSCodeTextField>
|
</VSCodeTextField>
|
||||||
|
{!apiConfiguration?.openRouterApiKey && (
|
||||||
|
<VSCodeButtonLink href={getOpenRouterAuthUrl(uriScheme)} style={{ margin: "5px 0 0 0" }}>
|
||||||
|
Get OpenRouter API Key
|
||||||
|
</VSCodeButtonLink>
|
||||||
|
)}
|
||||||
<p
|
<p
|
||||||
style={{
|
style={{
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
marginTop: "5px",
|
marginTop: "5px",
|
||||||
color: "var(--vscode-descriptionForeground)",
|
color: "var(--vscode-descriptionForeground)",
|
||||||
}}>
|
}}>
|
||||||
This key is stored locally and only used to make API requests from this extension.
|
This key is stored locally and only used to make API requests from this extension.{" "}
|
||||||
<VSCodeLink href="https://openrouter.ai/" style={{ display: "inline" }}>
|
{/* {!apiConfiguration?.openRouterApiKey && (
|
||||||
You can get an OpenRouter API key by signing up here.
|
<span style={{ color: "var(--vscode-charts-green)" }}>
|
||||||
</VSCodeLink>{" "}
|
(<span style={{ fontWeight: 500 }}>Note:</span> OpenRouter is recommended for high rate
|
||||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
limits, prompt caching, and wider selection of models.)
|
||||||
(<span style={{ fontWeight: 500 }}>Note:</span> OpenRouter support is experimental and may
|
|
||||||
not work well with large files.)
|
|
||||||
</span>
|
</span>
|
||||||
|
)} */}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -186,11 +193,13 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessa
|
|||||||
color: "var(--vscode-descriptionForeground)",
|
color: "var(--vscode-descriptionForeground)",
|
||||||
}}>
|
}}>
|
||||||
These credentials are stored locally and only used to make API requests from this extension.
|
These credentials are stored locally and only used to make API requests from this extension.
|
||||||
|
{!(apiConfiguration?.awsAccessKey && apiConfiguration?.awsSecretKey) && (
|
||||||
<VSCodeLink
|
<VSCodeLink
|
||||||
href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
|
href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
|
||||||
style={{ display: "inline" }}>
|
style={{ display: "inline" }}>
|
||||||
You can find your AWS access key and secret key here.
|
You can find your AWS access key and secret key here.
|
||||||
</VSCodeLink>
|
</VSCodeLink>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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) => {
|
export const formatPrice = (price: number) => {
|
||||||
return new Intl.NumberFormat("en-US", {
|
return new Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
|
|||||||
Reference in New Issue
Block a user