fix(orchestrator): make provider-aware Claude key startup requirements
All checks were successful
ci/woodpecker/push/infra Pipeline was successful
ci/woodpecker/push/orchestrator Pipeline was successful

This commit is contained in:
Jason Woltje
2026-02-17 17:15:42 -06:00
parent d3474cdd74
commit 6fd8e85266
13 changed files with 185 additions and 35 deletions

View File

@@ -14,6 +14,8 @@ import {
* This allows time for status queries before the session is removed
*/
const DEFAULT_SESSION_CLEANUP_DELAY_MS = 30000; // 30 seconds
const SUPPORTED_AI_PROVIDERS = ["ollama", "claude", "openai"] as const;
type SupportedAiProvider = (typeof SUPPORTED_AI_PROVIDERS)[number];
/**
* Service responsible for spawning Claude agents using Anthropic SDK
@@ -21,22 +23,38 @@ const DEFAULT_SESSION_CLEANUP_DELAY_MS = 30000; // 30 seconds
@Injectable()
export class AgentSpawnerService implements OnModuleDestroy {
private readonly logger = new Logger(AgentSpawnerService.name);
private readonly anthropic: Anthropic;
private readonly anthropic: Anthropic | undefined;
private readonly aiProvider: SupportedAiProvider;
private readonly sessions = new Map<string, AgentSession>();
private readonly maxConcurrentAgents: number;
private readonly sessionCleanupDelayMs: number;
private readonly cleanupTimers = new Map<string, NodeJS.Timeout>();
constructor(private readonly configService: ConfigService) {
const configuredProvider = this.configService.get<string>("orchestrator.aiProvider");
this.aiProvider = this.normalizeAiProvider(configuredProvider);
this.logger.log(`AgentSpawnerService resolved AI provider: ${this.aiProvider}`);
const apiKey = this.configService.get<string>("orchestrator.claude.apiKey");
if (!apiKey) {
throw new Error("CLAUDE_API_KEY is not configured");
}
if (this.aiProvider === "claude") {
if (!apiKey) {
throw new Error("CLAUDE_API_KEY is required when AI_PROVIDER is set to 'claude'");
}
this.anthropic = new Anthropic({
apiKey,
});
this.logger.log("CLAUDE_API_KEY is configured. Initializing Anthropic client.");
this.anthropic = new Anthropic({ apiKey });
} else {
if (apiKey) {
this.logger.debug(
`CLAUDE_API_KEY is set but ignored because AI provider is '${this.aiProvider}'`
);
} else {
this.logger.log(`CLAUDE_API_KEY not required for AI provider '${this.aiProvider}'.`);
}
this.anthropic = undefined;
}
// Default to 20 if not configured
this.maxConcurrentAgents =
@@ -48,10 +66,27 @@ export class AgentSpawnerService implements OnModuleDestroy {
DEFAULT_SESSION_CLEANUP_DELAY_MS;
this.logger.log(
`AgentSpawnerService initialized with Claude SDK (max concurrent agents: ${String(this.maxConcurrentAgents)}, cleanup delay: ${String(this.sessionCleanupDelayMs)}ms)`
`AgentSpawnerService initialized with ${this.aiProvider} AI provider (max concurrent agents: ${String(
this.maxConcurrentAgents
)}, cleanup delay: ${String(this.sessionCleanupDelayMs)}ms)`
);
}
private normalizeAiProvider(provider?: string): SupportedAiProvider {
const normalizedProvider = provider?.trim().toLowerCase();
if (!normalizedProvider) {
return "ollama";
}
if (!SUPPORTED_AI_PROVIDERS.includes(normalizedProvider as SupportedAiProvider)) {
this.logger.warn(`Unsupported AI provider '${normalizedProvider}'. Defaulting to 'ollama'.`);
return "ollama";
}
return normalizedProvider as SupportedAiProvider;
}
/**
* Clean up all pending cleanup timers on module destroy
*/