返回 DeepSeek-Reasonix
generate.go
根目录 / internal / extension / protocolgen / generate.go
1 // Package protocolgen generates every committed Extension protocol artifact
2 // from the frozen Go wire registry and its canonical JSON Schema document.
3 package protocolgen
4
5 import (
6 "bytes"
7 "crypto/sha256"
8 "encoding/hex"
9 "encoding/json"
10 "fmt"
11 "go/format"
12 "os"
13 "path/filepath"
14 "strings"
15
16 "reasonix/internal/extension/protocol"
17 )
18
19 const (
20 SchemaArtifactPath = "internal/extension/protocol/schema.generated.json"
21 HashArtifactPath = "internal/extension/protocol/schema_hash.generated.go"
22 MarkdownArtifactPath = "docs/EXTENSION_PROTOCOL.generated.md"
23 // SDKTypesArtifactPath is the Go DTO mirror compiled into the stdlib-only
24 // extension SDK module.
25 SDKTypesArtifactPath = "sdk/go/types_generated.go"
26 )
27
28 // Artifact is one deterministic generated file, relative to the repository
29 // root. Artifacts are always returned in the stable order declared above.
30 type Artifact struct {
31 Path string
32 Data []byte
33 }
34
35 // Generate builds all Extension protocol artifacts without reading the
36 // committed outputs. The schema hash is calculated from the exact JSON bytes
37 // returned as the schema artifact.
38 func Generate() ([]Artifact, error) {
39 if err := protocol.ValidateRegistry(); err != nil {
40 return nil, fmt.Errorf("validate registry: %w", err)
41 }
42 document, err := protocol.BuildSchemaDocument()
43 if err != nil {
44 return nil, fmt.Errorf("build schema: %w", err)
45 }
46 canonical, err := protocol.CanonicalSchemaBytes()
47 if err != nil {
48 return nil, fmt.Errorf("canonical schema: %w", err)
49 }
50 encoded, err := json.Marshal(document)
51 if err != nil {
52 return nil, fmt.Errorf("marshal schema document: %w", err)
53 }
54 if !bytes.Equal(canonical, encoded) {
55 return nil, fmt.Errorf("canonical schema bytes do not match BuildSchemaDocument")
56 }
57
58 digest := sha256.Sum256(canonical)
59 schemaHash := "sha256:" + hex.EncodeToString(digest[:])
60 hashSource, err := generateSchemaHashGo(schemaHash)
61 if err != nil {
62 return nil, err
63 }
64 markdown, err := generateMarkdown(schemaHash)
65 if err != nil {
66 return nil, fmt.Errorf("generate markdown: %w", err)
67 }
68 sdkTypes, err := generateSDKTypesGo()
69 if err != nil {
70 return nil, fmt.Errorf("generate sdk types: %w", err)
71 }
72
73 return []Artifact{
74 {Path: SchemaArtifactPath, Data: append([]byte(nil), canonical...)},
75 {Path: HashArtifactPath, Data: hashSource},
76 {Path: MarkdownArtifactPath, Data: markdown},
77 {Path: SDKTypesArtifactPath, Data: sdkTypes},
78 }, nil
79 }
80
81 func generateSchemaHashGo(schemaHash string) ([]byte, error) {
82 source := fmt.Sprintf(`// Code generated by cmd/extension-protocol-gen; DO NOT EDIT.
83
84 package protocol
85
86 // GeneratedSchemaHash is the SHA-256 of schema.generated.json. Handshake
87 // comparisons use this constant; protocol tests independently recompute it
88 // from CanonicalSchemaBytes to reject stale generated artifacts.
89 const GeneratedSchemaHash = %q
90 `, schemaHash)
91 formatted, err := format.Source([]byte(source))
92 if err != nil {
93 return nil, fmt.Errorf("format schema hash source: %w", err)
94 }
95 return formatted, nil
96 }
97
98 // generateMarkdown renders the generated method/event/limits/error index.
99 // Hand-written prose documentation lives elsewhere; this document is the
100 // machine-frozen contract summary and always carries the schema hash.
101 func generateMarkdown(schemaHash string) ([]byte, error) {
102 var out strings.Builder
103 out.WriteString("<!-- Code generated by cmd/extension-protocol-gen; DO NOT EDIT. -->\n\n")
104 out.WriteString("# Reasonix Extension Protocol v1 — Generated Index\n\n")
105 fmt.Fprintf(&out, "- Protocol ID: `%s`\n", protocol.ProtocolID)
106 fmt.Fprintf(&out, "- Protocol major: `%d`\n", protocol.ProtocolMajor)
107 fmt.Fprintf(&out, "- Schema: `%s`\n", SchemaArtifactPath)
108 fmt.Fprintf(&out, "- Schema hash: `%s`\n\n", schemaHash)
109 out.WriteString("Within major v1 only optional fields, new enum values, and new methods may\n")
110 out.WriteString("be added; existing required fields, directions, limits, error reasons, and\n")
111 out.WriteString("semantics never change.\n\n")
112
113 out.WriteString("## Methods\n\n")
114 out.WriteString("| Method | Direction | Class | Params | Result |\n")
115 out.WriteString("| --- | --- | --- | --- | --- |\n")
116 for _, spec := range protocol.Registry() {
117 result := spec.ResultType.Name()
118 if spec.Notification() {
119 result = "-"
120 }
121 fmt.Fprintf(&out, "| `%s` | `%s` | `%s` | `%s` | `%s` |\n",
122 spec.Name, spec.Direction, spec.Class, spec.ParamsType.Name(), result)
123 }
124
125 events := protocol.InterceptEvents()
126 fmt.Fprintf(&out, "\n## Intercept events (%d)\n\n", len(events))
127 out.WriteString("`extension/intercept` (blocking) and `extension/event` (observation) share\n")
128 out.WriteString("these frozen hook points:\n\n")
129 for _, event := range events {
130 fmt.Fprintf(&out, "- `%s`\n", event)
131 }
132
133 limits := protocol.FrozenLimits()
134 out.WriteString("\n## Limits\n\n")
135 out.WriteString("| Limit | Value |\n")
136 out.WriteString("| --- | --- |\n")
137 fmt.Fprintf(&out, "| `frameBytes` | %d |\n", limits.FrameBytes)
138 fmt.Fprintf(&out, "| `externalizeFieldBytes` | %d |\n", limits.ExternalizeFieldBytes)
139 fmt.Fprintf(&out, "| `contentRefChunkBytes` | %d |\n", limits.ContentRefChunkBytes)
140 fmt.Fprintf(&out, "| `contentRefObjectBytes` | %d |\n", limits.ContentRefObjectBytes)
141
142 out.WriteString("\n## Errors\n\n")
143 out.WriteString("| Reason | JSON-RPC code | Retryable | Message |\n")
144 out.WriteString("| --- | --- | --- | --- |\n")
145 for _, contract := range protocol.ErrorContracts() {
146 fmt.Fprintf(&out, "| `%s` | %d | %t | %s |\n",
147 contract.Reason, contract.JSONRPCCode, contract.Retryable, contract.Message)
148 }
149 return []byte(out.String()), nil
150 }
151
152 // Write writes a generated artifact set below root.
153 func Write(root string, artifacts []Artifact) error {
154 for _, artifact := range artifacts {
155 path := filepath.Join(root, filepath.FromSlash(artifact.Path))
156 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
157 return fmt.Errorf("create directory for %s: %w", artifact.Path, err)
158 }
159 if err := os.WriteFile(path, artifact.Data, 0o644); err != nil {
160 return fmt.Errorf("write %s: %w", artifact.Path, err)
161 }
162 }
163 return nil
164 }
165
166 // Check compares a generated artifact set byte-for-byte with files below root.
167 func Check(root string, artifacts []Artifact) error {
168 for _, artifact := range artifacts {
169 path := filepath.Join(root, filepath.FromSlash(artifact.Path))
170 committed, err := os.ReadFile(path)
171 if err != nil {
172 return fmt.Errorf("read %s: %w", artifact.Path, err)
173 }
174 if !bytes.Equal(committed, artifact.Data) {
175 return fmt.Errorf("generated artifact drift: %s", artifact.Path)
176 }
177 }
178 return nil
179 }
180
180 lines GO