返回 DeepSeek-Reasonix
goal_common.go
根目录 / internal / tool / builtin / goal_common.go
1 package builtin
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "strings"
9
10 goaldomain "reasonix/internal/goal"
11 "reasonix/internal/tool"
12 )
13
14 type goalToolValue struct {
15 Goal *goaldomain.Snapshot `json:"goal"`
16 Activation goaldomain.Activation `json:"activation,omitempty"`
17 StopReason string `json:"stopReason,omitempty"`
18 Instruction string `json:"instruction,omitempty"`
19 }
20
21 type optionalRoundLimit struct {
22 Present bool
23 Raw json.RawMessage
24 }
25
26 func (o *optionalRoundLimit) UnmarshalJSON(data []byte) error {
27 o.Present = true
28 o.Raw = append(o.Raw[:0], data...)
29 return nil
30 }
31
32 func goalToolResult(view *goaldomain.View) (string, error) {
33 return goalToolResultWithInstruction(view, "")
34 }
35
36 func goalToolResultWithInstruction(view *goaldomain.View, instruction string) (string, error) {
37 value := goalToolValue{}
38 if view != nil {
39 snapshot := view.Snapshot
40 value.Goal = &snapshot
41 value.Activation = view.Activation
42 value.StopReason = view.StopReason
43 }
44 value.Instruction = instruction
45 encoded, err := json.Marshal(value)
46 if err != nil {
47 return "", fmt.Errorf("encode goal result: %w", err)
48 }
49 return string(encoded), nil
50 }
51
52 func goalBinding(ctx context.Context) (tool.GoalLifecycleBinding, error) {
53 binding, ok := tool.GoalLifecycleFromContext(ctx)
54 if !ok {
55 return tool.GoalLifecycleBinding{}, fmt.Errorf("goal tool requires a current host-attested goal context")
56 }
57 return binding, nil
58 }
59
60 func goalToolError(operation string, err error) error {
61 if err == nil {
62 return nil
63 }
64 if code := goaldomain.ErrorCodeOf(err); code != "" {
65 return fmt.Errorf("%s [%s]: %w", operation, code, err)
66 }
67 return fmt.Errorf("%s: %w", operation, err)
68 }
69
70 func decodeGoalArgs(args json.RawMessage, target any, toolName string) error {
71 if len(bytes.TrimSpace(args)) == 0 {
72 args = json.RawMessage(`{}`)
73 }
74 decoder := json.NewDecoder(bytes.NewReader(args))
75 decoder.DisallowUnknownFields()
76 if err := decoder.Decode(target); err != nil {
77 return fmt.Errorf("invalid %s arguments: %w", toolName, err)
78 }
79 if err := ensureJSONEnd(decoder); err != nil {
80 return fmt.Errorf("invalid %s arguments: %w", toolName, err)
81 }
82 return nil
83 }
84
85 func parseRoundLimit(raw json.RawMessage) (*uint64, error) {
86 if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
87 return nil, nil
88 }
89 var value uint64
90 if err := json.Unmarshal(raw, &value); err != nil || value == 0 {
91 return nil, fmt.Errorf("max_goal_rounds must be null or a positive integer")
92 }
93 return &value, nil
94 }
95
96 func trimmedRequired(value, field string) (string, error) {
97 value = strings.TrimSpace(value)
98 if value == "" {
99 return "", fmt.Errorf("%s is required and must be non-empty", field)
100 }
101 return value, nil
102 }
103
103 lines GO