返回 DeepSeek-Reasonix
goal_state_compat.go
根目录 / internal / control / goal_state_compat.go
1 package control
2
3 import "encoding/json"
4
5 var goalStateKnownFields = map[string]struct{}{
6 "goal": {}, "status": {}, "researchMode": {}, "autoResearchTaskID": {},
7 "scopeID": {}, "deliveryCheckpoint": {}, "turns": {}, "blocks": {},
8 "block": {}, "strict": {}, "todos": {}, "budgetClass": {},
9 "turnsUsed": {}, "turnsLimit": {}, "tokensUsed": {}, "requestsUsed": {},
10 "workDurationMs": {},
11 "tokensLimit": {}, "noProgressTurns": {}, "noProgressLimit": {},
12 "lastContinuationReason": {}, "lastEvaluatorReason": {}, "stopCause": {},
13 "budgetExtensions": {}, "progressEvidence": {},
14 }
15
16 func goalStateUnknownFields(raw []byte) map[string]json.RawMessage {
17 var fields map[string]json.RawMessage
18 if json.Unmarshal(raw, &fields) != nil {
19 return nil
20 }
21 for key := range goalStateKnownFields {
22 delete(fields, key)
23 }
24 return cloneGoalStateExtra(fields)
25 }
26
27 func marshalGoalState(state goalState, extra map[string]json.RawMessage) ([]byte, error) {
28 known, err := json.Marshal(state)
29 if err != nil || len(extra) == 0 {
30 return known, err
31 }
32 var merged map[string]json.RawMessage
33 if err := json.Unmarshal(known, &merged); err != nil {
34 return nil, err
35 }
36 for key, value := range extra {
37 if _, current := goalStateKnownFields[key]; current {
38 continue
39 }
40 merged[key] = append(json.RawMessage(nil), value...)
41 }
42 return json.Marshal(merged)
43 }
44
45 func cloneGoalStateExtra(in map[string]json.RawMessage) map[string]json.RawMessage {
46 if len(in) == 0 {
47 return nil
48 }
49 out := make(map[string]json.RawMessage, len(in))
50 for key, value := range in {
51 out[key] = append(json.RawMessage(nil), value...)
52 }
53 return out
54 }
55
56 func (g *goalMachine) grantSpendSliceLocked(fresh bool) {
57 switch {
58 case g.tokenBudget <= 0:
59 g.tokensLimit = 0
60 case fresh || g.tokensLimit <= g.tokensUsed:
61 g.tokensLimit = g.tokensUsed + g.tokenBudget
62 }
63 }
64
65 // migrateRemovedGoalPause clears pauses produced by gates no longer enforced.
66 func (g *goalMachine) migrateRemovedGoalPause() bool {
67 if g.status != GoalStatusBlocked {
68 return false
69 }
70 switch g.stopCause {
71 case stopCauseBudgetTurns, stopCauseBudgetTokens, stopCauseGoalRunBudget, stopCauseGoalStuck, stopCauseNoProgress, stopCauseEvaluator, "final_readiness", "quality_readiness":
72 default:
73 return false
74 }
75 g.status = GoalStatusRunning
76 g.stopCause = ""
77 g.block = ""
78 return true
79 }
80
81 // normalizeContinuousState runs under g.mu while loading an active sidecar.
82 func (g *goalMachine) normalizeContinuousState(legacyMode GoalResearchMode, legacyTaskID string) bool {
83 if g.goal == "" {
84 return false
85 }
86 migrated := false
87 if g.budgetClass == "" {
88 g.budgetClass = budgetClassForLegacyMode(g.goal, legacyMode)
89 }
90 if legacyTaskID != "" {
91 g.budgetClass = budgetClassResearch
92 }
93 if g.turnsLimit != unlimitedGoalTurns {
94 g.turnsLimit, migrated = unlimitedGoalTurns, true
95 }
96 if g.noProgressLimit != 0 {
97 g.noProgressLimit, migrated = 0, true
98 }
99 if g.budgetExtensions != 0 {
100 g.budgetExtensions, migrated = 0, true
101 }
102 legacyNumericPause := false
103 switch g.stopCause {
104 case stopCauseBudgetTurns, stopCauseBudgetTokens, stopCauseGoalRunBudget, stopCauseGoalStuck, stopCauseNoProgress:
105 legacyNumericPause = true
106 }
107 if g.tokenBudget <= 0 {
108 if g.tokensLimit != 0 {
109 g.tokensLimit, migrated = 0, true
110 }
111 } else if legacyNumericPause || g.tokensLimit <= 0 {
112 g.tokensLimit, migrated = g.tokensUsed+g.tokenBudget, true
113 }
114 return g.migrateRemovedGoalPause() || migrated
115 }
116
116 lines GO