diff --git a/src/main/services/agents/interfaces/AgentStreamInterface.ts b/src/main/services/agents/interfaces/AgentStreamInterface.ts index 39eeb84f8c..2cb82c4942 100644 --- a/src/main/services/agents/interfaces/AgentStreamInterface.ts +++ b/src/main/services/agents/interfaces/AgentStreamInterface.ts @@ -3,6 +3,7 @@ import { EventEmitter } from 'node:events' +import { GetAgentSessionResponse } from '@types' import { UIMessageChunk } from 'ai' // Generic agent stream event that works with any agent type @@ -23,5 +24,5 @@ export interface AgentStream extends EventEmitter { // Base agent service interface export interface AgentServiceInterface { - invoke(prompt: string, cwd: string, sessionId?: string, options?: any): AgentStream + invoke(prompt: string, session: GetAgentSessionResponse, lastAgentSessionId?: string): Promise } diff --git a/src/main/services/agents/services/SessionMessageService.ts b/src/main/services/agents/services/SessionMessageService.ts index 8ae405e8ee..f83bff5756 100644 --- a/src/main/services/agents/services/SessionMessageService.ts +++ b/src/main/services/agents/services/SessionMessageService.ts @@ -198,10 +198,7 @@ export class SessionMessageService extends BaseService { } // Create the streaming agent invocation (using invokeStream for streaming) - const claudeStream = this.cc.invoke(req.content, session.accessible_paths[0], agentSessionId, { - permissionMode: session.configuration?.permission_mode, - maxTurns: session.configuration?.max_turns - }) + const claudeStream = await this.cc.invoke(req.content, session, agentSessionId) // Use chunk accumulator to manage streaming data const accumulator = new ChunkAccumulator() diff --git a/src/main/services/agents/services/claudecode/index.ts b/src/main/services/agents/services/claudecode/index.ts index e1a7971fdb..46ad151323 100644 --- a/src/main/services/agents/services/claudecode/index.ts +++ b/src/main/services/agents/services/claudecode/index.ts @@ -4,7 +4,10 @@ import { createRequire } from 'node:module' import { Options, query, SDKMessage } from '@anthropic-ai/claude-code' import { loggerService } from '@logger' +// import { config as apiConfig } from '@main/apiServer/config' +import { validateModelId } from '@main/apiServer/utils' +import { GetAgentSessionResponse } from '../..' import { AgentServiceInterface, AgentStream, AgentStreamEvent } from '../../interfaces/AgentStreamInterface' import { transformSDKMessageToUIChunk } from './transform' @@ -34,9 +37,45 @@ class ClaudeCodeService implements AgentServiceInterface { this.claudeExecutablePath = require_.resolve('@anthropic-ai/claude-code/cli.js') } - invoke(prompt: string, cwd: string, session_id?: string, base?: Options): AgentStream { + async invoke(prompt: string, session: GetAgentSessionResponse, lastAgentSessionId?: string): Promise { const aiStream = new ClaudeCodeStream() + // Validate session accessible paths and make sure it exists as a directory + const cwd = session.accessible_paths[0] + if (!cwd) { + aiStream.emit('data', { + type: 'error', + error: new Error('No accessible paths defined for the agent session') + }) + return aiStream + } + + // Validate model + const modelId = session.model + logger.info('Invoking Claude Code with model', { modelId, cwd }) + const modelInfo = await validateModelId(modelId) + if (!modelInfo.valid) { + aiStream.emit('data', { + type: 'error', + error: new Error(`Invalid model ID '${modelId}': ${JSON.stringify(modelInfo.error)}`) + }) + return aiStream + } + if (modelInfo.provider?.type !== 'anthropic' || modelInfo.provider.apiKey === '') { + aiStream.emit('data', { + type: 'error', + error: new Error(`Invalid provider type '${modelInfo.provider?.type}'. Expected 'anthropic' provider type.`) + }) + return aiStream + } + + // TODO: use cherry studio api server config instead of direct provider config to provide more flexibility (e.g. custom headers, proxy, statistics, etc). + // const cfg = await apiConfig.get() + // process.env.ANTHROPIC_AUTH_TOKEN = cfg.apiKey + // process.env.ANTHROPIC_BASE_URL = `http://${cfg.host}:${cfg.port}` + process.env.ANTHROPIC_AUTH_TOKEN = modelInfo.provider.apiKey + process.env.ANTHROPIC_BASE_URL = modelInfo.provider.apiHost + // Build SDK options from parameters const options: Options = { cwd, @@ -44,11 +83,12 @@ class ClaudeCodeService implements AgentServiceInterface { stderr: (chunk: string) => { logger.info('claude stderr', { chunk }) }, - ...base + permissionMode: session.configuration?.permission_mode, + maxTurns: session.configuration?.max_turns } - if (session_id) { - options.resume = session_id + if (lastAgentSessionId) { + options.resume = lastAgentSessionId } logger.info('Starting Claude Code SDK query', {