mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-08 14:52:42 +08:00
feat: Multiple features, improvements, and bug fixes (#208)
* feat: Add token budget tracking and multiple improvements ## Features - **Token Budget Visualization**: Added real-time token usage tracking with pie chart display showing percentage used (blue < 50%, orange < 75%, red ≥ 75%) - **Show Thinking Toggle**: Added quick settings option to show/hide reasoning sections in messages - **Cache Clearing Utility**: Added `/clear-cache.html` page for clearing service workers, caches, and storage ## Improvements - **Package Upgrades**: Migrated from deprecated `xterm` to `@xterm/*` scoped packages - **Testing Setup**: Added Playwright for end-to-end testing - **Build Optimization**: Implemented code splitting for React, CodeMirror, and XTerm vendors to improve initial load time - **Deployment Scripts**: Added `scripts/start.sh` and `scripts/stop.sh` for cleaner server management with automatic port conflict resolution - **Vite Update**: Upgraded Vite from 7.0.5 to 7.1.8 ## Bug Fixes - Fixed static file serving to properly handle routes vs assets - Fixed session state reset to preserve token budget on initial load - Updated default Vite dev server port to 5173 (Vite's standard) ## Technical Details - Token budget is parsed from Claude CLI `modelUsage` field in result messages - Budget updates are sent via WebSocket as `token-budget` events - Calculation includes input, output, cache read, and cache creation tokens - Token budget state persists during active sessions but resets on session switch * feat: Add session processing state persistence Fixes issue where "Thinking..." banner and stop button disappear when switching between sessions. Users can now navigate freely while Claude is processing without losing the ability to monitor or stop the session. Features: - Processing state tracked in processingSessions Set (App.jsx) - Backend session status queries via check-session-status WebSocket message - UI state (banner + stop button) restored when returning to processing sessions - Works after page reload by querying backend's authoritative process maps - Proper cleanup when sessions complete in background Backend Changes: - Added sessionId to claude-complete, cursor-result, session-aborted messages - Exported isClaudeSessionActive, isCursorSessionActive helper functions - Exported getActiveClaudeSessions, getActiveCursorSessions for status queries - Added check-session-status and get-active-sessions WebSocket handlers Frontend Changes: - processingSessions state tracking in App.jsx - onSessionProcessing/onSessionNotProcessing callbacks - Session status check on session load and switch - Completion handlers only update UI if message is for current session - Always clean up processing state regardless of which session is active * feat: Make context window size configurable via environment variables Removes hardcoded 160k token limit and makes it configurable through environment variables. This allows easier adjustment for different Claude models or use cases. Changes: - Added CONTEXT_WINDOW env var for backend (default: 160000) - Added VITE_CONTEXT_WINDOW env var for frontend (default: 160000) - Updated .env.example with documentation - Replaced hardcoded values in token usage calculations - Replaced hardcoded values in pie chart display Why 160k? Claude Code reserves ~40k tokens for auto-compact feature, leaving 160k available for actual usage from the 200k context window. * fix: Decode HTML entities in chat message display HTML entities like < and > were showing as-is instead of being decoded to < and > characters. Added decodeHtmlEntities helper function to properly display angle brackets and other special characters. Applied to: - Regular message content - Streaming content deltas - Session history loading - Both string and array content types * refactor: Align package.json with main branch standards - Revert to main branch's package.json scripts structure - Remove custom scripts/start.sh and scripts/stop.sh - Update xterm dependencies to scoped @xterm packages (required for code compatibility) - Replace xterm with @xterm/xterm - Replace xterm-addon-fit with @xterm/addon-fit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: Replace CLI implementation with Claude Agents SDK This commit completes the migration to the Claude Agents SDK, removing the legacy CLI-based implementation and making the SDK the exclusive integration method. Changes: - Remove claude-cli.js legacy implementation - Add claude-sdk.js with full SDK integration - Remove CLAUDE_USE_SDK feature flag (SDK is now always used) - Update server/index.js to use SDK functions directly - Add .serena/ to .gitignore for AI assistant cache Benefits: - Better performance (no child process overhead) - Native session management with interrupt support - Cleaner codebase without CLI/SDK branching - Full feature parity with previous CLI implementation - Maintains compatibility with Cursor integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Update server/claude-sdk.js Whoops. This is correct. Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update server/index.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/components/ChatInterface.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/components/ChatInterface.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/components/ChatInterface.jsx Left my test code in, but that's fixed. Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: Prevent stale token-usage data from updating state on session switch - Add AbortController to cancel in-flight token-usage requests when session/project changes - Capture session/project IDs before fetch and verify they match before updating state - Handle AbortError gracefully without logging as error - Prevents race condition where old session data overwrites current session's token budget 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Update src/components/TokenUsagePie.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: viper151 <simosmik@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
499
server/claude-sdk.js
Normal file
499
server/claude-sdk.js
Normal file
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* Claude SDK Integration
|
||||
*
|
||||
* This module provides SDK-based integration with Claude using the @anthropic-ai/claude-agent-sdk.
|
||||
* It mirrors the interface of claude-cli.js but uses the SDK internally for better performance
|
||||
* and maintainability.
|
||||
*
|
||||
* Key features:
|
||||
* - Direct SDK integration without child processes
|
||||
* - Session management with abort capability
|
||||
* - Options mapping between CLI and SDK formats
|
||||
* - WebSocket message streaming
|
||||
*/
|
||||
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
// Session tracking: Map of session IDs to active query instances
|
||||
const activeSessions = new Map();
|
||||
|
||||
/**
|
||||
* Maps CLI options to SDK-compatible options format
|
||||
* @param {Object} options - CLI options
|
||||
* @returns {Object} SDK-compatible options
|
||||
*/
|
||||
function mapCliOptionsToSDK(options = {}) {
|
||||
const { sessionId, cwd, toolsSettings, permissionMode, images } = options;
|
||||
|
||||
const sdkOptions = {};
|
||||
|
||||
// Map working directory
|
||||
if (cwd) {
|
||||
sdkOptions.cwd = cwd;
|
||||
}
|
||||
|
||||
// Map permission mode
|
||||
if (permissionMode && permissionMode !== 'default') {
|
||||
sdkOptions.permissionMode = permissionMode;
|
||||
}
|
||||
|
||||
// Map tool settings
|
||||
const settings = toolsSettings || {
|
||||
allowedTools: [],
|
||||
disallowedTools: [],
|
||||
skipPermissions: false
|
||||
};
|
||||
|
||||
// Handle tool permissions
|
||||
if (settings.skipPermissions && permissionMode !== 'plan') {
|
||||
// When skipping permissions, use bypassPermissions mode
|
||||
sdkOptions.permissionMode = 'bypassPermissions';
|
||||
} else {
|
||||
// Map allowed tools
|
||||
let allowedTools = [...(settings.allowedTools || [])];
|
||||
|
||||
// Add plan mode default tools
|
||||
if (permissionMode === 'plan') {
|
||||
const planModeTools = ['Read', 'Task', 'exit_plan_mode', 'TodoRead', 'TodoWrite'];
|
||||
for (const tool of planModeTools) {
|
||||
if (!allowedTools.includes(tool)) {
|
||||
allowedTools.push(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allowedTools.length > 0) {
|
||||
sdkOptions.allowedTools = allowedTools;
|
||||
}
|
||||
|
||||
// Map disallowed tools
|
||||
if (settings.disallowedTools && settings.disallowedTools.length > 0) {
|
||||
sdkOptions.disallowedTools = settings.disallowedTools;
|
||||
}
|
||||
}
|
||||
|
||||
// Map model (default to sonnet)
|
||||
// Map model (default to sonnet)
|
||||
sdkOptions.model = options.model || 'sonnet';
|
||||
|
||||
// Map resume session
|
||||
if (sessionId) {
|
||||
sdkOptions.resume = sessionId;
|
||||
}
|
||||
|
||||
return sdkOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a session to the active sessions map
|
||||
* @param {string} sessionId - Session identifier
|
||||
* @param {Object} queryInstance - SDK query instance
|
||||
* @param {Array<string>} tempImagePaths - Temp image file paths for cleanup
|
||||
* @param {string} tempDir - Temp directory for cleanup
|
||||
*/
|
||||
function addSession(sessionId, queryInstance, tempImagePaths = [], tempDir = null) {
|
||||
activeSessions.set(sessionId, {
|
||||
instance: queryInstance,
|
||||
startTime: Date.now(),
|
||||
status: 'active',
|
||||
tempImagePaths,
|
||||
tempDir
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a session from the active sessions map
|
||||
* @param {string} sessionId - Session identifier
|
||||
*/
|
||||
function removeSession(sessionId) {
|
||||
activeSessions.delete(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a session from the active sessions map
|
||||
* @param {string} sessionId - Session identifier
|
||||
* @returns {Object|undefined} Session data or undefined
|
||||
*/
|
||||
function getSession(sessionId) {
|
||||
return activeSessions.get(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all active session IDs
|
||||
* @returns {Array<string>} Array of active session IDs
|
||||
*/
|
||||
function getAllSessions() {
|
||||
return Array.from(activeSessions.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms SDK messages to WebSocket format expected by frontend
|
||||
* @param {Object} sdkMessage - SDK message object
|
||||
* @returns {Object} Transformed message ready for WebSocket
|
||||
*/
|
||||
function transformMessage(sdkMessage) {
|
||||
// SDK messages are already in a format compatible with the frontend
|
||||
// The CLI sends them wrapped in {type: 'claude-response', data: message}
|
||||
// We'll do the same here to maintain compatibility
|
||||
return sdkMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts token usage from SDK result messages
|
||||
* @param {Object} resultMessage - SDK result message
|
||||
* @returns {Object|null} Token budget object or null
|
||||
*/
|
||||
function extractTokenBudget(resultMessage) {
|
||||
if (resultMessage.type !== 'result' || !resultMessage.modelUsage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the first model's usage data (same as CLI implementation)
|
||||
const modelKey = Object.keys(resultMessage.modelUsage)[0];
|
||||
const modelData = resultMessage.modelUsage[modelKey];
|
||||
|
||||
if (!modelData || !modelData.contextWindow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate total tokens used (input + output + cache)
|
||||
const inputTokens = modelData.inputTokens || 0;
|
||||
const outputTokens = modelData.outputTokens || 0;
|
||||
const cacheReadTokens = modelData.cacheReadInputTokens || 0;
|
||||
const cacheCreationTokens = modelData.cacheCreationInputTokens || 0;
|
||||
|
||||
// Total used = input + output + cache tokens
|
||||
const totalUsed = inputTokens + outputTokens + cacheReadTokens + cacheCreationTokens;
|
||||
const contextWindow = modelData.contextWindow;
|
||||
|
||||
return {
|
||||
used: totalUsed,
|
||||
total: contextWindow
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles image processing for SDK queries
|
||||
* Saves base64 images to temporary files and returns modified prompt with file paths
|
||||
* @param {string} command - Original user prompt
|
||||
* @param {Array} images - Array of image objects with base64 data
|
||||
* @param {string} cwd - Working directory for temp file creation
|
||||
* @returns {Promise<Object>} {modifiedCommand, tempImagePaths, tempDir}
|
||||
*/
|
||||
async function handleImages(command, images, cwd) {
|
||||
const tempImagePaths = [];
|
||||
let tempDir = null;
|
||||
|
||||
if (!images || images.length === 0) {
|
||||
return { modifiedCommand: command, tempImagePaths, tempDir };
|
||||
}
|
||||
|
||||
try {
|
||||
// Create temp directory in the project directory
|
||||
const workingDir = cwd || process.cwd();
|
||||
tempDir = path.join(workingDir, '.tmp', 'images', Date.now().toString());
|
||||
await fs.mkdir(tempDir, { recursive: true });
|
||||
|
||||
// Save each image to a temp file
|
||||
for (const [index, image] of images.entries()) {
|
||||
// Extract base64 data and mime type
|
||||
const matches = image.data.match(/^data:([^;]+);base64,(.+)$/);
|
||||
if (!matches) {
|
||||
console.error('Invalid image data format');
|
||||
continue;
|
||||
}
|
||||
|
||||
const [, mimeType, base64Data] = matches;
|
||||
const extension = mimeType.split('/')[1] || 'png';
|
||||
const filename = `image_${index}.${extension}`;
|
||||
const filepath = path.join(tempDir, filename);
|
||||
|
||||
// Write base64 data to file
|
||||
await fs.writeFile(filepath, Buffer.from(base64Data, 'base64'));
|
||||
tempImagePaths.push(filepath);
|
||||
}
|
||||
|
||||
// Include the full image paths in the prompt
|
||||
let modifiedCommand = command;
|
||||
if (tempImagePaths.length > 0 && command && command.trim()) {
|
||||
const imageNote = `\n\n[Images provided at the following paths:]\n${tempImagePaths.map((p, i) => `${i + 1}. ${p}`).join('\n')}`;
|
||||
modifiedCommand = command + imageNote;
|
||||
}
|
||||
|
||||
console.log(`📸 Processed ${tempImagePaths.length} images to temp directory: ${tempDir}`);
|
||||
return { modifiedCommand, tempImagePaths, tempDir };
|
||||
} catch (error) {
|
||||
console.error('Error processing images for SDK:', error);
|
||||
return { modifiedCommand: command, tempImagePaths, tempDir };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up temporary image files
|
||||
* @param {Array<string>} tempImagePaths - Array of temp file paths to delete
|
||||
* @param {string} tempDir - Temp directory to remove
|
||||
*/
|
||||
async function cleanupTempFiles(tempImagePaths, tempDir) {
|
||||
if (!tempImagePaths || tempImagePaths.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Delete individual temp files
|
||||
for (const imagePath of tempImagePaths) {
|
||||
await fs.unlink(imagePath).catch(err =>
|
||||
console.error(`Failed to delete temp image ${imagePath}:`, err)
|
||||
);
|
||||
}
|
||||
|
||||
// Delete temp directory
|
||||
if (tempDir) {
|
||||
await fs.rm(tempDir, { recursive: true, force: true }).catch(err =>
|
||||
console.error(`Failed to delete temp directory ${tempDir}:`, err)
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`🧹 Cleaned up ${tempImagePaths.length} temp image files`);
|
||||
} catch (error) {
|
||||
console.error('Error during temp file cleanup:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads MCP server configurations from ~/.claude.json
|
||||
* @param {string} cwd - Current working directory for project-specific configs
|
||||
* @returns {Object|null} MCP servers object or null if none found
|
||||
*/
|
||||
async function loadMcpConfig(cwd) {
|
||||
try {
|
||||
const claudeConfigPath = path.join(os.homedir(), '.claude.json');
|
||||
|
||||
// Check if config file exists
|
||||
try {
|
||||
await fs.access(claudeConfigPath);
|
||||
} catch (error) {
|
||||
// File doesn't exist, return null
|
||||
console.log('📡 No ~/.claude.json found, proceeding without MCP servers');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read and parse config file
|
||||
let claudeConfig;
|
||||
try {
|
||||
const configContent = await fs.readFile(claudeConfigPath, 'utf8');
|
||||
claudeConfig = JSON.parse(configContent);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to parse ~/.claude.json:', error.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract MCP servers (merge global and project-specific)
|
||||
let mcpServers = {};
|
||||
|
||||
// Add global MCP servers
|
||||
if (claudeConfig.mcpServers && typeof claudeConfig.mcpServers === 'object') {
|
||||
mcpServers = { ...claudeConfig.mcpServers };
|
||||
console.log(`📡 Loaded ${Object.keys(mcpServers).length} global MCP servers`);
|
||||
}
|
||||
|
||||
// Add/override with project-specific MCP servers
|
||||
if (claudeConfig.claudeProjects && cwd) {
|
||||
const projectConfig = claudeConfig.claudeProjects[cwd];
|
||||
if (projectConfig && projectConfig.mcpServers && typeof projectConfig.mcpServers === 'object') {
|
||||
mcpServers = { ...mcpServers, ...projectConfig.mcpServers };
|
||||
console.log(`📡 Loaded ${Object.keys(projectConfig.mcpServers).length} project-specific MCP servers`);
|
||||
}
|
||||
}
|
||||
|
||||
// Return null if no servers found
|
||||
if (Object.keys(mcpServers).length === 0) {
|
||||
console.log('📡 No MCP servers configured');
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`✅ Total MCP servers loaded: ${Object.keys(mcpServers).length}`);
|
||||
return mcpServers;
|
||||
} catch (error) {
|
||||
console.error('❌ Error loading MCP config:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a Claude query using the SDK
|
||||
* @param {string} command - User prompt/command
|
||||
* @param {Object} options - Query options
|
||||
* @param {Object} ws - WebSocket connection
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function queryClaudeSDK(command, options = {}, ws) {
|
||||
const { sessionId } = options;
|
||||
let capturedSessionId = sessionId;
|
||||
let sessionCreatedSent = false;
|
||||
let tempImagePaths = [];
|
||||
let tempDir = null;
|
||||
|
||||
try {
|
||||
// Map CLI options to SDK format
|
||||
const sdkOptions = mapCliOptionsToSDK(options);
|
||||
|
||||
// Load MCP configuration
|
||||
const mcpServers = await loadMcpConfig(options.cwd);
|
||||
if (mcpServers) {
|
||||
sdkOptions.mcpServers = mcpServers;
|
||||
}
|
||||
|
||||
// Handle images - save to temp files and modify prompt
|
||||
const imageResult = await handleImages(command, options.images, options.cwd);
|
||||
const finalCommand = imageResult.modifiedCommand;
|
||||
tempImagePaths = imageResult.tempImagePaths;
|
||||
tempDir = imageResult.tempDir;
|
||||
|
||||
// Create SDK query instance
|
||||
const queryInstance = query({
|
||||
prompt: finalCommand,
|
||||
options: sdkOptions
|
||||
});
|
||||
|
||||
// Track the query instance for abort capability
|
||||
if (capturedSessionId) {
|
||||
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir);
|
||||
}
|
||||
|
||||
// Process streaming messages
|
||||
for await (const message of queryInstance) {
|
||||
// Capture session ID from first message
|
||||
if (message.session_id && !capturedSessionId) {
|
||||
capturedSessionId = message.session_id;
|
||||
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir);
|
||||
|
||||
// Send session-created event only once for new sessions
|
||||
if (!sessionId && !sessionCreatedSent) {
|
||||
sessionCreatedSent = true;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'session-created',
|
||||
sessionId: capturedSessionId
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Transform and send message to WebSocket
|
||||
const transformedMessage = transformMessage(message);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'claude-response',
|
||||
data: transformedMessage
|
||||
}));
|
||||
|
||||
// Extract and send token budget updates from result messages
|
||||
if (message.type === 'result') {
|
||||
const tokenBudget = extractTokenBudget(message);
|
||||
if (tokenBudget) {
|
||||
console.log('📊 Token budget from modelUsage:', tokenBudget);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'token-budget',
|
||||
data: tokenBudget
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up session on completion
|
||||
if (capturedSessionId) {
|
||||
removeSession(capturedSessionId);
|
||||
}
|
||||
|
||||
// Clean up temporary image files
|
||||
await cleanupTempFiles(tempImagePaths, tempDir);
|
||||
|
||||
// Send completion event
|
||||
ws.send(JSON.stringify({
|
||||
type: 'claude-complete',
|
||||
sessionId: capturedSessionId,
|
||||
exitCode: 0,
|
||||
isNewSession: !sessionId && !!command
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error('SDK query error:', error);
|
||||
|
||||
// Clean up session on error
|
||||
if (capturedSessionId) {
|
||||
removeSession(capturedSessionId);
|
||||
}
|
||||
|
||||
// Clean up temporary image files on error
|
||||
await cleanupTempFiles(tempImagePaths, tempDir);
|
||||
|
||||
// Send error to WebSocket
|
||||
ws.send(JSON.stringify({
|
||||
type: 'claude-error',
|
||||
error: error.message
|
||||
}));
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts an active SDK session
|
||||
* @param {string} sessionId - Session identifier
|
||||
* @returns {boolean} True if session was aborted, false if not found
|
||||
*/
|
||||
async function abortClaudeSDKSession(sessionId) {
|
||||
const session = getSession(sessionId);
|
||||
|
||||
if (!session) {
|
||||
console.log(`Session ${sessionId} not found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`🛑 Aborting SDK session: ${sessionId}`);
|
||||
|
||||
// Call interrupt() on the query instance
|
||||
await session.instance.interrupt();
|
||||
|
||||
// Update session status
|
||||
session.status = 'aborted';
|
||||
|
||||
// Clean up temporary image files
|
||||
await cleanupTempFiles(session.tempImagePaths, session.tempDir);
|
||||
|
||||
// Clean up session
|
||||
removeSession(sessionId);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`Error aborting session ${sessionId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an SDK session is currently active
|
||||
* @param {string} sessionId - Session identifier
|
||||
* @returns {boolean} True if session is active
|
||||
*/
|
||||
function isClaudeSDKSessionActive(sessionId) {
|
||||
const session = getSession(sessionId);
|
||||
return session && session.status === 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all active SDK session IDs
|
||||
* @returns {Array<string>} Array of active session IDs
|
||||
*/
|
||||
function getActiveClaudeSDKSessions() {
|
||||
return getAllSessions();
|
||||
}
|
||||
|
||||
// Export public API
|
||||
export {
|
||||
queryClaudeSDK,
|
||||
abortClaudeSDKSession,
|
||||
isClaudeSDKSessionActive,
|
||||
getActiveClaudeSDKSessions
|
||||
};
|
||||
Reference in New Issue
Block a user