返回 DeepSeek-Reasonix
contract.go
根目录 / desktop / internal / hostrpc / contract.go
1 package hostrpc
2
3 import (
4 "bytes"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "io"
9 "maps"
10 "slices"
11 )
12
13 // ProtocolVersion is the wire revision both sides must agree on in hello.
14 const ProtocolVersion = 11
15
16 // Contract is everything the shell needs to call the service: the accepted
17 // commands, the event names it may receive and every DTO shape they use.
18 type Contract struct {
19 ProtocolVersion int `json:"protocolVersion"`
20 Commands []Command `json:"commands"`
21 Events []string `json:"events"`
22 Types map[string]ObjectType `json:"types"`
23 }
24
25 // Build freezes a registry plus the event names into a Contract. Events are
26 // sorted and deduplicated so the digest never depends on caller order.
27 func Build(r *Registry, events []string) Contract {
28 sorted := slices.Compact(slices.Sorted(slices.Values(events)))
29 if sorted == nil {
30 sorted = []string{}
31 }
32 types := maps.Clone(r.types)
33 if types == nil {
34 types = map[string]ObjectType{}
35 }
36 return Contract{
37 ProtocolVersion: ProtocolVersion,
38 Commands: r.Commands(),
39 Events: sorted,
40 Types: types,
41 }
42 }
43
44 // Canonical is the contract as sorted-key JSON without whitespace: the bytes
45 // the digest covers and the shell must reproduce bit for bit.
46 func (c Contract) Canonical() []byte {
47 raw, err := json.Marshal(c)
48 if err != nil {
49 panic("hostrpc: contract is not JSON-serialisable: " + err.Error())
50 }
51 var generic any
52 if err := json.Unmarshal(raw, &generic); err != nil {
53 panic("hostrpc: contract JSON does not round-trip: " + err.Error())
54 }
55 var buf bytes.Buffer
56 enc := json.NewEncoder(&buf)
57 enc.SetEscapeHTML(false)
58 if err := enc.Encode(generic); err != nil {
59 panic("hostrpc: canonical contract encode: " + err.Error())
60 }
61 return bytes.TrimRight(buf.Bytes(), "\n")
62 }
63
64 // Digest is "sha256:" plus the hex SHA-256 of Canonical.
65 func (c Contract) Digest() string {
66 sum := sha256.Sum256(c.Canonical())
67 return "sha256:" + hex.EncodeToString(sum[:])
68 }
69
70 // WriteJSON writes the canonical contract indented for review, ending in a
71 // newline. Key order matches Canonical so the two never disagree.
72 func WriteJSON(w io.Writer, c Contract) error {
73 var buf bytes.Buffer
74 if err := json.Indent(&buf, c.Canonical(), "", " "); err != nil {
75 return err
76 }
77 buf.WriteByte('\n')
78 _, err := w.Write(buf.Bytes())
79 return err
80 }
81
81 lines GO