@oliverpople/agency-x
Version:
🚀 **Transform feature requests into production-ready code in seconds**
94 lines (80 loc) • 3.15 kB
text/typescript
import { createLogger } from '../utils/logger';
import { getContext, updateContext } from '../utils/contextStore';
import { getLlmClient } from '../llm/llmRouter';
import { productManagerPrompt, jsonRepairPrompt } from '../utils/promptTemplates';
const logger = createLogger('productManager');
const extractJsonFromResponse = (response: string): any | null => {
// First try to parse the response directly
try {
return JSON.parse(response.trim());
} catch {
// If that fails, try to extract JSON from between thinking tags or from the end
const lines = response.split('\n');
let jsonStart = -1;
// Look for the start of JSON (after thinking tags or just a { )
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line === '{' || line.startsWith('{"')) {
jsonStart = i;
break;
}
// Also check if this line contains </thinking> - JSON should be after this
if (line.includes('</thinking>')) {
jsonStart = i + 1;
break;
}
}
if (jsonStart >= 0) {
const jsonLines = lines.slice(jsonStart);
const potentialJson = jsonLines.join('\n').trim();
try {
return JSON.parse(potentialJson);
} catch {
// Try to find just the JSON object by looking for balanced braces
const match = potentialJson.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g);
if (match && match[0]) {
try {
return JSON.parse(match[0]);
} catch {
return null;
}
}
}
}
return null;
}
};
export const runProductManager = async () => {
logger.start();
const context = getContext();
const { featurePrompt } = context;
const llmClient = getLlmClient();
const prompt = productManagerPrompt.replace('{{featurePrompt}}', featurePrompt);
let response = await llmClient.generate(prompt);
let spec = extractJsonFromResponse(response);
if (!spec) {
logger.error('Invalid JSON response from LLM. Attempting to repair...');
const repairPrompt = jsonRepairPrompt.replace('{{invalidJson}}', response);
response = await llmClient.generate(repairPrompt);
spec = extractJsonFromResponse(response);
if (!spec) {
logger.error('Failed to repair JSON response. Skipping spec generation.');
return {
summary: 'Failed to generate product specification due to invalid LLM response',
error: 'JSON parsing failed',
completedAt: new Date().toISOString()
};
}
}
// Store spec in context for other agents to use
updateContext({ spec });
logger.stop();
logger.success('Generated product spec.');
// Return a condensed summary for the agent output (to avoid duplication)
return {
summary: `Product specification generated with ${spec.userStories?.length || 0} user stories and ${spec.acceptanceCriteria?.length || 0} acceptance criteria.`,
title: spec.title,
reasoning: response.includes('<thinking>') ? 'Used chain-of-thought reasoning' : 'Direct specification generation',
completedAt: new Date().toISOString()
};
};