返回 DeepSeek-Reasonix
test_support_test.go
根目录 / internal / agent / test_support_test.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "strings"
8 "sync"
9 "testing"
10
11 "reasonix/internal/event"
12 "reasonix/internal/evidence"
13 "reasonix/internal/provider"
14 "reasonix/internal/tool"
15 )
16
17 // readProbe is a small read-only tool used by budget and lifecycle tests.
18 type readProbe struct{}
19
20 func (readProbe) Name() string { return "read_file" }
21 func (readProbe) Description() string { return "read a file" }
22 func (readProbe) Schema() json.RawMessage {
23 return json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}`)
24 }
25 func (readProbe) ReadOnly() bool { return true }
26 func (readProbe) Execute(context.Context, json.RawMessage) (string, error) {
27 return "package main\n\nfunc main() {}\n", nil
28 }
29
30 type recoveryArgumentTool struct {
31 name string
32 schema json.RawMessage
33 mu sync.Mutex
34 inputs []string
35 }
36
37 func (t *recoveryArgumentTool) Name() string { return t.name }
38 func (t *recoveryArgumentTool) Description() string { return "argument fixture" }
39 func (t *recoveryArgumentTool) Schema() json.RawMessage { return t.schema }
40 func (t *recoveryArgumentTool) ReadOnly() bool { return true }
41 func (t *recoveryArgumentTool) Execute(_ context.Context, args json.RawMessage) (string, error) {
42 t.mu.Lock()
43 defer t.mu.Unlock()
44 t.inputs = append(t.inputs, string(args))
45 return "executed", nil
46 }
47
48 type readinessAuditSink struct{ events []evidence.ReadinessAudit }
49
50 func (*readinessAuditSink) Emit(event.Event) {}
51 func (s *readinessAuditSink) RecordReadinessAudit(a evidence.ReadinessAudit) {
52 s.events = append(s.events, a)
53 }
54
55 type okTool struct{ name string }
56
57 func (t okTool) Name() string { return t.name }
58 func (okTool) Description() string { return "ok" }
59 func (okTool) ReadOnly() bool { return true }
60 func (okTool) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
61 func (okTool) Execute(context.Context, json.RawMessage) (string, error) { return "ok", nil }
62
63 type failTool struct{ name string }
64
65 func (t failTool) Name() string { return t.name }
66 func (failTool) Description() string { return "fail" }
67 func (failTool) ReadOnly() bool { return true }
68 func (failTool) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
69 func (failTool) Execute(context.Context, json.RawMessage) (string, error) {
70 return "", errors.New("boom")
71 }
72
73 func stripReceiptCitation(result string) string { return result }
74
75 type fakeReadFileTool struct{}
76
77 func (fakeReadFileTool) Name() string { return "read_file" }
78 func (fakeReadFileTool) Description() string { return "fake read" }
79 func (fakeReadFileTool) ReadOnly() bool { return true }
80 func (fakeReadFileTool) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
81 func (fakeReadFileTool) Execute(context.Context, json.RawMessage) (string, error) {
82 return "contents", nil
83 }
84
85 type fakeWriterTool struct{}
86
87 func (fakeWriterTool) Name() string { return "fake_write" }
88 func (fakeWriterTool) Description() string { return "fake write" }
89 func (fakeWriterTool) ReadOnly() bool { return false }
90 func (fakeWriterTool) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
91 func (fakeWriterTool) Execute(context.Context, json.RawMessage) (string, error) {
92 return "wrote", nil
93 }
94
95 type scriptedProvider struct {
96 name string
97 turns [][]provider.Chunk
98 call int
99 requests []provider.Request
100 }
101
102 func (s *scriptedProvider) Name() string { return s.name }
103
104 func (s *scriptedProvider) Stream(_ context.Context, req provider.Request) (<-chan provider.Chunk, error) {
105 s.requests = append(s.requests, req)
106 i := s.call
107 if i >= len(s.turns) {
108 i = len(s.turns) - 1
109 }
110 s.call++
111 ch := make(chan provider.Chunk, len(s.turns[i]))
112 for _, chunk := range s.turns[i] {
113 ch <- chunk
114 }
115 close(ch)
116 return ch, nil
117 }
118
119 func toolCallChunk(id, name, args string) provider.Chunk {
120 return provider.Chunk{Type: provider.ChunkToolCall, ToolCall: &provider.ToolCall{ID: id, Name: name, Arguments: args}}
121 }
122
123 func toolResult(s *Session, name string) string {
124 for _, message := range s.Messages {
125 if message.Role == provider.RoleTool && message.Name == name {
126 return message.Content
127 }
128 }
129 return ""
130 }
131
132 func lastToolResult(s *Session, name string) string {
133 var result string
134 for _, message := range s.Messages {
135 if message.Role == provider.RoleTool && message.Name == name {
136 result = message.Content
137 }
138 }
139 return result
140 }
141
142 func toolResultByID(s *Session, id string) string {
143 for _, message := range s.Messages {
144 if message.Role == provider.RoleTool && message.ToolCallID == id {
145 return message.Content
146 }
147 }
148 return ""
149 }
150
151 func sessionHasUserMessageContaining(s *Session, needle string) bool {
152 for _, message := range s.Messages {
153 if message.Role == provider.RoleUser && strings.Contains(message.Content, needle) {
154 return true
155 }
156 }
157 return false
158 }
159
160 func readinessLedger(receipts ...evidence.Receipt) *evidence.Ledger {
161 ledger := evidence.NewLedger()
162 for _, receipt := range receipts {
163 ledger.Record(receipt)
164 }
165 return ledger
166 }
167
168 func mustBuiltinTool(t *testing.T, name string) tool.Tool {
169 t.Helper()
170 value, ok := tool.LookupBuiltin(name)
171 if !ok {
172 t.Fatalf("missing builtin %q", name)
173 }
174 return value
175 }
176
177 type stubBash struct{}
178
179 func (stubBash) Name() string { return "bash" }
180 func (stubBash) Description() string { return "stub bash" }
181 func (stubBash) ReadOnly() bool { return false }
182 func (stubBash) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
183 func (stubBash) Execute(context.Context, json.RawMessage) (string, error) { return "ok", nil }
184
185 type stubWrite struct{}
186
187 func (stubWrite) Name() string { return "write_file" }
188 func (stubWrite) Description() string { return "stub write" }
189 func (stubWrite) ReadOnly() bool { return false }
190 func (stubWrite) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
191 func (stubWrite) Execute(context.Context, json.RawMessage) (string, error) { return "wrote", nil }
192
193 func evidenceRegistry() *tool.Registry {
194 reg := tool.NewRegistry()
195 if todo, ok := tool.LookupBuiltin("todo_write"); ok {
196 reg.Add(todo)
197 }
198 reg.Add(stubBash{})
199 reg.Add(stubWrite{})
200 return reg
201 }
202
202 lines GO