mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
- Replace .passthrough() with explicit field definitions across all schemas - Add missing optional fields (homeDir, happyHomeDir, happyLibDir, displayName) - Refactor RawJSONLinesSchema to use structured base schema for clarity - Improve schema validation strictness and type safety - Update Machine interface to reflect explicit fields instead of index signature
234 lines
5.7 KiB
TypeScript
234 lines
5.7 KiB
TypeScript
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()
|
|
});
|
|
|
|
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;
|
|
userMessage?: string;
|
|
};
|
|
|
|
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') {
|
|
const message = asString(payloadRecord.message)
|
|
?? asString(payloadRecord.text)
|
|
?? asString(payloadRecord.content);
|
|
if (!message) {
|
|
return null;
|
|
}
|
|
return {
|
|
userMessage: message
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|