| 1 | package control |
| 2 | |
| 3 | import "reasonix/internal/provider" |
| 4 | |
| 5 | type interruptedTailEvidence struct { |
| 6 | turnID string |
| 7 | states map[string]provider.ToolRunState |
| 8 | } |
| 9 | |
| 10 | func (c *Controller) ledgerTailEvidence() *interruptedTailEvidence { |
| 11 | if c == nil || c.turnEventLedger() == nil { |
| 12 | return nil |
| 13 | } |
| 14 | o := c.turnEventLedger().OrphanRecovery() |
| 15 | if o == nil { |
| 16 | return nil |
| 17 | } |
| 18 | e := &interruptedTailEvidence{turnID: o.TurnID, states: map[string]provider.ToolRunState{}} |
| 19 | for _, t := range o.Tools { |
| 20 | if t.Started { |
| 21 | e.states[t.ID] = provider.ToolRunUnknown |
| 22 | } else { |
| 23 | e.states[t.ID] = provider.ToolRunCancelled |
| 24 | } |
| 25 | } |
| 26 | return e |
| 27 | } |
| 28 | |
| 29 | func recordInterruptedAssistantRecovery(r *provider.InterruptedTurnRecovery, msgs []provider.Message, i int, evidence ...*interruptedTailEvidence) { |
| 30 | results := make(map[string]provider.Message) |
| 31 | for j := i + 1; j < len(msgs) && msgs[j].Role == provider.RoleTool && !msgs[j].LocalOnly; j++ { |
| 32 | result := msgs[j] |
| 33 | results[result.ToolCallID+"\x00"+result.Name] = result |
| 34 | } |
| 35 | for _, call := range msgs[i].ToolCalls { |
| 36 | state := provider.ToolRunUnknown |
| 37 | if result, ok := results[call.ID+"\x00"+call.Name]; ok && !provider.IsInterruptedPlaceholder(result) { |
| 38 | state = provider.ToolResultRunState(result) |
| 39 | } else if len(evidence) > 0 && evidence[0] != nil { |
| 40 | if proven, ok := evidence[0].states[call.ID]; ok { |
| 41 | state = proven |
| 42 | } |
| 43 | } |
| 44 | provider.RecordToolRecovery(r, interruptedToolSummary(call), state) |
| 45 | r.ToolCalls = append(r.ToolCalls, provider.ToolCallRecord{Identity: provider.ActionIdentity{CallID: call.ID, CanonicalTool: call.Name}, Arguments: []byte(call.Arguments), State: state}) |
| 46 | } |
| 47 | } |
| 48 |