返回 DeepSeek-Reasonix
model_settings_run_test.go
根目录 / internal / boot / model_settings_run_test.go
1 package boot
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "sync"
11 "testing"
12 "time"
13
14 "reasonix/internal/config"
15 "reasonix/internal/event"
16 "reasonix/internal/provider"
17 )
18
19 func TestModelSettingsChildCreatedAfterSaveInheritsAcceptedRunSnapshot(t *testing.T) {
20 isolateConfigHome(t)
21 root := robustTempDir(t)
22 t.Chdir(root)
23 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
24 defer cancel()
25 entered, release := make(chan struct{}), make(chan struct{})
26 var first, unblock sync.Once
27 t.Cleanup(func() { unblock.Do(func() { close(release) }) })
28 type observed struct{ model, key string }
29 requests := make(chan observed, 16)
30 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31 var request struct {
32 Model string `json:"model"`
33 Messages []struct {
34 Role string `json:"role"`
35 } `json:"messages"`
36 }
37 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
38 http.Error(w, "bad request", http.StatusBadRequest)
39 return
40 }
41 requests <- observed{request.Model, r.Header.Get("Authorization")}
42 first.Do(func() {
43 close(entered)
44 select {
45 case <-release:
46 case <-r.Context().Done():
47 }
48 })
49 w.Header().Set("Content-Type", "text/event-stream")
50 if request.Model == "parent" && request.Messages[len(request.Messages)-1].Role != "tool" {
51 fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"child-task\",\"type\":\"function\",\"function\":{\"name\":\"task\",\"arguments\":\"{\\\"prompt\\\":\\\"answer the delegated task\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}\n\ndata: [DONE]\n\n")
52 return
53 }
54 fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"content\":\"snapshot child answer\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n")
55 }))
56 defer server.Close()
57 if _, err := config.SetCredential("SNAPSHOT_CHILD_KEY", "old-key"); err != nil {
58 t.Fatal(err)
59 }
60 cfg := config.Default()
61 cfg.DefaultModel, cfg.Agent.SubagentModel = "p/parent", "p/old-child"
62 cfg.Providers = []config.ProviderEntry{{Name: "p", Kind: "openai", BaseURL: server.URL, Model: "parent", Models: []string{"parent", "old-child", "new-child"}, APIKeyEnv: "SNAPSHOT_CHILD_KEY"}}
63 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
64 t.Fatal(err)
65 }
66 old, err := Build(ctx, Options{WorkspaceRoot: root, Sink: event.Discard})
67 if err != nil {
68 t.Fatal(err)
69 }
70 defer old.Close()
71 done := make(chan error, 1)
72 go func() { done <- old.Run(ctx, "delegate this task") }()
73 select {
74 case <-entered:
75 case <-ctx.Done():
76 t.Fatal(ctx.Err())
77 }
78 cfg.Agent.SubagentModel = "p/new-child"
79 if _, err := config.SetCredential("SNAPSHOT_CHILD_KEY", "new-key"); err != nil {
80 t.Fatal(err)
81 }
82 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
83 t.Fatal(err)
84 }
85 unblock.Do(func() { close(release) })
86 select {
87 case err := <-done:
88 if err != nil {
89 t.Fatal(err)
90 }
91 case <-ctx.Done():
92 t.Fatal(ctx.Err())
93 }
94 assertRequests := func(child, key string) {
95 t.Helper()
96 for _, model := range []string{"parent", child, "parent"} {
97 select {
98 case got := <-requests:
99 if got.model != model || got.key != "Bearer "+key {
100 t.Fatalf("request used another snapshot: model=%s expected=%s keyMatches=%v", got.model, model, got.key == "Bearer "+key)
101 }
102 case <-ctx.Done():
103 t.Fatal(ctx.Err())
104 }
105 }
106 }
107 assertRequests("old-child", "old-key")
108 var childResult strings.Builder
109 for _, message := range old.History() {
110 if message.Role == provider.RoleTool {
111 childResult.WriteString(message.Content)
112 }
113 }
114 if !strings.Contains(childResult.String(), "snapshot child answer") {
115 t.Fatal("the real task tool did not finish its child")
116 }
117 next, err := Build(ctx, Options{WorkspaceRoot: root, Sink: event.Discard})
118 if err != nil {
119 t.Fatal(err)
120 }
121 defer next.Close()
122 if err := next.Run(ctx, "delegate the next task"); err != nil {
123 t.Fatal(err)
124 }
125 assertRequests("new-child", "new-key")
126 }
127
127 lines GO