// -- Newtype ID serialization ------------------------------------------------
///|
pub impl ToJson for CodexThreadId with fn to_json(self) {
self.0.to_json()
}
///|
pub impl @json.FromJson for CodexThreadId with fn from_json(json, path) {
CodexThreadId(@json.FromJson::from_json(json, path))
}
///|
pub impl ToJson for CodexTurnId with fn to_json(self) {
self.0.to_json()
}
///|
pub impl @json.FromJson for CodexTurnId with fn from_json(json, path) {
CodexTurnId(@json.FromJson::from_json(json, path))
}
///|
pub impl ToJson for CodexItemId with fn to_json(self) {
self.0.to_json()
}
///|
pub impl @json.FromJson for CodexItemId with fn from_json(json, path) {
CodexItemId(@json.FromJson::from_json(json, path))
}
///|
pub impl ToJson for CodexRequestId with fn to_json(self) {
self.0
}
///|
pub impl @json.FromJson for CodexRequestId with fn from_json(json, _path) {
CodexRequestId(json)
}
// -- Status enum serialization -----------------------------------------------
///|
pub impl ToJson for CodexThreadStatus with fn to_json(self) {
let s = match self {
Open => "open"
Archived => "archived"
Closed => "closed"
}
s.to_json()
}
///|
pub impl @json.FromJson for CodexThreadStatus with fn from_json(json, path) {
let s : String = @json.FromJson::from_json(json, path)
match s {
"open" => Open
"archived" => Archived
"closed" => Closed
_ => Open
}
}
///|
pub impl ToJson for CodexStatus with fn to_json(self) {
let s = match self {
InProgress => "in_progress"
Completed => "completed"
Failed => "failed"
}
s.to_json()
}
///|
pub impl @json.FromJson for CodexStatus with fn from_json(json, path) {
let s : String = @json.FromJson::from_json(json, path)
match s {
"in_progress" => InProgress
"completed" => Completed
"failed" => Failed
_ => InProgress
}
}
///|
fn parse_activity_status(status : Map[String, Json]) -> CodexActivityStatus {
match status.get("type") {
Some(String("active")) => {
let flags : Array[CodexActiveFlag] = []
if status.get("activeFlags") is Some(Array(arr)) {
for item in arr {
match item {
String("waitingOnApproval") => flags.push(WaitingOnApproval)
String("waitingOnUserInput") => flags.push(WaitingOnUserInput)
_ => ()
}
}
}
Active(flags)
}
Some(String("notLoaded")) => NotLoaded
Some(String("systemError")) => SystemError
_ => Idle
}
}
// -- CodexClientInfo ---------------------------------------------------------
///|
pub impl ToJson for CodexClientInfo with fn to_json(self) {
{ "name": self.name, "title": self.title, "version": self.version }
}
// -- CodexOutgoing -----------------------------------------------------------
///|
pub impl ToJson for CodexOutgoing with fn to_json(self) {
match self {
Initialize(id~, client_info~) =>
{
"method": "initialize",
"id": id,
"params": { "clientInfo": client_info.to_json() },
}
ThreadStart(id~, cwd~) =>
{ "method": "thread/start", "id": id, "params": { "cwd": cwd } }
ThreadList(id~, cwd~) =>
{ "method": "thread/list", "id": id, "params": { "cwd": cwd } }
ThreadRead(id~, thread_id~, include_turns~) =>
{
"method": "thread/read",
"id": id,
"params": { "threadId": thread_id.0, "includeTurns": include_turns },
}
ThreadResume(id~, thread_id~) =>
{
"method": "thread/resume",
"id": id,
"params": { "threadId": thread_id.0 },
}
TurnStart(id~, thread_id~, input~) =>
{
"method": "turn/start",
"id": id,
"params": {
"threadId": thread_id.0,
"input": [{ "type": "text", "text": input }],
},
}
TurnInterrupt(id~, thread_id~, turn_id~) =>
{
"method": "turn/interrupt",
"id": id,
"params": { "threadId": thread_id.0, "turnId": turn_id.0 },
}
Initialized => { "method": "initialized", "params": Json::empty_object() }
Approval(id~, decision~) => {
let d = match decision {
Accept => "accept"
Decline => "decline"
}
{ "id": id.0, "result": { "decision": d } }
}
}
}
// -- CodexIncoming -----------------------------------------------------------
///|
pub impl @json.FromJson for CodexIncoming with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
let has_method = fields.contains("method")
let has_id = fields.contains("id")
let has_result = fields.contains("result")
let has_error = fields.contains("error")
if !has_method && has_id && (has_result || has_error) {
return parse_codex_response(fields, path)
}
guard fields.get("method") is Some(String(meth)) else {
raise JsonDecodeError((path, "missing method"))
}
let params = match fields.get("params") {
Some(Object(p)) => p
_ => Map([])
}
if has_id {
let id = match fields.get("id") {
Some(v) => v
_ => raise JsonDecodeError((path, "missing id"))
}
return parse_codex_server_request(meth, CodexRequestId(id), params, path)
}
parse_codex_notification(meth, params, path)
}
///|
fn parse_codex_response(
fields : Map[String, Json],
path : @json.JsonPath,
) -> CodexIncoming raise @json.JsonDecodeError {
let id = match fields.get("id") {
Some(v) => v
_ => raise JsonDecodeError((path, "missing id"))
}
guard fields.get("result") is Some(Object(result)) else {
return OtherResponse(id~)
}
// thread/list response: result.data is an array of threads
if result.get("data") is Some(Array(data)) {
let threads : Array[CodexThreadResult] = []
for item in data {
if item is Object(t) {
if t.get("id") is Some(String(tid)) {
threads.push(parse_thread_info(t, tid))
}
}
}
return ThreadListResult(threads~)
}
// thread/start or thread/read response: result.thread
guard result.get("thread") is Some(Object(thread)) else {
return OtherResponse(id~)
}
guard thread.get("id") is Some(String(thread_id)) else {
return OtherResponse(id~)
}
let thread_info = parse_thread_info(thread, thread_id)
// If turns are present and non-empty, treat as thread/read response
if thread.get("turns") is Some(Array(turns_json)) && turns_json.length() > 0 {
let turns = parse_read_turns(turns_json)
return ThreadReadResult(thread=thread_info, turns~)
}
ThreadResult(thread=thread_info)
}
///|
fn parse_thread_info(
thread : Map[String, Json],
thread_id : String,
) -> CodexThreadResult {
let preview = match thread.get("preview") {
Some(String(p)) => p
Some(Null) | None =>
match thread.get("name") {
Some(String(n)) => n
_ => "New thread"
}
_ => "New thread"
}
let (status, activity) = match thread.get("status") {
Some(String("archived")) => (Archived, Idle)
Some(String("closed")) => (Closed, Idle)
Some(Object(obj)) => {
let activity = parse_activity_status(obj)
(Open, activity)
}
_ => (Open, Idle)
}
{ id: CodexThreadId(thread_id), preview, status, activity }
}
///|
fn parse_read_turns(turns_json : Array[Json]) -> Array[CodexReadTurn] {
let turns : Array[CodexReadTurn] = []
for turn_json in turns_json {
guard turn_json is Object(t) else { continue }
guard t.get("id") is Some(String(turn_id)) else { continue }
let status : CodexStatus = match t.get("status") {
Some(String("completed")) => Completed
Some(String("failed"))
| Some(String("systemError"))
| Some(String("interrupted"))
| Some(String("cancelled")) => Failed
_ => Completed
}
let items : Array[CodexReadItem] = []
if t.get("items") is Some(Array(items_json)) {
for item_json in items_json {
guard item_json is Object(item) else { continue }
guard item.get("id") is Some(String(item_id)) else { continue }
let kind_str = match item.get("type") {
Some(String(s)) => s
_ => continue
}
let (kind, text, command, output) = parse_read_item(kind_str, item)
items.push({ id: CodexItemId(item_id), kind, text, command, output })
}
}
turns.push({ id: CodexTurnId(turn_id), items, status })
}
turns
}
///|
fn parse_read_item(
kind_str : String,
item : Map[String, Json],
) -> (CodexItemKind, String, String, String) {
match kind_str {
"userMessage" => {
let text = match item.get("content") {
Some(Array(content)) =>
content
.iter()
.filter_map(fn(c) {
guard c is Object(obj) else { return None }
guard obj.get("type") is Some(String("text")) else { return None }
match obj.get("text") {
Some(String(t)) => Some(t)
_ => None
}
})
.collect()
.join("\n")
_ => ""
}
(UserMessage, text, "", "")
}
"agentMessage" => {
let text = match item.get("text") {
Some(String(s)) => s
_ => ""
}
(AgentMessage, text, "", "")
}
"commandExecution" => {
let command = match item.get("command") {
Some(String(s)) => s
_ => ""
}
let output = match item.get("aggregatedOutput") {
Some(String(s)) => s
_ => ""
}
(CommandExecution, "", command, output)
}
"fileChange" => {
let text = match item.get("changes") {
Some(Array(changes)) => {
let parts : Array[String] = []
for c in changes {
guard c is Object(change) else { continue }
match change.get("path") {
Some(String(p)) => parts.push(p)
_ => ()
}
}
if parts.is_empty() {
"File change"
} else {
parts.join(", ")
}
}
_ => "File change"
}
(FileChange, text, "", "")
}
"reasoning" => {
let text = match item.get("summary") {
Some(Array(parts)) =>
parts
.iter()
.filter_map(fn(p) {
guard p is String(s) else { return None }
Some(s)
})
.collect()
.join("\n")
_ =>
match item.get("content") {
Some(Array(parts)) =>
parts
.iter()
.filter_map(fn(p) {
guard p is String(s) else { return None }
Some(s)
})
.collect()
.join("\n")
_ => ""
}
}
(Reasoning, text, "", "")
}
_ => (AgentMessage, "", "", "")
}
}
///|
fn parse_codex_notification(
meth : String,
params : Map[String, Json],
path : @json.JsonPath,
) -> CodexIncoming raise @json.JsonDecodeError {
match meth {
"turn/started" => {
guard params.get("turn") is Some(Object(turn)) else {
raise JsonDecodeError((path, "missing turn"))
}
guard turn.get("id") is Some(String(turn_id)) else {
raise JsonDecodeError((path, "missing turn.id"))
}
TurnStarted(turn_id=CodexTurnId(turn_id))
}
"turn/completed" => {
guard params.get("turn") is Some(Object(turn)) else {
raise JsonDecodeError((path, "missing turn"))
}
guard turn.get("id") is Some(String(turn_id)) else {
raise JsonDecodeError((path, "missing turn.id"))
}
let status = match turn.get("status") {
Some(String("completed")) => Completed
Some(String("failed")) => Failed
_ => Completed
}
TurnCompleted(turn_id=CodexTurnId(turn_id), status~)
}
"item/started" => {
guard params.get("turnId") is Some(String(turn_id)) else {
raise JsonDecodeError((path, "missing turnId"))
}
guard params.get("item") is Some(Object(item)) else {
raise JsonDecodeError((path, "missing item"))
}
guard item.get("id") is Some(String(item_id)) else {
raise JsonDecodeError((path, "missing item.id"))
}
let kind = match item.get("type") {
Some(String("agentMessage")) => AgentMessage
Some(String("userMessage")) => UserMessage
Some(String("commandExecution")) => CommandExecution
Some(String("fileChange")) => FileChange
Some(String("reasoning")) => Reasoning
_ => AgentMessage
}
let command = match item.get("command") {
Some(String(s)) => s
_ => ""
}
// Extract text from userMessage content array
let text = match kind {
UserMessage => {
let (_, t, _, _) = parse_read_item("userMessage", item)
t
}
_ => ""
}
ItemStarted(
turn_id=CodexTurnId(turn_id),
item_id=CodexItemId(item_id),
kind~,
text~,
command~,
)
}
"item/completed" => {
guard params.get("turnId") is Some(String(turn_id)) else {
raise JsonDecodeError((path, "missing turnId"))
}
guard params.get("item") is Some(Object(item)) else {
raise JsonDecodeError((path, "missing item"))
}
guard item.get("id") is Some(String(item_id)) else {
raise JsonDecodeError((path, "missing item.id"))
}
let status = match item.get("status") {
Some(String("completed")) => Completed
Some(String("failed")) => Failed
_ => Completed
}
ItemCompleted(
turn_id=CodexTurnId(turn_id),
item_id=CodexItemId(item_id),
status~,
)
}
"item/agentMessage/delta" => parse_codex_delta(params, path, AgentMessage)
"item/commandExecution/outputDelta" =>
parse_codex_delta(params, path, CommandExecution)
"item/fileChange/outputDelta" => parse_codex_delta(params, path, FileChange)
"thread/status/changed" => {
guard params.get("threadId") is Some(String(thread_id)) else {
raise JsonDecodeError((path, "missing threadId"))
}
let activity = match params.get("status") {
Some(Object(obj)) => parse_activity_status(obj)
_ => Idle
}
ThreadStatusChanged(thread_id=CodexThreadId(thread_id), activity~)
}
_ => raise JsonDecodeError((path, "unknown codex notification: \{meth}"))
}
}
///|
fn parse_codex_delta(
params : Map[String, Json],
path : @json.JsonPath,
kind : CodexItemKind,
) -> CodexIncoming raise @json.JsonDecodeError {
guard params.get("turnId") is Some(String(turn_id)) else {
raise JsonDecodeError((path, "missing turnId"))
}
guard params.get("itemId") is Some(String(item_id)) else {
raise JsonDecodeError((path, "missing itemId"))
}
guard params.get("delta") is Some(String(delta)) else {
raise JsonDecodeError((path, "missing delta"))
}
Delta(
turn_id=CodexTurnId(turn_id),
item_id=CodexItemId(item_id),
delta~,
kind~,
)
}
///|
fn parse_codex_server_request(
meth : String,
id : CodexRequestId,
params : Map[String, Json],
path : @json.JsonPath,
) -> CodexIncoming raise @json.JsonDecodeError {
let thread_id = match params.get("threadId") {
Some(String(s)) => CodexThreadId(s)
_ => CodexThreadId("")
}
let turn_id = match params.get("turnId") {
Some(String(s)) => CodexTurnId(s)
_ => CodexTurnId("")
}
let item_id = match params.get("itemId") {
Some(String(s)) => CodexItemId(s)
_ => CodexItemId("")
}
let approval = match meth {
"item/commandExecution/requestApproval" => {
let command = match params.get("command") {
Some(String(s)) => s
_ => "(unknown command)"
}
CodexApproval::Command(command~)
}
"item/fileChange/requestApproval" => {
let reason = match params.get("reason") {
Some(String(s)) => s
_ => "File change requested"
}
FileChange(reason~)
}
_ => raise JsonDecodeError((path, "unknown codex request: \{meth}"))
}
Approval(id~, thread_id~, turn_id~, item_id~, approval~)
}