/** * Claude Code API Functions * * Provides typed API functions for interacting with Claude Code CLI * through the backend endpoints. */ const API_BASE = '/api/claude-code'; // ============================================================================ // Types // ============================================================================ export interface ExtractorGenerationRequest { /** Description of what the extractor should do */ prompt: string; /** Optional study ID for context */ study_id?: string; /** Existing code to improve/modify */ existing_code?: string; /** Expected output variable names */ output_names?: string[]; } export interface ExtractorGenerationResponse { /** Generated Python code */ code: string; /** Detected output variable names */ outputs: string[]; /** Optional brief explanation */ explanation?: string; } export interface CodeValidationRequest { /** Python code to validate */ code: string; } export interface CodeValidationResponse { /** Whether the code is valid */ valid: boolean; /** Error message if invalid */ error?: string; } export interface DependencyCheckResponse { /** All imports found in code */ imports: string[]; /** Imports that are available */ available: string[]; /** Imports that are missing */ missing: string[]; /** Warnings about potentially problematic imports */ warnings: string[]; } export interface TestExtractorRequest { /** Python code to test */ code: string; /** Optional study ID for finding OP2 files */ study_id?: string; /** Subcase ID to test against (default 1) */ subcase_id?: number; } export interface TestExtractorResponse { /** Whether the test succeeded */ success: boolean; /** Extracted output values */ outputs?: Record; /** Error message if failed */ error?: string; /** Execution time in milliseconds */ execution_time_ms?: number; } // ============================================================================ // API Functions // ============================================================================ /** * Generate Python extractor code using Claude Code CLI. * * @param request - Generation request with prompt and context * @returns Promise with generated code and detected outputs * @throws Error if generation fails * * @example * ```typescript * const result = await generateExtractorCode({ * prompt: "Extract maximum von Mises stress from solid elements", * output_names: ["max_stress"], * }); * console.log(result.code); * ``` */ export async function generateExtractorCode( request: ExtractorGenerationRequest ): Promise { const response = await fetch(`${API_BASE}/generate-extractor`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Generation failed' })); throw new Error(error.detail || `HTTP ${response.status}`); } return response.json(); } /** * Validate Python extractor code syntax. * * @param code - Python code to validate * @returns Promise with validation result * * @example * ```typescript * const result = await validateExtractorCode("def extract(): pass"); * if (!result.valid) { * console.error(result.error); * } * ``` */ export async function validateExtractorCode( code: string ): Promise { const response = await fetch(`${API_BASE}/validate-extractor`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }), }); if (!response.ok) { // Even if HTTP fails, return as invalid return { valid: false, error: `Validation request failed: HTTP ${response.status}`, }; } return response.json(); } /** * Check dependencies in Python code. * * @param code - Python code to analyze * @returns Promise with dependency check results * * @example * ```typescript * const result = await checkCodeDependencies("import numpy as np"); * if (result.missing.length > 0) { * console.warn("Missing:", result.missing); * } * ``` */ export async function checkCodeDependencies( code: string ): Promise { const response = await fetch(`${API_BASE}/check-dependencies`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }), }); if (!response.ok) { return { imports: [], available: [], missing: [], warnings: ['Dependency check failed'], }; } return response.json(); } /** * Test extractor code against a sample OP2 file. * * @param request - Test request with code and optional study context * @returns Promise with test results * * @example * ```typescript * const result = await testExtractorCode({ * code: "def extract(...): ...", * study_id: "bracket_v1", * }); * if (result.success) { * console.log("Outputs:", result.outputs); * } * ``` */ export async function testExtractorCode( request: TestExtractorRequest ): Promise { const response = await fetch(`${API_BASE}/test-extractor`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); if (!response.ok) { return { success: false, error: `Test request failed: HTTP ${response.status}`, }; } return response.json(); } /** * Check if Claude Code CLI is available. * * @returns Promise with availability status */ export async function checkClaudeStatus(): Promise<{ available: boolean; message: string; }> { try { const response = await fetch('/api/claude/status'); if (!response.ok) { return { available: false, message: 'Status check failed' }; } return response.json(); } catch { return { available: false, message: 'Cannot reach backend' }; } } // ============================================================================ // Streaming Generation // ============================================================================ export interface StreamingGenerationCallbacks { /** Called when a new token is received */ onToken?: (token: string) => void; /** Called when generation is complete */ onComplete?: (code: string, outputs: string[]) => void; /** Called when an error occurs */ onError?: (error: string) => void; } /** * Stream Python extractor code generation using Server-Sent Events. * * This provides real-time feedback as Claude generates the code, * showing tokens as they arrive. * * @param request - Generation request with prompt and context * @param callbacks - Callbacks for streaming events * @returns AbortController to cancel the stream * * @example * ```typescript * const controller = streamExtractorCode( * { prompt: "Extract maximum stress" }, * { * onToken: (token) => setPartialCode(prev => prev + token), * onComplete: (code, outputs) => { * setCode(code); * setIsGenerating(false); * }, * onError: (error) => setError(error), * } * ); * * // To cancel: * controller.abort(); * ``` */ export function streamExtractorCode( request: ExtractorGenerationRequest, callbacks: StreamingGenerationCallbacks ): AbortController { const controller = new AbortController(); (async () => { try { const response = await fetch(`${API_BASE}/generate-extractor/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), signal: controller.signal, }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Stream failed' })); callbacks.onError?.(error.detail || `HTTP ${response.status}`); return; } const reader = response.body?.getReader(); if (!reader) { callbacks.onError?.('No response body'); return; } const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // Parse SSE events from buffer const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep incomplete line in buffer for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); if (data.type === 'token') { callbacks.onToken?.(data.content); } else if (data.type === 'done') { callbacks.onComplete?.(data.code, data.outputs); } else if (data.type === 'error') { callbacks.onError?.(data.message); } } catch { // Ignore parse errors for incomplete JSON } } } } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { // User cancelled, don't report as error return; } callbacks.onError?.(error instanceof Error ? error.message : 'Stream failed'); } })(); return controller; }