Skip to main content
Glama
systempromptio

SystemPrompt Coding Agent

Official
query-executor.ts8.09 kB
/** * @fileoverview Query execution for Claude Code service using SDK * @module services/claude-code/query-executor * * @remarks * This module provides query execution capabilities using the Claude Code SDK. * It handles timeout management, error processing, and message extraction. * The executor integrates with the Claude Code SDK to send prompts and * receive streaming responses. * * @example * ```typescript * import { QueryExecutor } from './query-executor'; * * const executor = new QueryExecutor(); * * const result = await executor.execute(session, 'Implement login', { * maxTurns: 20, * timeout: 300000 * }); * * console.log('Response:', result.content); * console.log('Messages:', result.messages.length); * ``` */ import { query, type SDKMessage, type SDKAssistantMessage, type Options } from '@anthropic-ai/claude-code'; import type { ClaudeCodeSession, ClaudeCodeOptions, QueryResult } from './types.js'; import { QueryTimeoutError, QueryAbortedError, CreditBalanceError, InvalidApiKeyError } from './errors.js'; import { DEFAULT_TIMEOUT_MS, DOCKER_WORKSPACE_PATH, ERROR_PATTERNS } from './constants.js'; import { logger } from '../../utils/logger.js'; /** * Executes queries using the Claude Code SDK * * @class QueryExecutor * * @remarks * This class provides a high-level interface for executing queries * with the Claude Code SDK. It manages timeouts, handles errors, * and processes responses into a structured format. */ export class QueryExecutor { /** * Executes a query using the Claude Code SDK * * @param session - The Claude Code session * @param prompt - The prompt to send to Claude * @param options - Optional query configuration * @returns The query result with content and messages * @throws {QueryTimeoutError} If query exceeds timeout * @throws {QueryAbortedError} If query is aborted * @throws {CreditBalanceError} If credit balance is insufficient * @throws {InvalidApiKeyError} If API key is invalid * * @example * ```typescript * try { * const result = await executor.execute( * session, * 'Add error handling to the user service', * { maxTurns: 25 } * ); * } catch (error) { * if (error instanceof QueryTimeoutError) { * console.error('Query timed out'); * } * } * ``` */ async execute( session: ClaudeCodeSession, prompt: string, options?: Partial<ClaudeCodeOptions> ): Promise<QueryResult> { const timeoutMs = options?.timeout || DEFAULT_TIMEOUT_MS; const messages: SDKMessage[] = []; let timeoutId: NodeJS.Timeout | null = null; const abortController = new AbortController(); session.abortController = abortController; try { const queryOptions = this.buildQueryOptions(session, options); const timeoutPromise = new Promise<never>((_, reject) => { timeoutId = setTimeout(() => { abortController.abort(); reject(new QueryTimeoutError(timeoutMs)); }, timeoutMs); }); const queryPromise = this.executeQuery( prompt, abortController, queryOptions, messages, session ); await Promise.race([queryPromise, timeoutPromise]); if (timeoutId) { clearTimeout(timeoutId); } return { content: this.extractContent(messages), messages }; } catch (error) { if (timeoutId) { clearTimeout(timeoutId); } const processedError = this.processError(error, messages); throw processedError; } finally { session.abortController = undefined; } } /** * Builds query options from session and override options * * @private * @param session - The Claude Code session * @param overrides - Optional override options * @returns Merged query options */ private buildQueryOptions( session: ClaudeCodeSession, overrides?: Partial<ClaudeCodeOptions> ): Options { const options: Options = {}; options.cwd = DOCKER_WORKSPACE_PATH; if (session.options.maxTurns) options.maxTurns = session.options.maxTurns; if (session.options.model) options.model = session.options.model; if (session.options.allowedTools) options.allowedTools = session.options.allowedTools; if (session.options.customSystemPrompt) options.customSystemPrompt = session.options.customSystemPrompt; if (overrides?.maxTurns) options.maxTurns = overrides.maxTurns; if (overrides?.model) options.model = overrides.model; if (overrides?.allowedTools) options.allowedTools = overrides.allowedTools; if (overrides?.customSystemPrompt) options.customSystemPrompt = overrides.customSystemPrompt; return options; } /** * Executes the actual query using the SDK * * @private * @param prompt - The prompt to execute * @param abortController - Controller for aborting the query * @param options - Query options * @param messages - Array to collect messages * @param session - The Claude Code session * @throws {QueryAbortedError} If query is aborted */ private async executeQuery( prompt: string, abortController: AbortController, options: Options, messages: SDKMessage[], session: ClaudeCodeSession ): Promise<void> { try { for await (const message of query({ prompt, abortController, options })) { messages.push(message); session.outputBuffer.push(message); session.lastActivity = new Date(); } } catch (error) { if (abortController.signal.aborted) { throw new QueryAbortedError(error instanceof Error ? error.message : 'Unknown error'); } throw error; } } /** * Extracts text content from SDK messages * * @private * @param messages - Array of SDK messages * @returns Concatenated text content */ private extractContent(messages: SDKMessage[]): string { return messages .filter((m): m is SDKAssistantMessage => m.type === 'assistant') .map(m => { if (m.message?.content && Array.isArray(m.message.content)) { return m.message.content .filter((c: unknown): c is { type: 'text'; text: string } => typeof c === 'object' && c !== null && 'type' in c && c.type === 'text' && 'text' in c && typeof c.text === 'string' ) .map((c: { type: 'text'; text: string }) => c.text) .join(''); } return ''; }) .join('\n'); } /** * Processes errors and returns appropriate error types * * @private * @param error - The original error * @param messages - Messages received before error * @returns Processed error with appropriate type * * @remarks * This method analyzes error messages to determine specific error * types like credit balance or API key issues. It examines the * last assistant message for known error patterns. */ private processError(error: unknown, messages: SDKMessage[]): Error { logger.error('Query execution failed', { error }); const lastAssistantMessage = messages .filter((m): m is SDKAssistantMessage => m.type === 'assistant') .pop(); if (lastAssistantMessage?.message?.content) { const content = lastAssistantMessage.message.content; if (Array.isArray(content)) { const textContent = content .filter((c: unknown): c is { type: 'text'; text: string } => typeof c === 'object' && c !== null && 'type' in c && c.type === 'text' && 'text' in c && typeof c.text === 'string' ) .map((c: { type: 'text'; text: string }) => c.text) .join(' '); if (textContent.includes(ERROR_PATTERNS.CREDIT_BALANCE)) { return new CreditBalanceError(); } if (textContent.includes(ERROR_PATTERNS.INVALID_API_KEY)) { return new InvalidApiKeyError(); } } } return error instanceof Error ? error : new Error(String(error)); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systempromptio/systemprompt-code-orchestrator'

If you have feedback or need assistance with the MCP directory API, please join our Discord server