返回 DeepSeek-Reasonix
task_profile_test.go
根目录 / internal / agent / task_profile_test.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8
9 "reasonix/internal/event"
10 "reasonix/internal/provider"
11 "reasonix/internal/tool"
12 )
13
14 func TestTaskSchemaIncludesProfileAndWritePaths(t *testing.T) {
15 task := NewTaskTool(&mockProvider{name: "sub"}, nil, tool.NewRegistry(), 20, 0, 0, 0, 0, 0, 0, 0.0, "", "sys", nil, 0, "", "", nil)
16 schema := string(task.Schema())
17 for _, want := range []string{`"profile"`, `"write_paths"`} {
18 if !strings.Contains(schema, want) {
19 t.Fatalf("schema missing %s", want)
20 }
21 }
22 // No dynamic profile enum.
23 if strings.Contains(schema, `"enum"`) {
24 t.Fatalf("profile names must not be enum'd in schema: %s", schema)
25 }
26 }
27
28 func TestTaskWriterWithoutPathsClaimsWholeWorkspace(t *testing.T) {
29 root := t.TempDir()
30 task := NewTaskTool(&mockProvider{name: "sub"}, nil, tool.NewRegistry(), 20, 0, 0, 0, 0, 0, 0, 0.0, "", "sys", nil, 0, "", "", nil).
31 WithTranscripts(mustSubagentStore(t), root, "base", "high").
32 WithScheduler(NewSubagentScheduler(6, 3))
33
34 spec, err := task.buildTaskSpec(context.Background(), "rewrite docs", "", "", nil, nil, 0, "", "", "", "", false, false)
35 if err != nil {
36 t.Fatal(err)
37 }
38 if !spec.WritePaths.WholeWorkspace || spec.WritePaths.WorkspaceRoot == "" {
39 t.Fatalf("writer without write_paths must claim the workspace, got %+v", spec.WritePaths)
40 }
41 }
42
43 func TestTaskUnknownProfileRejected(t *testing.T) {
44 root := t.TempDir()
45 task := NewTaskTool(&mockProvider{name: "sub"}, nil, tool.NewRegistry(), 20, 0, 0, 0, 0, 0, 0, 0.0, "", "sys", nil, 0, "", "", nil).
46 WithTranscripts(mustSubagentStore(t), root, "base", "high").
47 WithProfileLookup(func(string) (ProfileDefinition, bool) { return ProfileDefinition{}, false })
48 _, err := task.Execute(withCallContext(context.Background(), "c", event.Discard, nil, false),
49 json.RawMessage(`{"prompt":"x","profile":"nope"}`))
50 if err == nil || !strings.Contains(err.Error(), "unknown profile") {
51 t.Fatalf("err = %v", err)
52 }
53 }
54
55 func TestTaskProfileUsesBodyAsSystemPrompt(t *testing.T) {
56 root := t.TempDir()
57 var sawSystem string
58 prov := &captureSystemProvider{onReq: func(sys string) { sawSystem = sys }}
59 task := NewTaskTool(prov, nil, tool.NewRegistry(), 20, 0, 0, 0, 0, 0, 0, 0.0, "", DefaultTaskSystemPrompt, nil, 0, "", "", nil).
60 WithTranscripts(mustSubagentStore(t), root, "base", "high").
61 WithProfileLookup(func(name string) (ProfileDefinition, bool) {
62 if name != "doc-rewriter" {
63 return ProfileDefinition{}, false
64 }
65 return ProfileDefinition{Name: name, Body: "You rewrite docs carefully."}, true
66 })
67 _, err := task.Execute(withCallContext(context.Background(), "c", event.Discard, nil, false),
68 json.RawMessage(`{"prompt":"rewrite a.md","profile":"doc-rewriter"}`))
69 if err != nil {
70 t.Fatal(err)
71 }
72 if !strings.Contains(sawSystem, "You rewrite docs carefully.") {
73 t.Fatalf("system prompt = %q, want profile body", sawSystem)
74 }
75 if strings.Contains(sawSystem, "concise and self-contained") {
76 t.Fatalf("profile must not stack DefaultTaskSystemPrompt concise text: %q", sawSystem)
77 }
78 }
79
80 func TestTaskToolsIntersectionCannotExpand(t *testing.T) {
81 root := t.TempDir()
82 task := NewTaskTool(&mockProvider{name: "sub"}, nil, tool.NewRegistry(), 20, 0, 0, 0, 0, 0, 0, 0.0, "", "sys", nil, 0, "", "", nil).
83 WithTranscripts(mustSubagentStore(t), root, "base", "high").
84 WithProfileLookup(func(name string) (ProfileDefinition, bool) {
85 return ProfileDefinition{Name: name, Body: "body", AllowedTools: []string{"read_file"}}, true
86 })
87 _, err := task.Execute(withCallContext(context.Background(), "c", event.Discard, nil, false),
88 json.RawMessage(`{"prompt":"x","profile":"p","tools":["write_file"]}`))
89 if err == nil || !strings.Contains(err.Error(), "intersection") {
90 t.Fatalf("err = %v", err)
91 }
92 }
93
94 func TestTaskResolveProfilePrecedence(t *testing.T) {
95 task := NewTaskTool(&mockProvider{name: "sub"}, nil, tool.NewRegistry(), 20, 0, 0, 0, 0, 0, 0, 0.0, "", "sys", nil, 0, "global-m", "global-e", nil).
96 WithProfileLookup(func(name string) (ProfileDefinition, bool) {
97 return ProfileDefinition{Name: name, Body: "b", Model: "front-m", Effort: "front-e"}, true
98 }).
99 WithProfileConfigResolvers(
100 func(string) string { return "cfg-m" },
101 func(string) string { return "cfg-e" },
102 )
103 pr := task.ResolveProfile(json.RawMessage(`{"profile":"p","model":"call-m","effort":"call-e"}`))
104 if pr == nil || pr.Model != "cfg-m" || pr.Effort != "cfg-e" {
105 t.Fatalf("profile = %+v", pr)
106 }
107 }
108
109 // captureSystemProvider records the system prompt of the first request.
110 type captureSystemProvider struct {
111 onReq func(system string)
112 }
113
114 func (p *captureSystemProvider) Name() string { return "capture-sys" }
115
116 func (p *captureSystemProvider) Stream(_ context.Context, req provider.Request) (<-chan provider.Chunk, error) {
117 if p.onReq != nil {
118 for _, m := range req.Messages {
119 if m.Role == provider.RoleSystem {
120 p.onReq(m.Content)
121 break
122 }
123 }
124 }
125 ch := make(chan provider.Chunk, 1)
126 ch <- provider.Chunk{Type: provider.ChunkText, Text: "ok"}
127 close(ch)
128 return ch, nil
129 }
130
130 lines GO