From f6fd76823b502c2f5b993f9a48da3e2c0f45142c Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:02:25 -0400 Subject: [PATCH] Add Maestro login button --- package-lock.json | 50 ++++++++- package.json | 6 +- src/api/index.ts | 3 + src/api/maestro.ts | 114 +++++++++++++++++++++ src/extension.ts | 14 +++ src/maestro/auth.ts | 38 +++++++ src/maestro/index.ts | 1 + src/providers/ClaudeDevProvider.ts | 43 +++++++- src/shared/ExtensionMessage.ts | 2 + src/shared/WebviewMessage.ts | 2 + src/shared/api.ts | 10 +- src/shared/maestro.ts | 10 ++ webview-ui/package-lock.json | 11 +- webview-ui/package.json | 3 +- webview-ui/src/App.tsx | 4 + webview-ui/src/components/ApiOptions.tsx | 66 +++++++++++- webview-ui/src/components/SettingsView.tsx | 4 + webview-ui/src/utils/validate.ts | 5 + 18 files changed, 375 insertions(+), 11 deletions(-) create mode 100644 src/api/maestro.ts create mode 100644 src/maestro/auth.ts create mode 100644 src/maestro/index.ts create mode 100644 src/shared/maestro.ts diff --git a/package-lock.json b/package-lock.json index 40ae2e5..7d76abd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "claude-dev", - "version": "1.1.15", + "version": "1.3.43", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-dev", - "version": "1.1.15", + "version": "1.3.43", "license": "MIT", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.26.0", "@vscode/codicons": "^0.0.36", + "axios": "^1.7.4", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -23,7 +24,8 @@ "serialize-error": "^11.0.3", "tree-kill": "^1.2.2", "tree-sitter-wasms": "^0.1.11", - "web-tree-sitter": "^0.22.6" + "web-tree-sitter": "^0.22.6", + "zod": "^3.23.8" }, "devDependencies": { "@types/diff": "^5.2.1", @@ -5006,6 +5008,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6243,6 +6255,25 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -8523,6 +8554,11 @@ "dev": true, "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10049,6 +10085,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 75f757a..87acaf9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-dev", "displayName": "Claude Dev", "description": "Autonomous coding agent right in your IDE, capable of creating/editing files, executing commands, and more with your permission every step of the way.", - "version": "1.3.43", + "version": "1.4.0", "icon": "icon.png", "engines": { "vscode": "^1.84.0" @@ -135,6 +135,7 @@ "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.26.0", "@vscode/codicons": "^0.0.36", + "axios": "^1.7.4", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -146,6 +147,7 @@ "serialize-error": "^11.0.3", "tree-kill": "^1.2.2", "tree-sitter-wasms": "^0.1.11", - "web-tree-sitter": "^0.22.6" + "web-tree-sitter": "^0.22.6", + "zod": "^3.23.8" } } diff --git a/src/api/index.ts b/src/api/index.ts index bac0763..0e8f943 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,6 +3,7 @@ import { ApiConfiguration, ApiModelId, ModelInfo } from "../shared/api" import { AnthropicHandler } from "./anthropic" import { AwsBedrockHandler } from "./bedrock" import { OpenRouterHandler } from "./openrouter" +import { MaestroHandler } from "./maestro" export interface ApiHandler { createMessage( @@ -32,6 +33,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { return new OpenRouterHandler(options) case "bedrock": return new AwsBedrockHandler(options) + case "maestro": + return new MaestroHandler(options) default: return new AnthropicHandler(options) } diff --git a/src/api/maestro.ts b/src/api/maestro.ts new file mode 100644 index 0000000..cb0ca35 --- /dev/null +++ b/src/api/maestro.ts @@ -0,0 +1,114 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import { ApiHandler, withoutImageData } from "." +import { ApiHandlerOptions, maestroDefaultModelId, MaestroModelId, maestroModels, ModelInfo } from "../shared/api" + +export class MaestroHandler implements ApiHandler { + private options: ApiHandlerOptions + private client: Anthropic + + constructor(options: ApiHandlerOptions) { + this.options = options + this.client = new Anthropic({ apiKey: this.options.apiKey }) + } + + async createMessage( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + tools: Anthropic.Messages.Tool[] + ): Promise { + const modelId = this.getModel().id + switch (modelId) { + case "claude-3-5-sonnet-20240620": + case "claude-3-haiku-20240307": + const userMsgIndices = messages.reduce( + (acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc), + [] as number[] + ) + const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1 + const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1 + return await this.client.beta.promptCaching.messages.create( + { + model: modelId, + max_tokens: this.getModel().info.maxTokens, + system: [{ text: systemPrompt, type: "text", cache_control: { type: "ephemeral" } }], + messages: messages.map((message, index) => { + if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) { + return { + ...message, + content: + typeof message.content === "string" + ? [ + { + type: "text", + text: message.content, + cache_control: { type: "ephemeral" }, + }, + ] + : message.content.map((content, contentIndex) => + contentIndex === message.content.length - 1 + ? { ...content, cache_control: { type: "ephemeral" } } + : content + ), + } + } + return message + }), + tools, + tool_choice: { type: "auto" }, + }, + (() => { + switch (modelId) { + case "claude-3-5-sonnet-20240620": + return { + headers: { + "anthropic-beta": "prompt-caching-2024-07-31", + }, + } + case "claude-3-haiku-20240307": + return { + headers: { "anthropic-beta": "prompt-caching-2024-07-31" }, + } + default: + return undefined + } + })() + ) + default: + return await this.client.messages.create({ + model: modelId, + max_tokens: this.getModel().info.maxTokens, + system: [{ text: systemPrompt, type: "text" }], + messages, + tools, + tool_choice: { type: "auto" }, + }) + } + } + + createUserReadableRequest( + userContent: Array< + | Anthropic.TextBlockParam + | Anthropic.ImageBlockParam + | Anthropic.ToolUseBlockParam + | Anthropic.ToolResultBlockParam + > + ): any { + return { + model: this.getModel().id, + max_tokens: this.getModel().info.maxTokens, + system: "(see SYSTEM_PROMPT in src/ClaudeDev.ts)", + messages: [{ conversation_history: "..." }, { role: "user", content: withoutImageData(userContent) }], + tools: "(see tools in src/ClaudeDev.ts)", + tool_choice: { type: "auto" }, + } + } + + getModel(): { id: MaestroModelId; info: ModelInfo } { + const modelId = this.options.apiModelId + if (modelId && modelId in maestroModels) { + const id = modelId as MaestroModelId + return { id, info: maestroModels[id] } + } + return { id: maestroDefaultModelId, info: maestroModels[maestroDefaultModelId] } + } +} diff --git a/src/extension.ts b/src/extension.ts index 30d44d1..b1e2fb2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -108,6 +108,20 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.workspace.registerTextDocumentContentProvider("claude-dev-diff", diffContentProvider) ) + + // URI Handler + const handleUri = async (uri: vscode.Uri) => { + const query = new URLSearchParams(uri.query) + const token = query.get("token") + const fixedToken = token?.replaceAll("jwt?token=", "") + console.log(fixedToken) + console.log(uri) + + if (fixedToken) { + await sidebarProvider.saveMaestroToken(fixedToken) + } + } + context.subscriptions.push(vscode.window.registerUriHandler({ handleUri })) } // This method is called when your extension is deactivated diff --git a/src/maestro/auth.ts b/src/maestro/auth.ts new file mode 100644 index 0000000..23cfc62 --- /dev/null +++ b/src/maestro/auth.ts @@ -0,0 +1,38 @@ +import axios from "axios" +import * as vscode from "vscode" +import { MaestroUser, MaestroUserSchema } from "../shared/maestro" + +const MAESTRO_BASE_URL = "https://maestro.im-ada.ai" + +export function didClickMaestroSignIn() { + const loginUrl = `${MAESTRO_BASE_URL}/auth/login?ext=1&redirectTo=${vscode.env.uriScheme}://saoudrizwan.claude-dev?token=jwt` + vscode.env.openExternal(vscode.Uri.parse(loginUrl)) +} + +export async function validateMaestroToken({ + token, + showError = false, +}: { + token: string + showError?: boolean +}): Promise { + try { + const response = await axios.post(`${MAESTRO_BASE_URL}/api/extension/auth/callback`, { token }) + const user = MaestroUserSchema.parse(response.data.user) + console.log("retrieved user", user) + return user + } catch (error) { + if (showError) { + if (axios.isAxiosError(error)) { + vscode.window.showErrorMessage( + "Failed to validate token:", + error.response?.status, + error.response?.data + ) + } else { + vscode.window.showErrorMessage("An unexpected error occurred:", error) + } + } + throw error + } +} diff --git a/src/maestro/index.ts b/src/maestro/index.ts new file mode 100644 index 0000000..306751a --- /dev/null +++ b/src/maestro/index.ts @@ -0,0 +1 @@ +export * from "./auth" diff --git a/src/providers/ClaudeDevProvider.ts b/src/providers/ClaudeDevProvider.ts index 77d06f2..e0d53c7 100644 --- a/src/providers/ClaudeDevProvider.ts +++ b/src/providers/ClaudeDevProvider.ts @@ -8,6 +8,8 @@ import { downloadTask, getNonce, getUri, selectImages } from "../utils" import * as path from "path" import fs from "fs/promises" import { HistoryItem } from "../shared/HistoryItem" +import { didClickMaestroSignIn, validateMaestroToken } from "../maestro" +import { MaestroUser } from "../shared/maestro" /* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts @@ -15,7 +17,7 @@ https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts */ -type SecretKey = "apiKey" | "openRouterApiKey" | "awsAccessKey" | "awsSecretKey" +type SecretKey = "apiKey" | "openRouterApiKey" | "awsAccessKey" | "awsSecretKey" | "maestroToken" type GlobalStateKey = | "apiProvider" | "apiModelId" @@ -32,9 +34,11 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { private view?: vscode.WebviewView | vscode.WebviewPanel private claudeDev?: ClaudeDev private latestAnnouncementId = "aug-17-2024" // update to some unique identifier when we add a new announcement + private maestroUser?: MaestroUser constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) { this.outputChannel.appendLine("ClaudeDevProvider instantiated") + this.fetchMaestroUser({}) } /* @@ -340,6 +344,12 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { case "exportTaskWithId": this.exportTaskWithId(message.text!) break + case "didClickMaestroSignIn": + didClickMaestroSignIn() + break + case "didClickMaestroSignOut": + await this.signOutMaestro() + break // Add more switch case statements here as more webview message commands // are created within the webview context (i.e. inside media/main.js) } @@ -349,6 +359,36 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { ) } + // Maestro + + async saveMaestroToken(token: string) { + await this.storeSecret("maestroToken", token) + await this.updateGlobalState("apiProvider", "maestro") + await this.fetchMaestroUser({ showError: true }) + this.claudeDev?.updateApi({ apiProvider: "maestro", maestroToken: token }) + } + + async fetchMaestroUser({ showError = false }: { showError?: boolean }): Promise { + if (this.maestroUser) { + return this.maestroUser + } + const token = await this.getSecret("maestroToken") + if (!token) { + return undefined + } + const user = await validateMaestroToken({ token, showError }) + this.maestroUser = user + await this.postStateToWebview() + return user + } + + async signOutMaestro() { + await this.storeSecret("maestroToken", undefined) + this.claudeDev?.updateApi({ apiProvider: "maestro", maestroToken: undefined }) + this.maestroUser = undefined + await this.postStateToWebview() + } + // Task history async getTaskWithId(id: string): Promise<{ @@ -449,6 +489,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { claudeMessages: this.claudeDev?.claudeMessages || [], taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts), shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId, + maestroUser: this.maestroUser, }, }) } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index d456bb4..b674446 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -2,6 +2,7 @@ import { ApiConfiguration } from "./api" import { HistoryItem } from "./HistoryItem" +import { MaestroUser } from "./maestro" // webview will hold state export interface ExtensionMessage { @@ -21,6 +22,7 @@ export interface ExtensionState { claudeMessages: ClaudeMessage[] taskHistory: HistoryItem[] shouldShowAnnouncement: boolean + maestroUser?: MaestroUser } export interface ClaudeMessage { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d8815be..58dc8cb 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -15,6 +15,8 @@ export interface WebviewMessage { | "showTaskWithId" | "deleteTaskWithId" | "exportTaskWithId" + | "didClickMaestroSignIn" + | "didClickMaestroSignOut" text?: string askResponse?: ClaudeAskResponse apiConfiguration?: ApiConfiguration diff --git a/src/shared/api.ts b/src/shared/api.ts index 563fc10..9e31534 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -1,4 +1,4 @@ -export type ApiProvider = "anthropic" | "openrouter" | "bedrock" +export type ApiProvider = "anthropic" | "openrouter" | "bedrock" | "maestro" export interface ApiHandlerOptions { apiModelId?: ApiModelId @@ -7,6 +7,7 @@ export interface ApiHandlerOptions { awsAccessKey?: string awsSecretKey?: string awsRegion?: string + maestroToken?: string } export type ApiConfiguration = ApiHandlerOptions & { @@ -232,3 +233,10 @@ export const openRouterModels = { // outputPrice: 1.5, // }, } as const satisfies Record + +// Maestro +export type MaestroModelId = keyof typeof maestroModels +export const maestroDefaultModelId: MaestroModelId = "claude-3-5-sonnet-20240620" +export const maestroModels = { + ...anthropicModels, +} as const satisfies Record diff --git a/src/shared/maestro.ts b/src/shared/maestro.ts new file mode 100644 index 0000000..a0d5bcd --- /dev/null +++ b/src/shared/maestro.ts @@ -0,0 +1,10 @@ +import { z } from "zod" + +export const MaestroUserSchema = z.object({ + id: z.string(), + image: z.string().nullable(), + email: z.string().email(), + name: z.string().nullable(), + emailVerified: z.coerce.date().nullable(), +}) +export type MaestroUser = z.infer diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index e74837e..3887452 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -26,7 +26,8 @@ "react-virtuoso": "^4.7.13", "rewire": "^7.0.0", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zod": "^3.23.8" }, "devDependencies": { "@types/react-scroll": "^1.8.10", @@ -21449,6 +21450,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 65c0e10..2c66e48 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -21,7 +21,8 @@ "react-virtuoso": "^4.7.13", "rewire": "^7.0.0", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zod": "^3.23.8" }, "scripts": { "start": "react-scripts start", diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 5d2dfe8..fa0fdd9 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -10,6 +10,7 @@ import WelcomeView from "./components/WelcomeView" import { vscode } from "./utils/vscode" import HistoryView from "./components/HistoryView" import { HistoryItem } from "../../src/shared/HistoryItem" +import { MaestroUser } from "../../src/shared/maestro" /* The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab. @@ -30,6 +31,7 @@ const App: React.FC = () => { const [claudeMessages, setClaudeMessages] = useState([]) const [taskHistory, setTaskHistory] = useState([]) const [showAnnouncement, setShowAnnouncement] = useState(false) + const [maestroUser, setMaestroUser] = useState(undefined) useEffect(() => { vscode.postMessage({ type: "webviewDidLaunch" }) @@ -58,6 +60,7 @@ const App: React.FC = () => { setShowAnnouncement(true) vscode.postMessage({ type: "didShowAnnouncement" }) } + setMaestroUser(message.state!.maestroUser) setDidHydrateState(true) break case "action": @@ -100,6 +103,7 @@ const App: React.FC = () => { > + maestroUser?: MaestroUser } -const ApiOptions: React.FC = ({ showModelOptions, apiConfiguration, setApiConfiguration }) => { +const ApiOptions: React.FC = ({ + showModelOptions, + apiConfiguration, + setApiConfiguration, + maestroUser, +}) => { const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => { setApiConfiguration((prev) => ({ ...prev, [field]: event.target.value })) } @@ -69,6 +85,7 @@ const ApiOptions: React.FC = ({ showModelOptions, apiConfigurat Anthropic AWS Bedrock OpenRouter + Maestro @@ -122,6 +139,48 @@ const ApiOptions: React.FC = ({ showModelOptions, apiConfigurat )} + {selectedProvider === "maestro" && ( + <> + {maestroUser ? ( +
+

