Skip to content

Commit 44add3b

Browse files
markturanskyclaude
andcommitted
feat(sdk): multi-resource Go/Python/TypeScript SDK — agents, messages, sessions, roles, checkins
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f00e3f8 commit 44add3b

89 files changed

Lines changed: 5587 additions & 122 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

components/ambient-sdk/Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ build-generator:
1010
cd $(GENERATOR) && go build -o ../bin/ambient-sdk-generator .
1111

1212
generate-sdk: build-generator
13-
./bin/ambient-sdk-generator \
14-
-spec $(SPEC_PATH) \
15-
-go-out $(GO_OUT) \
16-
-python-out $(PYTHON_OUT) \
17-
-ts-out $(TS_OUT)
13+
cd $(GENERATOR) && ./../bin/ambient-sdk-generator \
14+
-spec ../$(SPEC_PATH) \
15+
-go-out ../${GO_OUT} \
16+
-python-out ../${PYTHON_OUT} \
17+
-ts-out ../${TS_OUT}
1818
cd $(GO_OUT) && go fmt ./...
1919

2020
verify-sdk: generate-sdk
5.13 MB
Binary file not shown.

components/ambient-sdk/generator/main.go

Lines changed: 194 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"log"
99
"os"
1010
"path/filepath"
11+
"sort"
1112
"strings"
1213
"text/template"
1314
"time"
@@ -18,8 +19,36 @@ func main() {
1819
goOut := flag.String("go-out", "", "output directory for Go SDK")
1920
pythonOut := flag.String("python-out", "", "output directory for Python SDK")
2021
tsOut := flag.String("ts-out", "", "output directory for TypeScript SDK")
22+
protoPath := flag.String("proto", "", "path to .proto file (required for --grpc-python-out)")
23+
grpcPythonOut := flag.String("grpc-python-out", "", "output directory for Python gRPC client")
2124
flag.Parse()
2225

26+
if *grpcPythonOut != "" {
27+
if *protoPath == "" {
28+
log.Fatal("--proto is required when --grpc-python-out is set")
29+
}
30+
protoSpec, err := parseProto(*protoPath)
31+
if err != nil {
32+
log.Fatalf("parse proto: %v", err)
33+
}
34+
protoHash, err := hashFile(*protoPath)
35+
if err != nil {
36+
log.Fatalf("hash proto: %v", err)
37+
}
38+
header := ProtoGeneratedHeader{
39+
ProtoPath: *protoPath,
40+
ProtoHash: protoHash,
41+
Timestamp: time.Now().UTC().Format(time.RFC3339),
42+
}
43+
if err := generateGRPCPython(protoSpec, *grpcPythonOut, header); err != nil {
44+
log.Fatalf("generate gRPC Python: %v", err)
45+
}
46+
fmt.Printf("Python gRPC client generated in %s\n", *grpcPythonOut)
47+
if *specPath == "" {
48+
return
49+
}
50+
}
51+
2352
if *specPath == "" {
2453
log.Fatal("--spec is required")
2554
}
@@ -79,6 +108,165 @@ type GeneratedHeader struct {
79108
Timestamp string
80109
}
81110

111+
type ProtoGeneratedHeader struct {
112+
ProtoPath string
113+
ProtoHash string
114+
Timestamp string
115+
}
116+
117+
type ProtoRPC struct {
118+
Name string
119+
InputType string
120+
OutputType string
121+
ServerStreaming bool
122+
}
123+
124+
type ProtoService struct {
125+
Name string
126+
Package string
127+
RPCs []ProtoRPC
128+
}
129+
130+
type ProtoSpec struct {
131+
Service ProtoService
132+
}
133+
134+
type grpcPythonTemplateData struct {
135+
Header ProtoGeneratedHeader
136+
Service ProtoService
137+
Spec *ProtoSpec
138+
}
139+
140+
func parseProto(path string) (*ProtoSpec, error) {
141+
data, err := os.ReadFile(path)
142+
if err != nil {
143+
return nil, fmt.Errorf("read proto: %w", err)
144+
}
145+
content := string(data)
146+
147+
pkg := ""
148+
for _, line := range strings.Split(content, "\n") {
149+
line = strings.TrimSpace(line)
150+
if strings.HasPrefix(line, "package ") {
151+
pkg = strings.TrimSuffix(strings.TrimPrefix(line, "package "), ";")
152+
pkg = strings.TrimSpace(pkg)
153+
break
154+
}
155+
}
156+
157+
var serviceName string
158+
var rpcs []ProtoRPC
159+
inService := false
160+
for _, line := range strings.Split(content, "\n") {
161+
trimmed := strings.TrimSpace(line)
162+
if !inService {
163+
if strings.HasPrefix(trimmed, "service ") {
164+
parts := strings.Fields(trimmed)
165+
if len(parts) >= 2 {
166+
serviceName = parts[1]
167+
}
168+
inService = true
169+
}
170+
continue
171+
}
172+
if trimmed == "}" {
173+
inService = false
174+
continue
175+
}
176+
if strings.HasPrefix(trimmed, "rpc ") {
177+
rpc := parseRPCLine(trimmed)
178+
if rpc != nil {
179+
rpcs = append(rpcs, *rpc)
180+
}
181+
}
182+
}
183+
184+
return &ProtoSpec{
185+
Service: ProtoService{
186+
Name: serviceName,
187+
Package: pkg,
188+
RPCs: rpcs,
189+
},
190+
}, nil
191+
}
192+
193+
func parseRPCLine(line string) *ProtoRPC {
194+
line = strings.TrimPrefix(line, "rpc ")
195+
parenIdx := strings.Index(line, "(")
196+
if parenIdx < 0 {
197+
return nil
198+
}
199+
name := strings.TrimSpace(line[:parenIdx])
200+
rest := line[parenIdx+1:]
201+
closeIdx := strings.Index(rest, ")")
202+
if closeIdx < 0 {
203+
return nil
204+
}
205+
inputType := strings.TrimSpace(rest[:closeIdx])
206+
rest = rest[closeIdx+1:]
207+
returnsIdx := strings.Index(rest, "returns")
208+
if returnsIdx < 0 {
209+
return nil
210+
}
211+
rest = rest[returnsIdx+len("returns"):]
212+
serverStreaming := strings.Contains(rest, "stream")
213+
rest = strings.ReplaceAll(rest, "stream", "")
214+
openParen := strings.Index(rest, "(")
215+
closeParen := strings.Index(rest, ")")
216+
if openParen < 0 || closeParen < 0 {
217+
return nil
218+
}
219+
outputType := strings.TrimSpace(rest[openParen+1 : closeParen])
220+
return &ProtoRPC{
221+
Name: name,
222+
InputType: inputType,
223+
OutputType: outputType,
224+
ServerStreaming: serverStreaming,
225+
}
226+
}
227+
228+
func hashFile(path string) (string, error) {
229+
h := sha256.New()
230+
f, err := os.Open(path)
231+
if err != nil {
232+
return "", err
233+
}
234+
defer func() { _ = f.Close() }()
235+
if _, err := io.Copy(h, f); err != nil {
236+
return "", err
237+
}
238+
return fmt.Sprintf("%x", h.Sum(nil)), nil
239+
}
240+
241+
func generateGRPCPython(spec *ProtoSpec, outDir string, header ProtoGeneratedHeader) error {
242+
if err := os.MkdirAll(outDir, 0755); err != nil {
243+
return err
244+
}
245+
246+
tmplDir := filepath.Join(getTemplateDir(), "grpc", "python")
247+
data := grpcPythonTemplateData{Header: header, Service: spec.Service, Spec: spec}
248+
249+
files := []struct {
250+
tmpl string
251+
out string
252+
}{
253+
{"grpc_client.py.tmpl", "_grpc_client.py"},
254+
{"messages_api.py.tmpl", "_session_messages_api.py"},
255+
}
256+
257+
for _, f := range files {
258+
tmpl, err := loadTemplate(filepath.Join(tmplDir, f.tmpl))
259+
if err != nil {
260+
return fmt.Errorf("load %s: %w", f.tmpl, err)
261+
}
262+
if err := executeTemplate(tmpl, filepath.Join(outDir, f.out), data); err != nil {
263+
return fmt.Errorf("execute %s: %w", f.tmpl, err)
264+
}
265+
}
266+
267+
return nil
268+
}
269+
82270
type goTemplateData struct {
83271
Header GeneratedHeader
84272
Resource Resource
@@ -356,13 +544,13 @@ func computeSpecHash(specPath string) (string, error) {
356544
specDir := filepath.Dir(specPath)
357545
h := sha256.New()
358546

359-
files := []string{
360-
specPath,
361-
filepath.Join(specDir, "openapi.sessions.yaml"),
362-
filepath.Join(specDir, "openapi.projects.yaml"),
363-
filepath.Join(specDir, "openapi.projectSettings.yaml"),
364-
filepath.Join(specDir, "openapi.users.yaml"),
547+
subSpecs, err := filepath.Glob(filepath.Join(specDir, "openapi.*.yaml"))
548+
if err != nil {
549+
return "", fmt.Errorf("glob sub-specs: %w", err)
365550
}
551+
sort.Strings(subSpecs)
552+
553+
files := append([]string{specPath}, subSpecs...)
366554

367555
for _, f := range files {
368556
fh, err := os.Open(f)

0 commit comments

Comments
 (0)