mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 21:23:07 +00:00
feat(server): Add Cursor provider routing to spec generation routes
Add Cursor model support to generate-spec.ts and generate-features-from-spec.ts routes, allowing them to use Cursor models when configured in phaseModels settings. - Both routes now detect Cursor models via isCursorModel() - Route to ProviderFactory for Cursor models, Claude SDK for Claude models - Use resolveModelString() for proper model ID resolution - Extract JSON from Cursor responses using shared json-extractor utility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,10 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
|
|||||||
import * as secureFs from '../../lib/secure-fs.js';
|
import * as secureFs from '../../lib/secure-fs.js';
|
||||||
import type { EventEmitter } from '../../lib/events.js';
|
import type { EventEmitter } from '../../lib/events.js';
|
||||||
import { createLogger } from '@automaker/utils';
|
import { createLogger } from '@automaker/utils';
|
||||||
import { DEFAULT_PHASE_MODELS } from '@automaker/types';
|
import { DEFAULT_PHASE_MODELS, isCursorModel } from '@automaker/types';
|
||||||
|
import { resolveModelString } from '@automaker/model-resolver';
|
||||||
import { createFeatureGenerationOptions } from '../../lib/sdk-options.js';
|
import { createFeatureGenerationOptions } from '../../lib/sdk-options.js';
|
||||||
|
import { ProviderFactory } from '../../providers/provider-factory.js';
|
||||||
import { logAuthStatus } from './common.js';
|
import { logAuthStatus } from './common.js';
|
||||||
import { parseAndCreateFeatures } from './parse-and-create-features.js';
|
import { parseAndCreateFeatures } from './parse-and-create-features.js';
|
||||||
import { getAppSpecPath } from '@automaker/platform';
|
import { getAppSpecPath } from '@automaker/platform';
|
||||||
@@ -109,14 +111,58 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge
|
|||||||
const settings = await settingsService?.getGlobalSettings();
|
const settings = await settingsService?.getGlobalSettings();
|
||||||
const featureGenerationModel =
|
const featureGenerationModel =
|
||||||
settings?.phaseModels?.featureGenerationModel || DEFAULT_PHASE_MODELS.featureGenerationModel;
|
settings?.phaseModels?.featureGenerationModel || DEFAULT_PHASE_MODELS.featureGenerationModel;
|
||||||
|
const model = resolveModelString(featureGenerationModel);
|
||||||
|
|
||||||
logger.info('Using model:', featureGenerationModel);
|
logger.info('Using model:', model);
|
||||||
|
|
||||||
|
let responseText = '';
|
||||||
|
let messageCount = 0;
|
||||||
|
|
||||||
|
// Route to appropriate provider based on model type
|
||||||
|
if (isCursorModel(model)) {
|
||||||
|
// Use Cursor provider for Cursor models
|
||||||
|
logger.info('[FeatureGeneration] Using Cursor provider');
|
||||||
|
|
||||||
|
const provider = ProviderFactory.getProviderForModel(model);
|
||||||
|
|
||||||
|
for await (const msg of provider.executeQuery({
|
||||||
|
prompt,
|
||||||
|
model,
|
||||||
|
cwd: projectPath,
|
||||||
|
maxTurns: 250,
|
||||||
|
allowedTools: ['Read', 'Glob', 'Grep'],
|
||||||
|
abortController,
|
||||||
|
})) {
|
||||||
|
messageCount++;
|
||||||
|
|
||||||
|
if (msg.type === 'assistant' && msg.message?.content) {
|
||||||
|
for (const block of msg.message.content) {
|
||||||
|
if (block.type === 'text' && block.text) {
|
||||||
|
responseText += block.text;
|
||||||
|
logger.debug(`Feature text block received (${block.text.length} chars)`);
|
||||||
|
events.emit('spec-regeneration:event', {
|
||||||
|
type: 'spec_regeneration_progress',
|
||||||
|
content: block.text,
|
||||||
|
projectPath: projectPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (msg.type === 'result' && msg.subtype === 'success' && msg.result) {
|
||||||
|
// Use result if it's a final accumulated message
|
||||||
|
if (msg.result.length > responseText.length) {
|
||||||
|
responseText = msg.result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use Claude SDK for Claude models
|
||||||
|
logger.info('[FeatureGeneration] Using Claude SDK');
|
||||||
|
|
||||||
const options = createFeatureGenerationOptions({
|
const options = createFeatureGenerationOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
autoLoadClaudeMd,
|
autoLoadClaudeMd,
|
||||||
model: featureGenerationModel,
|
model,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('SDK Options:', JSON.stringify(options, null, 2));
|
logger.debug('SDK Options:', JSON.stringify(options, null, 2));
|
||||||
@@ -134,9 +180,6 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge
|
|||||||
throw queryError;
|
throw queryError;
|
||||||
}
|
}
|
||||||
|
|
||||||
let responseText = '';
|
|
||||||
let messageCount = 0;
|
|
||||||
|
|
||||||
logger.debug('Starting to iterate over feature stream...');
|
logger.debug('Starting to iterate over feature stream...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -172,6 +215,7 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge
|
|||||||
logger.error('Stream error:', streamError);
|
logger.error('Stream error:', streamError);
|
||||||
throw streamError;
|
throw streamError;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`Feature stream complete. Total messages: ${messageCount}`);
|
logger.info(`Feature stream complete. Total messages: ${messageCount}`);
|
||||||
logger.info(`Feature response length: ${responseText.length} chars`);
|
logger.info(`Feature response length: ${responseText.length} chars`);
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ import {
|
|||||||
type SpecOutput,
|
type SpecOutput,
|
||||||
} from '../../lib/app-spec-format.js';
|
} from '../../lib/app-spec-format.js';
|
||||||
import { createLogger } from '@automaker/utils';
|
import { createLogger } from '@automaker/utils';
|
||||||
import { DEFAULT_PHASE_MODELS } from '@automaker/types';
|
import { DEFAULT_PHASE_MODELS, isCursorModel } from '@automaker/types';
|
||||||
|
import { resolveModelString } from '@automaker/model-resolver';
|
||||||
import { createSpecGenerationOptions } from '../../lib/sdk-options.js';
|
import { createSpecGenerationOptions } from '../../lib/sdk-options.js';
|
||||||
|
import { extractJson } from '../../lib/json-extractor.js';
|
||||||
|
import { ProviderFactory } from '../../providers/provider-factory.js';
|
||||||
import { logAuthStatus } from './common.js';
|
import { logAuthStatus } from './common.js';
|
||||||
import { generateFeaturesFromSpec } from './generate-features-from-spec.js';
|
import { generateFeaturesFromSpec } from './generate-features-from-spec.js';
|
||||||
import { ensureAutomakerDir, getAppSpecPath } from '@automaker/platform';
|
import { ensureAutomakerDir, getAppSpecPath } from '@automaker/platform';
|
||||||
@@ -101,14 +104,79 @@ ${getStructuredSpecPromptInstruction()}`;
|
|||||||
const settings = await settingsService?.getGlobalSettings();
|
const settings = await settingsService?.getGlobalSettings();
|
||||||
const specGenerationModel =
|
const specGenerationModel =
|
||||||
settings?.phaseModels?.specGenerationModel || DEFAULT_PHASE_MODELS.specGenerationModel;
|
settings?.phaseModels?.specGenerationModel || DEFAULT_PHASE_MODELS.specGenerationModel;
|
||||||
|
const model = resolveModelString(specGenerationModel);
|
||||||
|
|
||||||
logger.info('Using model:', specGenerationModel);
|
logger.info('Using model:', model);
|
||||||
|
|
||||||
|
let responseText = '';
|
||||||
|
let messageCount = 0;
|
||||||
|
let structuredOutput: SpecOutput | null = null;
|
||||||
|
|
||||||
|
// Route to appropriate provider based on model type
|
||||||
|
if (isCursorModel(model)) {
|
||||||
|
// Use Cursor provider for Cursor models
|
||||||
|
logger.info('[SpecGeneration] Using Cursor provider');
|
||||||
|
|
||||||
|
const provider = ProviderFactory.getProviderForModel(model);
|
||||||
|
|
||||||
|
// For Cursor, include the JSON schema in the prompt
|
||||||
|
const cursorPrompt = `${prompt}
|
||||||
|
|
||||||
|
IMPORTANT: You must respond with a valid JSON object matching this schema:
|
||||||
|
${JSON.stringify(specOutputSchema, null, 2)}`;
|
||||||
|
|
||||||
|
for await (const msg of provider.executeQuery({
|
||||||
|
prompt: cursorPrompt,
|
||||||
|
model,
|
||||||
|
cwd: projectPath,
|
||||||
|
maxTurns: 250,
|
||||||
|
allowedTools: ['Read', 'Glob', 'Grep'],
|
||||||
|
abortController,
|
||||||
|
})) {
|
||||||
|
messageCount++;
|
||||||
|
|
||||||
|
if (msg.type === 'assistant' && msg.message?.content) {
|
||||||
|
for (const block of msg.message.content) {
|
||||||
|
if (block.type === 'text' && block.text) {
|
||||||
|
responseText += block.text;
|
||||||
|
logger.info(
|
||||||
|
`Text block received (${block.text.length} chars), total now: ${responseText.length} chars`
|
||||||
|
);
|
||||||
|
events.emit('spec-regeneration:event', {
|
||||||
|
type: 'spec_regeneration_progress',
|
||||||
|
content: block.text,
|
||||||
|
projectPath: projectPath,
|
||||||
|
});
|
||||||
|
} else if (block.type === 'tool_use') {
|
||||||
|
logger.info('Tool use:', block.name);
|
||||||
|
events.emit('spec-regeneration:event', {
|
||||||
|
type: 'spec_tool',
|
||||||
|
tool: block.name,
|
||||||
|
input: block.input,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (msg.type === 'result' && msg.subtype === 'success' && msg.result) {
|
||||||
|
// Use result if it's a final accumulated message
|
||||||
|
if (msg.result.length > responseText.length) {
|
||||||
|
responseText = msg.result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON from the response text using shared utility
|
||||||
|
if (responseText) {
|
||||||
|
structuredOutput = extractJson<SpecOutput>(responseText, { logger });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use Claude SDK for Claude models
|
||||||
|
logger.info('[SpecGeneration] Using Claude SDK');
|
||||||
|
|
||||||
const options = createSpecGenerationOptions({
|
const options = createSpecGenerationOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
autoLoadClaudeMd,
|
autoLoadClaudeMd,
|
||||||
model: specGenerationModel,
|
model,
|
||||||
outputFormat: {
|
outputFormat: {
|
||||||
type: 'json_schema',
|
type: 'json_schema',
|
||||||
schema: specOutputSchema,
|
schema: specOutputSchema,
|
||||||
@@ -131,10 +199,6 @@ ${getStructuredSpecPromptInstruction()}`;
|
|||||||
throw queryError;
|
throw queryError;
|
||||||
}
|
}
|
||||||
|
|
||||||
let responseText = '';
|
|
||||||
let messageCount = 0;
|
|
||||||
let structuredOutput: SpecOutput | null = null;
|
|
||||||
|
|
||||||
logger.info('Starting to iterate over stream...');
|
logger.info('Starting to iterate over stream...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -202,6 +266,7 @@ ${getStructuredSpecPromptInstruction()}`;
|
|||||||
logger.error('Stream error:', streamError);
|
logger.error('Stream error:', streamError);
|
||||||
throw streamError;
|
throw streamError;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`Stream iteration complete. Total messages: ${messageCount}`);
|
logger.info(`Stream iteration complete. Total messages: ${messageCount}`);
|
||||||
logger.info(`Response text length: ${responseText.length} chars`);
|
logger.info(`Response text length: ${responseText.length} chars`);
|
||||||
|
|||||||
Reference in New Issue
Block a user