返回 DeepSeek-Reasonix
subagent_result_test.go
根目录 / internal / agent / subagent_result_test.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8 "unicode/utf8"
9
10 "reasonix/internal/provider"
11 "reasonix/internal/tool"
12 )
13
14 func TestSubagentResultToolContractIsStableAndReadOnly(t *testing.T) {
15 reader := NewSubagentResultTool(nil)
16 if reader.Name() != "read_subagent_result" || !reader.ReadOnly() || !reader.PlanModeSafe() {
17 t.Fatalf("reader contract = name %q readOnly=%v planSafe=%v", reader.Name(), reader.ReadOnly(), reader.PlanModeSafe())
18 }
19 if !json.Valid(reader.Schema()) {
20 t.Fatalf("reader schema is invalid: %s", reader.Schema())
21 }
22 canonical := provider.CanonicalizeSchema(reader.Schema())
23 if got := string(provider.CanonicalizeSchema(canonical)); got != string(canonical) {
24 t.Fatalf("reader schema canonicalization is not stable:\n%s", canonical)
25 }
26 }
27
28 func TestSplitSubagentRunResultDoesNotRewriteEphemeralAnswer(t *testing.T) {
29 ephemeral := "Research notes\n\nFinal answer:\nkeep this heading and everything before it"
30 if answer, ref := splitSubagentRunResult(ephemeral); answer != ephemeral || ref != "" {
31 t.Fatalf("ephemeral answer was treated as a persisted envelope: answer=%q ref=%q", answer, ref)
32 }
33 persisted := "Subagent reference: sa_test\n\nFinal answer:\nanswer only"
34 if answer, ref := splitSubagentRunResult(persisted); answer != "answer only" || ref != "sa_test" {
35 t.Fatalf("persisted envelope was not split: answer=%q ref=%q", answer, ref)
36 }
37 }
38
39 func TestSubagentResultToolPagesOnUTF8Boundaries(t *testing.T) {
40 workspace := t.TempDir()
41 store := NewSubagentStore(t.TempDir())
42 run, err := store.PrepareFresh(SubagentSpec{
43 Kind: "task",
44 Name: "task",
45 WorkspaceRoot: workspace,
46 ParentSession: "parent-session",
47 SystemPrompt: "sys",
48 Registry: tool.NewRegistry(),
49 })
50 if err != nil {
51 t.Fatalf("PrepareFresh: %v", err)
52 }
53 answer := "BEGIN\n" + strings.Repeat("中", 10_000) + "\nEND"
54 run.Session.Add(provider.Message{Role: provider.RoleUser, Content: "research"})
55 run.Session.Add(provider.Message{Role: provider.RoleAssistant, Content: answer})
56 if err := store.SaveCompleted(run); err != nil {
57 run.Release()
58 t.Fatalf("SaveCompleted: %v", err)
59 }
60 run.Release()
61
62 reader := &SubagentResultTool{store: store, workspaceRoot: workspace}
63 ctx := WithParentSession(context.Background(), "parent-session")
64 first, err := reader.Execute(ctx, json.RawMessage(`{"ref":"`+run.Ref+`","limit_bytes":10001}`))
65 if err != nil {
66 t.Fatalf("first page: %v", err)
67 }
68 if !utf8.ValidString(first) || !strings.Contains(first, "offset_bytes=9999") || !strings.Contains(first, "More remains") {
69 t.Fatalf("first page did not end on the expected UTF-8 boundary:\n%s", first[max(0, len(first)-240):])
70 }
71 second, err := reader.Execute(ctx, json.RawMessage(`{"ref":"`+run.Ref+`","offset_bytes":9999,"limit_bytes":24576}`))
72 if err != nil {
73 t.Fatalf("second page: %v", err)
74 }
75 if !utf8.ValidString(second) || !strings.Contains(second, "END") || !strings.Contains(second, "End of subagent result") {
76 t.Fatalf("second page did not finish the answer: %s", second[max(0, len(second)-240):])
77 }
78 if _, err := reader.Execute(ctx, json.RawMessage(`{"ref":"`+run.Ref+`","offset_bytes":10000}`)); err == nil || !strings.Contains(err.Error(), "UTF-8 character boundary") {
79 t.Fatalf("reader accepted a split UTF-8 offset: %v", err)
80 }
81 }
82
82 lines GO