-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathcodex_notify.go
More file actions
67 lines (59 loc) · 1.94 KB
/
Copy pathcodex_notify.go
File metadata and controls
67 lines (59 loc) · 1.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package adapter
import (
"encoding/json"
"fmt"
"io"
"github.com/escoffier-labs/agent-notify/internal/canonical"
)
// CodexNotify reads a Codex CLI notify event JSON and produces a canonical
// message. Codex's notify schema is younger than Claude Code's, so this
// adapter is especially defensive about field name variations.
func CodexNotify(r io.Reader) (canonical.Message, error) {
raw, err := ReadBounded(r)
if err != nil {
return canonical.Message{}, err
}
return CodexNotifyFromBytes(raw)
}
// CodexNotifyFromBytes parses a Codex CLI notify event JSON from a byte slice.
// Codex passes the event JSON as the last positional argv argument rather than
// on stdin, so the command layer can reach this directly with the arg payload.
func CodexNotifyFromBytes(raw []byte) (canonical.Message, error) {
if err := CheckSize(raw); err != nil {
return canonical.Message{}, err
}
var ev map[string]interface{}
if err := json.Unmarshal(raw, &ev); err != nil {
return canonical.Message{}, fmt.Errorf("parse codex event: %w", err)
}
// Try multiple known/likely field names for the message body.
body := firstString(ev,
"last-assistant-message", "last_assistant_message",
"message", "text", "msg",
)
if body == "" {
body = "Codex turn complete"
}
turnID := firstString(ev, "turn-id", "turn_id", "session_id", "id")
title := "Codex"
if turnID != "" {
title = "Codex (" + turnID + ")"
}
model := firstString(ev, "model", "model-name", "model_name")
if model == "" {
// Codex's notify event often omits the model; fall back to the model
// recorded for this turn in the Codex CLI session rollout. Resolve by
// the explicit thread id only (never session_id, which is not a
// thread alias).
threadID := firstString(ev, "thread-id", "thread_id")
if threadID != "" && turnID != "" {
model = resolveCodexModel(threadID, turnID)
}
}
return canonical.Message{
Title: title,
Body: body,
Source: "codex",
Model: model,
}, nil
}