返回 DeepSeek-Reasonix
intercept.go
根目录 / internal / extension / intercept.go
1 package extension
2
3 import (
4 "fmt"
5 "sort"
6 )
7
8 // InterceptorPoint names a hook point inside the runtime where interceptors
9 // observe or rewrite flow. The point of a KindInterceptor contribution is its
10 // ID, which keeps grouping and validation in one place.
11 type InterceptorPoint string
12
13 const (
14 PointSessionStart InterceptorPoint = "session.start"
15 PointSessionEnd InterceptorPoint = "session.end"
16 PointSessionLoad InterceptorPoint = "session.load"
17 PointSessionSave InterceptorPoint = "session.save"
18 PointSessionRotate InterceptorPoint = "session.rotate"
19 PointInputReceive InterceptorPoint = "input.receive"
20 PointAgentBeforeStart InterceptorPoint = "agent.before_start"
21 PointSystemPromptBuild InterceptorPoint = "system_prompt.build"
22 PointContextPrepare InterceptorPoint = "context.prepare"
23 PointProviderRequest InterceptorPoint = "provider.request"
24 PointProviderResponse InterceptorPoint = "provider.response"
25 PointToolBefore InterceptorPoint = "tool.before"
26 PointToolAfter InterceptorPoint = "tool.after"
27 PointPermissionDecision InterceptorPoint = "permission.decision"
28 PointCompactionPrepare InterceptorPoint = "compaction.prepare"
29 PointCompactionComplete InterceptorPoint = "compaction.complete"
30 PointFrontendEvent InterceptorPoint = "frontend.event"
31 )
32
33 // knownInterceptorPoint reports whether p is a declared point. Unknown points
34 // are rejected at validation: an interceptor registered at a misspelled point
35 // would sit in the chain unused and its author would never notice.
36 func knownInterceptorPoint(p InterceptorPoint) bool {
37 switch p {
38 case PointSessionStart, PointSessionEnd, PointSessionLoad, PointSessionSave,
39 PointSessionRotate, PointInputReceive, PointAgentBeforeStart,
40 PointSystemPromptBuild, PointContextPrepare, PointProviderRequest,
41 PointProviderResponse, PointToolBefore, PointToolAfter,
42 PointPermissionDecision, PointCompactionPrepare, PointCompactionComplete,
43 PointFrontendEvent:
44 return true
45 default:
46 return false
47 }
48 }
49
50 // Interceptor priority bounds. The range is wide enough for layering
51 // (security gates very early, observers very late) and narrow enough that a
52 // runaway value is certainly a bug.
53 const (
54 MinInterceptorPriority = -1000
55 MaxInterceptorPriority = 1000
56 )
57
58 // ValidatePriority bounds interceptor priorities. Unbounded priorities would
59 // let one contributor squeeze ahead of every future interceptor, including
60 // ones the runtime itself needs to run first.
61 func ValidatePriority(p int) error {
62 if p < MinInterceptorPriority || p > MaxInterceptorPriority {
63 return fmt.Errorf("extension: interceptor priority %d out of range [%d, %d]", p, MinInterceptorPriority, MaxInterceptorPriority)
64 }
65 return nil
66 }
67
68 // SortInterceptors orders an interceptor chain for execution: ascending
69 // priority (lower runs earlier), then plugin ID so one package's chain is
70 // contiguous, then per-contributor registration order. The final ID/Origin
71 // tiebreaks only fire when every specified key ties — they keep the result
72 // independent of contributor registration order, which callers permute
73 // freely. The input slice is not mutated; a sorted copy is returned.
74 func SortInterceptors(cs []Contribution) []Contribution {
75 out := make([]Contribution, len(cs))
76 copy(out, cs)
77 sort.SliceStable(out, func(i, j int) bool {
78 a, b := out[i], out[j]
79 if a.Priority != b.Priority {
80 return a.Priority < b.Priority
81 }
82 if a.Source.PluginID != b.Source.PluginID {
83 return a.Source.PluginID < b.Source.PluginID
84 }
85 if a.Order != b.Order {
86 return a.Order < b.Order
87 }
88 if a.ID != b.ID {
89 return a.ID < b.ID
90 }
91 return a.Source.Origin < b.Source.Origin
92 })
93 return out
94 }
95
95 lines GO