mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
import { randomUUID } from 'node:crypto';
|
|
import type { AgentMessage, PlanItem } from './types';
|
|
|
|
export type CodexMessage =
|
|
| { type: 'message'; message: string }
|
|
| { type: 'reasoning'; message: string; id: string }
|
|
| {
|
|
type: 'token_count';
|
|
model: string | null;
|
|
info: {
|
|
total: {
|
|
inputTokens: number;
|
|
outputTokens: number;
|
|
totalTokens?: number;
|
|
thoughtTokens?: number;
|
|
cachedInputTokens?: number;
|
|
};
|
|
contextTokens?: number;
|
|
modelContextWindow?: number;
|
|
};
|
|
}
|
|
| {
|
|
type: 'tool-call';
|
|
name: string;
|
|
callId: string;
|
|
input: unknown;
|
|
status?: 'pending' | 'in_progress' | 'completed' | 'failed';
|
|
nativeTitle?: string;
|
|
nativeKind?: string;
|
|
}
|
|
| {
|
|
type: 'tool-call-result';
|
|
callId: string;
|
|
output: unknown;
|
|
is_error?: boolean;
|
|
}
|
|
| { type: 'plan'; entries: PlanItem[] }
|
|
| { type: 'error'; message: string };
|
|
|
|
export function convertAgentMessage(message: AgentMessage, model?: string | null): CodexMessage | null {
|
|
switch (message.type) {
|
|
case 'text':
|
|
return { type: 'message', message: message.text };
|
|
case 'reasoning':
|
|
// AgentMessage uses `text` (consistent with the `text` variant);
|
|
// the wire-level CodexMessage uses `message` to match the
|
|
// existing reasoning format emitted by the Codex path.
|
|
return { type: 'reasoning', message: message.text, id: message.id ?? randomUUID() };
|
|
case 'usage':
|
|
return {
|
|
type: 'token_count',
|
|
model: typeof model === 'string' && model.trim() ? model.trim() : null,
|
|
info: {
|
|
total: {
|
|
inputTokens: message.inputTokens,
|
|
outputTokens: message.outputTokens,
|
|
totalTokens: message.totalTokens,
|
|
thoughtTokens: message.thoughtTokens,
|
|
cachedInputTokens: message.cacheReadTokens
|
|
},
|
|
contextTokens: message.contextTokens,
|
|
modelContextWindow: message.contextWindow
|
|
}
|
|
};
|
|
case 'tool_call':
|
|
return {
|
|
type: 'tool-call',
|
|
name: message.name,
|
|
callId: message.id,
|
|
input: message.input,
|
|
status: message.status,
|
|
...(message.title ? { nativeTitle: message.title } : {}),
|
|
...(message.kind ? { nativeKind: message.kind } : {})
|
|
};
|
|
case 'tool_result':
|
|
return {
|
|
type: 'tool-call-result',
|
|
callId: message.id,
|
|
output: message.output,
|
|
is_error: message.status === 'failed'
|
|
};
|
|
case 'plan':
|
|
return {
|
|
type: 'plan',
|
|
entries: message.items
|
|
};
|
|
case 'error':
|
|
return { type: 'error', message: message.message };
|
|
case 'turn_complete':
|
|
return null;
|
|
default: {
|
|
// Unreachable while every AgentMessage variant is handled above —
|
|
// the `never` binding is what enforces that at compile time. The
|
|
// runtime return is deliberately `null` rather than the message
|
|
// itself: callers forward a non-null result straight into the chat
|
|
// stream, so echoing an unrecognized shape here would put a raw
|
|
// object on screen instead of failing closed.
|
|
const _exhaustive: never = message;
|
|
void _exhaustive;
|
|
return null;
|
|
}
|
|
}
|
|
}
|