mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
feat: unified Claude API key and profile system with z.AI, MiniMax, OpenRouter support (#600)
* feat: add Claude API provider profiles for alternative endpoints Add support for managing multiple Claude-compatible API endpoints (z.AI GLM, AWS Bedrock, etc.) through provider profiles in settings. Features: - New ClaudeApiProfile type with base URL, API key, model mappings - Pre-configured z.AI GLM template with correct model names - Profile selector in Settings > Claude > API Profiles - Clean switching between profiles and direct Anthropic API - Immediate persistence to prevent data loss on restart Profile support added to all execution paths: - Agent service (chat) - Ideation service - Auto-mode service (feature agents, enhancements) - Simple query service (title generation, descriptions, etc.) - Backlog planning, commit messages, spec generation - GitHub issue validation, suggestions Environment variables set when profile is active: - ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN/API_KEY - ANTHROPIC_DEFAULT_HAIKU/SONNET/OPUS_MODEL - API_TIMEOUT_MS, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
This commit is contained in:
committed by
GitHub
parent
63b8eb0991
commit
d97c4b7b57
@@ -14,7 +14,11 @@ import { streamingQuery } from '../../providers/simple-query-service.js';
|
||||
import { parseAndCreateFeatures } from './parse-and-create-features.js';
|
||||
import { getAppSpecPath } from '@automaker/platform';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';
|
||||
import {
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../lib/settings-helpers.js';
|
||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
@@ -123,6 +127,13 @@ Generate ${featureCount} NEW features that build on each other logically. Rememb
|
||||
|
||||
logger.info('Using model:', model);
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[FeatureGeneration]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Use streamingQuery with event callbacks
|
||||
const result = await streamingQuery({
|
||||
prompt,
|
||||
@@ -134,6 +145,8 @@ Generate ${featureCount} NEW features that build on each other logically. Rememb
|
||||
thinkingLevel,
|
||||
readOnly: true, // Feature generation only reads code, doesn't write
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
onText: (text) => {
|
||||
logger.debug(`Feature text block received (${text.length} chars)`);
|
||||
events.emit('spec-regeneration:event', {
|
||||
|
||||
@@ -16,7 +16,11 @@ import { streamingQuery } from '../../providers/simple-query-service.js';
|
||||
import { generateFeaturesFromSpec } from './generate-features-from-spec.js';
|
||||
import { ensureAutomakerDir, getAppSpecPath } from '@automaker/platform';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';
|
||||
import {
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
@@ -100,6 +104,13 @@ ${prompts.appSpec.structuredSpecInstructions}`;
|
||||
|
||||
logger.info('Using model:', model);
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[SpecRegeneration]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
let responseText = '';
|
||||
let structuredOutput: SpecOutput | null = null;
|
||||
|
||||
@@ -132,6 +143,8 @@ Your entire response should be valid JSON starting with { and ending with }. No
|
||||
thinkingLevel,
|
||||
readOnly: true, // Spec generation only reads code, we write the spec ourselves
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
outputFormat: useStructuredOutput
|
||||
? {
|
||||
type: 'json_schema',
|
||||
|
||||
@@ -15,7 +15,10 @@ import { resolvePhaseModel } from '@automaker/model-resolver';
|
||||
import { streamingQuery } from '../../providers/simple-query-service.js';
|
||||
import { getAppSpecPath } from '@automaker/platform';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
|
||||
import {
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../lib/settings-helpers.js';
|
||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||
import {
|
||||
extractImplementedFeatures,
|
||||
@@ -157,6 +160,13 @@ export async function syncSpec(
|
||||
settings?.phaseModels?.specGenerationModel || DEFAULT_PHASE_MODELS.specGenerationModel;
|
||||
const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry);
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[SpecSync]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Use AI to analyze tech stack
|
||||
const techAnalysisPrompt = `Analyze this project and return ONLY a JSON object with the current technology stack.
|
||||
|
||||
@@ -185,6 +195,8 @@ Return ONLY this JSON format, no other text:
|
||||
thinkingLevel,
|
||||
readOnly: true,
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
onText: (text) => {
|
||||
logger.debug(`Tech analysis text: ${text.substring(0, 100)}`);
|
||||
},
|
||||
|
||||
@@ -25,7 +25,11 @@ import {
|
||||
saveBacklogPlan,
|
||||
} from './common.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';
|
||||
import {
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../lib/settings-helpers.js';
|
||||
|
||||
const featureLoader = new FeatureLoader();
|
||||
|
||||
@@ -161,6 +165,13 @@ ${userPrompt}`;
|
||||
finalSystemPrompt = undefined; // System prompt is now embedded in the user prompt
|
||||
}
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[BacklogPlan]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Execute the query
|
||||
const stream = provider.executeQuery({
|
||||
prompt: finalPrompt,
|
||||
@@ -173,6 +184,8 @@ ${userPrompt}`;
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project'] : undefined,
|
||||
readOnly: true, // Plan generation only generates text, doesn't write files
|
||||
thinkingLevel, // Pass thinking level for extended thinking
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
});
|
||||
|
||||
let responseText = '';
|
||||
|
||||
@@ -22,6 +22,7 @@ import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import {
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('DescribeFile');
|
||||
@@ -165,6 +166,13 @@ ${contentToAnalyze}`;
|
||||
|
||||
logger.info(`Resolved model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[DescribeFile]',
|
||||
cwd
|
||||
);
|
||||
|
||||
// Use simpleQuery - provider abstraction handles routing to correct provider
|
||||
const result = await simpleQuery({
|
||||
prompt,
|
||||
@@ -175,6 +183,8 @@ ${contentToAnalyze}`;
|
||||
thinkingLevel,
|
||||
readOnly: true, // File description only reads, doesn't write
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
});
|
||||
|
||||
const description = result.text;
|
||||
|
||||
@@ -22,6 +22,7 @@ import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import {
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('DescribeImage');
|
||||
@@ -284,6 +285,13 @@ export function createDescribeImageHandler(
|
||||
// Get customized prompts from settings
|
||||
const prompts = await getPromptCustomization(settingsService, '[DescribeImage]');
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[DescribeImage]',
|
||||
cwd
|
||||
);
|
||||
|
||||
// Build the instruction text from centralized prompts
|
||||
const instructionText = prompts.contextDescription.describeImagePrompt;
|
||||
|
||||
@@ -325,6 +333,8 @@ export function createDescribeImageHandler(
|
||||
thinkingLevel,
|
||||
readOnly: true, // Image description only reads, doesn't write
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
});
|
||||
|
||||
logger.info(`[${requestId}] simpleQuery completed in ${Date.now() - queryStart}ms`);
|
||||
|
||||
@@ -12,7 +12,10 @@ import { resolveModelString } from '@automaker/model-resolver';
|
||||
import { CLAUDE_MODEL_MAP, type ThinkingLevel } from '@automaker/types';
|
||||
import { simpleQuery } from '../../../providers/simple-query-service.js';
|
||||
import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import { getPromptCustomization } from '../../../lib/settings-helpers.js';
|
||||
import {
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../../lib/settings-helpers.js';
|
||||
import {
|
||||
buildUserPrompt,
|
||||
isValidEnhancementMode,
|
||||
@@ -33,6 +36,8 @@ interface EnhanceRequestBody {
|
||||
model?: string;
|
||||
/** Optional thinking level for Claude models */
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
/** Optional project path for per-project Claude API profile */
|
||||
projectPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +67,7 @@ export function createEnhanceHandler(
|
||||
): (req: Request, res: Response) => Promise<void> {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { originalText, enhancementMode, model, thinkingLevel } =
|
||||
const { originalText, enhancementMode, model, thinkingLevel, projectPath } =
|
||||
req.body as EnhanceRequestBody;
|
||||
|
||||
// Validate required fields
|
||||
@@ -126,6 +131,14 @@ export function createEnhanceHandler(
|
||||
|
||||
logger.debug(`Using model: ${resolvedModel}`);
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
// Uses project-specific profile if projectPath provided, otherwise global
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[EnhancePrompt]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Use simpleQuery - provider abstraction handles routing to correct provider
|
||||
// The system prompt is combined with user prompt since some providers
|
||||
// don't have a separate system prompt concept
|
||||
@@ -137,6 +150,8 @@ export function createEnhanceHandler(
|
||||
allowedTools: [],
|
||||
thinkingLevel,
|
||||
readOnly: true, // Prompt enhancement only generates text, doesn't write files
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
});
|
||||
|
||||
const enhancedText = result.text;
|
||||
|
||||
@@ -10,12 +10,16 @@ import { createLogger } from '@automaker/utils';
|
||||
import { CLAUDE_MODEL_MAP } from '@automaker/model-resolver';
|
||||
import { simpleQuery } from '../../../providers/simple-query-service.js';
|
||||
import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import { getPromptCustomization } from '../../../lib/settings-helpers.js';
|
||||
import {
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('GenerateTitle');
|
||||
|
||||
interface GenerateTitleRequestBody {
|
||||
description: string;
|
||||
projectPath?: string;
|
||||
}
|
||||
|
||||
interface GenerateTitleSuccessResponse {
|
||||
@@ -33,7 +37,7 @@ export function createGenerateTitleHandler(
|
||||
): (req: Request, res: Response) => Promise<void> {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { description } = req.body as GenerateTitleRequestBody;
|
||||
const { description, projectPath } = req.body as GenerateTitleRequestBody;
|
||||
|
||||
if (!description || typeof description !== 'string') {
|
||||
const response: GenerateTitleErrorResponse = {
|
||||
@@ -60,6 +64,14 @@ export function createGenerateTitleHandler(
|
||||
const prompts = await getPromptCustomization(settingsService, '[GenerateTitle]');
|
||||
const systemPrompt = prompts.titleGeneration.systemPrompt;
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
// Uses project-specific profile if projectPath provided, otherwise global
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[GenerateTitle]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
const userPrompt = `Generate a concise title for this feature:\n\n${trimmedDescription}`;
|
||||
|
||||
// Use simpleQuery - provider abstraction handles all the streaming/extraction
|
||||
@@ -69,6 +81,8 @@ export function createGenerateTitleHandler(
|
||||
cwd: process.cwd(),
|
||||
maxTurns: 1,
|
||||
allowedTools: [],
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
});
|
||||
|
||||
const title = result.text;
|
||||
|
||||
@@ -34,7 +34,11 @@ import {
|
||||
ValidationComment,
|
||||
ValidationLinkedPR,
|
||||
} from './validation-schema.js';
|
||||
import { getPromptCustomization } from '../../../lib/settings-helpers.js';
|
||||
import {
|
||||
getPromptCustomization,
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../../lib/settings-helpers.js';
|
||||
import {
|
||||
trySetValidationRunning,
|
||||
clearValidationStatus,
|
||||
@@ -43,7 +47,6 @@ import {
|
||||
logger,
|
||||
} from './validation-common.js';
|
||||
import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import { getAutoLoadClaudeMdSetting } from '../../../lib/settings-helpers.js';
|
||||
|
||||
/**
|
||||
* Request body for issue validation
|
||||
@@ -166,6 +169,13 @@ ${basePrompt}`;
|
||||
|
||||
logger.info(`Using model: ${model}`);
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[IssueValidation]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Use streamingQuery with event callbacks
|
||||
const result = await streamingQuery({
|
||||
prompt: finalPrompt,
|
||||
@@ -177,6 +187,8 @@ ${basePrompt}`;
|
||||
reasoningEffort: effectiveReasoningEffort,
|
||||
readOnly: true, // Issue validation only reads code, doesn't write
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
outputFormat: useStructuredOutput
|
||||
? {
|
||||
type: 'json_schema',
|
||||
|
||||
@@ -15,7 +15,11 @@ import { FeatureLoader } from '../../services/feature-loader.js';
|
||||
import { getAppSpecPath } from '@automaker/platform';
|
||||
import * as secureFs from '../../lib/secure-fs.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import { getAutoLoadClaudeMdSetting, getPromptCustomization } from '../../lib/settings-helpers.js';
|
||||
import {
|
||||
getAutoLoadClaudeMdSetting,
|
||||
getPromptCustomization,
|
||||
getActiveClaudeApiProfile,
|
||||
} from '../../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('Suggestions');
|
||||
|
||||
@@ -192,6 +196,13 @@ ${prompts.suggestions.baseTemplate}`;
|
||||
|
||||
logger.info('[Suggestions] Using model:', model);
|
||||
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[Suggestions]',
|
||||
projectPath
|
||||
);
|
||||
|
||||
let responseText = '';
|
||||
|
||||
// Determine if we should use structured output (Claude supports it, Cursor doesn't)
|
||||
@@ -223,6 +234,8 @@ Your entire response should be valid JSON starting with { and ending with }. No
|
||||
thinkingLevel,
|
||||
readOnly: true, // Suggestions only reads code, doesn't write
|
||||
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
outputFormat: useStructuredOutput
|
||||
? {
|
||||
type: 'json_schema',
|
||||
|
||||
@@ -10,7 +10,6 @@ import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { DEFAULT_PHASE_MODELS, isCursorModel, stripProviderPrefix } from '@automaker/types';
|
||||
import { resolvePhaseModel } from '@automaker/model-resolver';
|
||||
@@ -18,6 +17,7 @@ import { mergeCommitMessagePrompts } from '@automaker/prompts';
|
||||
import { ProviderFactory } from '../../../providers/provider-factory.js';
|
||||
import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
import { getActiveClaudeApiProfile } from '../../../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('GenerateCommitMessage');
|
||||
const execAsync = promisify(exec);
|
||||
@@ -74,33 +74,6 @@ interface GenerateCommitMessageErrorResponse {
|
||||
error: string;
|
||||
}
|
||||
|
||||
async function extractTextFromStream(
|
||||
stream: AsyncIterable<{
|
||||
type: string;
|
||||
subtype?: string;
|
||||
result?: string;
|
||||
message?: {
|
||||
content?: Array<{ type: string; text?: string }>;
|
||||
};
|
||||
}>
|
||||
): Promise<string> {
|
||||
let responseText = '';
|
||||
|
||||
for await (const msg of stream) {
|
||||
if (msg.type === 'assistant' && msg.message?.content) {
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === 'text' && block.text) {
|
||||
responseText += block.text;
|
||||
}
|
||||
}
|
||||
} else if (msg.type === 'result' && msg.subtype === 'success') {
|
||||
responseText = msg.result || responseText;
|
||||
}
|
||||
}
|
||||
|
||||
return responseText;
|
||||
}
|
||||
|
||||
export function createGenerateCommitMessageHandler(
|
||||
settingsService?: SettingsService
|
||||
): (req: Request, res: Response) => Promise<void> {
|
||||
@@ -195,57 +168,54 @@ export function createGenerateCommitMessageHandler(
|
||||
// Get the effective system prompt (custom or default)
|
||||
const systemPrompt = await getSystemPrompt(settingsService);
|
||||
|
||||
let message: string;
|
||||
// Get active Claude API profile for alternative endpoint configuration
|
||||
const { profile: claudeApiProfile, credentials } = await getActiveClaudeApiProfile(
|
||||
settingsService,
|
||||
'[GenerateCommitMessage]',
|
||||
worktreePath
|
||||
);
|
||||
|
||||
// Route to appropriate provider based on model type
|
||||
if (isCursorModel(model)) {
|
||||
// Use Cursor provider for Cursor models
|
||||
logger.info(`Using Cursor provider for model: ${model}`);
|
||||
// Get provider for the model type
|
||||
const provider = ProviderFactory.getProviderForModel(model);
|
||||
const bareModel = stripProviderPrefix(model);
|
||||
|
||||
const provider = ProviderFactory.getProviderForModel(model);
|
||||
const bareModel = stripProviderPrefix(model);
|
||||
// For Cursor models, combine prompts since Cursor doesn't support systemPrompt separation
|
||||
const effectivePrompt = isCursorModel(model)
|
||||
? `${systemPrompt}\n\n${userPrompt}`
|
||||
: userPrompt;
|
||||
const effectiveSystemPrompt = isCursorModel(model) ? undefined : systemPrompt;
|
||||
|
||||
const cursorPrompt = `${systemPrompt}\n\n${userPrompt}`;
|
||||
logger.info(`Using ${provider.getName()} provider for model: ${model}`);
|
||||
|
||||
let responseText = '';
|
||||
const cursorStream = provider.executeQuery({
|
||||
prompt: cursorPrompt,
|
||||
model: bareModel,
|
||||
cwd: worktreePath,
|
||||
maxTurns: 1,
|
||||
allowedTools: [],
|
||||
readOnly: true,
|
||||
});
|
||||
let responseText = '';
|
||||
const stream = provider.executeQuery({
|
||||
prompt: effectivePrompt,
|
||||
model: bareModel,
|
||||
cwd: worktreePath,
|
||||
systemPrompt: effectiveSystemPrompt,
|
||||
maxTurns: 1,
|
||||
allowedTools: [],
|
||||
readOnly: true,
|
||||
claudeApiProfile, // Pass active Claude API profile for alternative endpoint configuration
|
||||
credentials, // Pass credentials for resolving 'credentials' apiKeySource
|
||||
});
|
||||
|
||||
// Wrap with timeout to prevent indefinite hangs
|
||||
for await (const msg of withTimeout(cursorStream, AI_TIMEOUT_MS)) {
|
||||
if (msg.type === 'assistant' && msg.message?.content) {
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === 'text' && block.text) {
|
||||
responseText += block.text;
|
||||
}
|
||||
// Wrap with timeout to prevent indefinite hangs
|
||||
for await (const msg of withTimeout(stream, AI_TIMEOUT_MS)) {
|
||||
if (msg.type === 'assistant' && msg.message?.content) {
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === 'text' && block.text) {
|
||||
responseText += block.text;
|
||||
}
|
||||
}
|
||||
} else if (msg.type === 'result' && msg.subtype === 'success' && msg.result) {
|
||||
// Use result if available (some providers return final text here)
|
||||
responseText = msg.result;
|
||||
}
|
||||
|
||||
message = responseText.trim();
|
||||
} else {
|
||||
// Use Claude SDK for Claude models
|
||||
const stream = query({
|
||||
prompt: userPrompt,
|
||||
options: {
|
||||
model,
|
||||
systemPrompt,
|
||||
maxTurns: 1,
|
||||
allowedTools: [],
|
||||
permissionMode: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
// Wrap with timeout to prevent indefinite hangs
|
||||
message = await extractTextFromStream(withTimeout(stream, AI_TIMEOUT_MS));
|
||||
}
|
||||
|
||||
const message = responseText.trim();
|
||||
|
||||
if (!message || message.trim().length === 0) {
|
||||
logger.warn('Received empty response from model');
|
||||
const response: GenerateCommitMessageErrorResponse = {
|
||||
|
||||
Reference in New Issue
Block a user