| 1 | package provider |
| 2 | |
| 3 | import "reasonix/internal/nilutil" |
| 4 | |
| 5 | // ReasoningReplayConverter is an adapter-owned, loss-aware request-view |
| 6 | // conversion. It must not mutate the input or fabricate provider proof. A true |
| 7 | // result means the converted message can be sent without protocol recovery. |
| 8 | type ReasoningReplayConverter interface { |
| 9 | ConvertReasoningReplay(Message) (Message, bool) |
| 10 | } |
| 11 | |
| 12 | func completeReplayEvidence(m Message) bool { |
| 13 | if m.ReasoningStatus == "incomplete" || m.ReasoningStatus == "in_progress" { |
| 14 | return false |
| 15 | } |
| 16 | switch m.ReasoningState { |
| 17 | case "", ReasoningEmpty, ReasoningComplete: |
| 18 | return true |
| 19 | default: |
| 20 | return false |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | func compatibleReplayMessage(p Provider, m Message) (Message, bool) { |
| 25 | if nilutil.IsNil(p) || m.Role != RoleAssistant || !completeReplayEvidence(m) { |
| 26 | return m, false |
| 27 | } |
| 28 | converter, ok := p.(ReasoningReplayConverter) |
| 29 | if !ok { |
| 30 | return m, false |
| 31 | } |
| 32 | return converter.ConvertReasoningReplay(m) |
| 33 | } |
| 34 | |
| 35 | // projectCompatibleReplay preserves the canonical history and allocates only |
| 36 | // when an adapter actually converts an otherwise unsupported historical block. |
| 37 | func projectCompatibleReplay(p Provider, msgs []Message, prefix int) ([]Message, bool) { |
| 38 | work := msgs |
| 39 | changed := false |
| 40 | for i, m := range msgs[:prefix] { |
| 41 | converted, ok := compatibleReplayMessage(p, m) |
| 42 | if !ok { |
| 43 | continue |
| 44 | } |
| 45 | if !changed { |
| 46 | work = append([]Message(nil), msgs...) |
| 47 | changed = true |
| 48 | } |
| 49 | work[i] = converted |
| 50 | } |
| 51 | return work, changed |
| 52 | } |
| 53 |