refactor(codex): extract session management and launcher logic into separate modules

Reorganized runCodex.ts to improve maintainability by extracting:
- CodexSession class for session lifecycle management
- CodexLocalLauncher and CodexRemoteLauncher for mode-specific initialization
- CodexEventConverter for MCP message handling and UI buffer updates
- CodexSessionScanner for resume file discovery
- emitReadyIfIdle utility for ready event emission

Added codexSessionId field to metadata schema for session tracking.
Updated UI components to work with refactored architecture.
This commit is contained in:
weishu
2025-12-22 19:14:47 +08:00
parent 0c16b7b215
commit 4290f539bd
15 changed files with 1606 additions and 661 deletions
+224
View File
@@ -0,0 +1,224 @@
import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { logger } from '@/ui/logger';
const CodexSessionEventSchema = z.object({
timestamp: z.string().optional(),
type: z.string(),
payload: z.unknown().optional()
}).passthrough();
export type CodexSessionEvent = z.infer<typeof CodexSessionEventSchema>;
export type CodexMessage = {
type: 'message';
message: string;
id: string;
} | {
type: 'reasoning';
message: string;
id: string;
} | {
type: 'reasoning-delta';
delta: string;
} | {
type: 'token_count';
info: Record<string, unknown>;
id: string;
} | {
type: 'tool-call';
name: string;
callId: string;
input: unknown;
id: string;
} | {
type: 'tool-call-result';
callId: string;
output: unknown;
id: string;
};
export type CodexConversionResult = {
sessionId?: string;
message?: CodexMessage;
};
function asRecord(value: unknown): Record<string, unknown> | null {
if (!value || typeof value !== 'object') {
return null;
}
return value as Record<string, unknown>;
}
function asString(value: unknown): string | null {
return typeof value === 'string' && value.length > 0 ? value : null;
}
function parseArguments(value: unknown): unknown {
if (typeof value !== 'string') {
return value;
}
const trimmed = value.trim();
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
try {
return JSON.parse(trimmed);
} catch (error) {
logger.debug('[codexEventConverter] Failed to parse function_call arguments as JSON:', error);
}
}
return value;
}
function extractCallId(payload: Record<string, unknown>): string | null {
const candidates = [
'call_id',
'callId',
'tool_call_id',
'toolCallId',
'id'
];
for (const key of candidates) {
const value = payload[key];
if (typeof value === 'string' && value.length > 0) {
return value;
}
}
return null;
}
export function convertCodexEvent(rawEvent: unknown): CodexConversionResult | null {
const parsed = CodexSessionEventSchema.safeParse(rawEvent);
if (!parsed.success) {
return null;
}
const { type, payload } = parsed.data;
const payloadRecord = asRecord(payload);
if (type === 'session_meta') {
const sessionId = payloadRecord ? asString(payloadRecord.id) : null;
if (!sessionId) {
return null;
}
return { sessionId };
}
if (!payloadRecord) {
return null;
}
if (type === 'event_msg') {
const eventType = asString(payloadRecord.type);
if (!eventType) {
return null;
}
if (eventType === 'user_message') {
return null;
}
if (eventType === 'agent_message') {
const message = asString(payloadRecord.message);
if (!message) {
return null;
}
return {
message: {
type: 'message',
message,
id: randomUUID()
}
};
}
if (eventType === 'agent_reasoning') {
const message = asString(payloadRecord.text) ?? asString(payloadRecord.message);
if (!message) {
return null;
}
return {
message: {
type: 'reasoning',
message,
id: randomUUID()
}
};
}
if (eventType === 'agent_reasoning_delta') {
const delta = asString(payloadRecord.delta) ?? asString(payloadRecord.text) ?? asString(payloadRecord.message);
if (!delta) {
return null;
}
return {
message: {
type: 'reasoning-delta',
delta
}
};
}
if (eventType === 'token_count') {
const info = asRecord(payloadRecord.info);
if (!info) {
return null;
}
return {
message: {
type: 'token_count',
info,
id: randomUUID()
}
};
}
return null;
}
if (type === 'response_item') {
const itemType = asString(payloadRecord.type);
if (!itemType) {
return null;
}
if (itemType === 'function_call') {
const name = asString(payloadRecord.name);
const callId = extractCallId(payloadRecord);
if (!name || !callId) {
return null;
}
return {
message: {
type: 'tool-call',
name,
callId,
input: parseArguments(payloadRecord.arguments),
id: randomUUID()
}
};
}
if (itemType === 'function_call_output') {
const callId = extractCallId(payloadRecord);
if (!callId) {
return null;
}
return {
message: {
type: 'tool-call-result',
callId,
output: payloadRecord.output,
id: randomUUID()
}
};
}
return null;
}
return null;
}