mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
feat(suggestions): Wire to phaseModels.enhancementModel with Cursor support
The suggestions generation route (Feature Enhancement in UI) was not reading from phaseModels settings and always used the default haiku model. Changes: - Read enhancementModel from phaseModels settings - Add provider routing for Cursor vs Claude models - Pass model to createSuggestionsOptions for Claude SDK - For Cursor, include JSON schema in prompt and use ProviderFactory 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* Business logic for generating suggestions
|
* Business logic for generating suggestions
|
||||||
|
*
|
||||||
|
* Model is configurable via phaseModels.enhancementModel in settings
|
||||||
|
* (Feature Enhancement in the UI). Supports both Claude and Cursor models.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||||
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, isCursorModel } from '@automaker/types';
|
||||||
|
import { resolveModelString } from '@automaker/model-resolver';
|
||||||
import { createSuggestionsOptions } from '../../lib/sdk-options.js';
|
import { createSuggestionsOptions } from '../../lib/sdk-options.js';
|
||||||
|
import { ProviderFactory } from '../../providers/provider-factory.js';
|
||||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||||
import { getAppSpecPath } from '@automaker/platform';
|
import { getAppSpecPath } from '@automaker/platform';
|
||||||
import * as secureFs from '../../lib/secure-fs.js';
|
import * as secureFs from '../../lib/secure-fs.js';
|
||||||
@@ -164,55 +170,109 @@ The response will be automatically formatted as structured JSON.`;
|
|||||||
'[Suggestions]'
|
'[Suggestions]'
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = createSuggestionsOptions({
|
// Get model from phase settings (Feature Enhancement = enhancementModel)
|
||||||
cwd: projectPath,
|
const settings = await settingsService?.getGlobalSettings();
|
||||||
abortController,
|
const enhancementModel =
|
||||||
autoLoadClaudeMd,
|
settings?.phaseModels?.enhancementModel || DEFAULT_PHASE_MODELS.enhancementModel;
|
||||||
outputFormat: {
|
const model = resolveModelString(enhancementModel);
|
||||||
type: 'json_schema',
|
|
||||||
schema: suggestionsSchema,
|
logger.info('[Suggestions] Using model:', model);
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const stream = query({ prompt, options });
|
|
||||||
let responseText = '';
|
let responseText = '';
|
||||||
let structuredOutput: { suggestions: Array<Record<string, unknown>> } | null = null;
|
let structuredOutput: { suggestions: Array<Record<string, unknown>> } | null = null;
|
||||||
|
|
||||||
for await (const msg of stream) {
|
// Route to appropriate provider based on model type
|
||||||
if (msg.type === 'assistant' && msg.message.content) {
|
if (isCursorModel(model)) {
|
||||||
for (const block of msg.message.content) {
|
// Use Cursor provider for Cursor models
|
||||||
if (block.type === 'text') {
|
logger.info('[Suggestions] Using Cursor provider');
|
||||||
responseText += block.text;
|
|
||||||
events.emit('suggestions:event', {
|
const provider = ProviderFactory.getProviderForModel(model);
|
||||||
type: 'suggestions_progress',
|
|
||||||
content: block.text,
|
// For Cursor, include the JSON schema in the prompt
|
||||||
});
|
const cursorPrompt = `${prompt}
|
||||||
} else if (block.type === 'tool_use') {
|
|
||||||
events.emit('suggestions:event', {
|
IMPORTANT: You must respond with a valid JSON object matching this schema:
|
||||||
type: 'suggestions_tool',
|
${JSON.stringify(suggestionsSchema, null, 2)}`;
|
||||||
tool: block.name,
|
|
||||||
input: block.input,
|
for await (const msg of provider.executeQuery({
|
||||||
});
|
prompt: cursorPrompt,
|
||||||
|
model,
|
||||||
|
cwd: projectPath,
|
||||||
|
maxTurns: 250,
|
||||||
|
allowedTools: ['Read', 'Glob', 'Grep'],
|
||||||
|
abortController,
|
||||||
|
})) {
|
||||||
|
if (msg.type === 'assistant' && msg.message?.content) {
|
||||||
|
for (const block of msg.message.content) {
|
||||||
|
if (block.type === 'text' && block.text) {
|
||||||
|
responseText += block.text;
|
||||||
|
events.emit('suggestions:event', {
|
||||||
|
type: 'suggestions_progress',
|
||||||
|
content: block.text,
|
||||||
|
});
|
||||||
|
} else if (block.type === 'tool_use') {
|
||||||
|
events.emit('suggestions:event', {
|
||||||
|
type: 'suggestions_tool',
|
||||||
|
tool: block.name,
|
||||||
|
input: block.input,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msg.type === 'result' && msg.subtype === 'success') {
|
}
|
||||||
// Check for structured output
|
} else {
|
||||||
const resultMsg = msg as any;
|
// Use Claude SDK for Claude models
|
||||||
if (resultMsg.structured_output) {
|
logger.info('[Suggestions] Using Claude SDK');
|
||||||
structuredOutput = resultMsg.structured_output as {
|
|
||||||
suggestions: Array<Record<string, unknown>>;
|
const options = createSuggestionsOptions({
|
||||||
};
|
cwd: projectPath,
|
||||||
logger.debug('Received structured output:', structuredOutput);
|
abortController,
|
||||||
}
|
autoLoadClaudeMd,
|
||||||
} else if (msg.type === 'result') {
|
model, // Pass the model from settings
|
||||||
const resultMsg = msg as any;
|
outputFormat: {
|
||||||
if (resultMsg.subtype === 'error_max_structured_output_retries') {
|
type: 'json_schema',
|
||||||
logger.error('Failed to produce valid structured output after retries');
|
schema: suggestionsSchema,
|
||||||
throw new Error('Could not produce valid suggestions output');
|
},
|
||||||
} else if (resultMsg.subtype === 'error_max_turns') {
|
});
|
||||||
logger.error('Hit max turns limit before completing suggestions generation');
|
|
||||||
logger.warn(`Response text length: ${responseText.length} chars`);
|
const stream = query({ prompt, options });
|
||||||
// Still try to parse what we have
|
|
||||||
|
for await (const msg of stream) {
|
||||||
|
if (msg.type === 'assistant' && msg.message.content) {
|
||||||
|
for (const block of msg.message.content) {
|
||||||
|
if (block.type === 'text') {
|
||||||
|
responseText += block.text;
|
||||||
|
events.emit('suggestions:event', {
|
||||||
|
type: 'suggestions_progress',
|
||||||
|
content: block.text,
|
||||||
|
});
|
||||||
|
} else if (block.type === 'tool_use') {
|
||||||
|
events.emit('suggestions:event', {
|
||||||
|
type: 'suggestions_tool',
|
||||||
|
tool: block.name,
|
||||||
|
input: block.input,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (msg.type === 'result' && msg.subtype === 'success') {
|
||||||
|
// Check for structured output
|
||||||
|
const resultMsg = msg as any;
|
||||||
|
if (resultMsg.structured_output) {
|
||||||
|
structuredOutput = resultMsg.structured_output as {
|
||||||
|
suggestions: Array<Record<string, unknown>>;
|
||||||
|
};
|
||||||
|
logger.debug('Received structured output:', structuredOutput);
|
||||||
|
}
|
||||||
|
} else if (msg.type === 'result') {
|
||||||
|
const resultMsg = msg as any;
|
||||||
|
if (resultMsg.subtype === 'error_max_structured_output_retries') {
|
||||||
|
logger.error('Failed to produce valid structured output after retries');
|
||||||
|
throw new Error('Could not produce valid suggestions output');
|
||||||
|
} else if (resultMsg.subtype === 'error_max_turns') {
|
||||||
|
logger.error('Hit max turns limit before completing suggestions generation');
|
||||||
|
logger.warn(`Response text length: ${responseText.length} chars`);
|
||||||
|
// Still try to parse what we have
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user