* feat: プロジェクト単位のCLIパス設定を支援するconfig層を追加 validateCliPath汎用関数、Global/Project設定スキーマ拡張、 env override、3プロバイダ向けresolve関数(env→project→global→undefined)を追加。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: Claude/Cursor/CodexプロバイダにCLIパス解決を統合 各プロバイダのtoXxxOptions()でproject configを読み込み、 resolveXxxCliPath()経由でCLIパスを解決してSDKに渡す。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: per-project CLIパス機能のテストを追加 validateCliPath, resolveClaudeCliPath, resolveCursorCliPath, resolveCodexCliPath(project config層)のユニットテスト、 および既存プロバイダテストのモック更新。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
202 lines
6.0 KiB
TypeScript
202 lines
6.0 KiB
TypeScript
/**
|
|
* High-level Claude client for agent interactions
|
|
*
|
|
* Uses the Claude Agent SDK for native TypeScript integration.
|
|
*/
|
|
|
|
import { executeClaudeCli } from './process.js';
|
|
import type { ClaudeSpawnOptions, ClaudeCallOptions } from './types.js';
|
|
import type { AgentResponse, Status } from '../../core/models/index.js';
|
|
import { createLogger } from '../../shared/utils/index.js';
|
|
import { loadTemplate } from '../../shared/prompts/index.js';
|
|
|
|
export type { ClaudeCallOptions } from './types.js';
|
|
|
|
const log = createLogger('client');
|
|
|
|
/**
|
|
* High-level Claude client for calling Claude with various configurations.
|
|
*
|
|
* Handles agent prompts, custom agents, skills, and AI judge evaluation.
|
|
*/
|
|
export class ClaudeClient {
|
|
/** Determine status from execution result */
|
|
private static determineStatus(
|
|
result: { success: boolean; interrupted?: boolean; content: string; fullContent?: string },
|
|
): Status {
|
|
if (!result.success) {
|
|
if (result.interrupted) {
|
|
return 'interrupted';
|
|
}
|
|
return 'error';
|
|
}
|
|
return 'done';
|
|
}
|
|
|
|
/** Convert ClaudeCallOptions to ClaudeSpawnOptions */
|
|
private static toSpawnOptions(options: ClaudeCallOptions): ClaudeSpawnOptions {
|
|
return {
|
|
cwd: options.cwd,
|
|
abortSignal: options.abortSignal,
|
|
sessionId: options.sessionId,
|
|
allowedTools: options.allowedTools,
|
|
mcpServers: options.mcpServers,
|
|
model: options.model,
|
|
maxTurns: options.maxTurns,
|
|
systemPrompt: options.systemPrompt,
|
|
agents: options.agents,
|
|
permissionMode: options.permissionMode,
|
|
onStream: options.onStream,
|
|
onPermissionRequest: options.onPermissionRequest,
|
|
onAskUserQuestion: options.onAskUserQuestion,
|
|
bypassPermissions: options.bypassPermissions,
|
|
anthropicApiKey: options.anthropicApiKey,
|
|
outputSchema: options.outputSchema,
|
|
sandbox: options.sandbox,
|
|
pathToClaudeCodeExecutable: options.pathToClaudeCodeExecutable,
|
|
};
|
|
}
|
|
|
|
/** Call Claude with an agent prompt */
|
|
async call(
|
|
agentType: string,
|
|
prompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
const spawnOptions = ClaudeClient.toSpawnOptions(options);
|
|
const result = await executeClaudeCli(prompt, spawnOptions);
|
|
const status = ClaudeClient.determineStatus(result);
|
|
|
|
if (!result.success && result.error) {
|
|
log.error('Agent query failed', { agent: agentType, error: result.error });
|
|
}
|
|
|
|
return {
|
|
persona: agentType,
|
|
status,
|
|
content: result.content,
|
|
timestamp: new Date(),
|
|
sessionId: result.sessionId,
|
|
error: result.error,
|
|
structuredOutput: result.structuredOutput,
|
|
};
|
|
}
|
|
|
|
/** Call Claude with a custom agent configuration */
|
|
async callCustom(
|
|
agentName: string,
|
|
prompt: string,
|
|
systemPrompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
const spawnOptions: ClaudeSpawnOptions = {
|
|
...ClaudeClient.toSpawnOptions(options),
|
|
systemPrompt,
|
|
};
|
|
const result = await executeClaudeCli(prompt, spawnOptions);
|
|
const status = ClaudeClient.determineStatus(result);
|
|
|
|
if (!result.success && result.error) {
|
|
log.error('Agent query failed', { agent: agentName, error: result.error });
|
|
}
|
|
|
|
return {
|
|
persona: agentName,
|
|
status,
|
|
content: result.content,
|
|
timestamp: new Date(),
|
|
sessionId: result.sessionId,
|
|
error: result.error,
|
|
structuredOutput: result.structuredOutput,
|
|
};
|
|
}
|
|
|
|
/** Call a Claude Code built-in agent */
|
|
async callAgent(
|
|
claudeAgentName: string,
|
|
prompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
const systemPrompt = loadTemplate('perform_builtin_agent_system_prompt', 'en', { agentName: claudeAgentName });
|
|
return this.callCustom(claudeAgentName, prompt, systemPrompt, options);
|
|
}
|
|
|
|
/** Call a Claude Code skill (using /skill command) */
|
|
async callSkill(
|
|
skillName: string,
|
|
prompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
const fullPrompt = `/${skillName}\n\n${prompt}`;
|
|
const spawnOptions: ClaudeSpawnOptions = {
|
|
cwd: options.cwd,
|
|
abortSignal: options.abortSignal,
|
|
sessionId: options.sessionId,
|
|
allowedTools: options.allowedTools,
|
|
mcpServers: options.mcpServers,
|
|
model: options.model,
|
|
maxTurns: options.maxTurns,
|
|
permissionMode: options.permissionMode,
|
|
onStream: options.onStream,
|
|
onPermissionRequest: options.onPermissionRequest,
|
|
onAskUserQuestion: options.onAskUserQuestion,
|
|
bypassPermissions: options.bypassPermissions,
|
|
anthropicApiKey: options.anthropicApiKey,
|
|
};
|
|
|
|
const result = await executeClaudeCli(fullPrompt, spawnOptions);
|
|
|
|
if (!result.success && result.error) {
|
|
log.error('Skill query failed', { skill: skillName, error: result.error });
|
|
}
|
|
|
|
return {
|
|
persona: `skill:${skillName}`,
|
|
status: result.success ? 'done' : 'error',
|
|
content: result.content,
|
|
timestamp: new Date(),
|
|
sessionId: result.sessionId,
|
|
error: result.error,
|
|
structuredOutput: result.structuredOutput,
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
// ---- Module-level functions ----
|
|
|
|
const defaultClient = new ClaudeClient();
|
|
|
|
export async function callClaude(
|
|
agentType: string,
|
|
prompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
return defaultClient.call(agentType, prompt, options);
|
|
}
|
|
|
|
export async function callClaudeCustom(
|
|
agentName: string,
|
|
prompt: string,
|
|
systemPrompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
return defaultClient.callCustom(agentName, prompt, systemPrompt, options);
|
|
}
|
|
|
|
export async function callClaudeAgent(
|
|
claudeAgentName: string,
|
|
prompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
return defaultClient.callAgent(claudeAgentName, prompt, options);
|
|
}
|
|
|
|
export async function callClaudeSkill(
|
|
skillName: string,
|
|
prompt: string,
|
|
options: ClaudeCallOptions,
|
|
): Promise<AgentResponse> {
|
|
return defaultClient.callSkill(skillName, prompt, options);
|
|
}
|