+ Signed in as: + {maestroUser.email} +

+
+ vscode.postMessage({ type: "didClickMaestroSignOut" })}> + Sign out + +
+
+ ) : ( +
+
+ vscode.postMessage({ type: "didClickMaestroSignIn" })}> + Sign in to Maestro + +
+

+ This will open your browser to sign in to Maestro. You will be redirected back to the + extension after signing in. +

+
+ )} + + )} + {selectedProvider === "bedrock" && (
= ({ showModelOptions, apiConfigurat {selectedProvider === "anthropic" && createDropdown(anthropicModels)} {selectedProvider === "openrouter" && createDropdown(openRouterModels)} {selectedProvider === "bedrock" && createDropdown(bedrockModels)} + {selectedProvider === "maestro" && createDropdown(maestroModels)}
@@ -299,6 +359,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) { return getProviderData(openRouterModels, openRouterDefaultModelId) case "bedrock": return getProviderData(bedrockModels, bedrockDefaultModelId) + case "maestro": + return getProviderData(maestroModels, maestroDefaultModelId) } } diff --git a/webview-ui/src/components/SettingsView.tsx b/webview-ui/src/components/SettingsView.tsx index de39c64..fd812fa 100644 --- a/webview-ui/src/components/SettingsView.tsx +++ b/webview-ui/src/components/SettingsView.tsx @@ -4,10 +4,12 @@ import { ApiConfiguration } from "../../../src/shared/api" import { validateApiConfiguration, validateMaxRequestsPerTask } from "../utils/validate" import { vscode } from "../utils/vscode" import ApiOptions from "./ApiOptions" +import { MaestroUser } from "../../../src/shared/maestro" type SettingsViewProps = { version: string apiConfiguration?: ApiConfiguration + maestroUser?: MaestroUser setApiConfiguration: React.Dispatch> maxRequestsPerTask: string setMaxRequestsPerTask: React.Dispatch> @@ -19,6 +21,7 @@ type SettingsViewProps = { const SettingsView = ({ version, apiConfiguration, + maestroUser, setApiConfiguration, maxRequestsPerTask, setMaxRequestsPerTask, @@ -93,6 +96,7 @@ const SettingsView = ({
diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index 288ebd1..e5d793f 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -18,6 +18,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s return "You must provide a valid API key or choose a different provider." } break + case "maestro": + // if (!apiConfiguration.maestroApiKey) { + // return "You must provide a valid API key or choose a different provider." + // } + break } } return undefined