返回 DeepSeek-Reasonix
operation_diagnostic.go
根目录 / internal / tool / operation_diagnostic.go
1 package tool
2
3 import (
4 "encoding/json"
5 "fmt"
6 )
7
8 // OperationDiagnostic is content-free, host-only recovery metadata. It must
9 // not be used as an authorization or reconstructed from model-written text.
10 // AllowedRecovery and RetryBudget make a rejection machine-executable: the
11 // model picks an offered action instead of guessing another wording of the
12 // same call, and an exhausted budget means the operation belongs to the user.
13 type OperationDiagnostic struct {
14 Code string `json:"code"`
15 Path string `json:"path,omitempty"`
16 OperationID string `json:"operation_id,omitempty"`
17 ExpectedSnapshot string `json:"expected_snapshot,omitempty"`
18 ActualSnapshot string `json:"actual_snapshot,omitempty"`
19 RequiredRanges []ReadRange `json:"required_ranges,omitempty"`
20 Recovery string `json:"recovery"`
21 // AvailableReceipts are host-issued IDs the model may cite right now.
22 AvailableReceipts []string `json:"available_receipts,omitempty"`
23 // AllowedRecovery is the closed set of actions the host will accept, e.g.
24 // "use_receipt:r_123", "reread_target", "run_verifier", "mark_manual".
25 AllowedRecovery []string `json:"allowed_recovery,omitempty"`
26 Retryable bool `json:"retryable,omitempty"`
27 RetryBudget int `json:"retry_budget,omitempty"`
28 // State is the operation's host lifecycle state after this failure.
29 State string `json:"state,omitempty"`
30 }
31
32 const (
33 FSNotObserved = "FS_NOT_OBSERVED"
34 FSStaleVersion = "FS_STALE_VERSION"
35 FSNotFound = "FS_NOT_FOUND"
36 FSAlreadyExists = "FS_ALREADY_EXISTS"
37 ReadPartial = "READ_PARTIAL"
38 ReadCursorInvalid = "READ_CURSOR_INVALID"
39 ReadSourceChanged = "READ_SOURCE_CHANGED"
40 ReadHardStop = "READ_HARD_STOP"
41 WriteEvidenceMissing = "WRITE_EVIDENCE_MISSING"
42 WriteEvidenceStale = "WRITE_EVIDENCE_STALE"
43 WriteTargetAbsent = "WRITE_TARGET_ABSENT"
44 WriteTargetAmbiguous = "WRITE_TARGET_AMBIGUOUS"
45 // OperationNeedsUser is the terminal code for an operation the host stopped
46 // automating after the same failure twice.
47 OperationNeedsUser = "OPERATION_NEEDS_USER"
48 // VerificationReceiptMissing rejects a completion citing verification the
49 // host has no successful receipt for.
50 VerificationReceiptMissing = "VERIFICATION_RECEIPT_MISSING"
51 // VerificationReceiptMismatch rejects a real receipt that does not cover
52 // the operation being signed off.
53 VerificationReceiptMismatch = "VERIFICATION_RECEIPT_MISMATCH"
54 )
55
56 // Recovery actions the host offers. They are identifiers, not prose, so the
57 // model selects rather than composes.
58 const (
59 RecoveryRereadTarget = "reread_target"
60 RecoveryRunVerifier = "run_verifier"
61 RecoveryMarkManual = "mark_manual"
62 RecoveryAbandonEdit = "abandon_edit"
63 RecoveryUseReceipt = "use_receipt:"
64 )
65
66 // ModelFacing renders the closed set of recovery choices as compact JSON. The
67 // model selects an action from allowed_recovery; it never has to reconstruct a
68 // command string or guess which wording the host will accept.
69 func (d OperationDiagnostic) ModelFacing() string {
70 if len(d.AllowedRecovery) == 0 && len(d.AvailableReceipts) == 0 {
71 return ""
72 }
73 payload := struct {
74 Code string `json:"code"`
75 OperationID string `json:"operation_id,omitempty"`
76 Path string `json:"path,omitempty"`
77 State string `json:"state,omitempty"`
78 AvailableReceipts []string `json:"available_receipts,omitempty"`
79 AllowedRecovery []string `json:"allowed_recovery,omitempty"`
80 Retryable bool `json:"retryable"`
81 RetryBudget int `json:"retry_budget"`
82 }{d.Code, d.OperationID, d.Path, d.State, d.AvailableReceipts, d.AllowedRecovery, d.Retryable, d.RetryBudget}
83 encoded, err := json.Marshal(payload)
84 if err != nil {
85 return ""
86 }
87 return "recovery: " + string(encoded)
88 }
89
90 type OperationError struct {
91 Diagnostic OperationDiagnostic
92 Cause error
93 }
94
95 func (e *OperationError) Error() string {
96 return fmt.Sprintf("%s: %v; %s", e.Diagnostic.Code, e.Cause, e.Diagnostic.Recovery)
97 }
98 func (e *OperationError) Unwrap() error { return e.Cause }
99
99 lines GO