返回 DeepSeek-Reasonix
read_result_envelope.go
根目录 / internal / agent / read_result_envelope.go
1 package agent
2
3 import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "fmt"
9 "strings"
10
11 "reasonix/internal/provider"
12 "reasonix/internal/tool"
13 )
14
15 func readWindowID(callID, resultRef, path string) string {
16 sum := sha256.Sum256([]byte(callID + "\x00" + resultRef + "\x00" + path))
17 return "read-" + hex.EncodeToString(sum[:8])
18 }
19
20 // readResultEnvelopeFor builds the host-only envelope for one reader result:
21 // the reader supplies what it delivered and which store served it, and the host
22 // adds call identity, the logical read identity, and clips the envelope to the
23 // provider-visible bytes. ok=false means the tool cannot describe its delivery,
24 // so the message carries no envelope rather than a fabricated one.
25 func (a *Agent) readResultEnvelopeFor(ctx context.Context, call provider.ToolCall, o toolOutcome) (tool.ReadResultEnvelope, bool) {
26 if a == nil || a.svc.tools == nil || o.errMsg != "" || o.blocked {
27 return tool.ReadResultEnvelope{}, false
28 }
29 resolved, _, ambiguous := a.svc.tools.ResolveCall(call.Name)
30 if resolved == nil || len(ambiguous) > 0 {
31 return tool.ReadResultEnvelope{}, false
32 }
33 reader, ok := resolved.(tool.ReadEnvelopeProvider)
34 if !ok {
35 return tool.ReadResultEnvelope{}, false
36 }
37 raw := o.rawOutput
38 if raw == "" {
39 raw = o.output
40 }
41 env, ok := reader.ReadEnvelope(ctx, json.RawMessage(call.Arguments), raw)
42 if o.readEnvelope != nil {
43 env, ok = *o.readEnvelope, true
44 if window, parsed := tool.ParseReadWindow(raw); (!parsed && len(env.DeliveredRanges) > 0) || (parsed && tool.WindowDigest(env.Source.CanonicalPath, window) != env.WindowDigest) {
45 // An extension changed source text after execution. It remains a tool
46 // result, but cannot inherit the reader's version or coverage proof.
47 env.Source.Snapshot = ""
48 env.DeliveredRanges, env.SourceEnd = nil, nil
49 env.EOF, env.HasMore = false, true
50 }
51 }
52 if !ok {
53 return tool.ReadResultEnvelope{}, false
54 }
55 if o.rawOutput != "" && o.rawOutput != o.output {
56 env = clipDeliveredRead(env, raw, o.output)
57 }
58 env.ResultRef = toolResultRef(call.ID, raw)
59 env.ReadID = o.readTaskID
60 if env.ReadID == "" {
61 env.ReadID = readWindowID(call.ID, env.ResultRef, env.Source.CanonicalPath)
62 }
63 env.Source.WorkspaceID = a.workspaceID
64 if cursor, ok := tool.DecodeReadCursor(env.NextCursor); ok {
65 cursor.ReadID = env.ReadID
66 if a.reads.tasks != nil {
67 cursor.SessionID = a.reads.tasks.sessionID
68 cursor.RunGen = a.reads.tasks.generation
69 cursor.Binding = a.reads.tasks.binding
70 }
71 env.NextCursor = tool.EncodeReadCursor(cursor)
72 }
73 a.reads.tasks.remember(env.ReadID, env, readPathArg(json.RawMessage(call.Arguments)))
74 return env, true
75 }
76
77 // Only complete, byte-identical numbered lines may become evidence. A cut in
78 // the middle of a numbered line is not a delivered source line.
79 func clipDeliveredRead(env tool.ReadResultEnvelope, raw, visible string) tool.ReadResultEnvelope {
80 original, ok := tool.ParseReadWindow(raw)
81 shown, shownOK := tool.ParseReadWindow(visible)
82 if !ok || !shownOK {
83 return env.ClipTo("")
84 }
85 var exact strings.Builder
86 for i, line := range shown.Lines {
87 number := shown.StartLine + i
88 index := number - original.StartLine
89 if index < 0 || index >= len(original.Lines) || line != original.Lines[index] {
90 break
91 }
92 fmt.Fprintf(&exact, "%d→%s\n", number, line)
93 }
94 return env.ClipTo(exact.String())
95 }
96
96 lines GO