mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
refactor: extract agent error handlers and centralize GROUP_CHAT_PREFIX
- Extract agent:clearError and agent:retryAfterError handlers from main/index.ts to dedicated handlers/agent-error.ts module - Add comprehensive test coverage for agent error handlers (29 tests) - Centralize GROUP_CHAT_PREFIX constant in process-listeners/types.ts to eliminate duplication across 4 listener files - Remove unused ipcMain import from main/index.ts (all IPC handlers now registered through handlers module)
This commit is contained in:
350
src/__tests__/main/ipc/handlers/agent-error.test.ts
Normal file
350
src/__tests__/main/ipc/handlers/agent-error.test.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* Tests for agent error IPC handlers
|
||||
*
|
||||
* Tests the agent:clearError and agent:retryAfterError handlers
|
||||
* which manage error state transitions and recovery operations.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
// Create hoisted mocks for more reliable mocking
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockLogger: {
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock electron
|
||||
vi.mock('electron', () => ({
|
||||
ipcMain: {
|
||||
handle: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock logger
|
||||
vi.mock('../../../../main/utils/logger', () => ({
|
||||
logger: mocks.mockLogger,
|
||||
}));
|
||||
|
||||
// Alias for easier access in tests
|
||||
const mockLogger = mocks.mockLogger;
|
||||
|
||||
import { registerAgentErrorHandlers } from '../../../../main/ipc/handlers/agent-error';
|
||||
|
||||
describe('Agent Error IPC Handlers', () => {
|
||||
let handlers: Map<string, Function>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
handlers = new Map();
|
||||
|
||||
// Capture registered handlers
|
||||
vi.mocked(ipcMain.handle).mockImplementation((channel: string, handler: Function) => {
|
||||
handlers.set(channel, handler);
|
||||
});
|
||||
|
||||
registerAgentErrorHandlers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('handler registration', () => {
|
||||
it('should register agent:clearError handler', () => {
|
||||
expect(handlers.has('agent:clearError')).toBe(true);
|
||||
});
|
||||
|
||||
it('should register agent:retryAfterError handler', () => {
|
||||
expect(handlers.has('agent:retryAfterError')).toBe(true);
|
||||
});
|
||||
|
||||
it('should register exactly 2 handlers', () => {
|
||||
expect(handlers.size).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('agent:clearError', () => {
|
||||
it('should return success for valid session ID', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
const result = await handler({}, 'session-123');
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should log debug message with session ID', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
await handler({}, 'test-session-abc');
|
||||
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Clearing agent error for session',
|
||||
'AgentError',
|
||||
{ sessionId: 'test-session-abc' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty session ID', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
const result = await handler({}, '');
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Clearing agent error for session',
|
||||
'AgentError',
|
||||
{ sessionId: '' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle UUID format session ID', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
const uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
||||
const result = await handler({}, uuid);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Clearing agent error for session',
|
||||
'AgentError',
|
||||
{ sessionId: uuid }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle long session ID', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
const longSessionId = 'session-' + 'a'.repeat(500);
|
||||
const result = await handler({}, longSessionId);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should handle special characters in session ID', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
const specialId = 'session_with-special.chars:123';
|
||||
const result = await handler({}, specialId);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should handle group chat session ID format', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
const groupChatId = 'group-chat-test-123-participant-Agent-abc';
|
||||
const result = await handler({}, groupChatId);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Clearing agent error for session',
|
||||
'AgentError',
|
||||
{ sessionId: groupChatId }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('agent:retryAfterError', () => {
|
||||
it('should return success without options', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const result = await handler({}, 'session-123');
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should return success with empty options', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const result = await handler({}, 'session-123', {});
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should return success with prompt option', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const result = await handler({}, 'session-123', { prompt: 'Retry with this prompt' });
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should return success with newSession option', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const result = await handler({}, 'session-123', { newSession: true });
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should return success with both options', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const result = await handler({}, 'session-123', {
|
||||
prompt: 'Retry prompt',
|
||||
newSession: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should log info with session ID and no options', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
await handler({}, 'test-session-xyz');
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Retrying after agent error', 'AgentError', {
|
||||
sessionId: 'test-session-xyz',
|
||||
hasPrompt: false,
|
||||
newSession: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should log info with hasPrompt=true when prompt provided', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
await handler({}, 'session-abc', { prompt: 'Some prompt text' });
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Retrying after agent error', 'AgentError', {
|
||||
sessionId: 'session-abc',
|
||||
hasPrompt: true,
|
||||
newSession: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should log info with newSession=true when specified', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
await handler({}, 'session-def', { newSession: true });
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Retrying after agent error', 'AgentError', {
|
||||
sessionId: 'session-def',
|
||||
hasPrompt: false,
|
||||
newSession: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty prompt string as no prompt', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
await handler({}, 'session-123', { prompt: '' });
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Retrying after agent error', 'AgentError', {
|
||||
sessionId: 'session-123',
|
||||
hasPrompt: false,
|
||||
newSession: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle undefined newSession as false', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
await handler({}, 'session-123', { prompt: 'test' });
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith(
|
||||
'Retrying after agent error',
|
||||
'AgentError',
|
||||
expect.objectContaining({ newSession: false })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle newSession=false explicitly', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
await handler({}, 'session-123', { newSession: false });
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith(
|
||||
'Retrying after agent error',
|
||||
'AgentError',
|
||||
expect.objectContaining({ newSession: false })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle very long prompt', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const longPrompt = 'A'.repeat(10000);
|
||||
const result = await handler({}, 'session-123', { prompt: longPrompt });
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(mockLogger.info).toHaveBeenCalledWith(
|
||||
'Retrying after agent error',
|
||||
'AgentError',
|
||||
expect.objectContaining({ hasPrompt: true })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unicode in prompt', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const result = await handler({}, 'session-123', { prompt: '请重试这个操作 🔄' });
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(mockLogger.info).toHaveBeenCalledWith(
|
||||
'Retrying after agent error',
|
||||
'AgentError',
|
||||
expect.objectContaining({ hasPrompt: true })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handler idempotency', () => {
|
||||
it('should handle multiple clearError calls for same session', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
|
||||
const result1 = await handler({}, 'session-123');
|
||||
const result2 = await handler({}, 'session-123');
|
||||
const result3 = await handler({}, 'session-123');
|
||||
|
||||
expect(result1).toEqual({ success: true });
|
||||
expect(result2).toEqual({ success: true });
|
||||
expect(result3).toEqual({ success: true });
|
||||
expect(mockLogger.debug).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should handle multiple retryAfterError calls for same session', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
|
||||
const result1 = await handler({}, 'session-123', { prompt: 'First retry' });
|
||||
const result2 = await handler({}, 'session-123', { prompt: 'Second retry' });
|
||||
|
||||
expect(result1).toEqual({ success: true });
|
||||
expect(result2).toEqual({ success: true });
|
||||
expect(mockLogger.info).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('concurrent handler calls', () => {
|
||||
it('should handle concurrent clearError calls for different sessions', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
|
||||
const results = await Promise.all([
|
||||
handler({}, 'session-1'),
|
||||
handler({}, 'session-2'),
|
||||
handler({}, 'session-3'),
|
||||
]);
|
||||
|
||||
expect(results).toEqual([{ success: true }, { success: true }, { success: true }]);
|
||||
});
|
||||
|
||||
it('should handle concurrent retryAfterError calls', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
|
||||
const results = await Promise.all([
|
||||
handler({}, 'session-1', { prompt: 'Prompt 1' }),
|
||||
handler({}, 'session-2', { newSession: true }),
|
||||
handler({}, 'session-3', { prompt: 'Prompt 3', newSession: false }),
|
||||
]);
|
||||
|
||||
expect(results).toEqual([{ success: true }, { success: true }, { success: true }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle null-ish session ID', async () => {
|
||||
const handler = handlers.get('agent:clearError')!;
|
||||
|
||||
// TypeScript would normally prevent this, but testing runtime behavior
|
||||
const result = await handler({}, null as unknown as string);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should handle undefined options in retryAfterError', async () => {
|
||||
const handler = handlers.get('agent:retryAfterError')!;
|
||||
const result = await handler({}, 'session-123', undefined);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Retrying after agent error', 'AgentError', {
|
||||
sessionId: 'session-123',
|
||||
hasPrompt: false,
|
||||
newSession: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import path from 'path';
|
||||
import crypto from 'crypto';
|
||||
// Sentry is imported dynamically below to avoid module-load-time access to electron.app
|
||||
@@ -571,43 +571,7 @@ function setupIpcHandlers() {
|
||||
|
||||
// Claude Code sessions - extracted to src/main/ipc/handlers/claude.ts
|
||||
|
||||
// ==========================================================================
|
||||
// Agent Error Handling API
|
||||
// ==========================================================================
|
||||
|
||||
// Clear an error state for a session (called after recovery action)
|
||||
ipcMain.handle('agent:clearError', async (_event, sessionId: string) => {
|
||||
logger.debug('Clearing agent error for session', 'AgentError', { sessionId });
|
||||
// Note: The actual error state is managed in the renderer.
|
||||
// This handler is used to log the clear action and potentially
|
||||
// perform any main process cleanup needed.
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// Retry the last operation after an error (optionally with modified parameters)
|
||||
ipcMain.handle(
|
||||
'agent:retryAfterError',
|
||||
async (
|
||||
_event,
|
||||
sessionId: string,
|
||||
options?: {
|
||||
prompt?: string;
|
||||
newSession?: boolean;
|
||||
}
|
||||
) => {
|
||||
logger.info('Retrying after agent error', 'AgentError', {
|
||||
sessionId,
|
||||
hasPrompt: !!options?.prompt,
|
||||
newSession: options?.newSession || false,
|
||||
});
|
||||
// Note: The actual retry logic is handled in the renderer, which will:
|
||||
// 1. Clear the error state
|
||||
// 2. Optionally start a new session
|
||||
// 3. Re-send the last command or the provided prompt
|
||||
// This handler exists for logging and potential future main process coordination.
|
||||
return { success: true };
|
||||
}
|
||||
);
|
||||
// Agent Error Handling API - extracted to src/main/ipc/handlers/agent-error.ts
|
||||
|
||||
// Register notification handlers (extracted to handlers/notifications.ts)
|
||||
registerNotificationsHandlers();
|
||||
|
||||
73
src/main/ipc/handlers/agent-error.ts
Normal file
73
src/main/ipc/handlers/agent-error.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Agent Error Handling IPC Handlers
|
||||
*
|
||||
* Handles agent error state management:
|
||||
* - Clearing error states after recovery
|
||||
* - Retrying operations after errors
|
||||
*
|
||||
* Note: The actual error state is managed in the renderer. These handlers
|
||||
* provide logging and potential future main process coordination.
|
||||
*/
|
||||
|
||||
import { ipcMain } from 'electron';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
// ==========================================================================
|
||||
// Types
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* Options for retrying after an error
|
||||
*/
|
||||
export interface RetryOptions {
|
||||
/** Optional prompt to use for the retry */
|
||||
prompt?: string;
|
||||
/** Whether to start a new session for the retry */
|
||||
newSession?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from agent error operations
|
||||
*/
|
||||
export interface AgentErrorResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Handler Registration
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* Register all agent error-related IPC handlers
|
||||
*/
|
||||
export function registerAgentErrorHandlers(): void {
|
||||
// Clear an error state for a session (called after recovery action)
|
||||
ipcMain.handle(
|
||||
'agent:clearError',
|
||||
async (_event, sessionId: string): Promise<AgentErrorResponse> => {
|
||||
logger.debug('Clearing agent error for session', 'AgentError', { sessionId });
|
||||
// Note: The actual error state is managed in the renderer.
|
||||
// This handler is used to log the clear action and potentially
|
||||
// perform any main process cleanup needed.
|
||||
return { success: true };
|
||||
}
|
||||
);
|
||||
|
||||
// Retry the last operation after an error (optionally with modified parameters)
|
||||
ipcMain.handle(
|
||||
'agent:retryAfterError',
|
||||
async (_event, sessionId: string, options?: RetryOptions): Promise<AgentErrorResponse> => {
|
||||
logger.info('Retrying after agent error', 'AgentError', {
|
||||
sessionId,
|
||||
hasPrompt: !!options?.prompt,
|
||||
newSession: options?.newSession || false,
|
||||
});
|
||||
// Note: The actual retry logic is handled in the renderer, which will:
|
||||
// 1. Clear the error state
|
||||
// 2. Optionally start a new session
|
||||
// 3. Re-send the last command or the provided prompt
|
||||
// This handler exists for logging and potential future main process coordination.
|
||||
return { success: true };
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -49,6 +49,7 @@ import { registerWebHandlers, WebHandlerDependencies } from './web';
|
||||
import { registerLeaderboardHandlers, LeaderboardHandlerDependencies } from './leaderboard';
|
||||
import { registerNotificationsHandlers } from './notifications';
|
||||
import { registerSymphonyHandlers, SymphonyHandlerDependencies } from './symphony';
|
||||
import { registerAgentErrorHandlers } from './agent-error';
|
||||
import { AgentDetector } from '../../agent-detector';
|
||||
import { ProcessManager } from '../../process-manager';
|
||||
import { WebServer } from '../../web-server';
|
||||
@@ -87,6 +88,7 @@ export { registerLeaderboardHandlers };
|
||||
export type { LeaderboardHandlerDependencies };
|
||||
export { registerNotificationsHandlers };
|
||||
export { registerSymphonyHandlers };
|
||||
export { registerAgentErrorHandlers };
|
||||
export type { AgentsHandlerDependencies };
|
||||
export type { ProcessHandlerDependencies };
|
||||
export type { PersistenceHandlerDependencies };
|
||||
@@ -255,6 +257,8 @@ export function registerAllHandlers(deps: HandlerDependencies): void {
|
||||
app: deps.app,
|
||||
getMainWindow: deps.getMainWindow,
|
||||
});
|
||||
// Register agent error handlers (error state management)
|
||||
registerAgentErrorHandlers();
|
||||
// Setup logger event forwarding to renderer
|
||||
setupLoggerEventForwarding(deps.getMainWindow);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { ProcessManager } from '../process-manager';
|
||||
import type { ProcessListenerDependencies } from './types';
|
||||
import { GROUP_CHAT_PREFIX, type ProcessListenerDependencies } from './types';
|
||||
|
||||
/**
|
||||
* Maximum buffer size per session (10MB).
|
||||
@@ -12,12 +12,6 @@ import type { ProcessListenerDependencies } from './types';
|
||||
*/
|
||||
const MAX_BUFFER_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Prefix for group chat session IDs.
|
||||
* Used for fast string check before expensive regex matching.
|
||||
*/
|
||||
const GROUP_CHAT_PREFIX = 'group-chat-';
|
||||
|
||||
/**
|
||||
* Length of random suffix in message IDs (9 characters of base36).
|
||||
* Combined with timestamp provides uniqueness for web broadcast deduplication.
|
||||
|
||||
@@ -5,13 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { ProcessManager } from '../process-manager';
|
||||
import type { ProcessListenerDependencies } from './types';
|
||||
|
||||
/**
|
||||
* Prefix for group chat session IDs.
|
||||
* Used for fast string check before expensive regex matching.
|
||||
*/
|
||||
const GROUP_CHAT_PREFIX = 'group-chat-';
|
||||
import { GROUP_CHAT_PREFIX, type ProcessListenerDependencies } from './types';
|
||||
|
||||
/**
|
||||
* Sets up the exit listener for process termination.
|
||||
|
||||
@@ -4,13 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { ProcessManager } from '../process-manager';
|
||||
import type { ProcessListenerDependencies } from './types';
|
||||
|
||||
/**
|
||||
* Prefix for group chat session IDs.
|
||||
* Used for fast string check before expensive regex matching.
|
||||
*/
|
||||
const GROUP_CHAT_PREFIX = 'group-chat-';
|
||||
import { GROUP_CHAT_PREFIX, type ProcessListenerDependencies } from './types';
|
||||
|
||||
/**
|
||||
* Sets up the session-id listener.
|
||||
|
||||
@@ -4,6 +4,17 @@
|
||||
*/
|
||||
|
||||
import type { ProcessManager } from '../process-manager';
|
||||
|
||||
// ==========================================================================
|
||||
// Constants
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* Prefix for group chat session IDs.
|
||||
* Used for fast string check before expensive regex matching.
|
||||
* Session IDs starting with this prefix belong to group chat sessions.
|
||||
*/
|
||||
export const GROUP_CHAT_PREFIX = 'group-chat-';
|
||||
import type { WebServer } from '../web-server';
|
||||
import type { AgentDetector } from '../agent-detector';
|
||||
import type { SafeSendFn } from '../utils/safe-send';
|
||||
|
||||
@@ -4,13 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { ProcessManager } from '../process-manager';
|
||||
import type { ProcessListenerDependencies, UsageStats } from './types';
|
||||
|
||||
/**
|
||||
* Prefix for group chat session IDs.
|
||||
* Used for fast string check before expensive regex matching.
|
||||
*/
|
||||
const GROUP_CHAT_PREFIX = 'group-chat-';
|
||||
import { GROUP_CHAT_PREFIX, type ProcessListenerDependencies, type UsageStats } from './types';
|
||||
|
||||
/**
|
||||
* Sets up the usage listener for token/cost statistics.
|
||||
|
||||
Reference in New Issue
Block a user