mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat(server): Add Cursor provider support for describe-file and describe-image routes
- describe-file.ts: Route to Cursor provider when using Cursor models (composer-1, etc.) - describe-image.ts: Route to Cursor provider with image path context for Cursor models - auto-mode-service.ts: Fix logging to use console.log instead of this.logger Both routes now detect Cursor models using isCursorModel() and use ProviderFactory.getProviderForModel() to get the appropriate provider instead of always using the Claude SDK. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,10 +13,11 @@
|
|||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||||
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 { PathNotAllowedError } from '@automaker/platform';
|
import { PathNotAllowedError } from '@automaker/platform';
|
||||||
import { resolveModelString } from '@automaker/model-resolver';
|
import { resolveModelString } from '@automaker/model-resolver';
|
||||||
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
||||||
|
import { ProviderFactory } from '../../../providers/provider-factory.js';
|
||||||
import * as secureFs from '../../../lib/secure-fs.js';
|
import * as secureFs from '../../../lib/secure-fs.js';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { SettingsService } from '../../../services/settings-service.js';
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
@@ -187,30 +188,64 @@ File: ${fileName}${truncated ? ' (truncated)' : ''}`;
|
|||||||
|
|
||||||
logger.debug(`[DescribeFile] Using model: ${model}`);
|
logger.debug(`[DescribeFile] Using model: ${model}`);
|
||||||
|
|
||||||
// Use centralized SDK options with proper cwd validation
|
let description: string;
|
||||||
// No tools needed since we're passing file content directly
|
|
||||||
const sdkOptions = createCustomOptions({
|
|
||||||
cwd,
|
|
||||||
model,
|
|
||||||
maxTurns: 1,
|
|
||||||
allowedTools: [],
|
|
||||||
autoLoadClaudeMd,
|
|
||||||
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
const promptGenerator = (async function* () {
|
// Route to appropriate provider based on model type
|
||||||
yield {
|
if (isCursorModel(model)) {
|
||||||
type: 'user' as const,
|
// Use Cursor provider for Cursor models
|
||||||
session_id: '',
|
logger.info(`[DescribeFile] Using Cursor provider for model: ${model}`);
|
||||||
message: { role: 'user' as const, content: promptContent },
|
|
||||||
parent_tool_use_id: null,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
const stream = query({ prompt: promptGenerator, options: sdkOptions });
|
const provider = ProviderFactory.getProviderForModel(model);
|
||||||
|
|
||||||
// Extract the description from the response
|
// Build a simple text prompt for Cursor (no multi-part content blocks)
|
||||||
const description = await extractTextFromStream(stream);
|
const cursorPrompt = `${instructionText}\n\n--- FILE CONTENT ---\n${contentToAnalyze}`;
|
||||||
|
|
||||||
|
let responseText = '';
|
||||||
|
for await (const msg of provider.executeQuery({
|
||||||
|
prompt: cursorPrompt,
|
||||||
|
model,
|
||||||
|
cwd,
|
||||||
|
maxTurns: 1,
|
||||||
|
allowedTools: [],
|
||||||
|
})) {
|
||||||
|
if (msg.type === 'assistant' && msg.message?.content) {
|
||||||
|
for (const block of msg.message.content) {
|
||||||
|
if (block.type === 'text' && block.text) {
|
||||||
|
responseText += block.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description = responseText;
|
||||||
|
} else {
|
||||||
|
// Use Claude SDK for Claude models
|
||||||
|
logger.info(`[DescribeFile] Using Claude SDK for model: ${model}`);
|
||||||
|
|
||||||
|
// Use centralized SDK options with proper cwd validation
|
||||||
|
// No tools needed since we're passing file content directly
|
||||||
|
const sdkOptions = createCustomOptions({
|
||||||
|
cwd,
|
||||||
|
model,
|
||||||
|
maxTurns: 1,
|
||||||
|
allowedTools: [],
|
||||||
|
autoLoadClaudeMd,
|
||||||
|
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const promptGenerator = (async function* () {
|
||||||
|
yield {
|
||||||
|
type: 'user' as const,
|
||||||
|
session_id: '',
|
||||||
|
message: { role: 'user' as const, content: promptContent },
|
||||||
|
parent_tool_use_id: null,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const stream = query({ prompt: promptGenerator, options: sdkOptions });
|
||||||
|
|
||||||
|
// Extract the description from the response
|
||||||
|
description = await extractTextFromStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
if (!description || description.trim().length === 0) {
|
if (!description || description.trim().length === 0) {
|
||||||
logger.warn('Received empty response from Claude');
|
logger.warn('Received empty response from Claude');
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||||
import { createLogger, readImageAsBase64 } from '@automaker/utils';
|
import { createLogger, readImageAsBase64 } 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 { resolveModelString } from '@automaker/model-resolver';
|
||||||
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
import { createCustomOptions } from '../../../lib/sdk-options.js';
|
||||||
|
import { ProviderFactory } from '../../../providers/provider-factory.js';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { SettingsService } from '../../../services/settings-service.js';
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
@@ -347,40 +348,79 @@ export function createDescribeImageHandler(
|
|||||||
|
|
||||||
logger.info(`[${requestId}] Using model: ${model}`);
|
logger.info(`[${requestId}] Using model: ${model}`);
|
||||||
|
|
||||||
// Use the same centralized option builder used across the server (validates cwd)
|
let description: string;
|
||||||
const sdkOptions = createCustomOptions({
|
|
||||||
cwd,
|
|
||||||
model,
|
|
||||||
maxTurns: 1,
|
|
||||||
allowedTools: [],
|
|
||||||
autoLoadClaudeMd,
|
|
||||||
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(
|
// Route to appropriate provider based on model type
|
||||||
`[${requestId}] SDK options model=${sdkOptions.model} maxTurns=${sdkOptions.maxTurns} allowedTools=${JSON.stringify(
|
if (isCursorModel(model)) {
|
||||||
sdkOptions.allowedTools
|
// Use Cursor provider for Cursor models
|
||||||
)} sandbox=${JSON.stringify(sdkOptions.sandbox)}`
|
// Note: Cursor may have limited support for image content blocks
|
||||||
);
|
logger.info(`[${requestId}] Using Cursor provider for model: ${model}`);
|
||||||
|
|
||||||
const promptGenerator = (async function* () {
|
const provider = ProviderFactory.getProviderForModel(model);
|
||||||
yield {
|
|
||||||
type: 'user' as const,
|
|
||||||
session_id: '',
|
|
||||||
message: { role: 'user' as const, content: promptContent },
|
|
||||||
parent_tool_use_id: null,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
logger.info(`[${requestId}] Calling query()...`);
|
// Build prompt with image reference for Cursor
|
||||||
const queryStart = Date.now();
|
// Note: Cursor CLI may not support base64 image blocks directly,
|
||||||
const stream = query({ prompt: promptGenerator, options: sdkOptions });
|
// so we include the image path as context
|
||||||
logger.info(`[${requestId}] query() returned stream in ${Date.now() - queryStart}ms`);
|
const cursorPrompt = `${instructionText}\n\nImage file: ${actualPath}\nMIME type: ${imageData.mimeType}`;
|
||||||
|
|
||||||
// Extract the description from the response
|
let responseText = '';
|
||||||
const extractStart = Date.now();
|
const queryStart = Date.now();
|
||||||
const description = await extractTextFromStream(stream, requestId);
|
for await (const msg of provider.executeQuery({
|
||||||
logger.info(`[${requestId}] extractMs=${Date.now() - extractStart}`);
|
prompt: cursorPrompt,
|
||||||
|
model,
|
||||||
|
cwd,
|
||||||
|
maxTurns: 1,
|
||||||
|
allowedTools: ['Read'], // Allow Read tool so Cursor can read the image if needed
|
||||||
|
})) {
|
||||||
|
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(`[${requestId}] Cursor query completed in ${Date.now() - queryStart}ms`);
|
||||||
|
description = responseText;
|
||||||
|
} else {
|
||||||
|
// Use Claude SDK for Claude models (supports image content blocks)
|
||||||
|
logger.info(`[${requestId}] Using Claude SDK for model: ${model}`);
|
||||||
|
|
||||||
|
// Use the same centralized option builder used across the server (validates cwd)
|
||||||
|
const sdkOptions = createCustomOptions({
|
||||||
|
cwd,
|
||||||
|
model,
|
||||||
|
maxTurns: 1,
|
||||||
|
allowedTools: [],
|
||||||
|
autoLoadClaudeMd,
|
||||||
|
sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`[${requestId}] SDK options model=${sdkOptions.model} maxTurns=${sdkOptions.maxTurns} allowedTools=${JSON.stringify(
|
||||||
|
sdkOptions.allowedTools
|
||||||
|
)} sandbox=${JSON.stringify(sdkOptions.sandbox)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const promptGenerator = (async function* () {
|
||||||
|
yield {
|
||||||
|
type: 'user' as const,
|
||||||
|
session_id: '',
|
||||||
|
message: { role: 'user' as const, content: promptContent },
|
||||||
|
parent_tool_use_id: null,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
logger.info(`[${requestId}] Calling query()...`);
|
||||||
|
const queryStart = Date.now();
|
||||||
|
const stream = query({ prompt: promptGenerator, options: sdkOptions });
|
||||||
|
logger.info(`[${requestId}] query() returned stream in ${Date.now() - queryStart}ms`);
|
||||||
|
|
||||||
|
// Extract the description from the response
|
||||||
|
const extractStart = Date.now();
|
||||||
|
description = await extractTextFromStream(stream, requestId);
|
||||||
|
logger.info(`[${requestId}] extractMs=${Date.now() - extractStart}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!description || description.trim().length === 0) {
|
if (!description || description.trim().length === 0) {
|
||||||
logger.warn(`[${requestId}] Received empty response from Claude`);
|
logger.warn(`[${requestId}] Received empty response from Claude`);
|
||||||
|
|||||||
@@ -1146,7 +1146,7 @@ Format your response as a structured markdown document.`;
|
|||||||
const projectAnalysisModel =
|
const projectAnalysisModel =
|
||||||
settings?.phaseModels?.projectAnalysisModel || DEFAULT_PHASE_MODELS.projectAnalysisModel;
|
settings?.phaseModels?.projectAnalysisModel || DEFAULT_PHASE_MODELS.projectAnalysisModel;
|
||||||
const analysisModel = resolveModelString(projectAnalysisModel, DEFAULT_MODELS.claude);
|
const analysisModel = resolveModelString(projectAnalysisModel, DEFAULT_MODELS.claude);
|
||||||
this.logger.info('[AutoMode] Using model for project analysis:', analysisModel);
|
console.log('[AutoMode] Using model for project analysis:', analysisModel);
|
||||||
|
|
||||||
const provider = ProviderFactory.getProviderForModel(analysisModel);
|
const provider = ProviderFactory.getProviderForModel(analysisModel);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user