UNPKG

together-code

Version:

AI-powered coding assistant that plans, then builds

298 lines (279 loc) 11.4 kB
import axios from 'axios'; export class TogetherAIService { constructor(apiKey) { this.baseURL = 'https://api.together.xyz/v1'; this.apiKey = apiKey || process.env.TOGETHER_API_KEY || ''; if (!this.apiKey) { throw new Error('Together AI API key is required. Set TOGETHER_API_KEY environment variable.'); } } async generateCode(request) { try { const systemPrompt = `You are an expert software engineer and helpful coding assistant. Generate clean, well-documented, production-ready code based on the user's request. If the user asks to create files, provide the complete file structure and contents. Format your response as: \`\`\`filename:path/to/file.ext [file contents here] \`\`\` For multiple files, use multiple code blocks with the filename: prefix. If the user is asking to modify existing code or continue a conversation, provide helpful guidance and code improvements. Always include: - Proper error handling - Type definitions (if applicable) - Comments explaining complex logic - Best practices for the chosen language/framework`; // Build messages array with context const messages = [ { role: 'system', content: systemPrompt } ]; // Add context from previous conversation if (request.context && request.context.length > 0) { messages.push(...request.context); } // Add current prompt messages.push({ role: 'user', content: `${request.prompt}${request.language ? ` (using ${request.language})` : ''}${request.framework ? ` with ${request.framework}` : ''}` }); const response = await axios.post(`${this.baseURL}/chat/completions`, { model: 'deepseek-ai/deepseek-v3', messages, max_tokens: 4000, temperature: 0.7, top_p: 0.8, frequency_penalty: 0.1, presence_penalty: 0.1 }, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }); const content = response.data.choices[0]?.message?.content || 'No response generated'; const tokens = response.data.usage?.completion_tokens; return { content, tokens }; } catch (error) { if (axios.isAxiosError(error)) { const errorMessage = error.response?.data?.error?.message || error.message; throw new Error(`Together AI API Error: ${errorMessage}`); } throw error; } } async streamCode(request) { const systemPrompt = `You are an expert software engineer. Generate clean, well-documented, production-ready code based on the user's request.`; try { const response = await axios.post(`${this.baseURL}/chat/completions`, { model: 'deepseek-ai/deepseek-v3', messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: request.prompt } ], max_tokens: 4000, temperature: 0.7, stream: true }, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' }, responseType: 'stream' }); return this.parseStreamResponse(response.data); } catch (error) { if (axios.isAxiosError(error)) { const errorMessage = error.response?.data?.error?.message || error.message; throw new Error(`Together AI API Error: ${errorMessage}`); } throw error; } } async generatePlan(request) { try { const systemPrompt = `You are an expert software engineer and project planner. Your job is to analyze a user's request and create a concise step-by-step plan. IMPORTANT: You MUST respond with ONLY a valid JSON object. Do not include any explanatory text before or after the JSON. Return your response as a JSON object with this exact structure: { "overview": "Brief description of what will be built", "steps": [ { "id": "step-1", "title": "Brief step title", "description": "One concise sentence describing what this step accomplishes", "estimatedTime": "2-3 minutes", "dependencies": [] } ] } RULES FOR CONCISE PLANNING: - Keep step descriptions to ONE sentence each - Focus on the outcome, not the implementation details - Break down complex requests into 3-6 logical steps - Each step should be focused and achievable - Include realistic time estimates - Use clear, action-oriented language RESPOND WITH ONLY THE JSON OBJECT - NO OTHER TEXT.`; // Build messages array with context const messages = [ { role: 'system', content: systemPrompt } ]; // Add context from previous conversation if (request.context && request.context.length > 0) { messages.push(...request.context); } // Add current prompt messages.push({ role: 'user', content: `Please create a step-by-step plan for: ${request.prompt}` }); const response = await axios.post(`${this.baseURL}/chat/completions`, { model: 'deepseek-ai/deepseek-v3', messages, max_tokens: 2000, temperature: 0.3, top_p: 0.8, frequency_penalty: 0.1, presence_penalty: 0.1 }, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }); const content = response.data.choices[0]?.message?.content || '{}'; const tokens = response.data.usage?.completion_tokens; try { // Clean the content to extract just the JSON let cleanContent = content.trim(); // Remove any markdown code blocks cleanContent = cleanContent.replace(/```json\n?/g, '').replace(/```\n?/g, ''); // Find JSON object boundaries const startIndex = cleanContent.indexOf('{'); const lastIndex = cleanContent.lastIndexOf('}'); if (startIndex !== -1 && lastIndex !== -1) { cleanContent = cleanContent.substring(startIndex, lastIndex + 1); } const planData = JSON.parse(cleanContent); const plan = { overview: planData.overview || 'No overview provided', steps: planData.steps || [] }; return { plan, tokens }; } catch (parseError) { console.error('Failed to parse plan. Raw content:', content); throw new Error(`Failed to parse plan from AI response: ${parseError instanceof Error ? parseError.message : 'Unknown parsing error'}`); } } catch (error) { if (axios.isAxiosError(error)) { const errorMessage = error.response?.data?.error?.message || error.message; throw new Error(`Together AI API Error: ${errorMessage}`); } throw error; } } async executeStep(request) { try { const systemPrompt = `You are an expert software engineer executing a specific step in a development plan. Current step to execute: Title: ${request.step.title} Description: ${request.step.description} Overall project context: ${request.plan.overview} IMPORTANT: Structure your response as follows: ## Summary [One sentence describing what this step accomplishes] ## Changes Made [Bullet list of key changes/additions made in this step] ## Code [If creating files, format as code blocks with filenames] \`\`\`filename:path/to/file.ext [file contents here] \`\`\` ## Notes [Any important implementation details or next steps to be aware of] Focus only on this step - don't implement future steps. Generate clean, production-ready code with: - Proper error handling - Type definitions (if applicable) - Clear, concise comments - Best practices for the chosen language/framework`; // Build messages array with context const messages = [ { role: 'system', content: systemPrompt } ]; // Add context from previous conversation if (request.context && request.context.length > 0) { messages.push(...request.context); } // Add current step execution request messages.push({ role: 'user', content: `Execute step: ${request.step.title}\n\n${request.step.description}` }); const response = await axios.post(`${this.baseURL}/chat/completions`, { model: 'Qwen/Qwen2.5-Coder-32B-Instruct', messages, max_tokens: 4000, temperature: 0.7, top_p: 0.8, frequency_penalty: 0.1, presence_penalty: 0.1 }, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }); const content = response.data.choices[0]?.message?.content || 'No response generated'; const tokens = response.data.usage?.completion_tokens; return { content, tokens }; } catch (error) { if (axios.isAxiosError(error)) { const errorMessage = error.response?.data?.error?.message || error.message; throw new Error(`Together AI API Error: ${errorMessage}`); } throw error; } } async *parseStreamResponse(stream) { for await (const chunk of stream) { const lines = chunk.toString().split('\n').filter((line) => line.trim() !== ''); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { return; } try { const parsed = JSON.parse(data); const content = parsed.choices?.[0]?.delta?.content; if (content) { yield content; } } catch (e) { // Skip invalid JSON } } } } } }