Fix Codex MCP thread item mapping
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user