返回 DeepSeek-Reasonix
planner_route.go
根目录 / internal / agent / planner_route.go
1 package agent
2
3 import (
4 "context"
5 "strings"
6 )
7
8 // PlannerRoute describes how a two-model turn should flow. It is deliberately
9 // separate from explicit Plan Mode: ExecutorOnly lets the ordinary executor
10 // handle that host-owned workflow without invoking a second planner.
11 type PlannerRoute string
12
13 const (
14 PlannerRouteExecutorOnly PlannerRoute = "executor_only"
15 PlannerRoutePlanAndExecute PlannerRoute = "plan_and_execute"
16 PlannerRoutePlanForApproval PlannerRoute = "plan_for_approval"
17 PlannerRoutePlanOnly PlannerRoute = "plan_only"
18 )
19
20 // PlannerDepth controls the planning contract and research budget. None is only
21 // valid for ExecutorOnly.
22 type PlannerDepth string
23
24 const (
25 PlannerDepthNone PlannerDepth = "none"
26 PlannerDepthLight PlannerDepth = "light"
27 PlannerDepthFull PlannerDepth = "full"
28 )
29
30 // PlannerDecision is the deterministic, host-owned routing result for one turn.
31 // Reason is an opaque privacy-safe code for diagnostics; user text never belongs
32 // in it. MaxResearchRounds bounds planner tool-call rounds for this turn only.
33 type PlannerDecision struct {
34 Route PlannerRoute
35 Depth PlannerDepth
36 Reason string
37 MaxResearchRounds int
38 }
39
40 // PlannerPolicy makes one deterministic routing decision from trusted turn
41 // context plus the composed model input.
42 type PlannerPolicy func(context.Context, string) PlannerDecision
43
44 func normalizePlannerDecision(d PlannerDecision) PlannerDecision {
45 switch d.Route {
46 case PlannerRouteExecutorOnly:
47 d.Depth = PlannerDepthNone
48 d.MaxResearchRounds = 0
49 case PlannerRoutePlanAndExecute, PlannerRoutePlanForApproval, PlannerRoutePlanOnly:
50 if d.Depth != PlannerDepthLight && d.Depth != PlannerDepthFull {
51 d.Depth = PlannerDepthFull
52 }
53 if d.MaxResearchRounds < 0 {
54 d.MaxResearchRounds = 0
55 }
56 default:
57 d.Route = PlannerRoutePlanAndExecute
58 d.Depth = PlannerDepthFull
59 d.MaxResearchRounds = 0
60 }
61 d.Reason = strings.TrimSpace(d.Reason)
62 if d.Reason == "" {
63 d.Reason = "default"
64 }
65 return d
66 }
67
68 type runStepLimit struct {
69 steps int
70 key string
71 }
72
73 type runStepLimitContextKey struct{}
74
75 func withRunStepLimit(ctx context.Context, steps int, key string) context.Context {
76 if steps <= 0 {
77 return ctx
78 }
79 return context.WithValue(ctx, runStepLimitContextKey{}, runStepLimit{
80 steps: steps,
81 key: strings.TrimSpace(key),
82 })
83 }
84
85 func runStepLimitFromContext(ctx context.Context) (runStepLimit, bool) {
86 if ctx == nil {
87 return runStepLimit{}, false
88 }
89 limit, ok := ctx.Value(runStepLimitContextKey{}).(runStepLimit)
90 return limit, ok && limit.steps > 0
91 }
92
92 lines GO