together-code
Version:
AI-powered coding assistant that plans, then builds
298 lines (279 loc) • 11.4 kB
JavaScript
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
}
}
}
}
}
}