diff --git a/package.json b/package.json index baaf7f45..9bba8b8c 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,11 @@ "title": "Configure OpenRouter API Key", "category": "Superdesign" }, + { + "command": "superdesign.configureMoonshotApiKey", + "title": "Configure Moonshot API Key", + "category": "Superdesign" + }, { "command": "superdesign.showChatSidebar", "title": "Show Chat Sidebar", @@ -158,10 +163,16 @@ "enum": [ "openai", "anthropic", - "openrouter" + "openrouter", + "moonshot" ], "default": "anthropic", - "description": "AI model provider for custom agent (OpenAI, Anthropic, or OpenRouter)", + "description": "AI model provider for custom agent (OpenAI, Anthropic, OpenRouter, or Moonshot)", + "scope": "application" + }, + "superdesign.moonshotApiKey": { + "type": "string", + "description": "Moonshot AI API key for Kimi K2 integration", "scope": "application" }, "superdesign.aiModel": { @@ -231,4 +242,4 @@ "remark-gfm": "^4.0.1", "zod": "^3.25.67" } -} +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 4c67fa84..10d62d6e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -26,7 +26,7 @@ async function saveImageToMoodboard(data: { try { // Create .superdesign/moodboard directory if it doesn't exist const moodboardDir = vscode.Uri.joinPath(workspaceFolder.uri, '.superdesign', 'moodboard'); - + try { await vscode.workspace.fs.stat(moodboardDir); } catch { @@ -39,11 +39,11 @@ async function saveImageToMoodboard(data: { const base64Content = data.base64Data.split(',')[1]; // Remove data:image/jpeg;base64, prefix const buffer = Buffer.from(base64Content, 'base64'); const filePath = vscode.Uri.joinPath(moodboardDir, data.fileName); - + await vscode.workspace.fs.writeFile(filePath, buffer); - + Logger.info(`Image saved to moodboard: ${data.fileName} (${(data.size / 1024).toFixed(1)} KB)`); - + // Send back the full absolute path to the webview sidebarProvider.sendMessage({ command: 'imageSavedToMoodboard', @@ -53,11 +53,11 @@ async function saveImageToMoodboard(data: { fullPath: filePath.fsPath } }); - + } catch (error) { Logger.error(`Error saving image to moodboard: ${error}`); vscode.window.showErrorMessage(`Failed to save image: ${error}`); - + // Send error back to webview sidebarProvider.sendMessage({ command: 'imageSaveError', @@ -76,7 +76,7 @@ async function getBase64Image(filePath: string, sidebarProvider: ChatSidebarProv // Read the image file const fileUri = vscode.Uri.file(filePath); const fileData = await vscode.workspace.fs.readFile(fileUri); - + // Determine MIME type from file extension const extension = filePath.toLowerCase().split('.').pop(); let mimeType: string; @@ -100,13 +100,13 @@ async function getBase64Image(filePath: string, sidebarProvider: ChatSidebarProv default: mimeType = 'image/png'; // Default fallback } - + // Convert to base64 const base64Content = Buffer.from(fileData).toString('base64'); const base64DataUri = `data:${mimeType};base64,${base64Content}`; - + console.log(`Converted image to base64: ${filePath} (${(fileData.length / 1024).toFixed(1)} KB)`); - + // Send back the base64 data to webview sidebarProvider.sendMessage({ command: 'base64ImageResponse', @@ -115,10 +115,10 @@ async function getBase64Image(filePath: string, sidebarProvider: ChatSidebarProv mimeType: mimeType, size: fileData.length }); - + } catch (error) { console.error('Error converting image to base64:', error); - + // Send error back to webview sidebarProvider.sendMessage({ command: 'base64ImageResponse', @@ -133,30 +133,30 @@ async function getCssFileContent(filePath: string, sidebarProvider: ChatSidebarP try { // Handle relative paths - resolve them to workspace root let resolvedPath = filePath; - + if (!path.isAbsolute(filePath)) { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (!workspaceFolder) { throw new Error('No workspace folder found'); } - + // If path doesn't start with .superdesign, add it if (!filePath.startsWith('.superdesign/') && filePath.startsWith('design_iterations/')) { resolvedPath = `.superdesign/${filePath}`; } - + resolvedPath = path.join(workspaceFolder.uri.fsPath, resolvedPath); } - + // Read the CSS file const fileUri = vscode.Uri.file(resolvedPath); const fileData = await vscode.workspace.fs.readFile(fileUri); - + // Convert to string const cssContent = Buffer.from(fileData).toString('utf8'); - + console.log(`Read CSS file: ${resolvedPath} (${(fileData.length / 1024).toFixed(1)} KB)`); - + // Send back the CSS content to webview sidebarProvider.sendMessage({ command: 'cssFileContentResponse', @@ -164,10 +164,10 @@ async function getCssFileContent(filePath: string, sidebarProvider: ChatSidebarP content: cssContent, size: fileData.length }); - + } catch (error) { console.error('Error reading CSS file:', error); - + // Send error back to webview sidebarProvider.sendMessage({ command: 'cssFileContentResponse', @@ -1235,7 +1235,7 @@ html.dark { } vscode.window.showInformationMessage('✅ Superdesign project initialized successfully! Created .superdesign folder and design rules for Cursor, Claude, and Windsurf.'); - + } catch (error) { vscode.window.showErrorMessage(`Failed to initialize Superdesign project: ${error}`); } @@ -1274,10 +1274,14 @@ export function activate(context: vscode.ExtensionContext) { await configureOpenRouterApiKey(); }); + const configureMoonshotApiKeyDisposable = vscode.commands.registerCommand('superdesign.configureMoonshotApiKey', async () => { + await configureMoonshotApiKey(); + }); + // Create the chat sidebar provider const sidebarProvider = new ChatSidebarProvider(context.extensionUri, customAgent, Logger.getOutputChannel()); - + // Register the webview view provider for sidebar const sidebarDisposable = vscode.window.registerWebviewViewProvider( ChatSidebarProvider.VIEW_TYPE, @@ -1340,7 +1344,7 @@ export function activate(context: vscode.ExtensionContext) { isOpen: isCanvasOpen }); break; - + case 'autoOpenCanvas': // Auto-open canvas if not already open SuperdesignCanvasPanel.createOrShow(context.extensionUri, sidebarProvider); @@ -1388,7 +1392,7 @@ export function activate(context: vscode.ExtensionContext) { }); context.subscriptions.push( - helloWorldDisposable, + helloWorldDisposable, configureApiKeyDisposable, configureOpenAIApiKeyDisposable, configureOpenRouterApiKeyDisposable, @@ -1432,8 +1436,8 @@ async function configureAnthropicApiKey() { if (input !== '••••••••••••••••') { try { await vscode.workspace.getConfiguration('superdesign').update( - 'anthropicApiKey', - input.trim(), + 'anthropicApiKey', + input.trim(), vscode.ConfigurationTarget.Global ); vscode.window.showInformationMessage('✅ Anthropic API key configured successfully!'); @@ -1477,8 +1481,8 @@ async function configureOpenAIApiKey() { if (input !== '••••••••••••••••') { try { await vscode.workspace.getConfiguration('superdesign').update( - 'openaiApiKey', - input.trim(), + 'openaiApiKey', + input.trim(), vscode.ConfigurationTarget.Global ); vscode.window.showInformationMessage('✅ OpenAI API key configured successfully!'); @@ -1522,8 +1526,8 @@ async function configureOpenRouterApiKey() { if (input !== '••••••••••••••••') { try { await vscode.workspace.getConfiguration('superdesign').update( - 'openrouterApiKey', - input.trim(), + 'openrouterApiKey', + input.trim(), vscode.ConfigurationTarget.Global ); vscode.window.showInformationMessage('✅ OpenRouter API key configured successfully!'); @@ -1614,13 +1618,13 @@ class SuperdesignCanvasPanel { public dispose() { SuperdesignCanvasPanel.currentPanel = undefined; - + // Dispose of file watcher if (this._fileWatcher) { this._fileWatcher.dispose(); this._fileWatcher = undefined; } - + this._panel.dispose(); while (this._disposables.length) { const x = this._disposables.pop(); @@ -1638,7 +1642,7 @@ class SuperdesignCanvasPanel { // Watch for changes in .superdesign/design_iterations/*.html, *.svg, and *.css const pattern = new vscode.RelativePattern( - workspaceFolder, + workspaceFolder, '.superdesign/design_iterations/**/*.{html,svg,css}' ); @@ -1753,7 +1757,7 @@ class SuperdesignCanvasPanel { try { const designFolder = vscode.Uri.joinPath(workspaceFolder.uri, '.superdesign', 'design_iterations'); - + // Check if the design_files folder exists try { await vscode.workspace.fs.stat(designFolder); @@ -1773,9 +1777,9 @@ class SuperdesignCanvasPanel { // Read all files in the directory const files = await vscode.workspace.fs.readDirectory(designFolder); - const designFiles = files.filter(([name, type]) => + const designFiles = files.filter(([name, type]) => type === vscode.FileType.File && ( - name.toLowerCase().endsWith('.html') || + name.toLowerCase().endsWith('.html') || name.toLowerCase().endsWith('.svg') ) ); @@ -1783,7 +1787,7 @@ class SuperdesignCanvasPanel { const loadedFiles = await Promise.all( designFiles.map(async ([fileName, _]) => { const filePath = vscode.Uri.joinPath(designFolder, fileName); - + try { // Read file stats and content const [stat, content] = await Promise.all([ @@ -1793,12 +1797,12 @@ class SuperdesignCanvasPanel { const fileType = fileName.toLowerCase().endsWith('.svg') ? 'svg' : 'html'; let htmlContent = Buffer.from(content).toString('utf8'); - + // For HTML files, inline any external CSS files if (fileType === 'html') { htmlContent = await this._inlineExternalCSS(htmlContent, designFolder); } - + return { name: fileName, path: filePath.fsPath, @@ -1818,7 +1822,7 @@ class SuperdesignCanvasPanel { const validFiles = loadedFiles.filter(file => file !== null); Logger.info(`Loaded ${validFiles.length} design files (HTML & SVG)`); - + this._panel.webview.postMessage({ command: 'designFilesLoaded', data: { files: validFiles } @@ -1838,25 +1842,25 @@ class SuperdesignCanvasPanel { const linkRegex = /]*rel=["']stylesheet["'][^>]*href=["']([^"']+)["'][^>]*>/gi; let modifiedContent = htmlContent; const matches = Array.from(htmlContent.matchAll(linkRegex)); - + for (const match of matches) { const fullLinkTag = match[0]; const cssFileName = match[1]; - + try { // Only process relative paths (not absolute URLs) if (!cssFileName.startsWith('http') && !cssFileName.startsWith('//')) { const cssFilePath = vscode.Uri.joinPath(designFolder, cssFileName); - + // Check if CSS file exists try { const cssContent = await vscode.workspace.fs.readFile(cssFilePath); const cssText = Buffer.from(cssContent).toString('utf8'); - + // Replace the link tag with a style tag containing the CSS content const styleTag = ``; modifiedContent = modifiedContent.replace(fullLinkTag, styleTag); - + Logger.debug(`Inlined CSS file: ${cssFileName}`); } catch (cssError) { Logger.warn(`Could not read CSS file ${cssFileName}: ${cssError}`); @@ -1867,7 +1871,7 @@ class SuperdesignCanvasPanel { Logger.warn(`Error processing CSS link ${cssFileName}: ${error}`); } } - + return modifiedContent; } } @@ -1882,6 +1886,51 @@ function getNonce() { } // This method is called when your extension is deactivated +// Function to configure Moonshot API key +async function configureMoonshotApiKey() { + const currentKey = vscode.workspace.getConfiguration('superdesign').get('moonshotApiKey'); + + const input = await vscode.window.showInputBox({ + title: 'Configure Moonshot API Key', + prompt: 'Enter your Moonshot AI API key (get one from https://platform.moonshot.cn/)', + value: currentKey ? '••••••••••••••••' : '', + password: true, + placeHolder: 'sk-...', + validateInput: (value) => { + if (!value || value.trim().length === 0) { + return 'API key cannot be empty'; + } + if (value === '••••••••••••••••') { + return null; // User didn't change the masked value, that's OK + } + if (!value.startsWith('sk-')) { + return 'Moonshot API keys should start with "sk-"'; + } + return null; + } + }); + + if (input !== undefined) { + // Only update if user didn't just keep the masked value + if (input !== '••••••••••••••••') { + try { + await vscode.workspace.getConfiguration('superdesign').update( + 'moonshotApiKey', + input.trim(), + vscode.ConfigurationTarget.Global + ); + vscode.window.showInformationMessage('✅ Moonshot API key configured successfully!'); + } catch (error) { + vscode.window.showErrorMessage(`Failed to save API key: ${error}`); + } + } else if (currentKey) { + vscode.window.showInformationMessage('API key unchanged (already configured)'); + } else { + vscode.window.showWarningMessage('No API key was set'); + } + } +} + export function deactivate() { Logger.dispose(); } diff --git a/src/services/customAgentService.ts b/src/services/customAgentService.ts index b06b7c0e..1948d934 100644 --- a/src/services/customAgentService.ts +++ b/src/services/customAgentService.ts @@ -33,12 +33,12 @@ export class CustomAgentService implements AgentService { // Try to get workspace root first const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; this.outputChannel.appendLine(`Workspace root detected: ${workspaceRoot}`); - + if (workspaceRoot) { // Create .superdesign folder in workspace root const superdesignDir = path.join(workspaceRoot, '.superdesign'); this.outputChannel.appendLine(`Setting up .superdesign directory at: ${superdesignDir}`); - + // Create directory if it doesn't exist if (!fs.existsSync(superdesignDir)) { fs.mkdirSync(superdesignDir, { recursive: true }); @@ -46,27 +46,27 @@ export class CustomAgentService implements AgentService { } else { this.outputChannel.appendLine(`.superdesign directory already exists: ${superdesignDir}`); } - + this.workingDirectory = superdesignDir; this.outputChannel.appendLine(`Working directory set to: ${this.workingDirectory}`); } else { this.outputChannel.appendLine('No workspace root found, using fallback'); // Fallback to OS temp directory if no workspace const tempDir = path.join(os.tmpdir(), 'superdesign-custom'); - + if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); this.outputChannel.appendLine(`Created temporary superdesign directory: ${tempDir}`); } - + this.workingDirectory = tempDir; this.outputChannel.appendLine(`Working directory set to (fallback): ${this.workingDirectory}`); - + vscode.window.showWarningMessage( 'No workspace folder found. Using temporary directory for Custom Agent operations.' ); } - + this.isInitialized = true; } catch (error) { this.outputChannel.appendLine(`Failed to setup working directory: ${error}`); @@ -81,12 +81,12 @@ export class CustomAgentService implements AgentService { const config = vscode.workspace.getConfiguration('superdesign'); const specificModel = config.get('aiModel'); const provider = config.get('aiModelProvider', 'anthropic'); - + this.outputChannel.appendLine(`Using AI provider: ${provider}`); if (specificModel) { this.outputChannel.appendLine(`Using specific AI model: ${specificModel}`); } - + // Determine provider from model name if specific model is set let effectiveProvider = provider; if (specificModel) { @@ -94,37 +94,58 @@ export class CustomAgentService implements AgentService { effectiveProvider = 'openrouter'; } else if (specificModel.startsWith('claude-')) { effectiveProvider = 'anthropic'; + } else if (specificModel.includes('kimi') || specificModel.includes('moonshot')) { + effectiveProvider = 'moonshot'; } else { effectiveProvider = 'openai'; } } - + switch (effectiveProvider) { case 'openrouter': const openrouterKey = config.get('openrouterApiKey'); if (!openrouterKey) { throw new Error('OpenRouter API key not configured. Please run "Configure OpenRouter API Key" command.'); } - + this.outputChannel.appendLine(`OpenRouter API key found: ${openrouterKey.substring(0, 12)}...`); - + const openrouter = createOpenRouter({ apiKey: openrouterKey }); - + // Use specific model if available, otherwise default to Claude 3.7 Sonnet via OpenRouter const openrouterModel = specificModel || 'anthropic/claude-3-7-sonnet-20250219'; this.outputChannel.appendLine(`Using OpenRouter model: ${openrouterModel}`); return openrouter.chat(openrouterModel); - + + case 'moonshot': + const moonshotKey = config.get('moonshotApiKey'); + if (!moonshotKey) { + throw new Error('Moonshot API key not configured. Please run "Configure Moonshot API Key" command.'); + } + + this.outputChannel.appendLine(`Moonshot API key found: ${moonshotKey.substring(0, 12)}...`); + this.outputChannel.appendLine(`Using Moonshot API baseURL: https://api.moonshot.ai/v1`); + + const moonshot = createOpenAI({ + apiKey: moonshotKey, + baseURL: "https://api.moonshot.ai/v1", + }); + + // Use specific model if available, otherwise default to kimi-k2-0711-preview + const moonshotModel = specificModel || 'kimi-k2-0711-preview'; + this.outputChannel.appendLine(`Using Moonshot model: ${moonshotModel}`); + return moonshot(moonshotModel); + case 'anthropic': const anthropicKey = config.get('anthropicApiKey'); if (!anthropicKey) { throw new Error('Anthropic API key not configured. Please run "Configure Anthropic API Key" command.'); } - + this.outputChannel.appendLine(`Anthropic API key found: ${anthropicKey.substring(0, 12)}...`); - + const anthropic = createAnthropic({ apiKey: anthropicKey, baseURL: "https://anthropic.helicone.ai/v1", @@ -132,21 +153,21 @@ export class CustomAgentService implements AgentService { "Helicone-Auth": `Bearer sk-helicone-utidjzi-eprey7i-tvjl25y-yl7mosi`, } }); - + // Use specific model if available, otherwise default to claude-3-5-sonnet const anthropicModel = specificModel || 'claude-3-5-sonnet-20241022'; this.outputChannel.appendLine(`Using Anthropic model: ${anthropicModel}`); return anthropic(anthropicModel); - + case 'openai': default: const openaiKey = config.get('openaiApiKey'); if (!openaiKey) { throw new Error('OpenAI API key not configured. Please run "Configure OpenAI API Key" command.'); } - + this.outputChannel.appendLine(`OpenAI API key found: ${openaiKey.substring(0, 7)}...`); - + const openai = createOpenAI({ apiKey: openaiKey, baseURL: "https://oai.helicone.ai/v1", @@ -154,7 +175,7 @@ export class CustomAgentService implements AgentService { "Helicone-Auth": `Bearer sk-helicone-utidjzi-eprey7i-tvjl25y-yl7mosi`, } }); - + // Use specific model if available, otherwise default to gpt-4o const openaiModel = specificModel || 'gpt-4o'; this.outputChannel.appendLine(`Using OpenAI model: ${openaiModel}`); @@ -166,7 +187,7 @@ export class CustomAgentService implements AgentService { const config = vscode.workspace.getConfiguration('superdesign'); const specificModel = config.get('aiModel'); const provider = config.get('aiModelProvider', 'anthropic'); - + // Determine the actual model name being used let modelName: string; if (specificModel) { @@ -180,13 +201,16 @@ export class CustomAgentService implements AgentService { case 'openrouter': modelName = 'anthropic/claude-3-7-sonnet-20250219'; break; + case 'moonshot': + modelName = 'kimi-k2-0711-preview'; + break; case 'anthropic': default: modelName = 'claude-3-5-sonnet-20241022'; break; } } - + return `# Role You are superdesign, a senior frontend designer integrated into VS Code as part of the Super Design extension. Your goal is to help user generate amazing design using code @@ -560,20 +584,21 @@ I've created the html design, please reveiw and let me know if you need any chan - **ls**: List directory contents with optional filtering, sorting, and detailed information (shows files and subdirectories) - **bash**: Execute shell/bash commands within the workspace (secure execution with timeouts and output capture) - **generateTheme**: Generate a theme for the design -`;} +`; + } async query( prompt?: string, conversationHistory?: CoreMessage[], - options?: any, + options?: any, abortController?: AbortController, onMessage?: (message: any) => void ): Promise { this.outputChannel.appendLine('=== CUSTOM AGENT QUERY CALLED ==='); - + // Determine which input format we're using const usingConversationHistory = !!conversationHistory && conversationHistory.length > 0; - + if (usingConversationHistory) { this.outputChannel.appendLine(`Query using conversation history: ${conversationHistory!.length} messages`); } else if (prompt) { @@ -581,7 +606,7 @@ I've created the html design, please reveiw and let me know if you need any chan } else { throw new Error('Either prompt or conversationHistory must be provided'); } - + this.outputChannel.appendLine(`Query options: ${JSON.stringify(options, null, 2)}`); this.outputChannel.appendLine(`Streaming enabled: ${!!onMessage}`); @@ -592,7 +617,7 @@ I've created the html design, please reveiw and let me know if you need any chan const responseMessages: any[] = []; const sessionId = `session_${Date.now()}`; let messageBuffer = ''; - + // Tool call streaming state let currentToolCall: any = null; let toolCallBuffer = ''; @@ -630,12 +655,12 @@ I've created the html design, please reveiw and let me know if you need any chan maxSteps: 10, // Enable multi-step reasoning with tools maxTokens: 8192 // Increase token limit to prevent truncation }; - + if (usingConversationHistory) { // Use conversation messages streamTextConfig.messages = conversationHistory; this.outputChannel.appendLine(`Using conversation history with ${conversationHistory!.length} messages`); - + // Debug: Log the actual messages being sent to AI SDK this.outputChannel.appendLine('=== AI SDK MESSAGES DEBUG ==='); conversationHistory!.forEach((msg, index) => { @@ -655,7 +680,7 @@ I've created the html design, please reveiw and let me know if you need any chan this.outputChannel.appendLine('AI SDK streamText created, starting to process chunks...'); - + for await (const chunk of result.fullStream) { // Check for abort signal @@ -670,12 +695,12 @@ I've created the html design, please reveiw and let me know if you need any chan case 'text-delta': // Handle streaming text (assistant message chunks) - CoreMessage format messageBuffer += chunk.textDelta; - + const textMessage: CoreMessage = { role: 'assistant', content: chunk.textDelta }; - + onMessage?.(textMessage); responseMessages.push(textMessage); break; @@ -685,12 +710,12 @@ I've created the html design, please reveiw and let me know if you need any chan this.outputChannel.appendLine(`===Stream finished with reason: ${chunk.finishReason}`); this.outputChannel.appendLine(`${JSON.stringify(chunk)}`); this.outputChannel.appendLine(`========================================`); - + const resultMessage: CoreMessage = { role: 'assistant', content: chunk.finishReason === 'stop' ? 'Response completed successfully' : 'Response completed' }; - + onMessage?.(resultMessage); responseMessages.push(resultMessage); break; @@ -699,12 +724,12 @@ I've created the html design, please reveiw and let me know if you need any chan // Error handling - CoreMessage format const errorMsg = (chunk as any).error?.message || 'Unknown error occurred'; this.outputChannel.appendLine(`Stream error: ${errorMsg}`); - + const errorMessage: CoreMessage = { role: 'assistant', content: `Error: ${errorMsg}` }; - + onMessage?.(errorMessage); responseMessages.push(errorMessage); break; @@ -718,9 +743,9 @@ I've created the html design, please reveiw and let me know if you need any chan args: {} }; toolCallBuffer = ''; - + this.outputChannel.appendLine(`Tool call streaming started: ${streamStart.toolName} (ID: ${streamStart.toolCallId})`); - + // Send initial tool call message in CoreAssistantMessage format const toolCallStartMessage: CoreMessage = { role: 'assistant', @@ -731,7 +756,7 @@ I've created the html design, please reveiw and let me know if you need any chan args: {} // Empty initially, will be updated with deltas }] }; - + onMessage?.(toolCallStartMessage); responseMessages.push(toolCallStartMessage); break; @@ -741,11 +766,11 @@ I've created the html design, please reveiw and let me know if you need any chan const delta = chunk as any; if (currentToolCall && delta.argsTextDelta) { toolCallBuffer += delta.argsTextDelta; - + // Try to parse current buffer as JSON and send update try { const parsedArgs = JSON.parse(toolCallBuffer); - + // Send UPDATE signal (not new message) with special marker const updateMessage: CoreMessage & { _isUpdate?: boolean, _updateToolId?: string } = { role: 'assistant', @@ -758,9 +783,9 @@ I've created the html design, please reveiw and let me know if you need any chan _isUpdate: true, _updateToolId: currentToolCall.toolCallId }; - + onMessage?.(updateMessage); - + } catch (parseError) { // JSON not complete yet, continue buffering if (toolCallBuffer.length % 100 === 0) { @@ -775,7 +800,7 @@ I've created the html design, please reveiw and let me know if you need any chan const toolCall = chunk as any; this.outputChannel.appendLine(`=====Tool call complete: ${JSON.stringify(toolCall)}`); this.outputChannel.appendLine(`========================================`); - + // Skip sending duplicate tool call message if we already sent streaming start if (!currentToolCall) { // Only send if we didn't already send a streaming start message @@ -788,13 +813,13 @@ I've created the html design, please reveiw and let me know if you need any chan args: toolCall.args }] }; - + onMessage?.(toolCallMessage); responseMessages.push(toolCallMessage); } else { this.outputChannel.appendLine(`Skipping duplicate tool call message - already sent streaming start for ID: ${toolCall.toolCallId}`); } - + // Reset tool call streaming state currentToolCall = null; toolCallBuffer = ''; @@ -821,7 +846,7 @@ I've created the html design, please reveiw and let me know if you need any chan if ((chunk as any).type === 'tool-result') { const toolResult = chunk as any; this.outputChannel.appendLine(`Tool result received for ID: ${toolResult.toolCallId}: ${JSON.stringify(toolResult.result).substring(0, 200)}...`); - + // Send tool result in CoreToolMessage format const toolResultMessage: CoreMessage = { role: 'tool', @@ -833,7 +858,7 @@ I've created the html design, please reveiw and let me know if you need any chan isError: toolResult.isError || false }] }; - + onMessage?.(toolResultMessage); responseMessages.push(toolResultMessage); } else { @@ -845,13 +870,13 @@ I've created the html design, please reveiw and let me know if you need any chan this.outputChannel.appendLine(`Query completed successfully. Total messages: ${responseMessages.length}`); this.outputChannel.appendLine(`Complete response: "${messageBuffer}"`); - + return responseMessages; } catch (error) { this.outputChannel.appendLine(`Custom Agent query failed: ${error}`); this.outputChannel.appendLine(`Error stack: ${error instanceof Error ? error.stack : 'No stack trace'}`); - + // Send error message if streaming callback is available if (onMessage) { const errorMessage = { @@ -863,7 +888,7 @@ I've created the html design, please reveiw and let me know if you need any chan }; onMessage(errorMessage); } - + throw error; } } @@ -887,7 +912,7 @@ I've created the html design, please reveiw and let me know if you need any chan const config = vscode.workspace.getConfiguration('superdesign'); const specificModel = config.get('aiModel'); const provider = config.get('aiModelProvider', 'anthropic'); - + // Determine provider from model name if specific model is set let effectiveProvider = provider; if (specificModel) { @@ -895,16 +920,20 @@ I've created the html design, please reveiw and let me know if you need any chan effectiveProvider = 'openrouter'; } else if (specificModel.startsWith('claude-')) { effectiveProvider = 'anthropic'; + } else if (specificModel.includes('kimi') || specificModel.includes('k2')) { + effectiveProvider = 'moonshot'; } else { effectiveProvider = 'openai'; } } - + switch (effectiveProvider) { case 'openrouter': return !!config.get('openrouterApiKey'); case 'anthropic': return !!config.get('anthropicApiKey'); + case 'moonshot': + return !!config.get('moonshotApiKey'); case 'openai': default: return !!config.get('openaiApiKey'); @@ -915,14 +944,14 @@ I've created the html design, please reveiw and let me know if you need any chan if (!errorMessage) { return false; } - + const lowerError = errorMessage.toLowerCase(); return lowerError.includes('api key') || - lowerError.includes('authentication') || - lowerError.includes('unauthorized') || - lowerError.includes('invalid_api_key') || - lowerError.includes('permission_denied') || - lowerError.includes('api_key_invalid') || - lowerError.includes('unauthenticated'); + lowerError.includes('authentication') || + lowerError.includes('unauthorized') || + lowerError.includes('invalid_api_key') || + lowerError.includes('permission_denied') || + lowerError.includes('api_key_invalid') || + lowerError.includes('unauthenticated'); } } \ No newline at end of file diff --git a/src/webview/components/Chat/ModelSelector.tsx b/src/webview/components/Chat/ModelSelector.tsx index e77c2eb1..2032940d 100644 --- a/src/webview/components/Chat/ModelSelector.tsx +++ b/src/webview/components/Chat/ModelSelector.tsx @@ -55,7 +55,9 @@ const ModelSelector: React.FC = ({ selectedModel, onModelCha { id: 'rekaai/reka-flash-3', name: 'Reka Flash 3', provider: 'OpenRouter (Reka)', category: 'Balanced' }, // Existing OpenAI (direct) { id: 'gpt-4.1', name: 'GPT-4.1', provider: 'OpenAI', category: 'Balanced' }, - { id: 'gpt-4.1-mini', name: 'GPT-4.1 Mini', provider: 'OpenAI', category: 'Fast' } + { id: 'gpt-4.1-mini', name: 'GPT-4.1 Mini', provider: 'OpenAI', category: 'Fast' }, + // Moonshot AI + { id: 'kimi-k2-0711-preview', name: 'Kimi K2', provider: 'Moonshot', category: 'Balanced' } ]; const filteredModels = models.filter(model => @@ -75,7 +77,7 @@ const ModelSelector: React.FC = ({ selectedModel, onModelCha // Calculate vertical position (above the trigger) let top = triggerRect.top - modalHeight - padding; - + // If there's not enough space above, show below if (top < padding) { top = triggerRect.bottom + padding; @@ -83,7 +85,7 @@ const ModelSelector: React.FC = ({ selectedModel, onModelCha // Calculate horizontal position (align with trigger) let left = triggerRect.left; - + // Ensure modal doesn't go off-screen horizontally const rightEdge = left + modalWidth; if (rightEdge > window.innerWidth - padding) { @@ -358,7 +360,7 @@ const ModelSelector: React.FC = ({ selectedModel, onModelCha
-
{selectedModelName} - - @@ -387,8 +389,8 @@ const ModelSelector: React.FC = ({ selectedModel, onModelCha {isOpen && (
-