返回 DeepSeek-Reasonix
compress.go
根目录 / internal / tool / builtin / compress.go
1 package builtin
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "reasonix/internal/tool"
10 )
11
12 const (
13 maxCompressAnchorBytes = 512
14 maxCompressFocusBytes = 2000
15 )
16
17 func init() { tool.RegisterBuiltin(compressContext{}) }
18
19 type compressContext struct{}
20
21 func (compressContext) Name() string { return "compress" }
22
23 func (compressContext) Description() string {
24 return "Compress a selected part of the current model-visible conversation without deleting visible history. Use only when the user explicitly asks for context compression. Choose `before` to summarize everything before the uniquely matched user turn while keeping that turn and later context, or `after` to summarize from that turn through the last completed turn while keeping the active turn. The anchor must be an exact, unique excerpt from a real user message; use a longer excerpt if the tool reports multiple matches."
25 }
26
27 func (compressContext) Schema() json.RawMessage {
28 return json.RawMessage(`{
29 "type":"object",
30 "additionalProperties":false,
31 "properties":{
32 "direction":{"type":"string","enum":["before","after"],"description":"Which side of the anchor user turn to compress."},
33 "anchor":{"type":"string","minLength":1,"maxLength":512,"description":"An exact, unique excerpt from one real user message in the current model-visible conversation."},
34 "focus":{"type":"string","maxLength":2000,"description":"Optional guidance about facts or decisions the summary must preserve."}
35 },
36 "required":["direction","anchor"]
37 }`)
38 }
39
40 // ReadOnly is true in the permission/workspace sense: compress changes only
41 // the owning Agent's context projection. The Agent batcher separately forces it
42 // into a serial lane because projection installation is stateful.
43 func (compressContext) ReadOnly() bool { return true }
44
45 func (compressContext) PlanModeSafe() bool { return true }
46
47 func (compressContext) Execute(ctx context.Context, args json.RawMessage) (string, error) {
48 var request struct {
49 Direction string `json:"direction"`
50 Anchor string `json:"anchor"`
51 Focus string `json:"focus"`
52 }
53 if err := json.Unmarshal(args, &request); err != nil {
54 return "", fmt.Errorf("invalid compress args: %w", err)
55 }
56 request.Direction = strings.TrimSpace(request.Direction)
57 request.Anchor = strings.TrimSpace(request.Anchor)
58 request.Focus = strings.TrimSpace(request.Focus)
59 if request.Direction != "before" && request.Direction != "after" {
60 return "", fmt.Errorf("compress: direction must be before or after")
61 }
62 if request.Anchor == "" {
63 return "", fmt.Errorf("compress: anchor must not be empty")
64 }
65 if len(request.Anchor) > maxCompressAnchorBytes {
66 return "", fmt.Errorf("compress: anchor exceeds %d bytes", maxCompressAnchorBytes)
67 }
68 if len(request.Focus) > maxCompressFocusBytes {
69 return "", fmt.Errorf("compress: focus exceeds %d bytes", maxCompressFocusBytes)
70 }
71 compressor, ok := tool.ContextCompressorFromContext(ctx)
72 if !ok {
73 return "", fmt.Errorf("compress is unavailable outside an active agent session")
74 }
75 result, err := compressor.CompressContext(ctx, tool.CompressRequest{
76 Direction: request.Direction,
77 Anchor: request.Anchor,
78 Focus: request.Focus,
79 })
80 if err != nil {
81 return "", err
82 }
83 out, err := json.Marshal(result)
84 if err != nil {
85 return "", fmt.Errorf("encode compress result: %w", err)
86 }
87 return string(out), nil
88 }
89
89 lines GO