Skip to content

Commit f0d7d11

Browse files
committed
fix: handle missing codex renderer/parsing cases
1 parent 6a830c2 commit f0d7d11

File tree

3 files changed

+171
-1
lines changed

3 files changed

+171
-1
lines changed

src-tauri/src/providers/codex.rs

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,66 @@ fn convert_codex_event(
849849
}));
850850
Some(msg)
851851
}
852+
"agent_reasoning" => {
853+
let text = payload.get("text").and_then(Value::as_str)?.trim();
854+
if text.is_empty() {
855+
return None;
856+
}
857+
*counter += 1;
858+
let content = serde_json::json!([{
859+
"type": "thinking",
860+
"thinking": text
861+
}]);
862+
Some(build_codex_message(
863+
format!("codex-event-{counter}"),
864+
session_id,
865+
line_timestamp.to_string(),
866+
"assistant",
867+
Some("assistant"),
868+
Some(content),
869+
None,
870+
))
871+
}
872+
"agent_message" => {
873+
let text = payload.get("message").and_then(Value::as_str)?.trim();
874+
if text.is_empty() {
875+
return None;
876+
}
877+
*counter += 1;
878+
let content = serde_json::json!([{
879+
"type": "text",
880+
"text": text
881+
}]);
882+
Some(build_codex_message(
883+
format!("codex-event-{counter}"),
884+
session_id,
885+
line_timestamp.to_string(),
886+
"assistant",
887+
Some("assistant"),
888+
Some(content),
889+
None,
890+
))
891+
}
892+
"user_message" => {
893+
let text = payload.get("message").and_then(Value::as_str)?.trim();
894+
if text.is_empty() {
895+
return None;
896+
}
897+
*counter += 1;
898+
let content = serde_json::json!([{
899+
"type": "text",
900+
"text": text
901+
}]);
902+
Some(build_codex_message(
903+
format!("codex-event-{counter}"),
904+
session_id,
905+
line_timestamp.to_string(),
906+
"user",
907+
Some("user"),
908+
Some(content),
909+
None,
910+
))
911+
}
852912
// Unsupported/duplicated Codex events are intentionally ignored.
853913
_ => None,
854914
}
@@ -902,7 +962,7 @@ fn extract_last_token_usage(payload: &Value) -> Option<(u32, u32)> {
902962

903963
fn map_codex_tool_name(name: &str) -> &str {
904964
match name {
905-
"exec_command" | "shell" => "Bash",
965+
"exec_command" | "shell" | "write_stdin" => "Bash",
906966
_ => name,
907967
}
908968
}
@@ -1215,6 +1275,7 @@ mod tests {
12151275
fn map_exec_command_to_bash() {
12161276
assert_eq!(map_codex_tool_name("exec_command"), "Bash");
12171277
assert_eq!(map_codex_tool_name("shell"), "Bash");
1278+
assert_eq!(map_codex_tool_name("write_stdin"), "Bash");
12181279
assert_eq!(map_codex_tool_name("batch_execute"), "batch_execute");
12191280
}
12201281

@@ -1545,6 +1606,87 @@ mod tests {
15451606
assert_eq!(msg.subtype.as_deref(), Some("microcompact_boundary"));
15461607
}
15471608

1609+
#[test]
1610+
fn convert_agent_reasoning_event_to_thinking_message() {
1611+
let mut counter = 0u64;
1612+
let msg = convert_codex_event(
1613+
&json!({
1614+
"type": "agent_reasoning",
1615+
"text": "**Inspecting parsers**"
1616+
}),
1617+
"session-1",
1618+
"2026-02-19T12:00:00Z",
1619+
&mut counter,
1620+
)
1621+
.expect("agent_reasoning should be converted");
1622+
1623+
assert_eq!(msg.message_type, "assistant");
1624+
let arr = msg
1625+
.content
1626+
.as_ref()
1627+
.and_then(Value::as_array)
1628+
.expect("content should be an array");
1629+
assert_eq!(arr[0].get("type").and_then(Value::as_str), Some("thinking"));
1630+
assert_eq!(
1631+
arr[0].get("thinking").and_then(Value::as_str),
1632+
Some("**Inspecting parsers**")
1633+
);
1634+
}
1635+
1636+
#[test]
1637+
fn convert_agent_message_event_to_assistant_text_message() {
1638+
let mut counter = 0u64;
1639+
let msg = convert_codex_event(
1640+
&json!({
1641+
"type": "agent_message",
1642+
"message": "Working on requested changes"
1643+
}),
1644+
"session-1",
1645+
"2026-02-19T12:00:00Z",
1646+
&mut counter,
1647+
)
1648+
.expect("agent_message should be converted");
1649+
1650+
assert_eq!(msg.message_type, "assistant");
1651+
let arr = msg
1652+
.content
1653+
.as_ref()
1654+
.and_then(Value::as_array)
1655+
.expect("content should be an array");
1656+
assert_eq!(arr[0].get("type").and_then(Value::as_str), Some("text"));
1657+
assert_eq!(
1658+
arr[0].get("text").and_then(Value::as_str),
1659+
Some("Working on requested changes")
1660+
);
1661+
}
1662+
1663+
#[test]
1664+
fn convert_user_message_event_to_user_text_message() {
1665+
let mut counter = 0u64;
1666+
let msg = convert_codex_event(
1667+
&json!({
1668+
"type": "user_message",
1669+
"message": "Please patch this file"
1670+
}),
1671+
"session-1",
1672+
"2026-02-19T12:00:00Z",
1673+
&mut counter,
1674+
)
1675+
.expect("user_message should be converted");
1676+
1677+
assert_eq!(msg.message_type, "user");
1678+
let arr = msg
1679+
.content
1680+
.as_ref()
1681+
.and_then(Value::as_array)
1682+
.expect("content should be an array");
1683+
assert_eq!(arr[0].get("type").and_then(Value::as_str), Some("text"));
1684+
assert_eq!(
1685+
arr[0].get("text").and_then(Value::as_str),
1686+
Some("Please patch this file")
1687+
);
1688+
}
1689+
15481690
#[test]
15491691
fn convert_compacted_line_to_system_message() {
15501692
let mut counter = 0u64;

src/components/MessageViewer/components/ClaudeMessageNode.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
ToolExecutionResultRouter,
1717
ProgressRenderer,
1818
AgentProgressGroupRenderer,
19+
FileHistorySnapshotRenderer,
1920
} from "../../messageRenderer";
2021
import { AgentTaskGroupRenderer, TaskOperationGroupRenderer } from "../../toolResultRenderer";
2122
import { extractClaudeMessageContent } from "../../../utils/messageUtils";
@@ -194,6 +195,28 @@ export const ClaudeMessageNode = React.memo(({
194195
);
195196
}
196197

198+
// File history snapshot messages
199+
if (message.type === "file-history-snapshot" && message.snapshot) {
200+
return (
201+
<div
202+
data-message-uuid={message.uuid}
203+
className={cn(
204+
"relative w-full px-4 py-2 transition-all duration-200",
205+
isCaptureMode && CAPTURE_HOVER_BG
206+
)}
207+
>
208+
{CaptureHideButton}
209+
<div className="max-w-4xl mx-auto">
210+
<FileHistorySnapshotRenderer
211+
messageId={message.messageId ?? ""}
212+
snapshot={message.snapshot}
213+
isSnapshotUpdate={Boolean(message.isSnapshotUpdate)}
214+
/>
215+
</div>
216+
</div>
217+
);
218+
}
219+
197220
// Progress messages
198221
if (message.type === "progress" && message.data) {
199222
return (

src/components/MessageViewer/helpers/messageHelpers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ export const hasSystemCommandContent = (message: ClaudeMessage): boolean => {
2929
* ARE considered empty because they have no user-visible content.
3030
*/
3131
export const isEmptyMessage = (message: ClaudeMessage): boolean => {
32+
// Snapshot blocks have dedicated renderer and no standard "content" payload.
33+
if (message.type === "file-history-snapshot") {
34+
return false;
35+
}
36+
3237
// Messages with tool use or results should be shown
3338
if (
3439
(message.type === "assistant" && message.toolUse) ||

0 commit comments

Comments
 (0)