okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauffix(stream): restore unfinished responses on session attachmenta229862

main
9.0 KiB290 linesraw
1package daemon
2
3import (
4	"encoding/json"
5	"errors"
6	"strings"
7
8	"albedo/cli/internal/daemon/protocol"
9)
10
11type wireChatEvent struct {
12	CallID string
13	StreamEvent
14}
15type sessionEventEnvelope struct {
16	Type     string          `json:"type"`
17	Sequence *int64          `json:"sequence"`
18	Data     json.RawMessage `json:"data"`
19}
20
21func decodeChatEvent(raw json.RawMessage) (*wireChatEvent, error) {
22	var envelope sessionEventEnvelope
23	if err := decodeRequired(raw, &envelope, "type", "data"); err != nil {
24		return nil, err
25	}
26	return decodeChatEnvelope(envelope, len(raw), nil)
27}
28
29func decodeChatEnvelope(envelope sessionEventEnvelope, wireBytes int, content *eventContentReader) (*wireChatEvent, error) {
30	if envelope.Type == "" || envelope.Data == nil || string(envelope.Data) == "null" {
31		return nil, fieldError("event")
32	}
33	if envelope.Type != "reset" && envelope.Type != "failure" && (envelope.Sequence == nil || *envelope.Sequence < 1) {
34		return nil, fieldError("event sequence")
35	}
36	event := StreamEvent{Type: EventType(envelope.Type)}
37	switch envelope.Type {
38	case "reset":
39		var data struct {
40			Reason string `json:"reason"`
41		}
42		if err := decodeRequired(envelope.Data, &data, "reason"); err != nil {
43			return nil, err
44		}
45		switch data.Reason {
46		case "initial", "generation_changed", "replay_unavailable", "recovery":
47		default:
48			return nil, fieldError("reset reason")
49		}
50	case "status":
51		var data protocol.SessionStatus
52		if err := decodeRequired(envelope.Data, &data); err != nil {
53			return nil, err
54		}
55		if err := validateSessionStatus(data); err != nil {
56			return nil, err
57		}
58		status := statusValue(data, protocol.Kernel{})
59		event.Status = &status
60	case "input":
61		var data struct {
62			Input protocol.Input `json:"input"`
63		}
64		if err := decodeRequired(envelope.Data, &data, "input"); err != nil {
65			return nil, err
66		}
67		if err := validInput(data.Input); err != nil {
68			return nil, err
69		}
70		receipt := InputReceipt(data.Input)
71		event.Receipt = &receipt
72		event.OperationID = data.Input.ID
73		if data.Input.Turn != nil {
74			event.Type = EventTurnMembership
75			event.TurnID = data.Input.Turn.ID
76			event.SubmissionIDs = []string{data.Input.ID}
77		}
78	case "text", "thinking":
79		var data struct {
80			RunID     string `json:"run_id"`
81			MessageID string `json:"message_id"`
82			ElapsedMs *int64 `json:"elapsed_ms"`
83			Text      string `json:"text"`
84		}
85		if err := decodeRequired(envelope.Data, &data, "run_id", "message_id", "text"); err != nil {
86			return nil, err
87		}
88		if data.RunID == "" || data.MessageID == "" {
89			return nil, fieldError("message identity")
90		}
91		event.Text, event.TurnID, event.MessageID = data.Text, data.RunID, data.MessageID
92		if data.ElapsedMs != nil {
93			event.ElapsedMs = *data.ElapsedMs
94			event.ElapsedObserved = true
95		}
96	case "message":
97		var data struct {
98			Entry protocol.HistoryEntry `json:"entry"`
99		}
100		if err := decodeRequired(envelope.Data, &data, "entry"); err != nil {
101			return nil, err
102		}
103		if content != nil {
104			entry, err := content.hydrateEntry(data.Entry)
105			if err != nil {
106				return nil, classifyStreamFailure(err)
107			}
108			data.Entry = entry
109		}
110		entries, err := historyEntryEvents(data.Entry)
111		if err != nil {
112			return nil, err
113		}
114		if len(entries) == 0 {
115			return nil, nil
116		}
117		event = entries[0]
118		event.Replayed = false
119	case "note":
120		var data struct {
121			Text    string                 `json:"text"`
122			EntryID string                 `json:"entry_id"`
123			Origin  string                 `json:"origin"`
124			Mail    *protocol.MailMetadata `json:"mail"`
125		}
126		if err := decodeRequired(envelope.Data, &data, "entry_id", "origin", "text", "mail"); err != nil {
127			return nil, err
128		}
129		event.Text, event.Source = data.Text, data.Origin
130	case "tool_progress":
131		if wireBytes > 8192 {
132			return nil, errors.New("tool_progress exceeds 8 KiB")
133		}
134		var data struct {
135			Progress json.RawMessage `json:"progress"`
136		}
137		if err := decodeRequired(envelope.Data, &data, "progress"); err != nil {
138			return nil, err
139		}
140		progress, err := decodeToolProgress(data.Progress)
141		if err != nil {
142			return nil, err
143		}
144		event.Progress = progress
145	case "tool":
146		var data toolEventData
147		if err := decodeRequired(envelope.Data, &data, "tool_call_id", "progress_call_id", "name", "arguments", "result", "trace", "content_complete", "reference"); err != nil {
148			return nil, err
149		}
150		if data.ToolCallID == "" || data.ProgressCallID == "" || data.Name == "" {
151			return nil, fieldError("tool identity")
152		}
153		if content != nil {
154			if err := content.hydrateTool(&data); err != nil {
155				return nil, classifyStreamFailure(err)
156			}
157		}
158		event.ToolName, event.ProgressCallID = data.Name, data.ProgressCallID
159		arguments, _ := dynamicValue(data.Arguments)
160		event.ToolArgs, _ = arguments.(map[string]any)
161		if json.Unmarshal(data.Result, &event.ToolResult) != nil {
162			event.ToolResult = string(data.Result)
163		}
164		event.ToolTrace = ParseToolTrace(data.Trace)
165		return &wireChatEvent{CallID: data.ToolCallID, StreamEvent: event}, nil
166	case "usage":
167		var data protocol.Usage
168		if err := decodeRequired(envelope.Data, &data); err != nil {
169			return nil, err
170		}
171		event.Usage = usageValue(data)
172	case "committed":
173		var data struct {
174			HighWater       int64    `json:"high_water"`
175			ReplacesLiveIDs []string `json:"replaces_live_ids"`
176			ReplacesAllLive bool     `json:"replaces_all_live"`
177		}
178		if err := decodeRequired(envelope.Data, &data, "high_water", "replaces_live_ids", "replaces_all_live"); err != nil {
179			return nil, err
180		}
181		if data.HighWater < 0 {
182			return nil, fieldError("history high water")
183		}
184		if data.ReplacesLiveIDs == nil || len(data.ReplacesLiveIDs) > 256 {
185			return nil, fieldError("live replacement identities")
186		}
187		for _, id := range data.ReplacesLiveIDs {
188			if id == "" {
189				return nil, fieldError("live replacement identity")
190			}
191		}
192		event.Seq, event.ReplacesLiveIDs = data.HighWater, data.ReplacesLiveIDs
193		event.ReplacesAllLive = data.ReplacesAllLive
194	case "turn_completed":
195		var data struct {
196			RunID      string   `json:"run_id"`
197			State      string   `json:"state"`
198			InputIDs   []string `json:"input_ids"`
199			InputCount int64    `json:"input_count"`
200			Truncated  bool     `json:"truncated"`
201		}
202		if err := decodeRequired(envelope.Data, &data, "run_id", "state", "input_ids", "input_count", "truncated"); err != nil {
203			return nil, err
204		}
205		if data.RunID == "" || data.InputIDs == nil {
206			return nil, fieldError("turn completion")
207		}
208		event.TurnID, event.SubmissionIDs = data.RunID, data.InputIDs
209		if data.State == "interrupted" {
210			event.Source = "interrupted"
211		} else if data.State == "failed" || data.State == "abandoned" {
212			event.Source = "failed"
213		} else if data.State != "completed" {
214			return nil, fieldError("turn state")
215		}
216	case "retry":
217		var data struct {
218			RunID  string              `json:"run_id"`
219			Reason protocol.SafeReason `json:"reason"`
220		}
221		if err := decodeRequired(envelope.Data, &data, "run_id", "attempt", "reason", "delay_ms"); err != nil {
222			return nil, err
223		}
224		event.TurnID, event.Text = data.RunID, data.Reason.Detail
225	case "compacted":
226		var data protocol.CompactionObservation
227		if err := json.Unmarshal(envelope.Data, &data); err != nil {
228			return nil, err
229		}
230		event.Evicted = int(data.EvictedEntries)
231		event.Summary = data.Summary
232		event.Strategy = value(data.Strategy)
233	case "error":
234		var data struct {
235			RunID   *string `json:"run_id"`
236			Code    string  `json:"code"`
237			Message string  `json:"message"`
238		}
239		if err := decodeRequired(envelope.Data, &data, "run_id", "code", "message"); err != nil {
240			return nil, err
241		}
242		if data.Code == "history_publication_failed" {
243			return nil, &APIError{StatusCode: 503, Code: data.Code, Message: data.Message}
244		}
245		event.Text, event.TurnID = data.Message, value(data.RunID)
246	case "invalidate":
247		var data struct {
248			Kind string `json:"kind"`
249			URL  string `json:"url"`
250		}
251		if err := decodeRequired(envelope.Data, &data, "kind", "url"); err != nil {
252			return nil, err
253		}
254		var resources ResourceInvalidation
255		switch data.Kind {
256		case "session":
257			resources.Session = true
258		case "settings":
259			resources.Settings = true
260		case "catalog":
261			resources.Catalog = true
262		case "context":
263			resources.Context = true
264		case "extension":
265			resources.Extension = true
266		default:
267			return nil, fieldError("invalidation kind")
268		}
269		if !strings.HasPrefix(data.URL, "/") || strings.HasPrefix(data.URL, "//") {
270			return nil, fieldError("invalidation URL")
271		}
272		event.Invalidation = &resources
273	case "failure":
274		return nil, errors.New("failure must be a terminal batch")
275	default:
276		return nil, nil
277	}
278	return &wireChatEvent{StreamEvent: event}, nil
279}
280
281type toolEventData struct {
282	Reference       *protocol.ContentReference `json:"reference"`
283	ToolCallID      string                     `json:"tool_call_id"`
284	ProgressCallID  string                     `json:"progress_call_id"`
285	Name            string                     `json:"name"`
286	Arguments       json.RawMessage            `json:"arguments"`
287	Result          json.RawMessage            `json:"result"`
288	Trace           json.RawMessage            `json:"trace"`
289	ContentComplete bool                       `json:"content_complete"`
290}