Fix Codex MCP thread item mapping

This commit is contained in:
Mohamed Boudra
2025-12-24 19:41:14 +07:00
parent c51eb75930
commit 383870687f
2 changed files with 53 additions and 10 deletions
@@ -129,6 +129,16 @@ function extractThreadItemCallId(item: Record<string, unknown>): string | undefi
); );
} }
function normalizeThreadEventType(type: string): string {
if (type.startsWith("thread.item.")) {
return type.slice("thread.".length);
}
if (type.startsWith("thread.turn.")) {
return type.slice("thread.".length);
}
return type;
}
function buildCommandDisplayName(command?: unknown): string { function buildCommandDisplayName(command?: unknown): string {
if (typeof command === "string") { if (typeof command === "string") {
const trimmed = command.trim(); const trimmed = command.trim();
@@ -969,13 +979,22 @@ class CodexMcpAgentSession implements AgentSession {
if (!event || typeof event !== "object") { if (!event || typeof event !== "object") {
return; return;
} }
const type = (event as { type?: unknown }).type; const record = event as Record<string, unknown>;
if (typeof type !== "string") { const data = record.data && typeof record.data === "object" ? (record.data as Record<string, unknown>) : null;
const recordType = record.type;
const dataType = data?.type;
let type = typeof recordType === "string" ? recordType : undefined;
let eventRecord: Record<string, unknown> = record;
if ((!type || type === "event") && typeof dataType === "string") {
type = dataType;
eventRecord = { ...record, ...data, type };
}
if (!type) {
return; return;
} }
if (type.includes(".") || type.startsWith("turn.") || type.startsWith("thread.") || type.startsWith("item.")) { if (type.includes(".") || type.startsWith("turn.") || type.startsWith("thread.") || type.startsWith("item.")) {
this.handleThreadEvent(event as Record<string, unknown>); this.handleThreadEvent(eventRecord);
return; return;
} }
@@ -1163,10 +1182,14 @@ class CodexMcpAgentSession implements AgentSession {
} }
private handleThreadEvent(event: Record<string, unknown>): void { private handleThreadEvent(event: Record<string, unknown>): void {
const type = event.type as string; const rawType = event.type as string;
const type = normalizeThreadEventType(rawType);
const data =
event.data && typeof event.data === "object" ? (event.data as Record<string, unknown>) : null;
switch (type) { switch (type) {
case "thread.started": { case "thread.started": {
const threadId = event.thread_id as string | undefined; const threadId =
(event.thread_id as string | undefined) ?? (data?.thread_id as string | undefined);
if (threadId) { if (threadId) {
this.sessionId = threadId; this.sessionId = threadId;
this.flushPendingHistory(); this.flushPendingHistory();
@@ -1182,19 +1205,26 @@ class CodexMcpAgentSession implements AgentSession {
this.emitEvent({ type: "turn_started", provider: "codex-mcp" }); this.emitEvent({ type: "turn_started", provider: "codex-mcp" });
break; break;
case "turn.completed": { case "turn.completed": {
const usage = this.convertUsage((event as { usage?: unknown }).usage); const usage = this.convertUsage(
(event as { usage?: unknown }).usage ?? (data as { usage?: unknown } | null)?.usage
);
this.emitEvent({ type: "turn_completed", provider: "codex-mcp", usage }); this.emitEvent({ type: "turn_completed", provider: "codex-mcp", usage });
break; break;
} }
case "turn.failed": { case "turn.failed": {
const error = (event as { error?: { message?: string } }).error?.message ?? "Codex MCP turn failed"; const errorRecord =
(event as { error?: { message?: string } }).error ??
((data as { error?: { message?: string } } | null)?.error ?? null);
const error = errorRecord?.message ?? "Codex MCP turn failed";
this.emitEvent({ type: "turn_failed", provider: "codex-mcp", error }); this.emitEvent({ type: "turn_failed", provider: "codex-mcp", error });
break; break;
} }
case "item.started": case "item.started":
case "item.updated": case "item.updated":
case "item.completed": { case "item.completed": {
const item = event.item as Record<string, unknown> | undefined; const item =
(event.item as Record<string, unknown> | undefined) ??
(data?.item as Record<string, unknown> | undefined);
if (!item) break; if (!item) break;
const timelineItem = this.threadItemToTimeline(item); const timelineItem = this.threadItemToTimeline(item);
if (timelineItem) { if (timelineItem) {
@@ -1218,7 +1248,10 @@ class CodexMcpAgentSession implements AgentSession {
break; break;
} }
case "error": { case "error": {
const message = (event.message as string | undefined) ?? "Codex MCP stream error"; const message =
(event.message as string | undefined) ??
(data?.message as string | undefined) ??
"Codex MCP stream error";
this.emitEvent({ this.emitEvent({
type: "timeline", type: "timeline",
provider: "codex-mcp", provider: "codex-mcp",
+11 -1
View File
@@ -118,12 +118,22 @@ Build a new Codex MCP provider side‑by‑side with the existing Codex SDK prov
- [x] **Fix**: Codex MCP command output should include exit codes for command tool calls. - [x] **Fix**: Codex MCP command output should include exit codes for command tool calls.
- **Done (2025-12-24 19:36)**: Ensured command tool outputs include exit codes even when output text is missing. - **Done (2025-12-24 19:36)**: Ensured command tool outputs include exit codes even when output text is missing.
- [ ] **Fix**: Codex MCP thread/item event mapping should capture file_change, mcp_tool_call, web_search, and todo_list. - [x] **Fix**: Codex MCP thread/item event mapping should capture file_change, mcp_tool_call, web_search, and todo_list.
- **Done (2025-12-24 19:40)**: Normalized thread event types and added data fallback so item events map to timeline entries.
- [ ] **Fix**: Codex MCP should emit error timeline items for failed turns. - [ ] **Fix**: Codex MCP should emit error timeline items for failed turns.
- [ ] **Fix**: Codex MCP persistence should include conversationId metadata for resume. - [ ] **Fix**: Codex MCP persistence should include conversationId metadata for resume.
- [ ] **Investigate**: Elicitation was fixed in Codex 0.71.0 - why isn't it working for us?
- Web search found: Issue #6992 - Codex auto-declined elicitation requests, fixed in 0.71.0
- We're on 0.77.0 so elicitation SHOULD work now
- The bug was in `codex-rs/rmcp-client/src/logging_client_handler.rs`
- Verify we're actually receiving ElicitRequest but maybe handling it wrong
- Check GitHub issue #6992 for exact fix details
- Maybe happy-cli works because it's on a newer version?
- [ ] **Fix**: Codex MCP permission elicitation should surface permission_requested/resolved events (read-only/untrusted too). - [ ] **Fix**: Codex MCP permission elicitation should surface permission_requested/resolved events (read-only/untrusted too).
- [ ] **Fix**: Codex SDK persistence hydration should include completed shell_command tool entries. - [ ] **Fix**: Codex SDK persistence hydration should include completed shell_command tool entries.