From 652429377b99085d686d6b907c2f550c304e6b98 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Mon, 19 May 2025 15:43:14 -0400 Subject: [PATCH] sync --- cmd/root.go | 12 ++ go.mod | 12 +- go.sum | 17 ++ internal/app/app.go | 9 + js/.gitignore | 1 + js/bun.lock | 4 +- js/openapi.json | 90 --------- js/opencode.jsonc | 3 - js/package.json | 2 +- js/schema.json | 20 ++ js/src/bus/index.ts | 22 ++- js/src/index.ts | 19 +- js/src/storage/storage.ts | 2 +- pkg/client/.gitignore | 2 + pkg/client/client.go | 4 +- pkg/client/config.yml | 5 - pkg/client/event.go | 74 ++++++++ pkg/client/generated.go | 388 -------------------------------------- 18 files changed, 188 insertions(+), 498 deletions(-) delete mode 100644 js/openapi.json delete mode 100644 js/opencode.jsonc create mode 100644 js/schema.json create mode 100644 pkg/client/.gitignore delete mode 100644 pkg/client/config.yml create mode 100644 pkg/client/event.go delete mode 100644 pkg/client/generated.go diff --git a/cmd/root.go b/cmd/root.go index ab102afe..81083ef7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -182,6 +182,18 @@ to assist developers in writing, debugging, and understanding code directly from } }() + evts, err := app.Client.Event(ctx) + if err != nil { + slog.Error("Failed to subscribe to events", "error", err) + return err + } + + go func() { + for item := range evts { + program.Send(item) + } + }() + // Cleanup function for when the program exits cleanup := func() { // Cancel subscriptions first diff --git a/go.mod b/go.mod index 4f444c54..79d3221a 100644 --- a/go.mod +++ b/go.mod @@ -46,10 +46,13 @@ require ( require ( cloud.google.com/go v0.120.0 // indirect cloud.google.com/go/auth v0.15.0 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/atombender/go-jsonschema v0.20.0 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.17.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -57,11 +60,15 @@ require ( github.com/labstack/gommon v0.4.2 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sanity-io/litter v1.5.8 // indirect + github.com/sosodev/duration v1.3.1 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect @@ -166,4 +173,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen +tool ( + github.com/atombender/go-jsonschema + github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen +) diff --git a/go.sum b/go.sum index c08a2195..028f1edb 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ cloud.google.com/go/monitoring v1.24.1 h1:vKiypZVFD/5a3BbQMvI4gZdl8445ITzXFh257X cloud.google.com/go/monitoring v1.24.1/go.mod h1:Z05d1/vn9NaujqY2voG6pVQXoJGbp+r3laV+LySt9K0= cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw= cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= @@ -50,6 +52,8 @@ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsVi github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2 h1:h7qxtumNjKPWFv1QM/HJy60MteeW23iKeEtBoY7bYZk= github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c= +github.com/atombender/go-jsonschema v0.20.0 h1:AHg0LeI0HcjQ686ALwUNqVJjNRcSXpIR6U+wC2J0aFY= +github.com/atombender/go-jsonschema v0.20.0/go.mod h1:ZmbuR11v2+cMM0PdP6ySxtyZEGFBmhgF4xa4J6Hdls8= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= @@ -133,6 +137,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -186,6 +191,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= +github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -288,6 +295,8 @@ github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6B github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -335,8 +344,11 @@ github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0V github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU= @@ -355,6 +367,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg= +github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -364,6 +378,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= @@ -381,6 +397,7 @@ github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqj github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= diff --git a/internal/app/app.go b/internal/app/app.go index 41070684..0ef89d1b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -20,6 +20,7 @@ import ( "github.com/sst/opencode/internal/session" "github.com/sst/opencode/internal/status" "github.com/sst/opencode/internal/tui/theme" + "github.com/sst/opencode/pkg/client" ) type App struct { @@ -30,6 +31,7 @@ type App struct { History history.Service Permissions permission.Service Status status.Service + Client *client.Client PrimaryAgent agent.Service @@ -79,7 +81,14 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) { } fileutil.Init() + client, err := client.NewClient("http://localhost:16713") + if err != nil { + slog.Error("Failed to create client", "error", err) + return nil, err + } + app := &App{ + Client: client, CurrentSession: &session.Session{}, Logs: logging.GetService(), Sessions: session.GetService(), diff --git a/js/.gitignore b/js/.gitignore index f06235c4..5f9a3e7a 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -1,2 +1,3 @@ node_modules dist +gen diff --git a/js/bun.lock b/js/bun.lock index e6d8f907..df1b2a17 100644 --- a/js/bun.lock +++ b/js/bun.lock @@ -12,7 +12,7 @@ "clipanion": "^4.0.0-rc.4", "hono": "^4.7.10", "hono-openapi": "^0.4.8", - "zod": "^3.24.4", + "zod": "^3.25.3", "zod-openapi": "^4.2.4", }, "devDependencies": { @@ -199,7 +199,7 @@ "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="], + "zod": ["zod@3.25.3", "", {}, "sha512-VGZqnyYNrl8JpEJRZaFPqeVNIuqgXNu4cXZ5cOb6zEUO1OxKbRnWB4UdDIXMmiERWncs0yDQukssHov8JUxykQ=="], "zod-openapi": ["zod-openapi@4.2.4", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-tsrQpbpqFCXqVXUzi3TPwFhuMtLN3oNZobOtYnK6/5VkXsNdnIgyNr4r8no4wmYluaxzN3F7iS+8xCW8BmMQ8g=="], diff --git a/js/openapi.json b/js/openapi.json deleted file mode 100644 index 8dbe7855..00000000 --- a/js/openapi.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "opencode", - "description": "opencode api", - "version": "1.0.0" - }, - "paths": { - "/session_create": { - "post": { - "responses": { - "200": { - "description": "Successfully created session", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^ses" - }, - "title": { - "type": "string" - }, - "tokens": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "reasoning": { - "type": "number" - } - }, - "required": [ - "input", - "output", - "reasoning" - ] - } - }, - "required": [ - "id", - "title", - "tokens" - ] - } - } - } - } - }, - "operationId": "postSession_create", - "parameters": [], - "description": "Create a new session" - } - }, - "/session_chat": { - "post": { - "responses": {}, - "operationId": "postSession_chat", - "parameters": [], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "sessionID": { - "type": "string" - }, - "parts": {} - }, - "required": [ - "sessionID" - ] - } - } - } - } - } - } - }, - "components": { - "schemas": {} - } -} \ No newline at end of file diff --git a/js/opencode.jsonc b/js/opencode.jsonc deleted file mode 100644 index 87b07bc4..00000000 --- a/js/opencode.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "providers": {} -} diff --git a/js/package.json b/js/package.json index 63429b84..3703c10b 100644 --- a/js/package.json +++ b/js/package.json @@ -22,7 +22,7 @@ "clipanion": "^4.0.0-rc.4", "hono": "^4.7.10", "hono-openapi": "^0.4.8", - "zod": "^3.24.4", + "zod": "^3.25.3", "zod-openapi": "^4.2.4" } } diff --git a/js/schema.json b/js/schema.json new file mode 100644 index 00000000..bc08652e --- /dev/null +++ b/js/schema.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "properties": { + "type": { + "const": "storage.write" + }, + "properties": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "body": {} + }, + "required": ["key", "body"] + } + }, + "required": ["type", "properties"], + "$schema": "https://json-schema.org/draft-2020-12/schema" +} diff --git a/js/src/bus/index.ts b/js/src/bus/index.ts index 4f3b4004..f320aeef 100644 --- a/js/src/bus/index.ts +++ b/js/src/bus/index.ts @@ -1,4 +1,4 @@ -import type { z, ZodSchema } from "zod"; +import { z, type ZodType } from "zod/v4"; import { App } from "../app"; import { Log } from "../util/log"; @@ -16,14 +16,30 @@ export namespace Bus { export type EventDefinition = ReturnType; - export function event( + const registry = new Map(); + + export function event( type: Type, properties: Properties, ) { - return { + const result = { type, properties, }; + registry.set(type, result); + return result; + } + + export function specs() { + const children = {} as any; + for (const [type, def] of registry.entries()) { + children[def.type] = def.properties; + } + const result = z.toJSONSchema(z.object(children)) as any; + result.definitions = result.properties; + delete result.properties; + delete result.required; + return result; } export function publish( diff --git a/js/src/index.ts b/js/src/index.ts index 10b3fef3..b2a7c5f2 100644 --- a/js/src/index.ts +++ b/js/src/index.ts @@ -1,6 +1,9 @@ import { App } from "./app"; import { Server } from "./server/server"; import { Cli, Command, runExit } from "clipanion"; +import fs from "fs/promises"; +import path from "path"; +import { Bus } from "./bus"; const cli = new Cli({ binaryLabel: `opencode`, @@ -22,11 +25,21 @@ cli.register( }, ); cli.register( - class OpenApi extends Command { - static paths = [["openapi"]]; + class Generate extends Command { + static paths = [["generate"]]; async execute() { const specs = await Server.openapi(); - this.context.stdout.write(JSON.stringify(specs, null, 2)); + const dir = "gen"; + await fs.rmdir(dir, { recursive: true }).catch(() => {}); + await fs.mkdir(dir, { recursive: true }); + await Bun.write( + path.join(dir, "openapi.json"), + JSON.stringify(specs, null, 2), + ); + await Bun.write( + path.join(dir, "event.json"), + JSON.stringify(Bus.specs(), null, 2), + ); } }, ); diff --git a/js/src/storage/storage.ts b/js/src/storage/storage.ts index c24666c9..50364bee 100644 --- a/js/src/storage/storage.ts +++ b/js/src/storage/storage.ts @@ -5,7 +5,7 @@ import { Log } from "../util/log"; import { App } from "../app"; import { AppPath } from "../app/path"; import { Bus } from "../bus"; -import z from "zod"; +import z from "zod/v4"; export namespace Storage { const log = Log.create({ service: "storage" }); diff --git a/pkg/client/.gitignore b/pkg/client/.gitignore new file mode 100644 index 00000000..c56971e8 --- /dev/null +++ b/pkg/client/.gitignore @@ -0,0 +1,2 @@ +gen +generated-*.go diff --git a/pkg/client/client.go b/pkg/client/client.go index 0afda3ec..98a49a0d 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -1,3 +1,5 @@ package client -//go:generate go tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=./config.yml ../../js/openapi.json +//go:generate bun run ../../js/src/index.ts generate +//go:generate go tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --package=client --generate=types,client -o generated-client.go ./gen/openapi.json +//go:generate go tool github.com/atombender/go-jsonschema -p client -o generated-event.go ./gen/event.json diff --git a/pkg/client/config.yml b/pkg/client/config.yml deleted file mode 100644 index 24596917..00000000 --- a/pkg/client/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -package: client -output: generated.go -generate: - - client - - types diff --git a/pkg/client/event.go b/pkg/client/event.go new file mode 100644 index 00000000..a6d7798f --- /dev/null +++ b/pkg/client/event.go @@ -0,0 +1,74 @@ +package client + +import ( + "bufio" + "context" + "encoding/json" + "net/http" + "reflect" + "strings" +) + +var EventMap = map[string]any{ + "storage.write": StorageWrite{}, +} + +type EventMessage struct { + Type string `json:"type"` + Properties json.RawMessage `json:"properties"` +} + +func (c *Client) Event(ctx context.Context) (<-chan any, error) { + events := make(chan any) + req, err := http.NewRequestWithContext(ctx, "GET", c.Server+"/event", nil) + if err != nil { + return nil, err + } + + go func() { + defer close(events) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "data: ") { + data := strings.TrimPrefix(line, "data: ") + + var eventMsg EventMessage + if err := json.Unmarshal([]byte(data), &eventMsg); err != nil { + continue + } + + eventTemplate, exists := EventMap[eventMsg.Type] + if !exists { + select { + case events <- eventMsg: + case <-ctx.Done(): + return + } + continue + } + + eventValue := reflect.New(reflect.TypeOf(eventTemplate)).Interface() + + if err := json.Unmarshal(eventMsg.Properties, eventValue); err != nil { + continue + } + + select { + case events <- eventValue: + case <-ctx.Done(): + return + } + } + } + }() + + return events, nil +} diff --git a/pkg/client/generated.go b/pkg/client/generated.go deleted file mode 100644 index 26903ab9..00000000 --- a/pkg/client/generated.go +++ /dev/null @@ -1,388 +0,0 @@ -// Package client provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" -) - -// PostSessionChatJSONBody defines parameters for PostSessionChat. -type PostSessionChatJSONBody struct { - Parts *interface{} `json:"parts,omitempty"` - SessionID string `json:"sessionID"` -} - -// PostSessionChatJSONRequestBody defines body for PostSessionChat for application/json ContentType. -type PostSessionChatJSONRequestBody PostSessionChatJSONBody - -// RequestEditorFn is the function signature for the RequestEditor callback function -type RequestEditorFn func(ctx context.Context, req *http.Request) error - -// Doer performs HTTP requests. -// -// The standard http.Client implements this interface. -type HttpRequestDoer interface { - Do(req *http.Request) (*http.Response, error) -} - -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. This can contain a path relative - // to the server, such as https://api.deepmap.com/dev-test, and all the - // paths in the swagger spec will be appended to the server. - Server string - - // Doer for performing requests, typically a *http.Client with any - // customized settings, such as certificate chains. - Client HttpRequestDoer - - // A list of callbacks for modifying requests which are generated before sending over - // the network. - RequestEditors []RequestEditorFn -} - -// ClientOption allows setting custom parameters during construction -type ClientOption func(*Client) error - -// Creates a new Client, with reasonable defaults -func NewClient(server string, opts ...ClientOption) (*Client, error) { - // create a client with sane default values - client := Client{ - Server: server, - } - // mutate client and add all optional params - for _, o := range opts { - if err := o(&client); err != nil { - return nil, err - } - } - // ensure the server URL always has a trailing slash - if !strings.HasSuffix(client.Server, "/") { - client.Server += "/" - } - // create httpClient, if not already present - if client.Client == nil { - client.Client = &http.Client{} - } - return &client, nil -} - -// WithHTTPClient allows overriding the default Doer, which is -// automatically created using http.Client. This is useful for tests. -func WithHTTPClient(doer HttpRequestDoer) ClientOption { - return func(c *Client) error { - c.Client = doer - return nil - } -} - -// WithRequestEditorFn allows setting up a callback function, which will be -// called right before sending the request. This can be used to mutate the request. -func WithRequestEditorFn(fn RequestEditorFn) ClientOption { - return func(c *Client) error { - c.RequestEditors = append(c.RequestEditors, fn) - return nil - } -} - -// The interface specification for the client above. -type ClientInterface interface { - // PostSessionChatWithBody request with any body - PostSessionChatWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PostSessionChat(ctx context.Context, body PostSessionChatJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // PostSessionCreate request - PostSessionCreate(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) -} - -func (c *Client) PostSessionChatWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostSessionChatRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) PostSessionChat(ctx context.Context, body PostSessionChatJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostSessionChatRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) PostSessionCreate(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostSessionCreateRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -// NewPostSessionChatRequest calls the generic PostSessionChat builder with application/json body -func NewPostSessionChatRequest(server string, body PostSessionChatJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewPostSessionChatRequestWithBody(server, "application/json", bodyReader) -} - -// NewPostSessionChatRequestWithBody generates requests for PostSessionChat with any type of body -func NewPostSessionChatRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/session_chat") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewPostSessionCreateRequest generates requests for PostSessionCreate -func NewPostSessionCreateRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/session_create") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } - } - return nil -} - -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} - -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) - if err != nil { - return nil, err - } - return &ClientWithResponses{client}, nil -} - -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil - } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // PostSessionChatWithBodyWithResponse request with any body - PostSessionChatWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionChatResponse, error) - - PostSessionChatWithResponse(ctx context.Context, body PostSessionChatJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionChatResponse, error) - - // PostSessionCreateWithResponse request - PostSessionCreateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*PostSessionCreateResponse, error) -} - -type PostSessionChatResponse struct { - Body []byte - HTTPResponse *http.Response -} - -// Status returns HTTPResponse.Status -func (r PostSessionChatResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostSessionChatResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type PostSessionCreateResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *struct { - Id string `json:"id"` - Title string `json:"title"` - Tokens struct { - Input float32 `json:"input"` - Output float32 `json:"output"` - Reasoning float32 `json:"reasoning"` - } `json:"tokens"` - } -} - -// Status returns HTTPResponse.Status -func (r PostSessionCreateResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostSessionCreateResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// PostSessionChatWithBodyWithResponse request with arbitrary body returning *PostSessionChatResponse -func (c *ClientWithResponses) PostSessionChatWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionChatResponse, error) { - rsp, err := c.PostSessionChatWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostSessionChatResponse(rsp) -} - -func (c *ClientWithResponses) PostSessionChatWithResponse(ctx context.Context, body PostSessionChatJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionChatResponse, error) { - rsp, err := c.PostSessionChat(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostSessionChatResponse(rsp) -} - -// PostSessionCreateWithResponse request returning *PostSessionCreateResponse -func (c *ClientWithResponses) PostSessionCreateWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*PostSessionCreateResponse, error) { - rsp, err := c.PostSessionCreate(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostSessionCreateResponse(rsp) -} - -// ParsePostSessionChatResponse parses an HTTP response from a PostSessionChatWithResponse call -func ParsePostSessionChatResponse(rsp *http.Response) (*PostSessionChatResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostSessionChatResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - return response, nil -} - -// ParsePostSessionCreateResponse parses an HTTP response from a PostSessionCreateWithResponse call -func ParsePostSessionCreateResponse(rsp *http.Response) (*PostSessionCreateResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostSessionCreateResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest struct { - Id string `json:"id"` - Title string `json:"title"` - Tokens struct { - Input float32 `json:"input"` - Output float32 `json:"output"` - Reasoning float32 `json:"reasoning"` - } `json:"tokens"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -}