| 1 | package agent |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "testing" |
| 8 | |
| 9 | "reasonix/internal/event" |
| 10 | "reasonix/internal/evidence" |
| 11 | "reasonix/internal/provider" |
| 12 | "reasonix/internal/tool" |
| 13 | ) |
| 14 | |
| 15 | func TestFactDrivenModesShareExecutorSchemaBytes(t *testing.T) { |
| 16 | prov := &mockProvider{name: "p", chunks: []provider.Chunk{ |
| 17 | {Type: provider.ChunkText, Text: "ok"}, |
| 18 | {Type: provider.ChunkDone}, |
| 19 | }} |
| 20 | reg := tool.NewRegistry() |
| 21 | reg.Add(fakeReadFileTool{}) |
| 22 | reg.Add(fakeWriterTool{}) |
| 23 | a := New(prov, reg, NewSession("STABLE-SYS"), Options{}, event.Discard) |
| 24 | |
| 25 | capture := func(ctx context.Context, input string) (system, schemas string) { |
| 26 | t.Helper() |
| 27 | prov.chunks = []provider.Chunk{{Type: provider.ChunkText, Text: "ok"}, {Type: provider.ChunkDone}} |
| 28 | err := a.Run(ctx, input) |
| 29 | if err != nil { |
| 30 | var readiness *FinalReadinessError |
| 31 | if !errors.As(err, &readiness) { |
| 32 | t.Fatalf("Run(%q): %v", input, err) |
| 33 | } |
| 34 | } |
| 35 | if len(prov.lastReq.Messages) == 0 || prov.lastReq.Messages[0].Role != provider.RoleSystem { |
| 36 | t.Fatalf("missing system prefix for %q", input) |
| 37 | } |
| 38 | return prov.lastReq.Messages[0].Content, serializeToolSchemas(t, prov.lastReq.Tools) |
| 39 | } |
| 40 | |
| 41 | ordinarySys, ordinaryTools := capture(context.Background(), "解释 OAuth token") |
| 42 | |
| 43 | a.SetPlanMode(true) |
| 44 | planSys, planTools := capture(context.Background(), "先规划这个认证迁移,不要执行") |
| 45 | a.SetPlanMode(false) |
| 46 | |
| 47 | goalCtx := WithDeliveryExecutionScope(context.Background(), DeliveryExecutionScope{ |
| 48 | ID: "goal-cache", TaskText: "ship the parser", |
| 49 | }) |
| 50 | goalSys, goalTools := capture(goalCtx, "ship the parser") |
| 51 | |
| 52 | a.task.ledger.Record(evidence.Receipt{ |
| 53 | ToolName: "edit_file", Success: true, Write: true, Mutation: true, |
| 54 | Args: json.RawMessage(`{"path":"internal/agent/agent.go"}`), Paths: []string{"internal/agent/agent.go"}, |
| 55 | }) |
| 56 | afterSys, afterTools := capture(goalCtx, "continue") |
| 57 | |
| 58 | if ordinarySys != planSys || ordinarySys != goalSys || ordinarySys != afterSys { |
| 59 | t.Fatalf("executor system prefix drifted:\nordinary=%q\nplan=%q\ngoal=%q\nafter=%q", |
| 60 | ordinarySys, planSys, goalSys, afterSys) |
| 61 | } |
| 62 | if ordinaryTools != planTools || ordinaryTools != goalTools || ordinaryTools != afterTools { |
| 63 | t.Fatalf("tool schemas drifted across ordinary/plan/goal/obligation") |
| 64 | } |
| 65 | } |
| 66 |