diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..1332969 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.1" +} \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 63ed34a..ba8ad6d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 42 +configured_endpoints: 37 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-bb36e7ac24778d179b2e2e45f9390962ec6cf2947f54e0bd4467a5ff147bfeb0.yml openapi_spec_hash: a2d19cf8740f29955a05ac3d811e7f7a -config_hash: 7f717976c04aa098ef8ceb3f357abae7 +config_hash: 8a414b4fefb5300a6c653ae59259ec73 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ec1cbb..34620a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ To set up the repository, run: ```sh $ ./scripts/bootstrap -$ ./scripts/lint +$ ./scripts/build ``` This will install all the required dependencies and build the SDK. @@ -41,7 +41,7 @@ To use a local version of this library from source in another project, edit the directive. This can be done through the CLI with the following: ```sh -$ go mod edit -replace github.com/stainless-sdks/opencode-go=/path/to/opencode-go +$ go mod edit -replace github.com/sst/opencode-sdk-go=/path/to/opencode-sdk-go ``` ## Running tests diff --git a/LICENSE b/LICENSE index a56ceac..821edeb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,7 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright 2025 opencode - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - 1. Definitions. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2025 Opencode - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 95dbac4..f2fecd8 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,34 @@ # Opencode Go API Library -Go Reference +Go Reference -The Opencode Go library provides convenient access to the Opencode REST API +The Opencode Go library provides convenient access to the [Opencode REST API](https://opencode.ai/docs) from applications written in Go. It is generated with [Stainless](https://www.stainless.com/). ## Installation + + ```go import ( - "github.com/stainless-sdks/opencode-go" // imported as opencode + "github.com/sst/opencode-sdk-go" // imported as opencode ) ``` + + Or to pin the version: + + ```sh -go get -u 'github.com/stainless-sdks/opencode-go@v0.0.1' +go get -u 'github.com/sst/opencode-sdk-go@v0.0.1' ``` + + ## Requirements This library requires Go 1.18+. @@ -36,164 +44,83 @@ import ( "context" "fmt" - "github.com/stainless-sdks/opencode-go" - "github.com/stainless-sdks/opencode-go/option" + "github.com/sst/opencode-sdk-go" ) func main() { - client := opencode.NewClient( - option.WithAPIKey("My API Key"), // defaults to os.LookupEnv("OPENCODE_API_KEY") - ) - projects, err := client.Project.List(context.TODO()) + client := opencode.NewClient() + sessions, err := client.Session.List(context.TODO()) if err != nil { panic(err.Error()) } - fmt.Printf("%+v\n", projects) + fmt.Printf("%+v\n", sessions) } ``` ### Request fields -The opencode library uses the [`omitzero`](https://tip.golang.org/doc/go1.24#encodingjsonpkgencodingjson) -semantics from the Go 1.24+ `encoding/json` release for request fields. +All request parameters are wrapped in a generic `Field` type, +which we use to distinguish zero values from null or omitted fields. -Required primitive fields (`int64`, `string`, etc.) feature the tag \`json:"...,required"\`. These -fields are always serialized, even their zero values. +This prevents accidentally sending a zero value if you forget a required parameter, +and enables explicitly sending `null`, `false`, `''`, or `0` on optional parameters. +Any field not specified is not sent. -Optional primitive types are wrapped in a `param.Opt[T]`. These fields can be set with the provided constructors, `opencode.String(string)`, `opencode.Int(int64)`, etc. - -Any `param.Opt[T]`, map, slice, struct or string enum uses the -tag \`json:"...,omitzero"\`. Its zero value is considered omitted. - -The `param.IsOmitted(any)` function can confirm the presence of any `omitzero` field. +To construct fields with values, use the helpers `String()`, `Int()`, `Float()`, or most commonly, the generic `F[T]()`. +To send a null, use `Null[T]()`, and to send a nonconforming value, use `Raw[T](any)`. For example: ```go -p := opencode.ExampleParams{ - ID: "id_xxx", // required property - Name: opencode.String("..."), // optional property +params := FooParams{ + Name: opencode.F("hello"), - Point: opencode.Point{ - X: 0, // required field will serialize as 0 - Y: opencode.Int(1), // optional field will serialize as 1 - // ... omitted non-required fields will not be serialized - }, + // Explicitly send `"description": null` + Description: opencode.Null[string](), - Origin: opencode.Origin{}, // the zero value of [Origin] is considered omitted -} -``` + Point: opencode.F(opencode.Point{ + X: opencode.Int(0), + Y: opencode.Int(1), -To send `null` instead of a `param.Opt[T]`, use `param.Null[T]()`. -To send `null` instead of a struct `T`, use `param.NullStruct[T]()`. - -```go -p.Name = param.Null[string]() // 'null' instead of string -p.Point = param.NullStruct[Point]() // 'null' instead of struct - -param.IsNull(p.Name) // true -param.IsNull(p.Point) // true -``` - -Request structs contain a `.SetExtraFields(map[string]any)` method which can send non-conforming -fields in the request body. Extra fields overwrite any struct fields with a matching -key. For security reasons, only use `SetExtraFields` with trusted data. - -To send a custom value instead of a struct, use `param.Override[T](value)`. - -```go -// In cases where the API specifies a given type, -// but you want to send something else, use [SetExtraFields]: -p.SetExtraFields(map[string]any{ - "x": 0.01, // send "x" as a float instead of int -}) - -// Send a number instead of an object -custom := param.Override[opencode.FooParams](12) -``` - -### Request unions - -Unions are represented as a struct with fields prefixed by "Of" for each of it's variants, -only one field can be non-zero. The non-zero field will be serialized. - -Sub-properties of the union can be accessed via methods on the union struct. -These methods return a mutable pointer to the underlying data, if present. - -```go -// Only one field can be non-zero, use param.IsOmitted() to check if a field is set -type AnimalUnionParam struct { - OfCat *Cat `json:",omitzero,inline` - OfDog *Dog `json:",omitzero,inline` -} - -animal := AnimalUnionParam{ - OfCat: &Cat{ - Name: "Whiskers", - Owner: PersonParam{ - Address: AddressParam{Street: "3333 Coyote Hill Rd", Zip: 0}, - }, - }, -} - -// Mutating a field -if address := animal.GetOwner().GetAddress(); address != nil { - address.ZipCode = 94304 + // In cases where the API specifies a given type, + // but you want to send something else, use `Raw`: + Z: opencode.Raw[int64](0.01), // sends a float + }), } ``` ### Response objects -All fields in response structs are ordinary value types (not pointers or wrappers). -Response structs also include a special `JSON` field containing metadata about -each property. +All fields in response structs are value types (not pointers or wrappers). + +If a given field is `null`, not present, or invalid, the corresponding field +will simply be its zero value. + +All response structs also include a special `JSON` field, containing more detailed +information about each property, which you can use like so: ```go -type Animal struct { - Name string `json:"name,nullable"` - Owners int `json:"owners"` - Age int `json:"age"` - JSON struct { - Name respjson.Field - Owner respjson.Field - Age respjson.Field - ExtraFields map[string]respjson.Field - } `json:"-"` +if res.Name == "" { + // true if `"name"` is either not present or explicitly null + res.JSON.Name.IsNull() + + // true if the `"name"` key was not present in the response JSON at all + res.JSON.Name.IsMissing() + + // When the API returns data that cannot be coerced to the expected type: + if res.JSON.Name.IsInvalid() { + raw := res.JSON.Name.Raw() + + legacyName := struct{ + First string `json:"first"` + Last string `json:"last"` + }{} + json.Unmarshal([]byte(raw), &legacyName) + name = legacyName.First + " " + legacyName.Last + } } ``` -To handle optional data, use the `.Valid()` method on the JSON field. -`.Valid()` returns true if a field is not `null`, not present, or couldn't be marshaled. - -If `.Valid()` is false, the corresponding field will simply be its zero value. - -```go -raw := `{"owners": 1, "name": null}` - -var res Animal -json.Unmarshal([]byte(raw), &res) - -// Accessing regular fields - -res.Owners // 1 -res.Name // "" -res.Age // 0 - -// Optional field checks - -res.JSON.Owners.Valid() // true -res.JSON.Name.Valid() // false -res.JSON.Age.Valid() // false - -// Raw JSON values - -res.JSON.Owners.Raw() // "1" -res.JSON.Name.Raw() == "null" // true -res.JSON.Name.Raw() == respjson.Null // true -res.JSON.Age.Raw() == "" // true -res.JSON.Age.Raw() == respjson.Omitted // true -``` - -These `.JSON` structs also include an `ExtraFields` map containing +These `.JSON` structs also include an `Extras` map containing any properties in the json response that were not specified in the struct. This can be useful for API features not yet present in the SDK. @@ -202,45 +129,6 @@ present in the SDK. body := res.JSON.ExtraFields["my_unexpected_field"].Raw() ``` -### Response Unions - -In responses, unions are represented by a flattened struct containing all possible fields from each of the -object variants. -To convert it to a variant use the `.AsFooVariant()` method or the `.AsAny()` method if present. - -If a response value union contains primitive values, primitive fields will be alongside -the properties but prefixed with `Of` and feature the tag `json:"...,inline"`. - -```go -type AnimalUnion struct { - // From variants [Dog], [Cat] - Owner Person `json:"owner"` - // From variant [Dog] - DogBreed string `json:"dog_breed"` - // From variant [Cat] - CatBreed string `json:"cat_breed"` - // ... - - JSON struct { - Owner respjson.Field - // ... - } `json:"-"` -} - -// If animal variant -if animal.Owner.Address.ZipCode == "" { - panic("missing zip code") -} - -// Switch on the variant -switch variant := animal.AsAny().(type) { -case Dog: -case Cat: -default: - panic("unexpected type") -} -``` - ### RequestOptions This library uses the functional options pattern. Functions defined in the @@ -254,7 +142,7 @@ client := opencode.NewClient( option.WithHeader("X-Some-Header", "custom_header_info"), ) -client.Project.List(context.TODO(), ..., +client.Session.List(context.TODO(), ..., // Override the header option.WithHeader("X-Some-Header", "some_other_custom_header_info"), // Add an undocumented field to the request body, using sjson syntax @@ -262,9 +150,7 @@ client.Project.List(context.TODO(), ..., ) ``` -The request option `option.WithDebugLog(nil)` may be helpful while debugging. - -See the [full list of request options](https://pkg.go.dev/github.com/stainless-sdks/opencode-go/option). +See the [full list of request options](https://pkg.go.dev/github.com/sst/opencode-sdk-go/option). ### Pagination @@ -285,14 +171,14 @@ When the API returns a non-success status code, we return an error with type To handle errors, we recommend that you use the `errors.As` pattern: ```go -_, err := client.Project.List(context.TODO()) +_, err := client.Session.List(context.TODO()) if err != nil { var apierr *opencode.Error if errors.As(err, &apierr) { println(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request println(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response } - panic(err.Error()) // GET "/project": 400 Bad Request { ... } + panic(err.Error()) // GET "/session": 400 Bad Request { ... } } ``` @@ -310,7 +196,7 @@ To set a per-retry timeout, use `option.WithRequestTimeout()`. // This sets the timeout for the request, including all the retries. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() -client.Project.List( +client.Session.List( ctx, // This sets the per-retry timeout option.WithRequestTimeout(20*time.Second), @@ -320,14 +206,14 @@ client.Project.List( ### File uploads Request parameters that correspond to file uploads in multipart requests are typed as -`io.Reader`. The contents of the `io.Reader` will by default be sent as a multipart form +`param.Field[io.Reader]`. The contents of the `io.Reader` will by default be sent as a multipart form part with the file name of "anonymous_file" and content-type of "application/octet-stream". The file name and content-type can be customized by implementing `Name() string` or `ContentType() string` on the run-time type of `io.Reader`. Note that `os.File` implements `Name() string`, so a file returned by `os.Open` will be sent with the file name on disk. -We also provide a helper `opencode.File(reader io.Reader, filename string, contentType string)` +We also provide a helper `opencode.FileParam(reader io.Reader, filename string, contentType string)` which can be used to wrap any `io.Reader` with the appropriate file name and content type. ### Retries @@ -345,7 +231,7 @@ client := opencode.NewClient( ) // Override per-request: -client.Project.List(context.TODO(), option.WithMaxRetries(5)) +client.Session.List(context.TODO(), option.WithMaxRetries(5)) ``` ### Accessing raw response data (e.g. response headers) @@ -356,11 +242,11 @@ you need to examine response headers, status codes, or other details. ```go // Create a variable to store the HTTP response var response *http.Response -projects, err := client.Project.List(context.TODO(), option.WithResponseInto(&response)) +sessions, err := client.Session.List(context.TODO(), option.WithResponseInto(&response)) if err != nil { // handle error } -fmt.Printf("%+v\n", projects) +fmt.Printf("%+v\n", sessions) fmt.Printf("Status Code: %d\n", response.StatusCode) fmt.Printf("Headers: %+#v\n", response.Header) @@ -380,7 +266,7 @@ To make requests to undocumented endpoints, you can use `client.Get`, `client.Po var ( // params can be an io.Reader, a []byte, an encoding/json serializable object, // or a "…Params" struct defined in this library. - params map[string]any + params map[string]interface{} // result can be an []byte, *http.Response, a encoding/json deserializable object, // or a model defined in this library. @@ -399,10 +285,10 @@ or the `option.WithJSONSet()` methods. ```go params := FooNewParams{ - ID: "id_xxxx", - Data: FooNewParamsData{ - FirstName: opencode.String("John"), - }, + ID: opencode.F("id_xxxx"), + Data: opencode.F(FooNewParamsData{ + FirstName: opencode.F("John"), + }), } client.Foo.New(context.Background(), params, option.WithJSONSet("data.last_name", "Doe")) ``` @@ -461,7 +347,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/opencode-go/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/sst/opencode-sdk-go/issues) with questions, bugs, or suggestions. ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 0146cb2..6912e12 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,6 +18,10 @@ before making any information public. If you encounter security issues that are not directly related to SDKs but pertain to the services or products provided by Opencode, please follow the respective company's security reporting guidelines. +### Opencode Terms and Policies + +Please contact support@sst.dev for any questions or concerns regarding the security of our services. + --- Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/agent.go b/agent.go deleted file mode 100644 index 3e92fee..0000000 --- a/agent.go +++ /dev/null @@ -1,149 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package opencode - -import ( - "context" - "net/http" - - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" - "github.com/stainless-sdks/opencode-go/packages/respjson" -) - -// AgentService contains methods and other services that help with interacting with -// the opencode API. -// -// Note, unlike clients, this service does not read variables from the environment -// automatically. You should not instantiate this service directly, and instead use -// the [NewAgentService] method instead. -type AgentService struct { - Options []option.RequestOption -} - -// NewAgentService generates a new service that applies the given options to each -// request. These options are applied after the parent client's options (if there -// is one), and before any request-specific options. -func NewAgentService(opts ...option.RequestOption) (r AgentService) { - r = AgentService{} - r.Options = opts - return -} - -// List all agents -func (r *AgentService) List(ctx context.Context, opts ...option.RequestOption) (res *[]AgentListResponse, err error) { - opts = append(r.Options[:], opts...) - path := "agent" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return -} - -type AgentListResponse struct { - BuiltIn bool `json:"builtIn,required"` - // Any of "subagent", "primary", "all". - Mode AgentListResponseMode `json:"mode,required"` - Name string `json:"name,required"` - Options map[string]any `json:"options,required"` - Permission AgentListResponsePermission `json:"permission,required"` - Tools map[string]bool `json:"tools,required"` - Description string `json:"description"` - Model AgentListResponseModel `json:"model"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - TopP float64 `json:"topP"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - BuiltIn respjson.Field - Mode respjson.Field - Name respjson.Field - Options respjson.Field - Permission respjson.Field - Tools respjson.Field - Description respjson.Field - Model respjson.Field - Prompt respjson.Field - Temperature respjson.Field - TopP respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r AgentListResponse) RawJSON() string { return r.JSON.raw } -func (r *AgentListResponse) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type AgentListResponseMode string - -const ( - AgentListResponseModeSubagent AgentListResponseMode = "subagent" - AgentListResponseModePrimary AgentListResponseMode = "primary" - AgentListResponseModeAll AgentListResponseMode = "all" -) - -type AgentListResponsePermission struct { - Bash map[string]AgentListResponsePermissionBash `json:"bash,required"` - // Any of "ask", "allow", "deny". - Edit AgentListResponsePermissionEdit `json:"edit,required"` - // Any of "ask", "allow", "deny". - Webfetch AgentListResponsePermissionWebfetch `json:"webfetch"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Bash respjson.Field - Edit respjson.Field - Webfetch respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r AgentListResponsePermission) RawJSON() string { return r.JSON.raw } -func (r *AgentListResponsePermission) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type AgentListResponsePermissionBash string - -const ( - AgentListResponsePermissionBashAsk AgentListResponsePermissionBash = "ask" - AgentListResponsePermissionBashAllow AgentListResponsePermissionBash = "allow" - AgentListResponsePermissionBashDeny AgentListResponsePermissionBash = "deny" -) - -type AgentListResponsePermissionEdit string - -const ( - AgentListResponsePermissionEditAsk AgentListResponsePermissionEdit = "ask" - AgentListResponsePermissionEditAllow AgentListResponsePermissionEdit = "allow" - AgentListResponsePermissionEditDeny AgentListResponsePermissionEdit = "deny" -) - -type AgentListResponsePermissionWebfetch string - -const ( - AgentListResponsePermissionWebfetchAsk AgentListResponsePermissionWebfetch = "ask" - AgentListResponsePermissionWebfetchAllow AgentListResponsePermissionWebfetch = "allow" - AgentListResponsePermissionWebfetchDeny AgentListResponsePermissionWebfetch = "deny" -) - -type AgentListResponseModel struct { - ModelID string `json:"modelID,required"` - ProviderID string `json:"providerID,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ModelID respjson.Field - ProviderID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r AgentListResponseModel) RawJSON() string { return r.JSON.raw } -func (r *AgentListResponseModel) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} diff --git a/aliases.go b/aliases.go index 2815fd9..6ab36d0 100644 --- a/aliases.go +++ b/aliases.go @@ -3,14 +3,41 @@ package opencode import ( - "github.com/stainless-sdks/opencode-go/internal/apierror" - "github.com/stainless-sdks/opencode-go/packages/param" + "github.com/sst/opencode-sdk-go/internal/apierror" + "github.com/sst/opencode-sdk-go/shared" ) -// aliased to make [param.APIUnion] private when embedding -type paramUnion = param.APIUnion - -// aliased to make [param.APIObject] private when embedding -type paramObj = param.APIObject - type Error = apierror.Error + +// This is an alias to an internal type. +type MessageAbortedError = shared.MessageAbortedError + +// This is an alias to an internal type. +type MessageAbortedErrorName = shared.MessageAbortedErrorName + +// This is an alias to an internal value. +const MessageAbortedErrorNameMessageAbortedError = shared.MessageAbortedErrorNameMessageAbortedError + +// This is an alias to an internal type. +type ProviderAuthError = shared.ProviderAuthError + +// This is an alias to an internal type. +type ProviderAuthErrorData = shared.ProviderAuthErrorData + +// This is an alias to an internal type. +type ProviderAuthErrorName = shared.ProviderAuthErrorName + +// This is an alias to an internal value. +const ProviderAuthErrorNameProviderAuthError = shared.ProviderAuthErrorNameProviderAuthError + +// This is an alias to an internal type. +type UnknownError = shared.UnknownError + +// This is an alias to an internal type. +type UnknownErrorData = shared.UnknownErrorData + +// This is an alias to an internal type. +type UnknownErrorName = shared.UnknownErrorName + +// This is an alias to an internal value. +const UnknownErrorNameUnknownError = shared.UnknownErrorNameUnknownError diff --git a/api.md b/api.md index 59bb544..7214eae 100644 --- a/api.md +++ b/api.md @@ -1,168 +1,151 @@ -# Project +# Shared Response Types -Response Types: - -- opencode.ProjectListResponse - -Methods: - -- client.Project.List(ctx context.Context) ([]opencode.ProjectListResponse, error) +- shared.MessageAbortedError +- shared.ProviderAuthError +- shared.UnknownError # Event Response Types: -- opencode.EventListResponseUnion +- opencode.EventListResponse Methods: -- client.Event.List(ctx context.Context) (opencode.EventListResponseUnion, error) - -# Config - -Response Types: - -- opencode.ConfigGetResponse -- opencode.ConfigListProvidersResponse - -Methods: - -- client.Config.Get(ctx context.Context) (opencode.ConfigGetResponse, error) -- client.Config.ListProviders(ctx context.Context) (opencode.ConfigListProvidersResponse, error) - -# Session - -Response Types: - -- opencode.AssistantMessage -- opencode.MessageAbortedError -- opencode.MessageOutputLengthError -- opencode.ProviderAuthError -- opencode.Session -- opencode.UnknownError -- opencode.SessionSendCommandResponse - -Methods: - -- client.Session.New(ctx context.Context, body opencode.SessionNewParams) (opencode.Session, error) -- client.Session.Get(ctx context.Context, id string) (opencode.Session, error) -- client.Session.Update(ctx context.Context, id string, body opencode.SessionUpdateParams) (opencode.Session, error) -- client.Session.List(ctx context.Context) ([]opencode.Session, error) -- client.Session.Delete(ctx context.Context, id string) (bool, error) -- client.Session.Abort(ctx context.Context, id string) (bool, error) -- client.Session.Analyze(ctx context.Context, id string, body opencode.SessionAnalyzeParams) (bool, error) -- client.Session.GetChildren(ctx context.Context, id string) ([]opencode.Session, error) -- client.Session.RespondToPermission(ctx context.Context, permissionID string, params opencode.SessionRespondToPermissionParams) (bool, error) -- client.Session.RestoreReverted(ctx context.Context, id string) (opencode.Session, error) -- client.Session.Revert(ctx context.Context, id string, body opencode.SessionRevertParams) (opencode.Session, error) -- client.Session.RunShell(ctx context.Context, id string, body opencode.SessionRunShellParams) (opencode.AssistantMessage, error) -- client.Session.SendCommand(ctx context.Context, id string, body opencode.SessionSendCommandParams) (opencode.SessionSendCommandResponse, error) -- client.Session.Summarize(ctx context.Context, id string, body opencode.SessionSummarizeParams) (bool, error) - -## Share - -Methods: - -- client.Session.Share.New(ctx context.Context, id string) (opencode.Session, error) -- client.Session.Share.Delete(ctx context.Context, id string) (opencode.Session, error) - -## Message - -Params Types: - -- opencode.FilePartSourceUnionParam -- opencode.FilePartSourceTextParam - -Response Types: - -- opencode.FilePartSourceUnion -- opencode.FilePartSourceText -- opencode.MessageUnion -- opencode.PartUnion -- opencode.SessionMessageNewResponse -- opencode.SessionMessageGetResponse -- opencode.SessionMessageListResponse - -Methods: - -- client.Session.Message.New(ctx context.Context, id string, body opencode.SessionMessageNewParams) (opencode.SessionMessageNewResponse, error) -- client.Session.Message.Get(ctx context.Context, messageID string, query opencode.SessionMessageGetParams) (opencode.SessionMessageGetResponse, error) -- client.Session.Message.List(ctx context.Context, id string) ([]opencode.SessionMessageListResponse, error) - -# Command - -Response Types: - -- opencode.CommandListResponse - -Methods: - -- client.Command.List(ctx context.Context) ([]opencode.CommandListResponse, error) +- client.Event.List(ctx context.Context) (opencode.EventListResponse, error) # Find -Params Types: - -- opencode.RangeParam - Response Types: -- opencode.Range -- opencode.FindGetResponse -- opencode.FindGetSymbolResponse +- opencode.Symbol +- opencode.FindTextResponse Methods: -- client.Find.Get(ctx context.Context, query opencode.FindGetParams) ([]opencode.FindGetResponse, error) -- client.Find.GetFile(ctx context.Context, query opencode.FindGetFileParams) ([]string, error) -- client.Find.GetSymbol(ctx context.Context, query opencode.FindGetSymbolParams) ([]opencode.FindGetSymbolResponse, error) +- client.Find.Files(ctx context.Context, query opencode.FindFilesParams) ([]string, error) +- client.Find.Symbols(ctx context.Context, query opencode.FindSymbolsParams) ([]opencode.Symbol, error) +- client.Find.Text(ctx context.Context, query opencode.FindTextParams) ([]opencode.FindTextResponse, error) # File Response Types: -- opencode.FileListResponse -- opencode.FileGetStatusResponse -- opencode.FileReadResponse +- opencode.File +- opencode.FileNode +- opencode.FileReadResponse Methods: -- client.File.List(ctx context.Context, query opencode.FileListParams) ([]opencode.FileListResponse, error) -- client.File.GetStatus(ctx context.Context) ([]opencode.FileGetStatusResponse, error) -- client.File.Read(ctx context.Context, query opencode.FileReadParams) (opencode.FileReadResponse, error) +- client.File.List(ctx context.Context, query opencode.FileListParams) ([]opencode.FileNode, error) +- client.File.Read(ctx context.Context, query opencode.FileReadParams) (opencode.FileReadResponse, error) +- client.File.Status(ctx context.Context) ([]opencode.File, error) -# Log - -Methods: - -- client.Log.Write(ctx context.Context, body opencode.LogWriteParams) (bool, error) - -# Agent +# Config Response Types: -- opencode.AgentListResponse +- opencode.AgentConfig +- opencode.Config +- opencode.KeybindsConfig +- opencode.McpLocalConfig +- opencode.McpRemoteConfig Methods: -- client.Agent.List(ctx context.Context) ([]opencode.AgentListResponse, error) +- client.Config.Get(ctx context.Context) (opencode.Config, error) + +# Command + +Response Types: + +- opencode.Command + +Methods: + +- client.Command.List(ctx context.Context) ([]opencode.Command, error) + +# Session + +Params Types: + +- opencode.AgentPartInputParam +- opencode.FilePartInputParam +- opencode.FilePartSourceUnionParam +- opencode.FilePartSourceTextParam +- opencode.FileSourceParam +- opencode.SymbolSourceParam +- opencode.TextPartInputParam + +Response Types: + +- opencode.AgentPart +- opencode.AssistantMessage +- opencode.FilePart +- opencode.FilePartSource +- opencode.FilePartSourceText +- opencode.FileSource +- opencode.Message +- opencode.Part +- opencode.ReasoningPart +- opencode.Session +- opencode.SnapshotPart +- opencode.StepFinishPart +- opencode.StepStartPart +- opencode.SymbolSource +- opencode.TextPart +- opencode.ToolPart +- opencode.ToolStateCompleted +- opencode.ToolStateError +- opencode.ToolStatePending +- opencode.ToolStateRunning +- opencode.UserMessage +- opencode.SessionChatResponse +- opencode.SessionCommandResponse +- opencode.SessionMessageResponse +- opencode.SessionMessagesResponse + +Methods: + +- client.Session.New(ctx context.Context, body opencode.SessionNewParams) (opencode.Session, error) +- client.Session.Update(ctx context.Context, id string, body opencode.SessionUpdateParams) (opencode.Session, error) +- client.Session.List(ctx context.Context) ([]opencode.Session, error) +- client.Session.Delete(ctx context.Context, id string) (bool, error) +- client.Session.Abort(ctx context.Context, id string) (bool, error) +- client.Session.Chat(ctx context.Context, id string, body opencode.SessionChatParams) (opencode.SessionChatResponse, error) +- client.Session.Children(ctx context.Context, id string) ([]opencode.Session, error) +- client.Session.Command(ctx context.Context, id string, body opencode.SessionCommandParams) (opencode.SessionCommandResponse, error) +- client.Session.Get(ctx context.Context, id string) (opencode.Session, error) +- client.Session.Init(ctx context.Context, id string, body opencode.SessionInitParams) (bool, error) +- client.Session.Message(ctx context.Context, id string, messageID string) (opencode.SessionMessageResponse, error) +- client.Session.Messages(ctx context.Context, id string) ([]opencode.SessionMessagesResponse, error) +- client.Session.Revert(ctx context.Context, id string, body opencode.SessionRevertParams) (opencode.Session, error) +- client.Session.Share(ctx context.Context, id string) (opencode.Session, error) +- client.Session.Shell(ctx context.Context, id string, body opencode.SessionShellParams) (opencode.AssistantMessage, error) +- client.Session.Summarize(ctx context.Context, id string, body opencode.SessionSummarizeParams) (bool, error) +- client.Session.Unrevert(ctx context.Context, id string) (opencode.Session, error) +- client.Session.Unshare(ctx context.Context, id string) (opencode.Session, error) + +## Permissions + +Response Types: + +- opencode.Permission + +Methods: + +- client.Session.Permissions.Respond(ctx context.Context, id string, permissionID string, body opencode.SessionPermissionRespondParams) (bool, error) # Tui Methods: -- client.Tui.AppendPrompt(ctx context.Context, body opencode.TuiAppendPromptParams) (bool, error) -- client.Tui.ClearPrompt(ctx context.Context) (bool, error) -- client.Tui.ExecuteCommand(ctx context.Context, body opencode.TuiExecuteCommandParams) (bool, error) -- client.Tui.OpenHelp(ctx context.Context) (bool, error) -- client.Tui.OpenModels(ctx context.Context) (bool, error) -- client.Tui.OpenSessions(ctx context.Context) (bool, error) -- client.Tui.OpenThemes(ctx context.Context) (bool, error) -- client.Tui.ShowToast(ctx context.Context, body opencode.TuiShowToastParams) (bool, error) -- client.Tui.SubmitPrompt(ctx context.Context) (bool, error) - -# Auth - -Methods: - -- client.Auth.SetCredentials(ctx context.Context, id string, body opencode.AuthSetCredentialsParams) (bool, error) +- client.Tui.AppendPrompt(ctx context.Context, body opencode.TuiAppendPromptParams) (bool, error) +- client.Tui.ClearPrompt(ctx context.Context) (bool, error) +- client.Tui.ExecuteCommand(ctx context.Context, body opencode.TuiExecuteCommandParams) (bool, error) +- client.Tui.OpenHelp(ctx context.Context) (bool, error) +- client.Tui.OpenModels(ctx context.Context) (bool, error) +- client.Tui.OpenSessions(ctx context.Context) (bool, error) +- client.Tui.OpenThemes(ctx context.Context) (bool, error) +- client.Tui.ShowToast(ctx context.Context, body opencode.TuiShowToastParams) (bool, error) +- client.Tui.SubmitPrompt(ctx context.Context) (bool, error) diff --git a/auth.go b/auth.go deleted file mode 100644 index df555b8..0000000 --- a/auth.go +++ /dev/null @@ -1,121 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package opencode - -import ( - "context" - "errors" - "fmt" - "net/http" - - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" - "github.com/stainless-sdks/opencode-go/packages/param" - "github.com/stainless-sdks/opencode-go/shared/constant" -) - -// AuthService contains methods and other services that help with interacting with -// the opencode API. -// -// Note, unlike clients, this service does not read variables from the environment -// automatically. You should not instantiate this service directly, and instead use -// the [NewAuthService] method instead. -type AuthService struct { - Options []option.RequestOption -} - -// NewAuthService generates a new service that applies the given options to each -// request. These options are applied after the parent client's options (if there -// is one), and before any request-specific options. -func NewAuthService(opts ...option.RequestOption) (r AuthService) { - r = AuthService{} - r.Options = opts - return -} - -// Set authentication credentials -func (r *AuthService) SetCredentials(ctx context.Context, id string, body AuthSetCredentialsParams, opts ...option.RequestOption) (res *bool, err error) { - opts = append(r.Options[:], opts...) - if id == "" { - err = errors.New("missing required id parameter") - return - } - path := fmt.Sprintf("auth/%s", id) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) - return -} - -type AuthSetCredentialsParams struct { - - // - // Request body variants - // - - // This field is a request body variant, only one variant field can be set. - OfOAuth *AuthSetCredentialsParamsBodyOAuth `json:",inline"` - // This field is a request body variant, only one variant field can be set. - OfAPI *AuthSetCredentialsParamsBodyAPI `json:",inline"` - // This field is a request body variant, only one variant field can be set. - OfWellknown *AuthSetCredentialsParamsBodyWellknown `json:",inline"` - - paramObj -} - -func (u AuthSetCredentialsParams) MarshalJSON() ([]byte, error) { - return param.MarshalUnion(u, u.OfOAuth, u.OfAPI, u.OfWellknown) -} -func (r *AuthSetCredentialsParams) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties Access, Expires, Refresh, Type are required. -type AuthSetCredentialsParamsBodyOAuth struct { - Access string `json:"access,required"` - Expires float64 `json:"expires,required"` - Refresh string `json:"refresh,required"` - // This field can be elided, and will marshal its zero value as "oauth". - Type constant.OAuth `json:"type,required"` - paramObj -} - -func (r AuthSetCredentialsParamsBodyOAuth) MarshalJSON() (data []byte, err error) { - type shadow AuthSetCredentialsParamsBodyOAuth - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *AuthSetCredentialsParamsBodyOAuth) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties Key, Type are required. -type AuthSetCredentialsParamsBodyAPI struct { - Key string `json:"key,required"` - // This field can be elided, and will marshal its zero value as "api". - Type constant.API `json:"type,required"` - paramObj -} - -func (r AuthSetCredentialsParamsBodyAPI) MarshalJSON() (data []byte, err error) { - type shadow AuthSetCredentialsParamsBodyAPI - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *AuthSetCredentialsParamsBodyAPI) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties Token, Key, Type are required. -type AuthSetCredentialsParamsBodyWellknown struct { - Token string `json:"token,required"` - Key string `json:"key,required"` - // This field can be elided, and will marshal its zero value as "wellknown". - Type constant.Wellknown `json:"type,required"` - paramObj -} - -func (r AuthSetCredentialsParamsBodyWellknown) MarshalJSON() (data []byte, err error) { - type shadow AuthSetCredentialsParamsBodyWellknown - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *AuthSetCredentialsParamsBodyWellknown) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} diff --git a/auth_test.go b/auth_test.go deleted file mode 100644 index 32f2111..0000000 --- a/auth_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package opencode_test - -import ( - "context" - "errors" - "os" - "testing" - - "github.com/stainless-sdks/opencode-go" - "github.com/stainless-sdks/opencode-go/internal/testutil" - "github.com/stainless-sdks/opencode-go/option" -) - -func TestAuthSetCredentials(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := opencode.NewClient( - option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), - ) - _, err := client.Auth.SetCredentials( - context.TODO(), - "id", - opencode.AuthSetCredentialsParams{ - OfOAuth: &opencode.AuthSetCredentialsParamsBodyOAuth{ - Access: "access", - Expires: 0, - Refresh: "refresh", - }, - }, - ) - if err != nil { - var apierr *opencode.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} diff --git a/client.go b/client.go index 64968e7..ca41c83 100644 --- a/client.go +++ b/client.go @@ -7,8 +7,8 @@ import ( "net/http" "os" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" ) // Client creates a struct with services and top level methods that help with @@ -16,52 +16,41 @@ import ( // directly, and instead use the [NewClient] method instead. type Client struct { Options []option.RequestOption - Project ProjectService - Event EventService - Config ConfigService - Session SessionService - Command CommandService - Find FindService - File FileService - Log LogService - Agent AgentService - Tui TuiService - Auth AuthService + Event *EventService + Find *FindService + File *FileService + Config *ConfigService + Command *CommandService + Session *SessionService + Tui *TuiService } -// DefaultClientOptions read from the environment (OPENCODE_API_KEY, -// OPENCODE_BASE_URL). This should be used to initialize new clients. +// DefaultClientOptions read from the environment (OPENCODE_BASE_URL). This should +// be used to initialize new clients. func DefaultClientOptions() []option.RequestOption { defaults := []option.RequestOption{option.WithEnvironmentProduction()} if o, ok := os.LookupEnv("OPENCODE_BASE_URL"); ok { defaults = append(defaults, option.WithBaseURL(o)) } - if o, ok := os.LookupEnv("OPENCODE_API_KEY"); ok { - defaults = append(defaults, option.WithAPIKey(o)) - } return defaults } // NewClient generates a new client with the default option read from the -// environment (OPENCODE_API_KEY, OPENCODE_BASE_URL). The option passed in as -// arguments are applied after these default arguments, and all option will be -// passed down to the services and requests that this client makes. -func NewClient(opts ...option.RequestOption) (r Client) { +// environment (OPENCODE_BASE_URL). The option passed in as arguments are applied +// after these default arguments, and all option will be passed down to the +// services and requests that this client makes. +func NewClient(opts ...option.RequestOption) (r *Client) { opts = append(DefaultClientOptions(), opts...) - r = Client{Options: opts} + r = &Client{Options: opts} - r.Project = NewProjectService(opts...) r.Event = NewEventService(opts...) - r.Config = NewConfigService(opts...) - r.Session = NewSessionService(opts...) - r.Command = NewCommandService(opts...) r.Find = NewFindService(opts...) r.File = NewFileService(opts...) - r.Log = NewLogService(opts...) - r.Agent = NewAgentService(opts...) + r.Config = NewConfigService(opts...) + r.Command = NewCommandService(opts...) + r.Session = NewSessionService(opts...) r.Tui = NewTuiService(opts...) - r.Auth = NewAuthService(opts...) return } @@ -97,40 +86,40 @@ func NewClient(opts ...option.RequestOption) (r Client) { // // For even greater flexibility, see [option.WithResponseInto] and // [option.WithResponseBodyInto]. -func (r *Client) Execute(ctx context.Context, method string, path string, params any, res any, opts ...option.RequestOption) error { +func (r *Client) Execute(ctx context.Context, method string, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { opts = append(r.Options, opts...) return requestconfig.ExecuteNewRequest(ctx, method, path, params, res, opts...) } // Get makes a GET request with the given URL, params, and optionally deserializes // to a response. See [Execute] documentation on the params and response. -func (r *Client) Get(ctx context.Context, path string, params any, res any, opts ...option.RequestOption) error { +func (r *Client) Get(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { return r.Execute(ctx, http.MethodGet, path, params, res, opts...) } // Post makes a POST request with the given URL, params, and optionally // deserializes to a response. See [Execute] documentation on the params and // response. -func (r *Client) Post(ctx context.Context, path string, params any, res any, opts ...option.RequestOption) error { +func (r *Client) Post(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { return r.Execute(ctx, http.MethodPost, path, params, res, opts...) } // Put makes a PUT request with the given URL, params, and optionally deserializes // to a response. See [Execute] documentation on the params and response. -func (r *Client) Put(ctx context.Context, path string, params any, res any, opts ...option.RequestOption) error { +func (r *Client) Put(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { return r.Execute(ctx, http.MethodPut, path, params, res, opts...) } // Patch makes a PATCH request with the given URL, params, and optionally // deserializes to a response. See [Execute] documentation on the params and // response. -func (r *Client) Patch(ctx context.Context, path string, params any, res any, opts ...option.RequestOption) error { +func (r *Client) Patch(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { return r.Execute(ctx, http.MethodPatch, path, params, res, opts...) } // Delete makes a DELETE request with the given URL, params, and optionally // deserializes to a response. See [Execute] documentation on the params and // response. -func (r *Client) Delete(ctx context.Context, path string, params any, res any, opts ...option.RequestOption) error { +func (r *Client) Delete(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { return r.Execute(ctx, http.MethodDelete, path, params, res, opts...) } diff --git a/client_test.go b/client_test.go index 0fe509f..0f5b820 100644 --- a/client_test.go +++ b/client_test.go @@ -11,9 +11,9 @@ import ( "testing" "time" - "github.com/stainless-sdks/opencode-go" - "github.com/stainless-sdks/opencode-go/internal" - "github.com/stainless-sdks/opencode-go/option" + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal" + "github.com/sst/opencode-sdk-go/option" ) type closureTransport struct { @@ -27,7 +27,6 @@ func (t *closureTransport) RoundTrip(req *http.Request) (*http.Response, error) func TestUserAgentHeader(t *testing.T) { var userAgent string client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -39,7 +38,7 @@ func TestUserAgentHeader(t *testing.T) { }, }), ) - client.Project.List(context.Background()) + client.Session.List(context.Background()) if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) { t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent) } @@ -48,7 +47,6 @@ func TestUserAgentHeader(t *testing.T) { func TestRetryAfter(t *testing.T) { retryCountHeaders := make([]string, 0) client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -63,7 +61,7 @@ func TestRetryAfter(t *testing.T) { }, }), ) - _, err := client.Project.List(context.Background()) + _, err := client.Session.List(context.Background()) if err == nil { t.Error("Expected there to be a cancel error") } @@ -82,7 +80,6 @@ func TestRetryAfter(t *testing.T) { func TestDeleteRetryCountHeader(t *testing.T) { retryCountHeaders := make([]string, 0) client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -98,7 +95,7 @@ func TestDeleteRetryCountHeader(t *testing.T) { }), option.WithHeaderDel("X-Stainless-Retry-Count"), ) - _, err := client.Project.List(context.Background()) + _, err := client.Session.List(context.Background()) if err == nil { t.Error("Expected there to be a cancel error") } @@ -112,7 +109,6 @@ func TestDeleteRetryCountHeader(t *testing.T) { func TestOverwriteRetryCountHeader(t *testing.T) { retryCountHeaders := make([]string, 0) client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -128,7 +124,7 @@ func TestOverwriteRetryCountHeader(t *testing.T) { }), option.WithHeader("X-Stainless-Retry-Count", "42"), ) - _, err := client.Project.List(context.Background()) + _, err := client.Session.List(context.Background()) if err == nil { t.Error("Expected there to be a cancel error") } @@ -142,7 +138,6 @@ func TestOverwriteRetryCountHeader(t *testing.T) { func TestRetryAfterMs(t *testing.T) { attempts := 0 client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -157,7 +152,7 @@ func TestRetryAfterMs(t *testing.T) { }, }), ) - _, err := client.Project.List(context.Background()) + _, err := client.Session.List(context.Background()) if err == nil { t.Error("Expected there to be a cancel error") } @@ -168,7 +163,6 @@ func TestRetryAfterMs(t *testing.T) { func TestContextCancel(t *testing.T) { client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -180,7 +174,7 @@ func TestContextCancel(t *testing.T) { ) cancelCtx, cancel := context.WithCancel(context.Background()) cancel() - _, err := client.Project.List(cancelCtx) + _, err := client.Session.List(cancelCtx) if err == nil { t.Error("Expected there to be a cancel error") } @@ -188,7 +182,6 @@ func TestContextCancel(t *testing.T) { func TestContextCancelDelay(t *testing.T) { client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -200,7 +193,7 @@ func TestContextCancelDelay(t *testing.T) { ) cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) defer cancel() - _, err := client.Project.List(cancelCtx) + _, err := client.Session.List(cancelCtx) if err == nil { t.Error("expected there to be a cancel error") } @@ -216,7 +209,6 @@ func TestContextDeadline(t *testing.T) { go func() { client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -226,7 +218,7 @@ func TestContextDeadline(t *testing.T) { }, }), ) - _, err := client.Project.List(deadlineCtx) + _, err := client.Session.List(deadlineCtx) if err == nil { t.Error("expected there to be a deadline error") } @@ -253,7 +245,6 @@ func TestContextDeadlineStreaming(t *testing.T) { go func() { client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -298,7 +289,6 @@ func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) { go func() { client := opencode.NewClient( - option.WithAPIKey("My API Key"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { diff --git a/command.go b/command.go index 4cc42db..9ca70c3 100644 --- a/command.go +++ b/command.go @@ -6,10 +6,9 @@ import ( "context" "net/http" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" - "github.com/stainless-sdks/opencode-go/packages/respjson" + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" ) // CommandService contains methods and other services that help with interacting @@ -25,40 +24,44 @@ type CommandService struct { // NewCommandService generates a new service that applies the given options to each // request. These options are applied after the parent client's options (if there // is one), and before any request-specific options. -func NewCommandService(opts ...option.RequestOption) (r CommandService) { - r = CommandService{} +func NewCommandService(opts ...option.RequestOption) (r *CommandService) { + r = &CommandService{} r.Options = opts return } // List all commands -func (r *CommandService) List(ctx context.Context, opts ...option.RequestOption) (res *[]CommandListResponse, err error) { +func (r *CommandService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Command, err error) { opts = append(r.Options[:], opts...) path := "command" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return } -type CommandListResponse struct { - Name string `json:"name,required"` - Template string `json:"template,required"` - Agent string `json:"agent"` - Description string `json:"description"` - Model string `json:"model"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Name respjson.Field - Template respjson.Field - Agent respjson.Field - Description respjson.Field - Model respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +type Command struct { + Name string `json:"name,required"` + Template string `json:"template,required"` + Agent string `json:"agent"` + Description string `json:"description"` + Model string `json:"model"` + JSON commandJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r CommandListResponse) RawJSON() string { return r.JSON.raw } -func (r *CommandListResponse) UnmarshalJSON(data []byte) error { +// commandJSON contains the JSON metadata for the struct [Command] +type commandJSON struct { + Name apijson.Field + Template apijson.Field + Agent apijson.Field + Description apijson.Field + Model apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Command) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } + +func (r commandJSON) RawJSON() string { + return r.raw +} diff --git a/command_test.go b/command_test.go index 568f8e1..5e62ffb 100644 --- a/command_test.go +++ b/command_test.go @@ -8,9 +8,9 @@ import ( "os" "testing" - "github.com/stainless-sdks/opencode-go" - "github.com/stainless-sdks/opencode-go/internal/testutil" - "github.com/stainless-sdks/opencode-go/option" + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" ) func TestCommandList(t *testing.T) { @@ -24,7 +24,6 @@ func TestCommandList(t *testing.T) { } client := opencode.NewClient( option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), ) _, err := client.Command.List(context.TODO()) if err != nil { diff --git a/config.go b/config.go index 59b0afb..800f63d 100644 --- a/config.go +++ b/config.go @@ -4,14 +4,13 @@ package opencode import ( "context" - "encoding/json" "net/http" + "reflect" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" - "github.com/stainless-sdks/opencode-go/packages/respjson" - "github.com/stainless-sdks/opencode-go/shared/constant" + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" + "github.com/tidwall/gjson" ) // ConfigService contains methods and other services that help with interacting @@ -27,68 +26,226 @@ type ConfigService struct { // NewConfigService generates a new service that applies the given options to each // request. These options are applied after the parent client's options (if there // is one), and before any request-specific options. -func NewConfigService(opts ...option.RequestOption) (r ConfigService) { - r = ConfigService{} +func NewConfigService(opts ...option.RequestOption) (r *ConfigService) { + r = &ConfigService{} r.Options = opts return } // Get config info -func (r *ConfigService) Get(ctx context.Context, opts ...option.RequestOption) (res *ConfigGetResponse, err error) { +func (r *ConfigService) Get(ctx context.Context, opts ...option.RequestOption) (res *Config, err error) { opts = append(r.Options[:], opts...) path := "config" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return } -// List all providers -func (r *ConfigService) ListProviders(ctx context.Context, opts ...option.RequestOption) (res *ConfigListProvidersResponse, err error) { - opts = append(r.Options[:], opts...) - path := "config/providers" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return +type AgentConfig struct { + // Description of when to use the agent + Description string `json:"description"` + Disable bool `json:"disable"` + Mode AgentConfigMode `json:"mode"` + Model string `json:"model"` + Permission AgentConfigPermission `json:"permission"` + Prompt string `json:"prompt"` + Temperature float64 `json:"temperature"` + Tools map[string]bool `json:"tools"` + TopP float64 `json:"top_p"` + ExtraFields map[string]interface{} `json:"-,extras"` + JSON agentConfigJSON `json:"-"` } -type ConfigGetResponse struct { +// agentConfigJSON contains the JSON metadata for the struct [AgentConfig] +type agentConfigJSON struct { + Description apijson.Field + Disable apijson.Field + Mode apijson.Field + Model apijson.Field + Permission apijson.Field + Prompt apijson.Field + Temperature apijson.Field + Tools apijson.Field + TopP apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AgentConfig) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r agentConfigJSON) RawJSON() string { + return r.raw +} + +type AgentConfigMode string + +const ( + AgentConfigModeSubagent AgentConfigMode = "subagent" + AgentConfigModePrimary AgentConfigMode = "primary" + AgentConfigModeAll AgentConfigMode = "all" +) + +func (r AgentConfigMode) IsKnown() bool { + switch r { + case AgentConfigModeSubagent, AgentConfigModePrimary, AgentConfigModeAll: + return true + } + return false +} + +type AgentConfigPermission struct { + Bash AgentConfigPermissionBashUnion `json:"bash"` + Edit AgentConfigPermissionEdit `json:"edit"` + Webfetch AgentConfigPermissionWebfetch `json:"webfetch"` + JSON agentConfigPermissionJSON `json:"-"` +} + +// agentConfigPermissionJSON contains the JSON metadata for the struct +// [AgentConfigPermission] +type agentConfigPermissionJSON struct { + Bash apijson.Field + Edit apijson.Field + Webfetch apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AgentConfigPermission) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r agentConfigPermissionJSON) RawJSON() string { + return r.raw +} + +// Union satisfied by [AgentConfigPermissionBashString] or +// [AgentConfigPermissionBashMap]. +type AgentConfigPermissionBashUnion interface { + implementsAgentConfigPermissionBashUnion() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*AgentConfigPermissionBashUnion)(nil)).Elem(), + "", + apijson.UnionVariant{ + TypeFilter: gjson.String, + Type: reflect.TypeOf(AgentConfigPermissionBashString("")), + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(AgentConfigPermissionBashMap{}), + }, + ) +} + +type AgentConfigPermissionBashString string + +const ( + AgentConfigPermissionBashStringAsk AgentConfigPermissionBashString = "ask" + AgentConfigPermissionBashStringAllow AgentConfigPermissionBashString = "allow" + AgentConfigPermissionBashStringDeny AgentConfigPermissionBashString = "deny" +) + +func (r AgentConfigPermissionBashString) IsKnown() bool { + switch r { + case AgentConfigPermissionBashStringAsk, AgentConfigPermissionBashStringAllow, AgentConfigPermissionBashStringDeny: + return true + } + return false +} + +func (r AgentConfigPermissionBashString) implementsAgentConfigPermissionBashUnion() {} + +type AgentConfigPermissionBashMap map[string]AgentConfigPermissionBashMapItem + +func (r AgentConfigPermissionBashMap) implementsAgentConfigPermissionBashUnion() {} + +type AgentConfigPermissionBashMapItem string + +const ( + AgentConfigPermissionBashMapAsk AgentConfigPermissionBashMapItem = "ask" + AgentConfigPermissionBashMapAllow AgentConfigPermissionBashMapItem = "allow" + AgentConfigPermissionBashMapDeny AgentConfigPermissionBashMapItem = "deny" +) + +func (r AgentConfigPermissionBashMapItem) IsKnown() bool { + switch r { + case AgentConfigPermissionBashMapAsk, AgentConfigPermissionBashMapAllow, AgentConfigPermissionBashMapDeny: + return true + } + return false +} + +type AgentConfigPermissionEdit string + +const ( + AgentConfigPermissionEditAsk AgentConfigPermissionEdit = "ask" + AgentConfigPermissionEditAllow AgentConfigPermissionEdit = "allow" + AgentConfigPermissionEditDeny AgentConfigPermissionEdit = "deny" +) + +func (r AgentConfigPermissionEdit) IsKnown() bool { + switch r { + case AgentConfigPermissionEditAsk, AgentConfigPermissionEditAllow, AgentConfigPermissionEditDeny: + return true + } + return false +} + +type AgentConfigPermissionWebfetch string + +const ( + AgentConfigPermissionWebfetchAsk AgentConfigPermissionWebfetch = "ask" + AgentConfigPermissionWebfetchAllow AgentConfigPermissionWebfetch = "allow" + AgentConfigPermissionWebfetchDeny AgentConfigPermissionWebfetch = "deny" +) + +func (r AgentConfigPermissionWebfetch) IsKnown() bool { + switch r { + case AgentConfigPermissionWebfetchAsk, AgentConfigPermissionWebfetchAllow, AgentConfigPermissionWebfetchDeny: + return true + } + return false +} + +type Config struct { // JSON schema reference for configuration validation Schema string `json:"$schema"` // Agent configuration, see https://opencode.ai/docs/agent - Agent ConfigGetResponseAgent `json:"agent"` + Agent ConfigAgent `json:"agent"` // @deprecated Use 'share' field instead. Share newly created sessions // automatically Autoshare bool `json:"autoshare"` // Automatically update to the latest version Autoupdate bool `json:"autoupdate"` // Command configuration, see https://opencode.ai/docs/commands - Command map[string]ConfigGetResponseCommand `json:"command"` + Command map[string]ConfigCommand `json:"command"` // Disable providers that are loaded automatically - DisabledProviders []string `json:"disabled_providers"` - Experimental ConfigGetResponseExperimental `json:"experimental"` - Formatter map[string]ConfigGetResponseFormatter `json:"formatter"` + DisabledProviders []string `json:"disabled_providers"` + Experimental ConfigExperimental `json:"experimental"` + Formatter map[string]ConfigFormatter `json:"formatter"` // Additional instruction files or patterns to include Instructions []string `json:"instructions"` // Custom keybind configurations - Keybinds ConfigGetResponseKeybinds `json:"keybinds"` + Keybinds KeybindsConfig `json:"keybinds"` // @deprecated Always uses stretch layout. - // - // Any of "auto", "stretch". - Layout ConfigGetResponseLayout `json:"layout"` - Lsp map[string]ConfigGetResponseLspUnion `json:"lsp"` + Layout ConfigLayout `json:"layout"` + Lsp map[string]ConfigLsp `json:"lsp"` // MCP (Model Context Protocol) server configurations - Mcp map[string]ConfigGetResponseMcpUnion `json:"mcp"` + Mcp map[string]ConfigMcp `json:"mcp"` // @deprecated Use `agent` field instead. - Mode ConfigGetResponseMode `json:"mode"` + Mode ConfigMode `json:"mode"` // Model to use in the format of provider/model, eg anthropic/claude-2 - Model string `json:"model"` - Permission ConfigGetResponsePermission `json:"permission"` - Plugin []string `json:"plugin"` + Model string `json:"model"` + Permission ConfigPermission `json:"permission"` + Plugin []string `json:"plugin"` // Custom provider configurations and model overrides - Provider map[string]ConfigGetResponseProvider `json:"provider"` + Provider map[string]ConfigProvider `json:"provider"` // Control sharing behavior:'manual' allows manual sharing via commands, 'auto' // enables automatic sharing, 'disabled' disables all sharing - // - // Any of "manual", "auto", "disabled". - Share ConfigGetResponseShare `json:"share"` + Share ConfigShare `json:"share"` // Small model to use for tasks like title generation in the format of // provider/model SmallModel string `json:"small_model"` @@ -97,476 +254,781 @@ type ConfigGetResponse struct { Theme string `json:"theme"` Tools map[string]bool `json:"tools"` // TUI specific settings - Tui ConfigGetResponseTui `json:"tui"` + Tui ConfigTui `json:"tui"` // Custom username to display in conversations instead of system username - Username string `json:"username"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Schema respjson.Field - Agent respjson.Field - Autoshare respjson.Field - Autoupdate respjson.Field - Command respjson.Field - DisabledProviders respjson.Field - Experimental respjson.Field - Formatter respjson.Field - Instructions respjson.Field - Keybinds respjson.Field - Layout respjson.Field - Lsp respjson.Field - Mcp respjson.Field - Mode respjson.Field - Model respjson.Field - Permission respjson.Field - Plugin respjson.Field - Provider respjson.Field - Share respjson.Field - SmallModel respjson.Field - Snapshot respjson.Field - Theme respjson.Field - Tools respjson.Field - Tui respjson.Field - Username respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` + Username string `json:"username"` + JSON configJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponse) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponse) UnmarshalJSON(data []byte) error { +// configJSON contains the JSON metadata for the struct [Config] +type configJSON struct { + Schema apijson.Field + Agent apijson.Field + Autoshare apijson.Field + Autoupdate apijson.Field + Command apijson.Field + DisabledProviders apijson.Field + Experimental apijson.Field + Formatter apijson.Field + Instructions apijson.Field + Keybinds apijson.Field + Layout apijson.Field + Lsp apijson.Field + Mcp apijson.Field + Mode apijson.Field + Model apijson.Field + Permission apijson.Field + Plugin apijson.Field + Provider apijson.Field + Share apijson.Field + SmallModel apijson.Field + Snapshot apijson.Field + Theme apijson.Field + Tools apijson.Field + Tui apijson.Field + Username apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Config) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } +func (r configJSON) RawJSON() string { + return r.raw +} + // Agent configuration, see https://opencode.ai/docs/agent -type ConfigGetResponseAgent struct { - Build ConfigGetResponseAgentBuild `json:"build"` - General ConfigGetResponseAgentGeneral `json:"general"` - Plan ConfigGetResponseAgentPlan `json:"plan"` - ExtraFields map[string]ConfigGetResponseAgent `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Build respjson.Field - General respjson.Field - Plan respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +type ConfigAgent struct { + Build AgentConfig `json:"build"` + General AgentConfig `json:"general"` + Plan AgentConfig `json:"plan"` + ExtraFields map[string]AgentConfig `json:"-,extras"` + JSON configAgentJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseAgent) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseAgent) UnmarshalJSON(data []byte) error { +// configAgentJSON contains the JSON metadata for the struct [ConfigAgent] +type configAgentJSON struct { + Build apijson.Field + General apijson.Field + Plan apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigAgent) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseAgentBuild struct { - // Description of when to use the agent - Description string `json:"description"` - Disable bool `json:"disable"` - // Any of "subagent", "primary", "all". - Mode ConfigGetResponseAgentBuildMode `json:"mode"` - Model string `json:"model"` - Permission ConfigGetResponseAgentBuildPermission `json:"permission"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - ExtraFields map[string]any `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Description respjson.Field - Disable respjson.Field - Mode respjson.Field - Model respjson.Field - Permission respjson.Field - Prompt respjson.Field - Temperature respjson.Field - Tools respjson.Field - TopP respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configAgentJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseAgentBuild) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseAgentBuild) UnmarshalJSON(data []byte) error { +type ConfigCommand struct { + Template string `json:"template,required"` + Agent string `json:"agent"` + Description string `json:"description"` + Model string `json:"model"` + JSON configCommandJSON `json:"-"` +} + +// configCommandJSON contains the JSON metadata for the struct [ConfigCommand] +type configCommandJSON struct { + Template apijson.Field + Agent apijson.Field + Description apijson.Field + Model apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigCommand) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseAgentBuildMode string - -const ( - ConfigGetResponseAgentBuildModeSubagent ConfigGetResponseAgentBuildMode = "subagent" - ConfigGetResponseAgentBuildModePrimary ConfigGetResponseAgentBuildMode = "primary" - ConfigGetResponseAgentBuildModeAll ConfigGetResponseAgentBuildMode = "all" -) - -type ConfigGetResponseAgentBuildPermission struct { - Bash ConfigGetResponseAgentBuildPermissionBashUnion `json:"bash"` - // Any of "ask", "allow", "deny". - Edit ConfigGetResponseAgentBuildPermissionEdit `json:"edit"` - // Any of "ask", "allow", "deny". - Webfetch ConfigGetResponseAgentBuildPermissionWebfetch `json:"webfetch"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Bash respjson.Field - Edit respjson.Field - Webfetch respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configCommandJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseAgentBuildPermission) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseAgentBuildPermission) UnmarshalJSON(data []byte) error { +type ConfigExperimental struct { + Hook ConfigExperimentalHook `json:"hook"` + JSON configExperimentalJSON `json:"-"` +} + +// configExperimentalJSON contains the JSON metadata for the struct +// [ConfigExperimental] +type configExperimentalJSON struct { + Hook apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimental) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseAgentBuildPermissionBashString string - -const ( - ConfigGetResponseAgentBuildPermissionBashStringAsk ConfigGetResponseAgentBuildPermissionBashString = "ask" - ConfigGetResponseAgentBuildPermissionBashStringAllow ConfigGetResponseAgentBuildPermissionBashString = "allow" - ConfigGetResponseAgentBuildPermissionBashStringDeny ConfigGetResponseAgentBuildPermissionBashString = "deny" -) - -type ConfigGetResponseAgentBuildPermissionBashMapItem string - -const ( - ConfigGetResponseAgentBuildPermissionBashMapItemAsk ConfigGetResponseAgentBuildPermissionBashMapItem = "ask" - ConfigGetResponseAgentBuildPermissionBashMapItemAllow ConfigGetResponseAgentBuildPermissionBashMapItem = "allow" - ConfigGetResponseAgentBuildPermissionBashMapItemDeny ConfigGetResponseAgentBuildPermissionBashMapItem = "deny" -) - -type ConfigGetResponseAgentBuildPermissionEdit string - -const ( - ConfigGetResponseAgentBuildPermissionEditAsk ConfigGetResponseAgentBuildPermissionEdit = "ask" - ConfigGetResponseAgentBuildPermissionEditAllow ConfigGetResponseAgentBuildPermissionEdit = "allow" - ConfigGetResponseAgentBuildPermissionEditDeny ConfigGetResponseAgentBuildPermissionEdit = "deny" -) - -type ConfigGetResponseAgentBuildPermissionWebfetch string - -const ( - ConfigGetResponseAgentBuildPermissionWebfetchAsk ConfigGetResponseAgentBuildPermissionWebfetch = "ask" - ConfigGetResponseAgentBuildPermissionWebfetchAllow ConfigGetResponseAgentBuildPermissionWebfetch = "allow" - ConfigGetResponseAgentBuildPermissionWebfetchDeny ConfigGetResponseAgentBuildPermissionWebfetch = "deny" -) - -type ConfigGetResponseAgentGeneral struct { - // Description of when to use the agent - Description string `json:"description"` - Disable bool `json:"disable"` - // Any of "subagent", "primary", "all". - Mode ConfigGetResponseAgentGeneralMode `json:"mode"` - Model string `json:"model"` - Permission ConfigGetResponseAgentGeneralPermission `json:"permission"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - ExtraFields map[string]any `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Description respjson.Field - Disable respjson.Field - Mode respjson.Field - Model respjson.Field - Permission respjson.Field - Prompt respjson.Field - Temperature respjson.Field - Tools respjson.Field - TopP respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configExperimentalJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseAgentGeneral) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseAgentGeneral) UnmarshalJSON(data []byte) error { +type ConfigExperimentalHook struct { + FileEdited map[string][]ConfigExperimentalHookFileEdited `json:"file_edited"` + SessionCompleted []ConfigExperimentalHookSessionCompleted `json:"session_completed"` + JSON configExperimentalHookJSON `json:"-"` +} + +// configExperimentalHookJSON contains the JSON metadata for the struct +// [ConfigExperimentalHook] +type configExperimentalHookJSON struct { + FileEdited apijson.Field + SessionCompleted apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimentalHook) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseAgentGeneralMode string - -const ( - ConfigGetResponseAgentGeneralModeSubagent ConfigGetResponseAgentGeneralMode = "subagent" - ConfigGetResponseAgentGeneralModePrimary ConfigGetResponseAgentGeneralMode = "primary" - ConfigGetResponseAgentGeneralModeAll ConfigGetResponseAgentGeneralMode = "all" -) - -type ConfigGetResponseAgentGeneralPermission struct { - Bash ConfigGetResponseAgentGeneralPermissionBashUnion `json:"bash"` - // Any of "ask", "allow", "deny". - Edit ConfigGetResponseAgentGeneralPermissionEdit `json:"edit"` - // Any of "ask", "allow", "deny". - Webfetch ConfigGetResponseAgentGeneralPermissionWebfetch `json:"webfetch"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Bash respjson.Field - Edit respjson.Field - Webfetch respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configExperimentalHookJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseAgentGeneralPermission) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseAgentGeneralPermission) UnmarshalJSON(data []byte) error { +type ConfigExperimentalHookFileEdited struct { + Command []string `json:"command,required"` + Environment map[string]string `json:"environment"` + JSON configExperimentalHookFileEditedJSON `json:"-"` +} + +// configExperimentalHookFileEditedJSON contains the JSON metadata for the struct +// [ConfigExperimentalHookFileEdited] +type configExperimentalHookFileEditedJSON struct { + Command apijson.Field + Environment apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimentalHookFileEdited) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseAgentGeneralPermissionBashString string - -const ( - ConfigGetResponseAgentGeneralPermissionBashStringAsk ConfigGetResponseAgentGeneralPermissionBashString = "ask" - ConfigGetResponseAgentGeneralPermissionBashStringAllow ConfigGetResponseAgentGeneralPermissionBashString = "allow" - ConfigGetResponseAgentGeneralPermissionBashStringDeny ConfigGetResponseAgentGeneralPermissionBashString = "deny" -) - -type ConfigGetResponseAgentGeneralPermissionBashMapItem string - -const ( - ConfigGetResponseAgentGeneralPermissionBashMapItemAsk ConfigGetResponseAgentGeneralPermissionBashMapItem = "ask" - ConfigGetResponseAgentGeneralPermissionBashMapItemAllow ConfigGetResponseAgentGeneralPermissionBashMapItem = "allow" - ConfigGetResponseAgentGeneralPermissionBashMapItemDeny ConfigGetResponseAgentGeneralPermissionBashMapItem = "deny" -) - -type ConfigGetResponseAgentGeneralPermissionEdit string - -const ( - ConfigGetResponseAgentGeneralPermissionEditAsk ConfigGetResponseAgentGeneralPermissionEdit = "ask" - ConfigGetResponseAgentGeneralPermissionEditAllow ConfigGetResponseAgentGeneralPermissionEdit = "allow" - ConfigGetResponseAgentGeneralPermissionEditDeny ConfigGetResponseAgentGeneralPermissionEdit = "deny" -) - -type ConfigGetResponseAgentGeneralPermissionWebfetch string - -const ( - ConfigGetResponseAgentGeneralPermissionWebfetchAsk ConfigGetResponseAgentGeneralPermissionWebfetch = "ask" - ConfigGetResponseAgentGeneralPermissionWebfetchAllow ConfigGetResponseAgentGeneralPermissionWebfetch = "allow" - ConfigGetResponseAgentGeneralPermissionWebfetchDeny ConfigGetResponseAgentGeneralPermissionWebfetch = "deny" -) - -type ConfigGetResponseAgentPlan struct { - // Description of when to use the agent - Description string `json:"description"` - Disable bool `json:"disable"` - // Any of "subagent", "primary", "all". - Mode ConfigGetResponseAgentPlanMode `json:"mode"` - Model string `json:"model"` - Permission ConfigGetResponseAgentPlanPermission `json:"permission"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - ExtraFields map[string]any `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Description respjson.Field - Disable respjson.Field - Mode respjson.Field - Model respjson.Field - Permission respjson.Field - Prompt respjson.Field - Temperature respjson.Field - Tools respjson.Field - TopP respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configExperimentalHookFileEditedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseAgentPlan) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseAgentPlan) UnmarshalJSON(data []byte) error { +type ConfigExperimentalHookSessionCompleted struct { + Command []string `json:"command,required"` + Environment map[string]string `json:"environment"` + JSON configExperimentalHookSessionCompletedJSON `json:"-"` +} + +// configExperimentalHookSessionCompletedJSON contains the JSON metadata for the +// struct [ConfigExperimentalHookSessionCompleted] +type configExperimentalHookSessionCompletedJSON struct { + Command apijson.Field + Environment apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimentalHookSessionCompleted) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseAgentPlanMode string +func (r configExperimentalHookSessionCompletedJSON) RawJSON() string { + return r.raw +} + +type ConfigFormatter struct { + Command []string `json:"command"` + Disabled bool `json:"disabled"` + Environment map[string]string `json:"environment"` + Extensions []string `json:"extensions"` + JSON configFormatterJSON `json:"-"` +} + +// configFormatterJSON contains the JSON metadata for the struct [ConfigFormatter] +type configFormatterJSON struct { + Command apijson.Field + Disabled apijson.Field + Environment apijson.Field + Extensions apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigFormatter) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configFormatterJSON) RawJSON() string { + return r.raw +} + +// @deprecated Always uses stretch layout. +type ConfigLayout string const ( - ConfigGetResponseAgentPlanModeSubagent ConfigGetResponseAgentPlanMode = "subagent" - ConfigGetResponseAgentPlanModePrimary ConfigGetResponseAgentPlanMode = "primary" - ConfigGetResponseAgentPlanModeAll ConfigGetResponseAgentPlanMode = "all" + ConfigLayoutAuto ConfigLayout = "auto" + ConfigLayoutStretch ConfigLayout = "stretch" ) -type ConfigGetResponseAgentPlanPermission struct { - Bash ConfigGetResponseAgentPlanPermissionBashUnion `json:"bash"` - // Any of "ask", "allow", "deny". - Edit ConfigGetResponseAgentPlanPermissionEdit `json:"edit"` - // Any of "ask", "allow", "deny". - Webfetch ConfigGetResponseAgentPlanPermissionWebfetch `json:"webfetch"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Bash respjson.Field - Edit respjson.Field - Webfetch respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r ConfigLayout) IsKnown() bool { + switch r { + case ConfigLayoutAuto, ConfigLayoutStretch: + return true + } + return false } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseAgentPlanPermission) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseAgentPlanPermission) UnmarshalJSON(data []byte) error { +type ConfigLsp struct { + // This field can have the runtime type of [[]string]. + Command interface{} `json:"command"` + Disabled bool `json:"disabled"` + // This field can have the runtime type of [map[string]string]. + Env interface{} `json:"env"` + // This field can have the runtime type of [[]string]. + Extensions interface{} `json:"extensions"` + // This field can have the runtime type of [map[string]interface{}]. + Initialization interface{} `json:"initialization"` + JSON configLspJSON `json:"-"` + union ConfigLspUnion +} + +// configLspJSON contains the JSON metadata for the struct [ConfigLsp] +type configLspJSON struct { + Command apijson.Field + Disabled apijson.Field + Env apijson.Field + Extensions apijson.Field + Initialization apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r configLspJSON) RawJSON() string { + return r.raw +} + +func (r *ConfigLsp) UnmarshalJSON(data []byte) (err error) { + *r = ConfigLsp{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [ConfigLspUnion] interface which you can cast to the specific +// types for more type safety. +// +// Possible runtime types of the union are [ConfigLspDisabled], [ConfigLspObject]. +func (r ConfigLsp) AsUnion() ConfigLspUnion { + return r.union +} + +// Union satisfied by [ConfigLspDisabled] or [ConfigLspObject]. +type ConfigLspUnion interface { + implementsConfigLsp() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*ConfigLspUnion)(nil)).Elem(), + "", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ConfigLspDisabled{}), + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ConfigLspObject{}), + }, + ) +} + +type ConfigLspDisabled struct { + Disabled ConfigLspDisabledDisabled `json:"disabled,required"` + JSON configLspDisabledJSON `json:"-"` +} + +// configLspDisabledJSON contains the JSON metadata for the struct +// [ConfigLspDisabled] +type configLspDisabledJSON struct { + Disabled apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigLspDisabled) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseAgentPlanPermissionBashString string +func (r configLspDisabledJSON) RawJSON() string { + return r.raw +} + +func (r ConfigLspDisabled) implementsConfigLsp() {} + +type ConfigLspDisabledDisabled bool const ( - ConfigGetResponseAgentPlanPermissionBashStringAsk ConfigGetResponseAgentPlanPermissionBashString = "ask" - ConfigGetResponseAgentPlanPermissionBashStringAllow ConfigGetResponseAgentPlanPermissionBashString = "allow" - ConfigGetResponseAgentPlanPermissionBashStringDeny ConfigGetResponseAgentPlanPermissionBashString = "deny" + ConfigLspDisabledDisabledTrue ConfigLspDisabledDisabled = true ) -type ConfigGetResponseAgentPlanPermissionBashMapItem string +func (r ConfigLspDisabledDisabled) IsKnown() bool { + switch r { + case ConfigLspDisabledDisabledTrue: + return true + } + return false +} + +type ConfigLspObject struct { + Command []string `json:"command,required"` + Disabled bool `json:"disabled"` + Env map[string]string `json:"env"` + Extensions []string `json:"extensions"` + Initialization map[string]interface{} `json:"initialization"` + JSON configLspObjectJSON `json:"-"` +} + +// configLspObjectJSON contains the JSON metadata for the struct [ConfigLspObject] +type configLspObjectJSON struct { + Command apijson.Field + Disabled apijson.Field + Env apijson.Field + Extensions apijson.Field + Initialization apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigLspObject) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configLspObjectJSON) RawJSON() string { + return r.raw +} + +func (r ConfigLspObject) implementsConfigLsp() {} + +type ConfigMcp struct { + // Type of MCP server connection + Type ConfigMcpType `json:"type,required"` + // This field can have the runtime type of [[]string]. + Command interface{} `json:"command"` + // Enable or disable the MCP server on startup + Enabled bool `json:"enabled"` + // This field can have the runtime type of [map[string]string]. + Environment interface{} `json:"environment"` + // This field can have the runtime type of [map[string]string]. + Headers interface{} `json:"headers"` + // URL of the remote MCP server + URL string `json:"url"` + JSON configMcpJSON `json:"-"` + union ConfigMcpUnion +} + +// configMcpJSON contains the JSON metadata for the struct [ConfigMcp] +type configMcpJSON struct { + Type apijson.Field + Command apijson.Field + Enabled apijson.Field + Environment apijson.Field + Headers apijson.Field + URL apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r configMcpJSON) RawJSON() string { + return r.raw +} + +func (r *ConfigMcp) UnmarshalJSON(data []byte) (err error) { + *r = ConfigMcp{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [ConfigMcpUnion] interface which you can cast to the specific +// types for more type safety. +// +// Possible runtime types of the union are [McpLocalConfig], [McpRemoteConfig]. +func (r ConfigMcp) AsUnion() ConfigMcpUnion { + return r.union +} + +// Union satisfied by [McpLocalConfig] or [McpRemoteConfig]. +type ConfigMcpUnion interface { + implementsConfigMcp() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*ConfigMcpUnion)(nil)).Elem(), + "type", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(McpLocalConfig{}), + DiscriminatorValue: "local", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(McpRemoteConfig{}), + DiscriminatorValue: "remote", + }, + ) +} + +// Type of MCP server connection +type ConfigMcpType string const ( - ConfigGetResponseAgentPlanPermissionBashMapItemAsk ConfigGetResponseAgentPlanPermissionBashMapItem = "ask" - ConfigGetResponseAgentPlanPermissionBashMapItemAllow ConfigGetResponseAgentPlanPermissionBashMapItem = "allow" - ConfigGetResponseAgentPlanPermissionBashMapItemDeny ConfigGetResponseAgentPlanPermissionBashMapItem = "deny" + ConfigMcpTypeLocal ConfigMcpType = "local" + ConfigMcpTypeRemote ConfigMcpType = "remote" ) -type ConfigGetResponseAgentPlanPermissionEdit string +func (r ConfigMcpType) IsKnown() bool { + switch r { + case ConfigMcpTypeLocal, ConfigMcpTypeRemote: + return true + } + return false +} + +// @deprecated Use `agent` field instead. +type ConfigMode struct { + Build AgentConfig `json:"build"` + Plan AgentConfig `json:"plan"` + ExtraFields map[string]AgentConfig `json:"-,extras"` + JSON configModeJSON `json:"-"` +} + +// configModeJSON contains the JSON metadata for the struct [ConfigMode] +type configModeJSON struct { + Build apijson.Field + Plan apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigMode) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configModeJSON) RawJSON() string { + return r.raw +} + +type ConfigPermission struct { + Bash ConfigPermissionBashUnion `json:"bash"` + Edit ConfigPermissionEdit `json:"edit"` + Webfetch ConfigPermissionWebfetch `json:"webfetch"` + JSON configPermissionJSON `json:"-"` +} + +// configPermissionJSON contains the JSON metadata for the struct +// [ConfigPermission] +type configPermissionJSON struct { + Bash apijson.Field + Edit apijson.Field + Webfetch apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigPermission) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configPermissionJSON) RawJSON() string { + return r.raw +} + +// Union satisfied by [ConfigPermissionBashString] or [ConfigPermissionBashMap]. +type ConfigPermissionBashUnion interface { + implementsConfigPermissionBashUnion() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*ConfigPermissionBashUnion)(nil)).Elem(), + "", + apijson.UnionVariant{ + TypeFilter: gjson.String, + Type: reflect.TypeOf(ConfigPermissionBashString("")), + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ConfigPermissionBashMap{}), + }, + ) +} + +type ConfigPermissionBashString string const ( - ConfigGetResponseAgentPlanPermissionEditAsk ConfigGetResponseAgentPlanPermissionEdit = "ask" - ConfigGetResponseAgentPlanPermissionEditAllow ConfigGetResponseAgentPlanPermissionEdit = "allow" - ConfigGetResponseAgentPlanPermissionEditDeny ConfigGetResponseAgentPlanPermissionEdit = "deny" + ConfigPermissionBashStringAsk ConfigPermissionBashString = "ask" + ConfigPermissionBashStringAllow ConfigPermissionBashString = "allow" + ConfigPermissionBashStringDeny ConfigPermissionBashString = "deny" ) -type ConfigGetResponseAgentPlanPermissionWebfetch string +func (r ConfigPermissionBashString) IsKnown() bool { + switch r { + case ConfigPermissionBashStringAsk, ConfigPermissionBashStringAllow, ConfigPermissionBashStringDeny: + return true + } + return false +} + +func (r ConfigPermissionBashString) implementsConfigPermissionBashUnion() {} + +type ConfigPermissionBashMap map[string]ConfigPermissionBashMapItem + +func (r ConfigPermissionBashMap) implementsConfigPermissionBashUnion() {} + +type ConfigPermissionBashMapItem string const ( - ConfigGetResponseAgentPlanPermissionWebfetchAsk ConfigGetResponseAgentPlanPermissionWebfetch = "ask" - ConfigGetResponseAgentPlanPermissionWebfetchAllow ConfigGetResponseAgentPlanPermissionWebfetch = "allow" - ConfigGetResponseAgentPlanPermissionWebfetchDeny ConfigGetResponseAgentPlanPermissionWebfetch = "deny" + ConfigPermissionBashMapAsk ConfigPermissionBashMapItem = "ask" + ConfigPermissionBashMapAllow ConfigPermissionBashMapItem = "allow" + ConfigPermissionBashMapDeny ConfigPermissionBashMapItem = "deny" ) -type ConfigGetResponseCommand struct { - Template string `json:"template,required"` - Agent string `json:"agent"` - Description string `json:"description"` - Model string `json:"model"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Template respjson.Field - Agent respjson.Field - Description respjson.Field - Model respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r ConfigPermissionBashMapItem) IsKnown() bool { + switch r { + case ConfigPermissionBashMapAsk, ConfigPermissionBashMapAllow, ConfigPermissionBashMapDeny: + return true + } + return false } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseCommand) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseCommand) UnmarshalJSON(data []byte) error { +type ConfigPermissionEdit string + +const ( + ConfigPermissionEditAsk ConfigPermissionEdit = "ask" + ConfigPermissionEditAllow ConfigPermissionEdit = "allow" + ConfigPermissionEditDeny ConfigPermissionEdit = "deny" +) + +func (r ConfigPermissionEdit) IsKnown() bool { + switch r { + case ConfigPermissionEditAsk, ConfigPermissionEditAllow, ConfigPermissionEditDeny: + return true + } + return false +} + +type ConfigPermissionWebfetch string + +const ( + ConfigPermissionWebfetchAsk ConfigPermissionWebfetch = "ask" + ConfigPermissionWebfetchAllow ConfigPermissionWebfetch = "allow" + ConfigPermissionWebfetchDeny ConfigPermissionWebfetch = "deny" +) + +func (r ConfigPermissionWebfetch) IsKnown() bool { + switch r { + case ConfigPermissionWebfetchAsk, ConfigPermissionWebfetchAllow, ConfigPermissionWebfetchDeny: + return true + } + return false +} + +type ConfigProvider struct { + ID string `json:"id"` + API string `json:"api"` + Env []string `json:"env"` + Models map[string]ConfigProviderModel `json:"models"` + Name string `json:"name"` + Npm string `json:"npm"` + Options ConfigProviderOptions `json:"options"` + JSON configProviderJSON `json:"-"` +} + +// configProviderJSON contains the JSON metadata for the struct [ConfigProvider] +type configProviderJSON struct { + ID apijson.Field + API apijson.Field + Env apijson.Field + Models apijson.Field + Name apijson.Field + Npm apijson.Field + Options apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProvider) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseExperimental struct { - Hook ConfigGetResponseExperimentalHook `json:"hook"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Hook respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configProviderJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseExperimental) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseExperimental) UnmarshalJSON(data []byte) error { +type ConfigProviderModel struct { + ID string `json:"id"` + Attachment bool `json:"attachment"` + Cost ConfigProviderModelsCost `json:"cost"` + Limit ConfigProviderModelsLimit `json:"limit"` + Name string `json:"name"` + Options map[string]interface{} `json:"options"` + Reasoning bool `json:"reasoning"` + ReleaseDate string `json:"release_date"` + Temperature bool `json:"temperature"` + ToolCall bool `json:"tool_call"` + JSON configProviderModelJSON `json:"-"` +} + +// configProviderModelJSON contains the JSON metadata for the struct +// [ConfigProviderModel] +type configProviderModelJSON struct { + ID apijson.Field + Attachment apijson.Field + Cost apijson.Field + Limit apijson.Field + Name apijson.Field + Options apijson.Field + Reasoning apijson.Field + ReleaseDate apijson.Field + Temperature apijson.Field + ToolCall apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderModel) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseExperimentalHook struct { - FileEdited map[string][]ConfigGetResponseExperimentalHookFileEdited `json:"file_edited"` - SessionCompleted []ConfigGetResponseExperimentalHookSessionCompleted `json:"session_completed"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - FileEdited respjson.Field - SessionCompleted respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configProviderModelJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseExperimentalHook) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseExperimentalHook) UnmarshalJSON(data []byte) error { +type ConfigProviderModelsCost struct { + Input float64 `json:"input,required"` + Output float64 `json:"output,required"` + CacheRead float64 `json:"cache_read"` + CacheWrite float64 `json:"cache_write"` + JSON configProviderModelsCostJSON `json:"-"` +} + +// configProviderModelsCostJSON contains the JSON metadata for the struct +// [ConfigProviderModelsCost] +type configProviderModelsCostJSON struct { + Input apijson.Field + Output apijson.Field + CacheRead apijson.Field + CacheWrite apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderModelsCost) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseExperimentalHookFileEdited struct { - Command []string `json:"command,required"` - Environment map[string]string `json:"environment"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Command respjson.Field - Environment respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configProviderModelsCostJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseExperimentalHookFileEdited) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseExperimentalHookFileEdited) UnmarshalJSON(data []byte) error { +type ConfigProviderModelsLimit struct { + Context float64 `json:"context,required"` + Output float64 `json:"output,required"` + JSON configProviderModelsLimitJSON `json:"-"` +} + +// configProviderModelsLimitJSON contains the JSON metadata for the struct +// [ConfigProviderModelsLimit] +type configProviderModelsLimitJSON struct { + Context apijson.Field + Output apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderModelsLimit) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseExperimentalHookSessionCompleted struct { - Command []string `json:"command,required"` - Environment map[string]string `json:"environment"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Command respjson.Field - Environment respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configProviderModelsLimitJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseExperimentalHookSessionCompleted) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseExperimentalHookSessionCompleted) UnmarshalJSON(data []byte) error { +type ConfigProviderOptions struct { + APIKey string `json:"apiKey"` + BaseURL string `json:"baseURL"` + ExtraFields map[string]interface{} `json:"-,extras"` + JSON configProviderOptionsJSON `json:"-"` +} + +// configProviderOptionsJSON contains the JSON metadata for the struct +// [ConfigProviderOptions] +type configProviderOptionsJSON struct { + APIKey apijson.Field + BaseURL apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderOptions) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseFormatter struct { - Command []string `json:"command"` - Disabled bool `json:"disabled"` - Environment map[string]string `json:"environment"` - Extensions []string `json:"extensions"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Command respjson.Field - Disabled respjson.Field - Environment respjson.Field - Extensions respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r configProviderOptionsJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseFormatter) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseFormatter) UnmarshalJSON(data []byte) error { +// Control sharing behavior:'manual' allows manual sharing via commands, 'auto' +// enables automatic sharing, 'disabled' disables all sharing +type ConfigShare string + +const ( + ConfigShareManual ConfigShare = "manual" + ConfigShareAuto ConfigShare = "auto" + ConfigShareDisabled ConfigShare = "disabled" +) + +func (r ConfigShare) IsKnown() bool { + switch r { + case ConfigShareManual, ConfigShareAuto, ConfigShareDisabled: + return true + } + return false +} + +// TUI specific settings +type ConfigTui struct { + // TUI scroll speed + ScrollSpeed float64 `json:"scroll_speed,required"` + JSON configTuiJSON `json:"-"` +} + +// configTuiJSON contains the JSON metadata for the struct [ConfigTui] +type configTuiJSON struct { + ScrollSpeed apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigTui) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// Custom keybind configurations -type ConfigGetResponseKeybinds struct { +func (r configTuiJSON) RawJSON() string { + return r.raw +} + +type KeybindsConfig struct { // Next agent AgentCycle string `json:"agent_cycle,required"` // Previous agent @@ -664,815 +1126,163 @@ type ConfigGetResponseKeybinds struct { // Toggle thinking blocks ThinkingBlocks string `json:"thinking_blocks,required"` // Toggle tool details - ToolDetails string `json:"tool_details,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - AgentCycle respjson.Field - AgentCycleReverse respjson.Field - AgentList respjson.Field - AppExit respjson.Field - AppHelp respjson.Field - EditorOpen respjson.Field - FileClose respjson.Field - FileDiffToggle respjson.Field - FileList respjson.Field - FileSearch respjson.Field - InputClear respjson.Field - InputNewline respjson.Field - InputPaste respjson.Field - InputSubmit respjson.Field - Leader respjson.Field - MessagesCopy respjson.Field - MessagesFirst respjson.Field - MessagesHalfPageDown respjson.Field - MessagesHalfPageUp respjson.Field - MessagesLast respjson.Field - MessagesLayoutToggle respjson.Field - MessagesNext respjson.Field - MessagesPageDown respjson.Field - MessagesPageUp respjson.Field - MessagesPrevious respjson.Field - MessagesRedo respjson.Field - MessagesRevert respjson.Field - MessagesUndo respjson.Field - ModelCycleRecent respjson.Field - ModelCycleRecentReverse respjson.Field - ModelList respjson.Field - ProjectInit respjson.Field - SessionChildCycle respjson.Field - SessionChildCycleReverse respjson.Field - SessionCompact respjson.Field - SessionExport respjson.Field - SessionInterrupt respjson.Field - SessionList respjson.Field - SessionNew respjson.Field - SessionShare respjson.Field - SessionTimeline respjson.Field - SessionUnshare respjson.Field - SwitchAgent respjson.Field - SwitchAgentReverse respjson.Field - SwitchMode respjson.Field - SwitchModeReverse respjson.Field - ThemeList respjson.Field - ThinkingBlocks respjson.Field - ToolDetails respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` + ToolDetails string `json:"tool_details,required"` + JSON keybindsConfigJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseKeybinds) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseKeybinds) UnmarshalJSON(data []byte) error { +// keybindsConfigJSON contains the JSON metadata for the struct [KeybindsConfig] +type keybindsConfigJSON struct { + AgentCycle apijson.Field + AgentCycleReverse apijson.Field + AgentList apijson.Field + AppExit apijson.Field + AppHelp apijson.Field + EditorOpen apijson.Field + FileClose apijson.Field + FileDiffToggle apijson.Field + FileList apijson.Field + FileSearch apijson.Field + InputClear apijson.Field + InputNewline apijson.Field + InputPaste apijson.Field + InputSubmit apijson.Field + Leader apijson.Field + MessagesCopy apijson.Field + MessagesFirst apijson.Field + MessagesHalfPageDown apijson.Field + MessagesHalfPageUp apijson.Field + MessagesLast apijson.Field + MessagesLayoutToggle apijson.Field + MessagesNext apijson.Field + MessagesPageDown apijson.Field + MessagesPageUp apijson.Field + MessagesPrevious apijson.Field + MessagesRedo apijson.Field + MessagesRevert apijson.Field + MessagesUndo apijson.Field + ModelCycleRecent apijson.Field + ModelCycleRecentReverse apijson.Field + ModelList apijson.Field + ProjectInit apijson.Field + SessionChildCycle apijson.Field + SessionChildCycleReverse apijson.Field + SessionCompact apijson.Field + SessionExport apijson.Field + SessionInterrupt apijson.Field + SessionList apijson.Field + SessionNew apijson.Field + SessionShare apijson.Field + SessionTimeline apijson.Field + SessionUnshare apijson.Field + SwitchAgent apijson.Field + SwitchAgentReverse apijson.Field + SwitchMode apijson.Field + SwitchModeReverse apijson.Field + ThemeList apijson.Field + ThinkingBlocks apijson.Field + ToolDetails apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *KeybindsConfig) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// @deprecated Always uses stretch layout. -type ConfigGetResponseLayout string - -const ( - ConfigGetResponseLayoutAuto ConfigGetResponseLayout = "auto" - ConfigGetResponseLayoutStretch ConfigGetResponseLayout = "stretch" -) - -// ConfigGetResponseLspUnion contains all possible properties and values from -// [ConfigGetResponseLspDisabled], [ConfigGetResponseLspObject]. -// -// Use the methods beginning with 'As' to cast the union to one of its variants. -type ConfigGetResponseLspUnion struct { - Disabled bool `json:"disabled"` - // This field is from variant [ConfigGetResponseLspObject]. - Command []string `json:"command"` - // This field is from variant [ConfigGetResponseLspObject]. - Env map[string]string `json:"env"` - // This field is from variant [ConfigGetResponseLspObject]. - Extensions []string `json:"extensions"` - // This field is from variant [ConfigGetResponseLspObject]. - Initialization map[string]any `json:"initialization"` - JSON struct { - Disabled respjson.Field - Command respjson.Field - Env respjson.Field - Extensions respjson.Field - Initialization respjson.Field - raw string - } `json:"-"` +func (r keybindsConfigJSON) RawJSON() string { + return r.raw } -func (u ConfigGetResponseLspUnion) AsConfigGetResponseLspDisabled() (v ConfigGetResponseLspDisabled) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u ConfigGetResponseLspUnion) AsConfigGetResponseLspObject() (v ConfigGetResponseLspObject) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -// Returns the unmodified JSON received from the API -func (u ConfigGetResponseLspUnion) RawJSON() string { return u.JSON.raw } - -func (r *ConfigGetResponseLspUnion) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseLspDisabled struct { - Disabled bool `json:"disabled,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Disabled respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseLspDisabled) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseLspDisabled) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseLspObject struct { - Command []string `json:"command,required"` - Disabled bool `json:"disabled"` - Env map[string]string `json:"env"` - Extensions []string `json:"extensions"` - Initialization map[string]any `json:"initialization"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Command respjson.Field - Disabled respjson.Field - Env respjson.Field - Extensions respjson.Field - Initialization respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseLspObject) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseLspObject) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// ConfigGetResponseMcpUnion contains all possible properties and values from -// [ConfigGetResponseMcpLocal], [ConfigGetResponseMcpRemote]. -// -// Use the [ConfigGetResponseMcpUnion.AsAny] method to switch on the variant. -// -// Use the methods beginning with 'As' to cast the union to one of its variants. -type ConfigGetResponseMcpUnion struct { - // This field is from variant [ConfigGetResponseMcpLocal]. - Command []string `json:"command"` - // Any of "local", "remote". - Type string `json:"type"` - Enabled bool `json:"enabled"` - // This field is from variant [ConfigGetResponseMcpLocal]. - Environment map[string]string `json:"environment"` - // This field is from variant [ConfigGetResponseMcpRemote]. - URL string `json:"url"` - // This field is from variant [ConfigGetResponseMcpRemote]. - Headers map[string]string `json:"headers"` - JSON struct { - Command respjson.Field - Type respjson.Field - Enabled respjson.Field - Environment respjson.Field - URL respjson.Field - Headers respjson.Field - raw string - } `json:"-"` -} - -// anyConfigGetResponseMcp is implemented by each variant of -// [ConfigGetResponseMcpUnion] to add type safety for the return type of -// [ConfigGetResponseMcpUnion.AsAny] -type anyConfigGetResponseMcp interface { - implConfigGetResponseMcpUnion() -} - -func (ConfigGetResponseMcpLocal) implConfigGetResponseMcpUnion() {} -func (ConfigGetResponseMcpRemote) implConfigGetResponseMcpUnion() {} - -// Use the following switch statement to find the correct variant -// -// switch variant := ConfigGetResponseMcpUnion.AsAny().(type) { -// case opencode.ConfigGetResponseMcpLocal: -// case opencode.ConfigGetResponseMcpRemote: -// default: -// fmt.Errorf("no variant present") -// } -func (u ConfigGetResponseMcpUnion) AsAny() anyConfigGetResponseMcp { - switch u.Type { - case "local": - return u.AsLocal() - case "remote": - return u.AsRemote() - } - return nil -} - -func (u ConfigGetResponseMcpUnion) AsLocal() (v ConfigGetResponseMcpLocal) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u ConfigGetResponseMcpUnion) AsRemote() (v ConfigGetResponseMcpRemote) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -// Returns the unmodified JSON received from the API -func (u ConfigGetResponseMcpUnion) RawJSON() string { return u.JSON.raw } - -func (r *ConfigGetResponseMcpUnion) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseMcpLocal struct { +type McpLocalConfig struct { // Command and arguments to run the MCP server Command []string `json:"command,required"` // Type of MCP server connection - Type constant.Local `json:"type,required"` + Type McpLocalConfigType `json:"type,required"` // Enable or disable the MCP server on startup Enabled bool `json:"enabled"` // Environment variables to set when running the MCP server - Environment map[string]string `json:"environment"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Command respjson.Field - Type respjson.Field - Enabled respjson.Field - Environment respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` + Environment map[string]string `json:"environment"` + JSON mcpLocalConfigJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseMcpLocal) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseMcpLocal) UnmarshalJSON(data []byte) error { +// mcpLocalConfigJSON contains the JSON metadata for the struct [McpLocalConfig] +type mcpLocalConfigJSON struct { + Command apijson.Field + Type apijson.Field + Enabled apijson.Field + Environment apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *McpLocalConfig) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type ConfigGetResponseMcpRemote struct { +func (r mcpLocalConfigJSON) RawJSON() string { + return r.raw +} + +func (r McpLocalConfig) implementsConfigMcp() {} + +// Type of MCP server connection +type McpLocalConfigType string + +const ( + McpLocalConfigTypeLocal McpLocalConfigType = "local" +) + +func (r McpLocalConfigType) IsKnown() bool { + switch r { + case McpLocalConfigTypeLocal: + return true + } + return false +} + +type McpRemoteConfig struct { // Type of MCP server connection - Type constant.Remote `json:"type,required"` + Type McpRemoteConfigType `json:"type,required"` // URL of the remote MCP server URL string `json:"url,required"` // Enable or disable the MCP server on startup Enabled bool `json:"enabled"` // Headers to send with the request - Headers map[string]string `json:"headers"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Type respjson.Field - URL respjson.Field - Enabled respjson.Field - Headers respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` + Headers map[string]string `json:"headers"` + JSON mcpRemoteConfigJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseMcpRemote) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseMcpRemote) UnmarshalJSON(data []byte) error { +// mcpRemoteConfigJSON contains the JSON metadata for the struct [McpRemoteConfig] +type mcpRemoteConfigJSON struct { + Type apijson.Field + URL apijson.Field + Enabled apijson.Field + Headers apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *McpRemoteConfig) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// @deprecated Use `agent` field instead. -type ConfigGetResponseMode struct { - Build ConfigGetResponseModeBuild `json:"build"` - Plan ConfigGetResponseModePlan `json:"plan"` - ExtraFields map[string]ConfigGetResponseMode `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Build respjson.Field - Plan respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r mcpRemoteConfigJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseMode) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseMode) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} +func (r McpRemoteConfig) implementsConfigMcp() {} -type ConfigGetResponseModeBuild struct { - // Description of when to use the agent - Description string `json:"description"` - Disable bool `json:"disable"` - // Any of "subagent", "primary", "all". - Mode ConfigGetResponseModeBuildMode `json:"mode"` - Model string `json:"model"` - Permission ConfigGetResponseModeBuildPermission `json:"permission"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - ExtraFields map[string]any `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Description respjson.Field - Disable respjson.Field - Mode respjson.Field - Model respjson.Field - Permission respjson.Field - Prompt respjson.Field - Temperature respjson.Field - Tools respjson.Field - TopP respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseModeBuild) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseModeBuild) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseModeBuildMode string +// Type of MCP server connection +type McpRemoteConfigType string const ( - ConfigGetResponseModeBuildModeSubagent ConfigGetResponseModeBuildMode = "subagent" - ConfigGetResponseModeBuildModePrimary ConfigGetResponseModeBuildMode = "primary" - ConfigGetResponseModeBuildModeAll ConfigGetResponseModeBuildMode = "all" + McpRemoteConfigTypeRemote McpRemoteConfigType = "remote" ) -type ConfigGetResponseModeBuildPermission struct { - Bash ConfigGetResponseModeBuildPermissionBashUnion `json:"bash"` - // Any of "ask", "allow", "deny". - Edit ConfigGetResponseModeBuildPermissionEdit `json:"edit"` - // Any of "ask", "allow", "deny". - Webfetch ConfigGetResponseModeBuildPermissionWebfetch `json:"webfetch"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Bash respjson.Field - Edit respjson.Field - Webfetch respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseModeBuildPermission) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseModeBuildPermission) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseModeBuildPermissionBashString string - -const ( - ConfigGetResponseModeBuildPermissionBashStringAsk ConfigGetResponseModeBuildPermissionBashString = "ask" - ConfigGetResponseModeBuildPermissionBashStringAllow ConfigGetResponseModeBuildPermissionBashString = "allow" - ConfigGetResponseModeBuildPermissionBashStringDeny ConfigGetResponseModeBuildPermissionBashString = "deny" -) - -type ConfigGetResponseModeBuildPermissionBashMapItem string - -const ( - ConfigGetResponseModeBuildPermissionBashMapItemAsk ConfigGetResponseModeBuildPermissionBashMapItem = "ask" - ConfigGetResponseModeBuildPermissionBashMapItemAllow ConfigGetResponseModeBuildPermissionBashMapItem = "allow" - ConfigGetResponseModeBuildPermissionBashMapItemDeny ConfigGetResponseModeBuildPermissionBashMapItem = "deny" -) - -type ConfigGetResponseModeBuildPermissionEdit string - -const ( - ConfigGetResponseModeBuildPermissionEditAsk ConfigGetResponseModeBuildPermissionEdit = "ask" - ConfigGetResponseModeBuildPermissionEditAllow ConfigGetResponseModeBuildPermissionEdit = "allow" - ConfigGetResponseModeBuildPermissionEditDeny ConfigGetResponseModeBuildPermissionEdit = "deny" -) - -type ConfigGetResponseModeBuildPermissionWebfetch string - -const ( - ConfigGetResponseModeBuildPermissionWebfetchAsk ConfigGetResponseModeBuildPermissionWebfetch = "ask" - ConfigGetResponseModeBuildPermissionWebfetchAllow ConfigGetResponseModeBuildPermissionWebfetch = "allow" - ConfigGetResponseModeBuildPermissionWebfetchDeny ConfigGetResponseModeBuildPermissionWebfetch = "deny" -) - -type ConfigGetResponseModePlan struct { - // Description of when to use the agent - Description string `json:"description"` - Disable bool `json:"disable"` - // Any of "subagent", "primary", "all". - Mode ConfigGetResponseModePlanMode `json:"mode"` - Model string `json:"model"` - Permission ConfigGetResponseModePlanPermission `json:"permission"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - ExtraFields map[string]any `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Description respjson.Field - Disable respjson.Field - Mode respjson.Field - Model respjson.Field - Permission respjson.Field - Prompt respjson.Field - Temperature respjson.Field - Tools respjson.Field - TopP respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseModePlan) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseModePlan) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseModePlanMode string - -const ( - ConfigGetResponseModePlanModeSubagent ConfigGetResponseModePlanMode = "subagent" - ConfigGetResponseModePlanModePrimary ConfigGetResponseModePlanMode = "primary" - ConfigGetResponseModePlanModeAll ConfigGetResponseModePlanMode = "all" -) - -type ConfigGetResponseModePlanPermission struct { - Bash ConfigGetResponseModePlanPermissionBashUnion `json:"bash"` - // Any of "ask", "allow", "deny". - Edit ConfigGetResponseModePlanPermissionEdit `json:"edit"` - // Any of "ask", "allow", "deny". - Webfetch ConfigGetResponseModePlanPermissionWebfetch `json:"webfetch"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Bash respjson.Field - Edit respjson.Field - Webfetch respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseModePlanPermission) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseModePlanPermission) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseModePlanPermissionBashString string - -const ( - ConfigGetResponseModePlanPermissionBashStringAsk ConfigGetResponseModePlanPermissionBashString = "ask" - ConfigGetResponseModePlanPermissionBashStringAllow ConfigGetResponseModePlanPermissionBashString = "allow" - ConfigGetResponseModePlanPermissionBashStringDeny ConfigGetResponseModePlanPermissionBashString = "deny" -) - -type ConfigGetResponseModePlanPermissionBashMapItem string - -const ( - ConfigGetResponseModePlanPermissionBashMapItemAsk ConfigGetResponseModePlanPermissionBashMapItem = "ask" - ConfigGetResponseModePlanPermissionBashMapItemAllow ConfigGetResponseModePlanPermissionBashMapItem = "allow" - ConfigGetResponseModePlanPermissionBashMapItemDeny ConfigGetResponseModePlanPermissionBashMapItem = "deny" -) - -type ConfigGetResponseModePlanPermissionEdit string - -const ( - ConfigGetResponseModePlanPermissionEditAsk ConfigGetResponseModePlanPermissionEdit = "ask" - ConfigGetResponseModePlanPermissionEditAllow ConfigGetResponseModePlanPermissionEdit = "allow" - ConfigGetResponseModePlanPermissionEditDeny ConfigGetResponseModePlanPermissionEdit = "deny" -) - -type ConfigGetResponseModePlanPermissionWebfetch string - -const ( - ConfigGetResponseModePlanPermissionWebfetchAsk ConfigGetResponseModePlanPermissionWebfetch = "ask" - ConfigGetResponseModePlanPermissionWebfetchAllow ConfigGetResponseModePlanPermissionWebfetch = "allow" - ConfigGetResponseModePlanPermissionWebfetchDeny ConfigGetResponseModePlanPermissionWebfetch = "deny" -) - -type ConfigGetResponsePermission struct { - Bash ConfigGetResponsePermissionBashUnion `json:"bash"` - // Any of "ask", "allow", "deny". - Edit ConfigGetResponsePermissionEdit `json:"edit"` - // Any of "ask", "allow", "deny". - Webfetch ConfigGetResponsePermissionWebfetch `json:"webfetch"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Bash respjson.Field - Edit respjson.Field - Webfetch respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponsePermission) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponsePermission) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponsePermissionBashString string - -const ( - ConfigGetResponsePermissionBashStringAsk ConfigGetResponsePermissionBashString = "ask" - ConfigGetResponsePermissionBashStringAllow ConfigGetResponsePermissionBashString = "allow" - ConfigGetResponsePermissionBashStringDeny ConfigGetResponsePermissionBashString = "deny" -) - -type ConfigGetResponsePermissionBashMapItem string - -const ( - ConfigGetResponsePermissionBashMapItemAsk ConfigGetResponsePermissionBashMapItem = "ask" - ConfigGetResponsePermissionBashMapItemAllow ConfigGetResponsePermissionBashMapItem = "allow" - ConfigGetResponsePermissionBashMapItemDeny ConfigGetResponsePermissionBashMapItem = "deny" -) - -type ConfigGetResponsePermissionEdit string - -const ( - ConfigGetResponsePermissionEditAsk ConfigGetResponsePermissionEdit = "ask" - ConfigGetResponsePermissionEditAllow ConfigGetResponsePermissionEdit = "allow" - ConfigGetResponsePermissionEditDeny ConfigGetResponsePermissionEdit = "deny" -) - -type ConfigGetResponsePermissionWebfetch string - -const ( - ConfigGetResponsePermissionWebfetchAsk ConfigGetResponsePermissionWebfetch = "ask" - ConfigGetResponsePermissionWebfetchAllow ConfigGetResponsePermissionWebfetch = "allow" - ConfigGetResponsePermissionWebfetchDeny ConfigGetResponsePermissionWebfetch = "deny" -) - -type ConfigGetResponseProvider struct { - ID string `json:"id"` - API string `json:"api"` - Env []string `json:"env"` - Models map[string]ConfigGetResponseProviderModel `json:"models"` - Name string `json:"name"` - Npm string `json:"npm"` - Options ConfigGetResponseProviderOptions `json:"options"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - API respjson.Field - Env respjson.Field - Models respjson.Field - Name respjson.Field - Npm respjson.Field - Options respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseProvider) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseProvider) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseProviderModel struct { - ID string `json:"id"` - Attachment bool `json:"attachment"` - Cost ConfigGetResponseProviderModelCost `json:"cost"` - Limit ConfigGetResponseProviderModelLimit `json:"limit"` - Name string `json:"name"` - Options map[string]any `json:"options"` - Reasoning bool `json:"reasoning"` - ReleaseDate string `json:"release_date"` - Temperature bool `json:"temperature"` - ToolCall bool `json:"tool_call"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Attachment respjson.Field - Cost respjson.Field - Limit respjson.Field - Name respjson.Field - Options respjson.Field - Reasoning respjson.Field - ReleaseDate respjson.Field - Temperature respjson.Field - ToolCall respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseProviderModel) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseProviderModel) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseProviderModelCost struct { - Input float64 `json:"input,required"` - Output float64 `json:"output,required"` - CacheRead float64 `json:"cache_read"` - CacheWrite float64 `json:"cache_write"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Input respjson.Field - Output respjson.Field - CacheRead respjson.Field - CacheWrite respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseProviderModelCost) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseProviderModelCost) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseProviderModelLimit struct { - Context float64 `json:"context,required"` - Output float64 `json:"output,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Context respjson.Field - Output respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseProviderModelLimit) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseProviderModelLimit) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigGetResponseProviderOptions struct { - APIKey string `json:"apiKey"` - BaseURL string `json:"baseURL"` - ExtraFields map[string]any `json:",extras"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - APIKey respjson.Field - BaseURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseProviderOptions) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseProviderOptions) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// Control sharing behavior:'manual' allows manual sharing via commands, 'auto' -// enables automatic sharing, 'disabled' disables all sharing -type ConfigGetResponseShare string - -const ( - ConfigGetResponseShareManual ConfigGetResponseShare = "manual" - ConfigGetResponseShareAuto ConfigGetResponseShare = "auto" - ConfigGetResponseShareDisabled ConfigGetResponseShare = "disabled" -) - -// TUI specific settings -type ConfigGetResponseTui struct { - // TUI scroll speed - ScrollSpeed float64 `json:"scroll_speed,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ScrollSpeed respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigGetResponseTui) RawJSON() string { return r.JSON.raw } -func (r *ConfigGetResponseTui) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigListProvidersResponse struct { - Default map[string]string `json:"default,required"` - Providers []ConfigListProvidersResponseProvider `json:"providers,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Default respjson.Field - Providers respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigListProvidersResponse) RawJSON() string { return r.JSON.raw } -func (r *ConfigListProvidersResponse) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigListProvidersResponseProvider struct { - ID string `json:"id,required"` - Env []string `json:"env,required"` - Models map[string]ConfigListProvidersResponseProviderModel `json:"models,required"` - Name string `json:"name,required"` - API string `json:"api"` - Npm string `json:"npm"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Env respjson.Field - Models respjson.Field - Name respjson.Field - API respjson.Field - Npm respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigListProvidersResponseProvider) RawJSON() string { return r.JSON.raw } -func (r *ConfigListProvidersResponseProvider) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigListProvidersResponseProviderModel struct { - ID string `json:"id,required"` - Attachment bool `json:"attachment,required"` - Cost ConfigListProvidersResponseProviderModelCost `json:"cost,required"` - Limit ConfigListProvidersResponseProviderModelLimit `json:"limit,required"` - Name string `json:"name,required"` - Options map[string]any `json:"options,required"` - Reasoning bool `json:"reasoning,required"` - ReleaseDate string `json:"release_date,required"` - Temperature bool `json:"temperature,required"` - ToolCall bool `json:"tool_call,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Attachment respjson.Field - Cost respjson.Field - Limit respjson.Field - Name respjson.Field - Options respjson.Field - Reasoning respjson.Field - ReleaseDate respjson.Field - Temperature respjson.Field - ToolCall respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigListProvidersResponseProviderModel) RawJSON() string { return r.JSON.raw } -func (r *ConfigListProvidersResponseProviderModel) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigListProvidersResponseProviderModelCost struct { - Input float64 `json:"input,required"` - Output float64 `json:"output,required"` - CacheRead float64 `json:"cache_read"` - CacheWrite float64 `json:"cache_write"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Input respjson.Field - Output respjson.Field - CacheRead respjson.Field - CacheWrite respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigListProvidersResponseProviderModelCost) RawJSON() string { return r.JSON.raw } -func (r *ConfigListProvidersResponseProviderModelCost) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ConfigListProvidersResponseProviderModelLimit struct { - Context float64 `json:"context,required"` - Output float64 `json:"output,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Context respjson.Field - Output respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ConfigListProvidersResponseProviderModelLimit) RawJSON() string { return r.JSON.raw } -func (r *ConfigListProvidersResponseProviderModelLimit) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) +func (r McpRemoteConfigType) IsKnown() bool { + switch r { + case McpRemoteConfigTypeRemote: + return true + } + return false } diff --git a/config_test.go b/config_test.go index ac3250b..9a2676b 100644 --- a/config_test.go +++ b/config_test.go @@ -8,9 +8,9 @@ import ( "os" "testing" - "github.com/stainless-sdks/opencode-go" - "github.com/stainless-sdks/opencode-go/internal/testutil" - "github.com/stainless-sdks/opencode-go/option" + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" ) func TestConfigGet(t *testing.T) { @@ -24,7 +24,6 @@ func TestConfigGet(t *testing.T) { } client := opencode.NewClient( option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), ) _, err := client.Config.Get(context.TODO()) if err != nil { @@ -35,26 +34,3 @@ func TestConfigGet(t *testing.T) { t.Fatalf("err should be nil: %s", err.Error()) } } - -func TestConfigListProviders(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := opencode.NewClient( - option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), - ) - _, err := client.Config.ListProviders(context.TODO()) - if err != nil { - var apierr *opencode.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} diff --git a/event.go b/event.go index 175cc95..dd6afc4 100644 --- a/event.go +++ b/event.go @@ -4,15 +4,15 @@ package opencode import ( "context" - "encoding/json" "net/http" + "reflect" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" - "github.com/stainless-sdks/opencode-go/packages/respjson" - "github.com/stainless-sdks/opencode-go/packages/ssestream" - "github.com/stainless-sdks/opencode-go/shared/constant" + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" + "github.com/sst/opencode-sdk-go/packages/ssestream" + "github.com/sst/opencode-sdk-go/shared" + "github.com/tidwall/gjson" ) // EventService contains methods and other services that help with interacting with @@ -28,14 +28,14 @@ type EventService struct { // NewEventService generates a new service that applies the given options to each // request. These options are applied after the parent client's options (if there // is one), and before any request-specific options. -func NewEventService(opts ...option.RequestOption) (r EventService) { - r = EventService{} +func NewEventService(opts ...option.RequestOption) (r *EventService) { + r = &EventService{} r.Options = opts return } // Get events -func (r *EventService) ListStreaming(ctx context.Context, opts ...option.RequestOption) (stream *ssestream.Stream[EventListResponseUnion]) { +func (r *EventService) ListStreaming(ctx context.Context, opts ...option.RequestOption) (stream *ssestream.Stream[EventListResponse]) { var ( raw *http.Response err error @@ -44,990 +44,1129 @@ func (r *EventService) ListStreaming(ctx context.Context, opts ...option.Request opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...) path := "event" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...) - return ssestream.NewStream[EventListResponseUnion](ssestream.NewDecoder(raw), err) + return ssestream.NewStream[EventListResponse](ssestream.NewDecoder(raw), err) } -// EventListResponseUnion contains all possible properties and values from -// [EventListResponseInstallationUpdated], [EventListResponseLspClientDiagnostics], -// [EventListResponseMessageUpdated], [EventListResponseMessageRemoved], -// [EventListResponseMessagePartUpdated], [EventListResponseMessagePartRemoved], -// [EventListResponsePermissionUpdated], [EventListResponsePermissionReplied], -// [EventListResponseFileEdited], [EventListResponseSessionUpdated], -// [EventListResponseSessionDeleted], [EventListResponseSessionIdle], -// [EventListResponseSessionError], [EventListResponseServerConnected]. -// -// Use the [EventListResponseUnion.AsAny] method to switch on the variant. -// -// Use the methods beginning with 'As' to cast the union to one of its variants. -type EventListResponseUnion struct { - // This field is a union of [EventListResponseInstallationUpdatedProperties], - // [EventListResponseLspClientDiagnosticsProperties], - // [EventListResponseMessageUpdatedProperties], - // [EventListResponseMessageRemovedProperties], - // [EventListResponseMessagePartUpdatedProperties], - // [EventListResponseMessagePartRemovedProperties], - // [EventListResponsePermissionUpdatedProperties], - // [EventListResponsePermissionRepliedProperties], - // [EventListResponseFileEditedProperties], - // [EventListResponseSessionUpdatedProperties], - // [EventListResponseSessionDeletedProperties], - // [EventListResponseSessionIdleProperties], - // [EventListResponseSessionErrorProperties], [any] - Properties EventListResponseUnionProperties `json:"properties"` - // Any of "installation.updated", "lsp.client.diagnostics", "message.updated", - // "message.removed", "message.part.updated", "message.part.removed", - // "permission.updated", "permission.replied", "file.edited", "session.updated", - // "session.deleted", "session.idle", "session.error", "server.connected". - Type string `json:"type"` - JSON struct { - Properties respjson.Field - Type respjson.Field - raw string - } `json:"-"` +type EventListResponse struct { + // This field can have the runtime type of + // [EventListResponseEventInstallationUpdatedProperties], + // [EventListResponseEventLspClientDiagnosticsProperties], + // [EventListResponseEventMessageUpdatedProperties], + // [EventListResponseEventMessageRemovedProperties], + // [EventListResponseEventMessagePartUpdatedProperties], + // [EventListResponseEventMessagePartRemovedProperties], [Permission], + // [EventListResponseEventPermissionRepliedProperties], + // [EventListResponseEventFileEditedProperties], + // [EventListResponseEventSessionUpdatedProperties], + // [EventListResponseEventSessionDeletedProperties], + // [EventListResponseEventSessionIdleProperties], + // [EventListResponseEventSessionErrorProperties], [interface{}]. + Properties interface{} `json:"properties,required"` + Type EventListResponseType `json:"type,required"` + JSON eventListResponseJSON `json:"-"` + union EventListResponseUnion } -// anyEventListResponse is implemented by each variant of [EventListResponseUnion] -// to add type safety for the return type of [EventListResponseUnion.AsAny] -type anyEventListResponse interface { - implEventListResponseUnion() +// eventListResponseJSON contains the JSON metadata for the struct +// [EventListResponse] +type eventListResponseJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field } -func (EventListResponseInstallationUpdated) implEventListResponseUnion() {} -func (EventListResponseLspClientDiagnostics) implEventListResponseUnion() {} -func (EventListResponseMessageUpdated) implEventListResponseUnion() {} -func (EventListResponseMessageRemoved) implEventListResponseUnion() {} -func (EventListResponseMessagePartUpdated) implEventListResponseUnion() {} -func (EventListResponseMessagePartRemoved) implEventListResponseUnion() {} -func (EventListResponsePermissionUpdated) implEventListResponseUnion() {} -func (EventListResponsePermissionReplied) implEventListResponseUnion() {} -func (EventListResponseFileEdited) implEventListResponseUnion() {} -func (EventListResponseSessionUpdated) implEventListResponseUnion() {} -func (EventListResponseSessionDeleted) implEventListResponseUnion() {} -func (EventListResponseSessionIdle) implEventListResponseUnion() {} -func (EventListResponseSessionError) implEventListResponseUnion() {} -func (EventListResponseServerConnected) implEventListResponseUnion() {} +func (r eventListResponseJSON) RawJSON() string { + return r.raw +} -// Use the following switch statement to find the correct variant -// -// switch variant := EventListResponseUnion.AsAny().(type) { -// case opencode.EventListResponseInstallationUpdated: -// case opencode.EventListResponseLspClientDiagnostics: -// case opencode.EventListResponseMessageUpdated: -// case opencode.EventListResponseMessageRemoved: -// case opencode.EventListResponseMessagePartUpdated: -// case opencode.EventListResponseMessagePartRemoved: -// case opencode.EventListResponsePermissionUpdated: -// case opencode.EventListResponsePermissionReplied: -// case opencode.EventListResponseFileEdited: -// case opencode.EventListResponseSessionUpdated: -// case opencode.EventListResponseSessionDeleted: -// case opencode.EventListResponseSessionIdle: -// case opencode.EventListResponseSessionError: -// case opencode.EventListResponseServerConnected: -// default: -// fmt.Errorf("no variant present") -// } -func (u EventListResponseUnion) AsAny() anyEventListResponse { - switch u.Type { - case "installation.updated": - return u.AsInstallationUpdated() - case "lsp.client.diagnostics": - return u.AsLspClientDiagnostics() - case "message.updated": - return u.AsMessageUpdated() - case "message.removed": - return u.AsMessageRemoved() - case "message.part.updated": - return u.AsMessagePartUpdated() - case "message.part.removed": - return u.AsMessagePartRemoved() - case "permission.updated": - return u.AsPermissionUpdated() - case "permission.replied": - return u.AsPermissionReplied() - case "file.edited": - return u.AsFileEdited() - case "session.updated": - return u.AsSessionUpdated() - case "session.deleted": - return u.AsSessionDeleted() - case "session.idle": - return u.AsSessionIdle() - case "session.error": - return u.AsSessionError() - case "server.connected": - return u.AsServerConnected() +func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) { + *r = EventListResponse{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err } - return nil + return apijson.Port(r.union, &r) } -func (u EventListResponseUnion) AsInstallationUpdated() (v EventListResponseInstallationUpdated) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsLspClientDiagnostics() (v EventListResponseLspClientDiagnostics) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsMessageUpdated() (v EventListResponseMessageUpdated) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsMessageRemoved() (v EventListResponseMessageRemoved) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsMessagePartUpdated() (v EventListResponseMessagePartUpdated) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsMessagePartRemoved() (v EventListResponseMessagePartRemoved) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsPermissionUpdated() (v EventListResponsePermissionUpdated) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsPermissionReplied() (v EventListResponsePermissionReplied) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsFileEdited() (v EventListResponseFileEdited) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsSessionUpdated() (v EventListResponseSessionUpdated) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsSessionDeleted() (v EventListResponseSessionDeleted) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsSessionIdle() (v EventListResponseSessionIdle) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsSessionError() (v EventListResponseSessionError) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseUnion) AsServerConnected() (v EventListResponseServerConnected) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -// Returns the unmodified JSON received from the API -func (u EventListResponseUnion) RawJSON() string { return u.JSON.raw } - -func (r *EventListResponseUnion) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// EventListResponseUnionProperties is an implicit subunion of -// [EventListResponseUnion]. EventListResponseUnionProperties provides convenient -// access to the sub-properties of the union. +// AsUnion returns a [EventListResponseUnion] interface which you can cast to the +// specific types for more type safety. // -// For type safety it is recommended to directly use a variant of the -// [EventListResponseUnion]. -// -// If the underlying value is not a json object, one of the following properties -// will be valid: OfEventListResponseServerConnectedProperties] -type EventListResponseUnionProperties struct { - // This field will be present if the value is a [any] instead of an object. - OfEventListResponseServerConnectedProperties any `json:",inline"` - // This field is from variant [EventListResponseInstallationUpdatedProperties]. - Version string `json:"version"` - // This field is from variant [EventListResponseLspClientDiagnosticsProperties]. - Path string `json:"path"` - // This field is from variant [EventListResponseLspClientDiagnosticsProperties]. - ServerID string `json:"serverID"` - // This field is a union of [MessageUnion], [Session] - Info EventListResponseUnionPropertiesInfo `json:"info"` - MessageID string `json:"messageID"` - SessionID string `json:"sessionID"` - // This field is from variant [EventListResponseMessagePartUpdatedProperties]. - Part PartUnion `json:"part"` - // This field is from variant [EventListResponseMessagePartRemovedProperties]. - PartID string `json:"partID"` - // This field is from variant [EventListResponsePermissionUpdatedProperties]. - ID string `json:"id"` - // This field is from variant [EventListResponsePermissionUpdatedProperties]. - Metadata map[string]any `json:"metadata"` - // This field is from variant [EventListResponsePermissionUpdatedProperties]. - Time EventListResponsePermissionUpdatedPropertiesTime `json:"time"` - // This field is from variant [EventListResponsePermissionUpdatedProperties]. - Title string `json:"title"` - // This field is from variant [EventListResponsePermissionUpdatedProperties]. - Type string `json:"type"` - // This field is from variant [EventListResponsePermissionUpdatedProperties]. - CallID string `json:"callID"` - // This field is from variant [EventListResponsePermissionUpdatedProperties]. - Pattern string `json:"pattern"` - // This field is from variant [EventListResponsePermissionRepliedProperties]. - PermissionID string `json:"permissionID"` - // This field is from variant [EventListResponsePermissionRepliedProperties]. - Response string `json:"response"` - // This field is from variant [EventListResponseFileEditedProperties]. - File string `json:"file"` - // This field is from variant [EventListResponseSessionErrorProperties]. - Error EventListResponseSessionErrorPropertiesErrorUnion `json:"error"` - JSON struct { - OfEventListResponseServerConnectedProperties respjson.Field - Version respjson.Field - Path respjson.Field - ServerID respjson.Field - Info respjson.Field - MessageID respjson.Field - SessionID respjson.Field - Part respjson.Field - PartID respjson.Field - ID respjson.Field - Metadata respjson.Field - Time respjson.Field - Title respjson.Field - Type respjson.Field - CallID respjson.Field - Pattern respjson.Field - PermissionID respjson.Field - Response respjson.Field - File respjson.Field - Error respjson.Field - raw string - } `json:"-"` +// Possible runtime types of the union are +// [EventListResponseEventInstallationUpdated], +// [EventListResponseEventLspClientDiagnostics], +// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved], +// [EventListResponseEventMessagePartUpdated], +// [EventListResponseEventMessagePartRemoved], +// [EventListResponseEventPermissionUpdated], +// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited], +// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted], +// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError], +// [EventListResponseEventServerConnected]. +func (r EventListResponse) AsUnion() EventListResponseUnion { + return r.union } -func (r *EventListResponseUnionProperties) UnmarshalJSON(data []byte) error { +// Union satisfied by [EventListResponseEventInstallationUpdated], +// [EventListResponseEventLspClientDiagnostics], +// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved], +// [EventListResponseEventMessagePartUpdated], +// [EventListResponseEventMessagePartRemoved], +// [EventListResponseEventPermissionUpdated], +// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited], +// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted], +// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError] or +// [EventListResponseEventServerConnected]. +type EventListResponseUnion interface { + implementsEventListResponse() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*EventListResponseUnion)(nil)).Elem(), + "type", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventInstallationUpdated{}), + DiscriminatorValue: "installation.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventLspClientDiagnostics{}), + DiscriminatorValue: "lsp.client.diagnostics", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessageUpdated{}), + DiscriminatorValue: "message.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessageRemoved{}), + DiscriminatorValue: "message.removed", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessagePartUpdated{}), + DiscriminatorValue: "message.part.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessagePartRemoved{}), + DiscriminatorValue: "message.part.removed", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}), + DiscriminatorValue: "permission.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventPermissionReplied{}), + DiscriminatorValue: "permission.replied", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventFileEdited{}), + DiscriminatorValue: "file.edited", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionUpdated{}), + DiscriminatorValue: "session.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionDeleted{}), + DiscriminatorValue: "session.deleted", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionIdle{}), + DiscriminatorValue: "session.idle", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionError{}), + DiscriminatorValue: "session.error", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventServerConnected{}), + DiscriminatorValue: "server.connected", + }, + ) +} + +type EventListResponseEventInstallationUpdated struct { + Properties EventListResponseEventInstallationUpdatedProperties `json:"properties,required"` + Type EventListResponseEventInstallationUpdatedType `json:"type,required"` + JSON eventListResponseEventInstallationUpdatedJSON `json:"-"` +} + +// eventListResponseEventInstallationUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventInstallationUpdated] +type eventListResponseEventInstallationUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventInstallationUpdated) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// EventListResponseUnionPropertiesInfo is an implicit subunion of -// [EventListResponseUnion]. EventListResponseUnionPropertiesInfo provides -// convenient access to the sub-properties of the union. -// -// For type safety it is recommended to directly use a variant of the -// [EventListResponseUnion]. -type EventListResponseUnionPropertiesInfo struct { - ID string `json:"id"` - Role string `json:"role"` - SessionID string `json:"sessionID"` - // This field is a union of [MessageUserTime], [AssistantMessageTime], - // [SessionTime] - Time EventListResponseUnionPropertiesInfoTime `json:"time"` - // This field is from variant [MessageUnion]. - Cost float64 `json:"cost"` - // This field is from variant [MessageUnion]. - Mode string `json:"mode"` - // This field is from variant [MessageUnion]. - ModelID string `json:"modelID"` - // This field is from variant [MessageUnion]. - Path AssistantMessagePath `json:"path"` - // This field is from variant [MessageUnion]. - ProviderID string `json:"providerID"` - // This field is from variant [MessageUnion]. - System []string `json:"system"` - // This field is from variant [MessageUnion]. - Tokens AssistantMessageTokens `json:"tokens"` - // This field is from variant [MessageUnion]. - Error AssistantMessageErrorUnion `json:"error"` - // This field is from variant [MessageUnion]. - Summary bool `json:"summary"` - // This field is from variant [Session]. - Directory string `json:"directory"` - // This field is from variant [Session]. - ProjectID string `json:"projectID"` - // This field is from variant [Session]. - Title string `json:"title"` - // This field is from variant [Session]. - Version string `json:"version"` - // This field is from variant [Session]. - ParentID string `json:"parentID"` - // This field is from variant [Session]. - Revert SessionRevert `json:"revert"` - // This field is from variant [Session]. - Share SessionShare `json:"share"` - JSON struct { - ID respjson.Field - Role respjson.Field - SessionID respjson.Field - Time respjson.Field - Cost respjson.Field - Mode respjson.Field - ModelID respjson.Field - Path respjson.Field - ProviderID respjson.Field - System respjson.Field - Tokens respjson.Field - Error respjson.Field - Summary respjson.Field - Directory respjson.Field - ProjectID respjson.Field - Title respjson.Field - Version respjson.Field - ParentID respjson.Field - Revert respjson.Field - Share respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventInstallationUpdatedJSON) RawJSON() string { + return r.raw } -func (r *EventListResponseUnionPropertiesInfo) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventInstallationUpdated) implementsEventListResponse() {} + +type EventListResponseEventInstallationUpdatedProperties struct { + Version string `json:"version,required"` + JSON eventListResponseEventInstallationUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventInstallationUpdatedPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventInstallationUpdatedProperties] +type eventListResponseEventInstallationUpdatedPropertiesJSON struct { + Version apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventInstallationUpdatedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// EventListResponseUnionPropertiesInfoTime is an implicit subunion of -// [EventListResponseUnion]. EventListResponseUnionPropertiesInfoTime provides -// convenient access to the sub-properties of the union. -// -// For type safety it is recommended to directly use a variant of the -// [EventListResponseUnion]. -type EventListResponseUnionPropertiesInfoTime struct { - Created float64 `json:"created"` - // This field is from variant [AssistantMessageTime]. - Completed float64 `json:"completed"` - // This field is from variant [SessionTime]. - Updated float64 `json:"updated"` - JSON struct { - Created respjson.Field - Completed respjson.Field - Updated respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventInstallationUpdatedPropertiesJSON) RawJSON() string { + return r.raw } -func (r *EventListResponseUnionPropertiesInfoTime) UnmarshalJSON(data []byte) error { +type EventListResponseEventInstallationUpdatedType string + +const ( + EventListResponseEventInstallationUpdatedTypeInstallationUpdated EventListResponseEventInstallationUpdatedType = "installation.updated" +) + +func (r EventListResponseEventInstallationUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventInstallationUpdatedTypeInstallationUpdated: + return true + } + return false +} + +type EventListResponseEventLspClientDiagnostics struct { + Properties EventListResponseEventLspClientDiagnosticsProperties `json:"properties,required"` + Type EventListResponseEventLspClientDiagnosticsType `json:"type,required"` + JSON eventListResponseEventLspClientDiagnosticsJSON `json:"-"` +} + +// eventListResponseEventLspClientDiagnosticsJSON contains the JSON metadata for +// the struct [EventListResponseEventLspClientDiagnostics] +type eventListResponseEventLspClientDiagnosticsJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventLspClientDiagnostics) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseInstallationUpdated struct { - Properties EventListResponseInstallationUpdatedProperties `json:"properties,required"` - Type constant.InstallationUpdated `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventLspClientDiagnosticsJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseInstallationUpdated) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseInstallationUpdated) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventLspClientDiagnostics) implementsEventListResponse() {} + +type EventListResponseEventLspClientDiagnosticsProperties struct { + Path string `json:"path,required"` + ServerID string `json:"serverID,required"` + JSON eventListResponseEventLspClientDiagnosticsPropertiesJSON `json:"-"` +} + +// eventListResponseEventLspClientDiagnosticsPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventLspClientDiagnosticsProperties] +type eventListResponseEventLspClientDiagnosticsPropertiesJSON struct { + Path apijson.Field + ServerID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventLspClientDiagnosticsProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseInstallationUpdatedProperties struct { - Version string `json:"version,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Version respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventLspClientDiagnosticsPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseInstallationUpdatedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseInstallationUpdatedProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventLspClientDiagnosticsType string + +const ( + EventListResponseEventLspClientDiagnosticsTypeLspClientDiagnostics EventListResponseEventLspClientDiagnosticsType = "lsp.client.diagnostics" +) + +func (r EventListResponseEventLspClientDiagnosticsType) IsKnown() bool { + switch r { + case EventListResponseEventLspClientDiagnosticsTypeLspClientDiagnostics: + return true + } + return false +} + +type EventListResponseEventMessageUpdated struct { + Properties EventListResponseEventMessageUpdatedProperties `json:"properties,required"` + Type EventListResponseEventMessageUpdatedType `json:"type,required"` + JSON eventListResponseEventMessageUpdatedJSON `json:"-"` +} + +// eventListResponseEventMessageUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessageUpdated] +type eventListResponseEventMessageUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageUpdated) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseLspClientDiagnostics struct { - Properties EventListResponseLspClientDiagnosticsProperties `json:"properties,required"` - Type constant.LspClientDiagnostics `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessageUpdatedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseLspClientDiagnostics) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseLspClientDiagnostics) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventMessageUpdated) implementsEventListResponse() {} + +type EventListResponseEventMessageUpdatedProperties struct { + Info Message `json:"info,required"` + JSON eventListResponseEventMessageUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessageUpdatedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventMessageUpdatedProperties] +type eventListResponseEventMessageUpdatedPropertiesJSON struct { + Info apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageUpdatedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseLspClientDiagnosticsProperties struct { - Path string `json:"path,required"` - ServerID string `json:"serverID,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Path respjson.Field - ServerID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessageUpdatedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseLspClientDiagnosticsProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseLspClientDiagnosticsProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventMessageUpdatedType string + +const ( + EventListResponseEventMessageUpdatedTypeMessageUpdated EventListResponseEventMessageUpdatedType = "message.updated" +) + +func (r EventListResponseEventMessageUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventMessageUpdatedTypeMessageUpdated: + return true + } + return false +} + +type EventListResponseEventMessageRemoved struct { + Properties EventListResponseEventMessageRemovedProperties `json:"properties,required"` + Type EventListResponseEventMessageRemovedType `json:"type,required"` + JSON eventListResponseEventMessageRemovedJSON `json:"-"` +} + +// eventListResponseEventMessageRemovedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessageRemoved] +type eventListResponseEventMessageRemovedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageRemoved) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessageUpdated struct { - Properties EventListResponseMessageUpdatedProperties `json:"properties,required"` - Type constant.MessageUpdated `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessageRemovedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessageUpdated) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessageUpdated) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventMessageRemoved) implementsEventListResponse() {} + +type EventListResponseEventMessageRemovedProperties struct { + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + JSON eventListResponseEventMessageRemovedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessageRemovedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventMessageRemovedProperties] +type eventListResponseEventMessageRemovedPropertiesJSON struct { + MessageID apijson.Field + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageRemovedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessageUpdatedProperties struct { - Info MessageUnion `json:"info,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Info respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessageRemovedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessageUpdatedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessageUpdatedProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventMessageRemovedType string + +const ( + EventListResponseEventMessageRemovedTypeMessageRemoved EventListResponseEventMessageRemovedType = "message.removed" +) + +func (r EventListResponseEventMessageRemovedType) IsKnown() bool { + switch r { + case EventListResponseEventMessageRemovedTypeMessageRemoved: + return true + } + return false +} + +type EventListResponseEventMessagePartUpdated struct { + Properties EventListResponseEventMessagePartUpdatedProperties `json:"properties,required"` + Type EventListResponseEventMessagePartUpdatedType `json:"type,required"` + JSON eventListResponseEventMessagePartUpdatedJSON `json:"-"` +} + +// eventListResponseEventMessagePartUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessagePartUpdated] +type eventListResponseEventMessagePartUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartUpdated) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessageRemoved struct { - Properties EventListResponseMessageRemovedProperties `json:"properties,required"` - Type constant.MessageRemoved `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessagePartUpdatedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessageRemoved) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessageRemoved) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventMessagePartUpdated) implementsEventListResponse() {} + +type EventListResponseEventMessagePartUpdatedProperties struct { + Part Part `json:"part,required"` + JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessagePartUpdatedPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventMessagePartUpdatedProperties] +type eventListResponseEventMessagePartUpdatedPropertiesJSON struct { + Part apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartUpdatedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessageRemovedProperties struct { - MessageID string `json:"messageID,required"` - SessionID string `json:"sessionID,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - MessageID respjson.Field - SessionID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessagePartUpdatedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessageRemovedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessageRemovedProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventMessagePartUpdatedType string + +const ( + EventListResponseEventMessagePartUpdatedTypeMessagePartUpdated EventListResponseEventMessagePartUpdatedType = "message.part.updated" +) + +func (r EventListResponseEventMessagePartUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventMessagePartUpdatedTypeMessagePartUpdated: + return true + } + return false +} + +type EventListResponseEventMessagePartRemoved struct { + Properties EventListResponseEventMessagePartRemovedProperties `json:"properties,required"` + Type EventListResponseEventMessagePartRemovedType `json:"type,required"` + JSON eventListResponseEventMessagePartRemovedJSON `json:"-"` +} + +// eventListResponseEventMessagePartRemovedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessagePartRemoved] +type eventListResponseEventMessagePartRemovedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartRemoved) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessagePartUpdated struct { - Properties EventListResponseMessagePartUpdatedProperties `json:"properties,required"` - Type constant.MessagePartUpdated `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessagePartRemovedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessagePartUpdated) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessagePartUpdated) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventMessagePartRemoved) implementsEventListResponse() {} + +type EventListResponseEventMessagePartRemovedProperties struct { + MessageID string `json:"messageID,required"` + PartID string `json:"partID,required"` + SessionID string `json:"sessionID,required"` + JSON eventListResponseEventMessagePartRemovedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessagePartRemovedPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventMessagePartRemovedProperties] +type eventListResponseEventMessagePartRemovedPropertiesJSON struct { + MessageID apijson.Field + PartID apijson.Field + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartRemovedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessagePartUpdatedProperties struct { - Part PartUnion `json:"part,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Part respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventMessagePartRemovedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessagePartUpdatedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessagePartUpdatedProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventMessagePartRemovedType string + +const ( + EventListResponseEventMessagePartRemovedTypeMessagePartRemoved EventListResponseEventMessagePartRemovedType = "message.part.removed" +) + +func (r EventListResponseEventMessagePartRemovedType) IsKnown() bool { + switch r { + case EventListResponseEventMessagePartRemovedTypeMessagePartRemoved: + return true + } + return false +} + +type EventListResponseEventPermissionUpdated struct { + Properties Permission `json:"properties,required"` + Type EventListResponseEventPermissionUpdatedType `json:"type,required"` + JSON eventListResponseEventPermissionUpdatedJSON `json:"-"` +} + +// eventListResponseEventPermissionUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventPermissionUpdated] +type eventListResponseEventPermissionUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventPermissionUpdated) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessagePartRemoved struct { - Properties EventListResponseMessagePartRemovedProperties `json:"properties,required"` - Type constant.MessagePartRemoved `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessagePartRemoved) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessagePartRemoved) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {} + +type EventListResponseEventPermissionUpdatedType string + +const ( + EventListResponseEventPermissionUpdatedTypePermissionUpdated EventListResponseEventPermissionUpdatedType = "permission.updated" +) + +func (r EventListResponseEventPermissionUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventPermissionUpdatedTypePermissionUpdated: + return true + } + return false +} + +type EventListResponseEventPermissionReplied struct { + Properties EventListResponseEventPermissionRepliedProperties `json:"properties,required"` + Type EventListResponseEventPermissionRepliedType `json:"type,required"` + JSON eventListResponseEventPermissionRepliedJSON `json:"-"` +} + +// eventListResponseEventPermissionRepliedJSON contains the JSON metadata for the +// struct [EventListResponseEventPermissionReplied] +type eventListResponseEventPermissionRepliedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventPermissionReplied) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseMessagePartRemovedProperties struct { - MessageID string `json:"messageID,required"` - PartID string `json:"partID,required"` - SessionID string `json:"sessionID,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - MessageID respjson.Field - PartID respjson.Field - SessionID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventPermissionRepliedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseMessagePartRemovedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseMessagePartRemovedProperties) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventPermissionReplied) implementsEventListResponse() {} + +type EventListResponseEventPermissionRepliedProperties struct { + PermissionID string `json:"permissionID,required"` + Response string `json:"response,required"` + SessionID string `json:"sessionID,required"` + JSON eventListResponseEventPermissionRepliedPropertiesJSON `json:"-"` +} + +// eventListResponseEventPermissionRepliedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventPermissionRepliedProperties] +type eventListResponseEventPermissionRepliedPropertiesJSON struct { + PermissionID apijson.Field + Response apijson.Field + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventPermissionRepliedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponsePermissionUpdated struct { - Properties EventListResponsePermissionUpdatedProperties `json:"properties,required"` - Type constant.PermissionUpdated `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventPermissionRepliedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponsePermissionUpdated) RawJSON() string { return r.JSON.raw } -func (r *EventListResponsePermissionUpdated) UnmarshalJSON(data []byte) error { +type EventListResponseEventPermissionRepliedType string + +const ( + EventListResponseEventPermissionRepliedTypePermissionReplied EventListResponseEventPermissionRepliedType = "permission.replied" +) + +func (r EventListResponseEventPermissionRepliedType) IsKnown() bool { + switch r { + case EventListResponseEventPermissionRepliedTypePermissionReplied: + return true + } + return false +} + +type EventListResponseEventFileEdited struct { + Properties EventListResponseEventFileEditedProperties `json:"properties,required"` + Type EventListResponseEventFileEditedType `json:"type,required"` + JSON eventListResponseEventFileEditedJSON `json:"-"` +} + +// eventListResponseEventFileEditedJSON contains the JSON metadata for the struct +// [EventListResponseEventFileEdited] +type eventListResponseEventFileEditedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventFileEdited) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponsePermissionUpdatedProperties struct { - ID string `json:"id,required"` - MessageID string `json:"messageID,required"` - Metadata map[string]any `json:"metadata,required"` - SessionID string `json:"sessionID,required"` - Time EventListResponsePermissionUpdatedPropertiesTime `json:"time,required"` - Title string `json:"title,required"` - Type string `json:"type,required"` - CallID string `json:"callID"` - Pattern string `json:"pattern"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - MessageID respjson.Field - Metadata respjson.Field - SessionID respjson.Field - Time respjson.Field - Title respjson.Field - Type respjson.Field - CallID respjson.Field - Pattern respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventFileEditedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponsePermissionUpdatedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponsePermissionUpdatedProperties) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventFileEdited) implementsEventListResponse() {} + +type EventListResponseEventFileEditedProperties struct { + File string `json:"file,required"` + JSON eventListResponseEventFileEditedPropertiesJSON `json:"-"` +} + +// eventListResponseEventFileEditedPropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventFileEditedProperties] +type eventListResponseEventFileEditedPropertiesJSON struct { + File apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventFileEditedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponsePermissionUpdatedPropertiesTime struct { - Created float64 `json:"created,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Created respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventFileEditedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponsePermissionUpdatedPropertiesTime) RawJSON() string { return r.JSON.raw } -func (r *EventListResponsePermissionUpdatedPropertiesTime) UnmarshalJSON(data []byte) error { +type EventListResponseEventFileEditedType string + +const ( + EventListResponseEventFileEditedTypeFileEdited EventListResponseEventFileEditedType = "file.edited" +) + +func (r EventListResponseEventFileEditedType) IsKnown() bool { + switch r { + case EventListResponseEventFileEditedTypeFileEdited: + return true + } + return false +} + +type EventListResponseEventSessionUpdated struct { + Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"` + Type EventListResponseEventSessionUpdatedType `json:"type,required"` + JSON eventListResponseEventSessionUpdatedJSON `json:"-"` +} + +// eventListResponseEventSessionUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventSessionUpdated] +type eventListResponseEventSessionUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionUpdated) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponsePermissionReplied struct { - Properties EventListResponsePermissionRepliedProperties `json:"properties,required"` - Type constant.PermissionReplied `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionUpdatedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponsePermissionReplied) RawJSON() string { return r.JSON.raw } -func (r *EventListResponsePermissionReplied) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventSessionUpdated) implementsEventListResponse() {} + +type EventListResponseEventSessionUpdatedProperties struct { + Info Session `json:"info,required"` + JSON eventListResponseEventSessionUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventSessionUpdatedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventSessionUpdatedProperties] +type eventListResponseEventSessionUpdatedPropertiesJSON struct { + Info apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionUpdatedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponsePermissionRepliedProperties struct { - PermissionID string `json:"permissionID,required"` - Response string `json:"response,required"` - SessionID string `json:"sessionID,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - PermissionID respjson.Field - Response respjson.Field - SessionID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionUpdatedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponsePermissionRepliedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponsePermissionRepliedProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventSessionUpdatedType string + +const ( + EventListResponseEventSessionUpdatedTypeSessionUpdated EventListResponseEventSessionUpdatedType = "session.updated" +) + +func (r EventListResponseEventSessionUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventSessionUpdatedTypeSessionUpdated: + return true + } + return false +} + +type EventListResponseEventSessionDeleted struct { + Properties EventListResponseEventSessionDeletedProperties `json:"properties,required"` + Type EventListResponseEventSessionDeletedType `json:"type,required"` + JSON eventListResponseEventSessionDeletedJSON `json:"-"` +} + +// eventListResponseEventSessionDeletedJSON contains the JSON metadata for the +// struct [EventListResponseEventSessionDeleted] +type eventListResponseEventSessionDeletedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionDeleted) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseFileEdited struct { - Properties EventListResponseFileEditedProperties `json:"properties,required"` - Type constant.FileEdited `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionDeletedJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseFileEdited) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseFileEdited) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventSessionDeleted) implementsEventListResponse() {} + +type EventListResponseEventSessionDeletedProperties struct { + Info Session `json:"info,required"` + JSON eventListResponseEventSessionDeletedPropertiesJSON `json:"-"` +} + +// eventListResponseEventSessionDeletedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventSessionDeletedProperties] +type eventListResponseEventSessionDeletedPropertiesJSON struct { + Info apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionDeletedProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseFileEditedProperties struct { - File string `json:"file,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - File respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionDeletedPropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseFileEditedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseFileEditedProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventSessionDeletedType string + +const ( + EventListResponseEventSessionDeletedTypeSessionDeleted EventListResponseEventSessionDeletedType = "session.deleted" +) + +func (r EventListResponseEventSessionDeletedType) IsKnown() bool { + switch r { + case EventListResponseEventSessionDeletedTypeSessionDeleted: + return true + } + return false +} + +type EventListResponseEventSessionIdle struct { + Properties EventListResponseEventSessionIdleProperties `json:"properties,required"` + Type EventListResponseEventSessionIdleType `json:"type,required"` + JSON eventListResponseEventSessionIdleJSON `json:"-"` +} + +// eventListResponseEventSessionIdleJSON contains the JSON metadata for the struct +// [EventListResponseEventSessionIdle] +type eventListResponseEventSessionIdleJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionIdle) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseSessionUpdated struct { - Properties EventListResponseSessionUpdatedProperties `json:"properties,required"` - Type constant.SessionUpdated `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionIdleJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionUpdated) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionUpdated) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventSessionIdle) implementsEventListResponse() {} + +type EventListResponseEventSessionIdleProperties struct { + SessionID string `json:"sessionID,required"` + JSON eventListResponseEventSessionIdlePropertiesJSON `json:"-"` +} + +// eventListResponseEventSessionIdlePropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventSessionIdleProperties] +type eventListResponseEventSessionIdlePropertiesJSON struct { + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionIdleProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseSessionUpdatedProperties struct { - Info Session `json:"info,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Info respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionIdlePropertiesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionUpdatedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionUpdatedProperties) UnmarshalJSON(data []byte) error { +type EventListResponseEventSessionIdleType string + +const ( + EventListResponseEventSessionIdleTypeSessionIdle EventListResponseEventSessionIdleType = "session.idle" +) + +func (r EventListResponseEventSessionIdleType) IsKnown() bool { + switch r { + case EventListResponseEventSessionIdleTypeSessionIdle: + return true + } + return false +} + +type EventListResponseEventSessionError struct { + Properties EventListResponseEventSessionErrorProperties `json:"properties,required"` + Type EventListResponseEventSessionErrorType `json:"type,required"` + JSON eventListResponseEventSessionErrorJSON `json:"-"` +} + +// eventListResponseEventSessionErrorJSON contains the JSON metadata for the struct +// [EventListResponseEventSessionError] +type eventListResponseEventSessionErrorJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionError) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseSessionDeleted struct { - Properties EventListResponseSessionDeletedProperties `json:"properties,required"` - Type constant.SessionDeleted `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionErrorJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionDeleted) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionDeleted) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} +func (r EventListResponseEventSessionError) implementsEventListResponse() {} -type EventListResponseSessionDeletedProperties struct { - Info Session `json:"info,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Info respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionDeletedProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionDeletedProperties) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type EventListResponseSessionIdle struct { - Properties EventListResponseSessionIdleProperties `json:"properties,required"` - Type constant.SessionIdle `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionIdle) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionIdle) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type EventListResponseSessionIdleProperties struct { - SessionID string `json:"sessionID,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - SessionID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionIdleProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionIdleProperties) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type EventListResponseSessionError struct { - Properties EventListResponseSessionErrorProperties `json:"properties,required"` - Type constant.SessionError `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionError) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionError) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type EventListResponseSessionErrorProperties struct { - Error EventListResponseSessionErrorPropertiesErrorUnion `json:"error"` +type EventListResponseEventSessionErrorProperties struct { + Error EventListResponseEventSessionErrorPropertiesError `json:"error"` SessionID string `json:"sessionID"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Error respjson.Field - SessionID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` + JSON eventListResponseEventSessionErrorPropertiesJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r EventListResponseSessionErrorProperties) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseSessionErrorProperties) UnmarshalJSON(data []byte) error { +// eventListResponseEventSessionErrorPropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventSessionErrorProperties] +type eventListResponseEventSessionErrorPropertiesJSON struct { + Error apijson.Field + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionErrorProperties) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// EventListResponseSessionErrorPropertiesErrorUnion contains all possible -// properties and values from [ProviderAuthError], [UnknownError], -// [MessageOutputLengthError], [MessageAbortedError]. -// -// Use the [EventListResponseSessionErrorPropertiesErrorUnion.AsAny] method to -// switch on the variant. -// -// Use the methods beginning with 'As' to cast the union to one of its variants. -type EventListResponseSessionErrorPropertiesErrorUnion struct { - // This field is a union of [ProviderAuthErrorData], [UnknownErrorData], [any], - // [any] - Data EventListResponseSessionErrorPropertiesErrorUnionData `json:"data"` - // Any of "ProviderAuthError", "UnknownError", "MessageOutputLengthError", - // "MessageAbortedError". - Name string `json:"name"` - JSON struct { - Data respjson.Field - Name respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionErrorPropertiesJSON) RawJSON() string { + return r.raw } -// anyEventListResponseSessionErrorPropertiesError is implemented by each variant -// of [EventListResponseSessionErrorPropertiesErrorUnion] to add type safety for -// the return type of [EventListResponseSessionErrorPropertiesErrorUnion.AsAny] -type anyEventListResponseSessionErrorPropertiesError interface { - implEventListResponseSessionErrorPropertiesErrorUnion() +type EventListResponseEventSessionErrorPropertiesError struct { + // This field can have the runtime type of [shared.ProviderAuthErrorData], + // [shared.UnknownErrorData], [interface{}]. + Data interface{} `json:"data,required"` + Name EventListResponseEventSessionErrorPropertiesErrorName `json:"name,required"` + JSON eventListResponseEventSessionErrorPropertiesErrorJSON `json:"-"` + union EventListResponseEventSessionErrorPropertiesErrorUnion } -func (ProviderAuthError) implEventListResponseSessionErrorPropertiesErrorUnion() {} -func (UnknownError) implEventListResponseSessionErrorPropertiesErrorUnion() {} -func (MessageOutputLengthError) implEventListResponseSessionErrorPropertiesErrorUnion() {} -func (MessageAbortedError) implEventListResponseSessionErrorPropertiesErrorUnion() {} +// eventListResponseEventSessionErrorPropertiesErrorJSON contains the JSON metadata +// for the struct [EventListResponseEventSessionErrorPropertiesError] +type eventListResponseEventSessionErrorPropertiesErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} -// Use the following switch statement to find the correct variant -// -// switch variant := EventListResponseSessionErrorPropertiesErrorUnion.AsAny().(type) { -// case opencode.ProviderAuthError: -// case opencode.UnknownError: -// case opencode.MessageOutputLengthError: -// case opencode.MessageAbortedError: -// default: -// fmt.Errorf("no variant present") -// } -func (u EventListResponseSessionErrorPropertiesErrorUnion) AsAny() anyEventListResponseSessionErrorPropertiesError { - switch u.Name { - case "ProviderAuthError": - return u.AsProviderAuthError() - case "UnknownError": - return u.AsUnknownError() - case "MessageOutputLengthError": - return u.AsMessageOutputLengthError() - case "MessageAbortedError": - return u.AsMessageAbortedError() +func (r eventListResponseEventSessionErrorPropertiesErrorJSON) RawJSON() string { + return r.raw +} + +func (r *EventListResponseEventSessionErrorPropertiesError) UnmarshalJSON(data []byte) (err error) { + *r = EventListResponseEventSessionErrorPropertiesError{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err } - return nil + return apijson.Port(r.union, &r) } -func (u EventListResponseSessionErrorPropertiesErrorUnion) AsProviderAuthError() (v ProviderAuthError) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseSessionErrorPropertiesErrorUnion) AsUnknownError() (v UnknownError) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseSessionErrorPropertiesErrorUnion) AsMessageOutputLengthError() (v MessageOutputLengthError) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -func (u EventListResponseSessionErrorPropertiesErrorUnion) AsMessageAbortedError() (v MessageAbortedError) { - apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) - return -} - -// Returns the unmodified JSON received from the API -func (u EventListResponseSessionErrorPropertiesErrorUnion) RawJSON() string { return u.JSON.raw } - -func (r *EventListResponseSessionErrorPropertiesErrorUnion) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// EventListResponseSessionErrorPropertiesErrorUnionData is an implicit subunion of -// [EventListResponseSessionErrorPropertiesErrorUnion]. -// EventListResponseSessionErrorPropertiesErrorUnionData provides convenient access -// to the sub-properties of the union. +// AsUnion returns a [EventListResponseEventSessionErrorPropertiesErrorUnion] +// interface which you can cast to the specific types for more type safety. // -// For type safety it is recommended to directly use a variant of the -// [EventListResponseSessionErrorPropertiesErrorUnion]. -// -// If the underlying value is not a json object, one of the following properties -// will be valid: OfMessageAbortedErrorData] -type EventListResponseSessionErrorPropertiesErrorUnionData struct { - // This field will be present if the value is a [any] instead of an object. - OfMessageAbortedErrorData any `json:",inline"` - Message string `json:"message"` - // This field is from variant [ProviderAuthErrorData]. - ProviderID string `json:"providerID"` - JSON struct { - OfMessageAbortedErrorData respjson.Field - Message respjson.Field - ProviderID respjson.Field - raw string - } `json:"-"` +// Possible runtime types of the union are [shared.ProviderAuthError], +// [shared.UnknownError], +// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError], +// [shared.MessageAbortedError]. +func (r EventListResponseEventSessionErrorPropertiesError) AsUnion() EventListResponseEventSessionErrorPropertiesErrorUnion { + return r.union } -func (r *EventListResponseSessionErrorPropertiesErrorUnionData) UnmarshalJSON(data []byte) error { +// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError], +// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError] or +// [shared.MessageAbortedError]. +type EventListResponseEventSessionErrorPropertiesErrorUnion interface { + ImplementsEventListResponseEventSessionErrorPropertiesError() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*EventListResponseEventSessionErrorPropertiesErrorUnion)(nil)).Elem(), + "name", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.ProviderAuthError{}), + DiscriminatorValue: "ProviderAuthError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.UnknownError{}), + DiscriminatorValue: "UnknownError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError{}), + DiscriminatorValue: "MessageOutputLengthError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.MessageAbortedError{}), + DiscriminatorValue: "MessageAbortedError", + }, + ) +} + +type EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError struct { + Data interface{} `json:"data,required"` + Name EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName `json:"name,required"` + JSON eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON `json:"-"` +} + +// eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON +// contains the JSON metadata for the struct +// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError] +type eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type EventListResponseServerConnected struct { - Properties any `json:"properties,required"` - Type constant.ServerConnected `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Properties respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r EventListResponseServerConnected) RawJSON() string { return r.JSON.raw } -func (r *EventListResponseServerConnected) UnmarshalJSON(data []byte) error { +func (r EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError) ImplementsEventListResponseEventSessionErrorPropertiesError() { +} + +type EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName string + +const ( + EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorNameMessageOutputLengthError EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName = "MessageOutputLengthError" +) + +func (r EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName) IsKnown() bool { + switch r { + case EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorNameMessageOutputLengthError: + return true + } + return false +} + +type EventListResponseEventSessionErrorPropertiesErrorName string + +const ( + EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError EventListResponseEventSessionErrorPropertiesErrorName = "ProviderAuthError" + EventListResponseEventSessionErrorPropertiesErrorNameUnknownError EventListResponseEventSessionErrorPropertiesErrorName = "UnknownError" + EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError EventListResponseEventSessionErrorPropertiesErrorName = "MessageOutputLengthError" + EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError EventListResponseEventSessionErrorPropertiesErrorName = "MessageAbortedError" +) + +func (r EventListResponseEventSessionErrorPropertiesErrorName) IsKnown() bool { + switch r { + case EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError, EventListResponseEventSessionErrorPropertiesErrorNameUnknownError, EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError, EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError: + return true + } + return false +} + +type EventListResponseEventSessionErrorType string + +const ( + EventListResponseEventSessionErrorTypeSessionError EventListResponseEventSessionErrorType = "session.error" +) + +func (r EventListResponseEventSessionErrorType) IsKnown() bool { + switch r { + case EventListResponseEventSessionErrorTypeSessionError: + return true + } + return false +} + +type EventListResponseEventServerConnected struct { + Properties interface{} `json:"properties,required"` + Type EventListResponseEventServerConnectedType `json:"type,required"` + JSON eventListResponseEventServerConnectedJSON `json:"-"` +} + +// eventListResponseEventServerConnectedJSON contains the JSON metadata for the +// struct [EventListResponseEventServerConnected] +type eventListResponseEventServerConnectedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } + +func (r eventListResponseEventServerConnectedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventServerConnected) implementsEventListResponse() {} + +type EventListResponseEventServerConnectedType string + +const ( + EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected" +) + +func (r EventListResponseEventServerConnectedType) IsKnown() bool { + switch r { + case EventListResponseEventServerConnectedTypeServerConnected: + return true + } + return false +} + +type EventListResponseType string + +const ( + EventListResponseTypeInstallationUpdated EventListResponseType = "installation.updated" + EventListResponseTypeLspClientDiagnostics EventListResponseType = "lsp.client.diagnostics" + EventListResponseTypeMessageUpdated EventListResponseType = "message.updated" + EventListResponseTypeMessageRemoved EventListResponseType = "message.removed" + EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated" + EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed" + EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated" + EventListResponseTypePermissionReplied EventListResponseType = "permission.replied" + EventListResponseTypeFileEdited EventListResponseType = "file.edited" + EventListResponseTypeSessionUpdated EventListResponseType = "session.updated" + EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted" + EventListResponseTypeSessionIdle EventListResponseType = "session.idle" + EventListResponseTypeSessionError EventListResponseType = "session.error" + EventListResponseTypeServerConnected EventListResponseType = "server.connected" +) + +func (r EventListResponseType) IsKnown() bool { + switch r { + case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected: + return true + } + return false +} diff --git a/field.go b/field.go index ca1d8e8..56d2f89 100644 --- a/field.go +++ b/field.go @@ -1,28 +1,43 @@ package opencode import ( - "github.com/stainless-sdks/opencode-go/packages/param" + "github.com/sst/opencode-sdk-go/internal/param" "io" - "time" ) -func String(s string) param.Opt[string] { return param.NewOpt(s) } -func Int(i int64) param.Opt[int64] { return param.NewOpt(i) } -func Bool(b bool) param.Opt[bool] { return param.NewOpt(b) } -func Float(f float64) param.Opt[float64] { return param.NewOpt(f) } -func Time(t time.Time) param.Opt[time.Time] { return param.NewOpt(t) } +// F is a param field helper used to initialize a [param.Field] generic struct. +// This helps specify null, zero values, and overrides, as well as normal values. +// You can read more about this in our [README]. +// +// [README]: https://pkg.go.dev/github.com/sst/opencode-sdk-go#readme-request-fields +func F[T any](value T) param.Field[T] { return param.Field[T]{Value: value, Present: true} } -func Opt[T comparable](v T) param.Opt[T] { return param.NewOpt(v) } -func Ptr[T any](v T) *T { return &v } +// Null is a param field helper which explicitly sends null to the API. +func Null[T any]() param.Field[T] { return param.Field[T]{Null: true, Present: true} } -func IntPtr(v int64) *int64 { return &v } -func BoolPtr(v bool) *bool { return &v } -func FloatPtr(v float64) *float64 { return &v } -func StringPtr(v string) *string { return &v } -func TimePtr(v time.Time) *time.Time { return &v } +// Raw is a param field helper for specifying values for fields when the +// type you are looking to send is different from the type that is specified in +// the SDK. For example, if the type of the field is an integer, but you want +// to send a float, you could do that by setting the corresponding field with +// Raw[int](0.5). +func Raw[T any](value any) param.Field[T] { return param.Field[T]{Raw: value, Present: true} } -func File(rdr io.Reader, filename string, contentType string) file { - return file{rdr, filename, contentType} +// Int is a param field helper which helps specify integers. This is +// particularly helpful when specifying integer constants for fields. +func Int(value int64) param.Field[int64] { return F(value) } + +// String is a param field helper which helps specify strings. +func String(value string) param.Field[string] { return F(value) } + +// Float is a param field helper which helps specify floats. +func Float(value float64) param.Field[float64] { return F(value) } + +// Bool is a param field helper which helps specify bools. +func Bool(value bool) param.Field[bool] { return F(value) } + +// FileParam is a param field helper which helps files with a mime content-type. +func FileParam(reader io.Reader, filename string, contentType string) param.Field[io.Reader] { + return F[io.Reader](&file{reader, filename, contentType}) } type file struct { @@ -31,15 +46,5 @@ type file struct { contentType string } -func (f file) Filename() string { - if f.name != "" { - return f.name - } else if named, ok := f.Reader.(interface{ Name() string }); ok { - return named.Name() - } - return "" -} - -func (f file) ContentType() string { - return f.contentType -} +func (f *file) ContentType() string { return f.contentType } +func (f *file) Filename() string { return f.name } diff --git a/file.go b/file.go index ff18b64..9af7f53 100644 --- a/file.go +++ b/file.go @@ -7,11 +7,11 @@ import ( "net/http" "net/url" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/internal/apiquery" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" - "github.com/stainless-sdks/opencode-go/packages/respjson" + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/apiquery" + "github.com/sst/opencode-sdk-go/internal/param" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" ) // FileService contains methods and other services that help with interacting with @@ -27,28 +27,20 @@ type FileService struct { // NewFileService generates a new service that applies the given options to each // request. These options are applied after the parent client's options (if there // is one), and before any request-specific options. -func NewFileService(opts ...option.RequestOption) (r FileService) { - r = FileService{} +func NewFileService(opts ...option.RequestOption) (r *FileService) { + r = &FileService{} r.Options = opts return } // List files and directories -func (r *FileService) List(ctx context.Context, query FileListParams, opts ...option.RequestOption) (res *[]FileListResponse, err error) { +func (r *FileService) List(ctx context.Context, query FileListParams, opts ...option.RequestOption) (res *[]FileNode, err error) { opts = append(r.Options[:], opts...) path := "file" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) return } -// Get file status -func (r *FileService) GetStatus(ctx context.Context, opts ...option.RequestOption) (res *[]FileGetStatusResponse, err error) { - opts = append(r.Options[:], opts...) - path := "file/status" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return -} - // Read a file func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...option.RequestOption) (res *FileReadResponse, err error) { opts = append(r.Options[:], opts...) @@ -57,86 +49,120 @@ func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...op return } -type FileListResponse struct { - Ignored bool `json:"ignored,required"` - Name string `json:"name,required"` - Path string `json:"path,required"` - // Any of "file", "directory". - Type FileListResponseType `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Ignored respjson.Field - Name respjson.Field - Path respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +// Get file status +func (r *FileService) Status(ctx context.Context, opts ...option.RequestOption) (res *[]File, err error) { + opts = append(r.Options[:], opts...) + path := "file/status" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return } -// Returns the unmodified JSON received from the API -func (r FileListResponse) RawJSON() string { return r.JSON.raw } -func (r *FileListResponse) UnmarshalJSON(data []byte) error { +type File struct { + Added int64 `json:"added,required"` + Path string `json:"path,required"` + Removed int64 `json:"removed,required"` + Status FileStatus `json:"status,required"` + JSON fileJSON `json:"-"` +} + +// fileJSON contains the JSON metadata for the struct [File] +type fileJSON struct { + Added apijson.Field + Path apijson.Field + Removed apijson.Field + Status apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *File) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type FileListResponseType string - -const ( - FileListResponseTypeFile FileListResponseType = "file" - FileListResponseTypeDirectory FileListResponseType = "directory" -) - -type FileGetStatusResponse struct { - Added int64 `json:"added,required"` - Path string `json:"path,required"` - Removed int64 `json:"removed,required"` - // Any of "added", "deleted", "modified". - Status FileGetStatusResponseStatus `json:"status,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Added respjson.Field - Path respjson.Field - Removed respjson.Field - Status respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r fileJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r FileGetStatusResponse) RawJSON() string { return r.JSON.raw } -func (r *FileGetStatusResponse) UnmarshalJSON(data []byte) error { +type FileStatus string + +const ( + FileStatusAdded FileStatus = "added" + FileStatusDeleted FileStatus = "deleted" + FileStatusModified FileStatus = "modified" +) + +func (r FileStatus) IsKnown() bool { + switch r { + case FileStatusAdded, FileStatusDeleted, FileStatusModified: + return true + } + return false +} + +type FileNode struct { + Ignored bool `json:"ignored,required"` + Name string `json:"name,required"` + Path string `json:"path,required"` + Type FileNodeType `json:"type,required"` + JSON fileNodeJSON `json:"-"` +} + +// fileNodeJSON contains the JSON metadata for the struct [FileNode] +type fileNodeJSON struct { + Ignored apijson.Field + Name apijson.Field + Path apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FileNode) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type FileGetStatusResponseStatus string +func (r fileNodeJSON) RawJSON() string { + return r.raw +} + +type FileNodeType string const ( - FileGetStatusResponseStatusAdded FileGetStatusResponseStatus = "added" - FileGetStatusResponseStatusDeleted FileGetStatusResponseStatus = "deleted" - FileGetStatusResponseStatusModified FileGetStatusResponseStatus = "modified" + FileNodeTypeFile FileNodeType = "file" + FileNodeTypeDirectory FileNodeType = "directory" ) +func (r FileNodeType) IsKnown() bool { + switch r { + case FileNodeTypeFile, FileNodeTypeDirectory: + return true + } + return false +} + type FileReadResponse struct { - Content string `json:"content,required"` - // Any of "raw", "patch". - Type FileReadResponseType `json:"type,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Content respjson.Field - Type respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` + Content string `json:"content,required"` + Type FileReadResponseType `json:"type,required"` + JSON fileReadResponseJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r FileReadResponse) RawJSON() string { return r.JSON.raw } -func (r *FileReadResponse) UnmarshalJSON(data []byte) error { +// fileReadResponseJSON contains the JSON metadata for the struct +// [FileReadResponse] +type fileReadResponseJSON struct { + Content apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FileReadResponse) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } +func (r fileReadResponseJSON) RawJSON() string { + return r.raw +} + type FileReadResponseType string const ( @@ -144,13 +170,20 @@ const ( FileReadResponseTypePatch FileReadResponseType = "patch" ) +func (r FileReadResponseType) IsKnown() bool { + switch r { + case FileReadResponseTypeRaw, FileReadResponseTypePatch: + return true + } + return false +} + type FileListParams struct { - Path string `query:"path,required" json:"-"` - paramObj + Path param.Field[string] `query:"path,required"` } // URLQuery serializes [FileListParams]'s query parameters as `url.Values`. -func (r FileListParams) URLQuery() (v url.Values, err error) { +func (r FileListParams) URLQuery() (v url.Values) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatComma, NestedFormat: apiquery.NestedQueryFormatBrackets, @@ -158,12 +191,11 @@ func (r FileListParams) URLQuery() (v url.Values, err error) { } type FileReadParams struct { - Path string `query:"path,required" json:"-"` - paramObj + Path param.Field[string] `query:"path,required"` } // URLQuery serializes [FileReadParams]'s query parameters as `url.Values`. -func (r FileReadParams) URLQuery() (v url.Values, err error) { +func (r FileReadParams) URLQuery() (v url.Values) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatComma, NestedFormat: apiquery.NestedQueryFormatBrackets, diff --git a/file_test.go b/file_test.go index 25a2876..273f6e8 100644 --- a/file_test.go +++ b/file_test.go @@ -8,9 +8,9 @@ import ( "os" "testing" - "github.com/stainless-sdks/opencode-go" - "github.com/stainless-sdks/opencode-go/internal/testutil" - "github.com/stainless-sdks/opencode-go/option" + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" ) func TestFileList(t *testing.T) { @@ -24,10 +24,9 @@ func TestFileList(t *testing.T) { } client := opencode.NewClient( option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), ) _, err := client.File.List(context.TODO(), opencode.FileListParams{ - Path: "path", + Path: opencode.F("path"), }) if err != nil { var apierr *opencode.Error @@ -38,29 +37,6 @@ func TestFileList(t *testing.T) { } } -func TestFileGetStatus(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := opencode.NewClient( - option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), - ) - _, err := client.File.GetStatus(context.TODO()) - if err != nil { - var apierr *opencode.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} - func TestFileRead(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" @@ -72,10 +48,9 @@ func TestFileRead(t *testing.T) { } client := opencode.NewClient( option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), ) _, err := client.File.Read(context.TODO(), opencode.FileReadParams{ - Path: "path", + Path: opencode.F("path"), }) if err != nil { var apierr *opencode.Error @@ -85,3 +60,25 @@ func TestFileRead(t *testing.T) { t.Fatalf("err should be nil: %s", err.Error()) } } + +func TestFileStatus(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.File.Status(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/find.go b/find.go index 417aaad..a993a35 100644 --- a/find.go +++ b/find.go @@ -4,16 +4,14 @@ package opencode import ( "context" - "encoding/json" "net/http" "net/url" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/internal/apiquery" - "github.com/stainless-sdks/opencode-go/internal/requestconfig" - "github.com/stainless-sdks/opencode-go/option" - "github.com/stainless-sdks/opencode-go/packages/param" - "github.com/stainless-sdks/opencode-go/packages/respjson" + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/apiquery" + "github.com/sst/opencode-sdk-go/internal/param" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" ) // FindService contains methods and other services that help with interacting with @@ -29,22 +27,14 @@ type FindService struct { // NewFindService generates a new service that applies the given options to each // request. These options are applied after the parent client's options (if there // is one), and before any request-specific options. -func NewFindService(opts ...option.RequestOption) (r FindService) { - r = FindService{} +func NewFindService(opts ...option.RequestOption) (r *FindService) { + r = &FindService{} r.Options = opts return } -// Find text in files -func (r *FindService) Get(ctx context.Context, query FindGetParams, opts ...option.RequestOption) (res *[]FindGetResponse, err error) { - opts = append(r.Options[:], opts...) - path := "find" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) - return -} - // Find files -func (r *FindService) GetFile(ctx context.Context, query FindGetFileParams, opts ...option.RequestOption) (res *[]string, err error) { +func (r *FindService) Files(ctx context.Context, query FindFilesParams, opts ...option.RequestOption) (res *[]string, err error) { opts = append(r.Options[:], opts...) path := "find/file" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) @@ -52,284 +42,283 @@ func (r *FindService) GetFile(ctx context.Context, query FindGetFileParams, opts } // Find workspace symbols -func (r *FindService) GetSymbol(ctx context.Context, query FindGetSymbolParams, opts ...option.RequestOption) (res *[]FindGetSymbolResponse, err error) { +func (r *FindService) Symbols(ctx context.Context, query FindSymbolsParams, opts ...option.RequestOption) (res *[]Symbol, err error) { opts = append(r.Options[:], opts...) path := "find/symbol" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) return } -type Range struct { - End RangeEnd `json:"end,required"` - Start RangeStart `json:"start,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - End respjson.Field - Start respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +// Find text in files +func (r *FindService) Text(ctx context.Context, query FindTextParams, opts ...option.RequestOption) (res *[]FindTextResponse, err error) { + opts = append(r.Options[:], opts...) + path := "find" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return } -// Returns the unmodified JSON received from the API -func (r Range) RawJSON() string { return r.JSON.raw } -func (r *Range) UnmarshalJSON(data []byte) error { +type Symbol struct { + Kind float64 `json:"kind,required"` + Location SymbolLocation `json:"location,required"` + Name string `json:"name,required"` + JSON symbolJSON `json:"-"` +} + +// symbolJSON contains the JSON metadata for the struct [Symbol] +type symbolJSON struct { + Kind apijson.Field + Location apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Symbol) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// ToParam converts this Range to a RangeParam. -// -// Warning: the fields of the param type will not be present. ToParam should only -// be used at the last possible moment before sending a request. Test for this with -// RangeParam.Overrides() -func (r Range) ToParam() RangeParam { - return param.Override[RangeParam](json.RawMessage(r.RawJSON())) +func (r symbolJSON) RawJSON() string { + return r.raw } -type RangeEnd struct { - Character float64 `json:"character,required"` - Line float64 `json:"line,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Character respjson.Field - Line respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +type SymbolLocation struct { + Range SymbolLocationRange `json:"range,required"` + Uri string `json:"uri,required"` + JSON symbolLocationJSON `json:"-"` } -// Returns the unmodified JSON received from the API -func (r RangeEnd) RawJSON() string { return r.JSON.raw } -func (r *RangeEnd) UnmarshalJSON(data []byte) error { +// symbolLocationJSON contains the JSON metadata for the struct [SymbolLocation] +type symbolLocationJSON struct { + Range apijson.Field + Uri apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocation) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type RangeStart struct { - Character float64 `json:"character,required"` - Line float64 `json:"line,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Character respjson.Field - Line respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r symbolLocationJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r RangeStart) RawJSON() string { return r.JSON.raw } -func (r *RangeStart) UnmarshalJSON(data []byte) error { +type SymbolLocationRange struct { + End SymbolLocationRangeEnd `json:"end,required"` + Start SymbolLocationRangeStart `json:"start,required"` + JSON symbolLocationRangeJSON `json:"-"` +} + +// symbolLocationRangeJSON contains the JSON metadata for the struct +// [SymbolLocationRange] +type symbolLocationRangeJSON struct { + End apijson.Field + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocationRange) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// The properties End, Start are required. -type RangeParam struct { - End RangeEndParam `json:"end,omitzero,required"` - Start RangeStartParam `json:"start,omitzero,required"` - paramObj +func (r symbolLocationRangeJSON) RawJSON() string { + return r.raw } -func (r RangeParam) MarshalJSON() (data []byte, err error) { - type shadow RangeParam - return param.MarshalObject(r, (*shadow)(&r)) +type SymbolLocationRangeEnd struct { + Character float64 `json:"character,required"` + Line float64 `json:"line,required"` + JSON symbolLocationRangeEndJSON `json:"-"` } -func (r *RangeParam) UnmarshalJSON(data []byte) error { + +// symbolLocationRangeEndJSON contains the JSON metadata for the struct +// [SymbolLocationRangeEnd] +type symbolLocationRangeEndJSON struct { + Character apijson.Field + Line apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocationRangeEnd) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// The properties Character, Line are required. -type RangeEndParam struct { - Character float64 `json:"character,required"` - Line float64 `json:"line,required"` - paramObj +func (r symbolLocationRangeEndJSON) RawJSON() string { + return r.raw } -func (r RangeEndParam) MarshalJSON() (data []byte, err error) { - type shadow RangeEndParam - return param.MarshalObject(r, (*shadow)(&r)) +type SymbolLocationRangeStart struct { + Character float64 `json:"character,required"` + Line float64 `json:"line,required"` + JSON symbolLocationRangeStartJSON `json:"-"` } -func (r *RangeEndParam) UnmarshalJSON(data []byte) error { + +// symbolLocationRangeStartJSON contains the JSON metadata for the struct +// [SymbolLocationRangeStart] +type symbolLocationRangeStartJSON struct { + Character apijson.Field + Line apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocationRangeStart) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -// The properties Character, Line are required. -type RangeStartParam struct { - Character float64 `json:"character,required"` - Line float64 `json:"line,required"` - paramObj +func (r symbolLocationRangeStartJSON) RawJSON() string { + return r.raw } -func (r RangeStartParam) MarshalJSON() (data []byte, err error) { - type shadow RangeStartParam - return param.MarshalObject(r, (*shadow)(&r)) +type FindTextResponse struct { + AbsoluteOffset float64 `json:"absolute_offset,required"` + LineNumber float64 `json:"line_number,required"` + Lines FindTextResponseLines `json:"lines,required"` + Path FindTextResponsePath `json:"path,required"` + Submatches []FindTextResponseSubmatch `json:"submatches,required"` + JSON findTextResponseJSON `json:"-"` } -func (r *RangeStartParam) UnmarshalJSON(data []byte) error { + +// findTextResponseJSON contains the JSON metadata for the struct +// [FindTextResponse] +type findTextResponseJSON struct { + AbsoluteOffset apijson.Field + LineNumber apijson.Field + Lines apijson.Field + Path apijson.Field + Submatches apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponse) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type FindGetResponse struct { - AbsoluteOffset float64 `json:"absolute_offset,required"` - LineNumber float64 `json:"line_number,required"` - Lines FindGetResponseLines `json:"lines,required"` - Path FindGetResponsePath `json:"path,required"` - Submatches []FindGetResponseSubmatch `json:"submatches,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - AbsoluteOffset respjson.Field - LineNumber respjson.Field - Lines respjson.Field - Path respjson.Field - Submatches respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r findTextResponseJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r FindGetResponse) RawJSON() string { return r.JSON.raw } -func (r *FindGetResponse) UnmarshalJSON(data []byte) error { +type FindTextResponseLines struct { + Text string `json:"text,required"` + JSON findTextResponseLinesJSON `json:"-"` +} + +// findTextResponseLinesJSON contains the JSON metadata for the struct +// [FindTextResponseLines] +type findTextResponseLinesJSON struct { + Text apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponseLines) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type FindGetResponseLines struct { - Text string `json:"text,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Text respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r findTextResponseLinesJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r FindGetResponseLines) RawJSON() string { return r.JSON.raw } -func (r *FindGetResponseLines) UnmarshalJSON(data []byte) error { +type FindTextResponsePath struct { + Text string `json:"text,required"` + JSON findTextResponsePathJSON `json:"-"` +} + +// findTextResponsePathJSON contains the JSON metadata for the struct +// [FindTextResponsePath] +type findTextResponsePathJSON struct { + Text apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponsePath) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type FindGetResponsePath struct { - Text string `json:"text,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Text respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r findTextResponsePathJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r FindGetResponsePath) RawJSON() string { return r.JSON.raw } -func (r *FindGetResponsePath) UnmarshalJSON(data []byte) error { +type FindTextResponseSubmatch struct { + End float64 `json:"end,required"` + Match FindTextResponseSubmatchesMatch `json:"match,required"` + Start float64 `json:"start,required"` + JSON findTextResponseSubmatchJSON `json:"-"` +} + +// findTextResponseSubmatchJSON contains the JSON metadata for the struct +// [FindTextResponseSubmatch] +type findTextResponseSubmatchJSON struct { + End apijson.Field + Match apijson.Field + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponseSubmatch) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type FindGetResponseSubmatch struct { - End float64 `json:"end,required"` - Match FindGetResponseSubmatchMatch `json:"match,required"` - Start float64 `json:"start,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - End respjson.Field - Match respjson.Field - Start respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r findTextResponseSubmatchJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r FindGetResponseSubmatch) RawJSON() string { return r.JSON.raw } -func (r *FindGetResponseSubmatch) UnmarshalJSON(data []byte) error { +type FindTextResponseSubmatchesMatch struct { + Text string `json:"text,required"` + JSON findTextResponseSubmatchesMatchJSON `json:"-"` +} + +// findTextResponseSubmatchesMatchJSON contains the JSON metadata for the struct +// [FindTextResponseSubmatchesMatch] +type findTextResponseSubmatchesMatchJSON struct { + Text apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponseSubmatchesMatch) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -type FindGetResponseSubmatchMatch struct { - Text string `json:"text,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Text respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +func (r findTextResponseSubmatchesMatchJSON) RawJSON() string { + return r.raw } -// Returns the unmodified JSON received from the API -func (r FindGetResponseSubmatchMatch) RawJSON() string { return r.JSON.raw } -func (r *FindGetResponseSubmatchMatch) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) +type FindFilesParams struct { + Query param.Field[string] `query:"query,required"` } -type FindGetSymbolResponse struct { - Kind float64 `json:"kind,required"` - Location FindGetSymbolResponseLocation `json:"location,required"` - Name string `json:"name,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Kind respjson.Field - Location respjson.Field - Name respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r FindGetSymbolResponse) RawJSON() string { return r.JSON.raw } -func (r *FindGetSymbolResponse) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type FindGetSymbolResponseLocation struct { - Range Range `json:"range,required"` - Uri string `json:"uri,required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Range respjson.Field - Uri respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r FindGetSymbolResponseLocation) RawJSON() string { return r.JSON.raw } -func (r *FindGetSymbolResponseLocation) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type FindGetParams struct { - Pattern string `query:"pattern,required" json:"-"` - paramObj -} - -// URLQuery serializes [FindGetParams]'s query parameters as `url.Values`. -func (r FindGetParams) URLQuery() (v url.Values, err error) { +// URLQuery serializes [FindFilesParams]'s query parameters as `url.Values`. +func (r FindFilesParams) URLQuery() (v url.Values) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatComma, NestedFormat: apiquery.NestedQueryFormatBrackets, }) } -type FindGetFileParams struct { - Query string `query:"query,required" json:"-"` - paramObj +type FindSymbolsParams struct { + Query param.Field[string] `query:"query,required"` } -// URLQuery serializes [FindGetFileParams]'s query parameters as `url.Values`. -func (r FindGetFileParams) URLQuery() (v url.Values, err error) { +// URLQuery serializes [FindSymbolsParams]'s query parameters as `url.Values`. +func (r FindSymbolsParams) URLQuery() (v url.Values) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatComma, NestedFormat: apiquery.NestedQueryFormatBrackets, }) } -type FindGetSymbolParams struct { - Query string `query:"query,required" json:"-"` - paramObj +type FindTextParams struct { + Pattern param.Field[string] `query:"pattern,required"` } -// URLQuery serializes [FindGetSymbolParams]'s query parameters as `url.Values`. -func (r FindGetSymbolParams) URLQuery() (v url.Values, err error) { +// URLQuery serializes [FindTextParams]'s query parameters as `url.Values`. +func (r FindTextParams) URLQuery() (v url.Values) { return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ ArrayFormat: apiquery.ArrayQueryFormatComma, NestedFormat: apiquery.NestedQueryFormatBrackets, diff --git a/find_test.go b/find_test.go index f9841d6..22b5b95 100644 --- a/find_test.go +++ b/find_test.go @@ -8,12 +8,12 @@ import ( "os" "testing" - "github.com/stainless-sdks/opencode-go" - "github.com/stainless-sdks/opencode-go/internal/testutil" - "github.com/stainless-sdks/opencode-go/option" + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" ) -func TestFindGet(t *testing.T) { +func TestFindFiles(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -24,10 +24,9 @@ func TestFindGet(t *testing.T) { } client := opencode.NewClient( option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), ) - _, err := client.Find.Get(context.TODO(), opencode.FindGetParams{ - Pattern: "pattern", + _, err := client.Find.Files(context.TODO(), opencode.FindFilesParams{ + Query: opencode.F("query"), }) if err != nil { var apierr *opencode.Error @@ -38,7 +37,7 @@ func TestFindGet(t *testing.T) { } } -func TestFindGetFile(t *testing.T) { +func TestFindSymbols(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -49,10 +48,9 @@ func TestFindGetFile(t *testing.T) { } client := opencode.NewClient( option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), ) - _, err := client.Find.GetFile(context.TODO(), opencode.FindGetFileParams{ - Query: "query", + _, err := client.Find.Symbols(context.TODO(), opencode.FindSymbolsParams{ + Query: opencode.F("query"), }) if err != nil { var apierr *opencode.Error @@ -63,7 +61,7 @@ func TestFindGetFile(t *testing.T) { } } -func TestFindGetSymbol(t *testing.T) { +func TestFindText(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -74,10 +72,9 @@ func TestFindGetSymbol(t *testing.T) { } client := opencode.NewClient( option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), ) - _, err := client.Find.GetSymbol(context.TODO(), opencode.FindGetSymbolParams{ - Query: "query", + _, err := client.Find.Text(context.TODO(), opencode.FindTextParams{ + Pattern: opencode.F("pattern"), }) if err != nil { var apierr *opencode.Error diff --git a/go.mod b/go.mod index fb4837a..2817d30 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/stainless-sdks/opencode-go +module github.com/sst/opencode-sdk-go go 1.21 diff --git a/internal/apierror/apierror.go b/internal/apierror/apierror.go index aee7ead..24307fc 100644 --- a/internal/apierror/apierror.go +++ b/internal/apierror/apierror.go @@ -7,33 +7,36 @@ import ( "net/http" "net/http/httputil" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/packages/respjson" + "github.com/sst/opencode-sdk-go/internal/apijson" ) // Error represents an error that originates from the API, i.e. when a request is // made and the API returns a response with a HTTP status code. Other errors are // not wrapped by this SDK. type Error struct { - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` + JSON errorJSON `json:"-"` StatusCode int Request *http.Request Response *http.Response } -// Returns the unmodified JSON received from the API -func (r Error) RawJSON() string { return r.JSON.raw } -func (r *Error) UnmarshalJSON(data []byte) error { +// errorJSON contains the JSON metadata for the struct [Error] +type errorJSON struct { + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Error) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } +func (r errorJSON) RawJSON() string { + return r.raw +} + func (r *Error) Error() string { // Attempt to re-populate the response body - return fmt.Sprintf("%s %q: %d %s %s", r.Request.Method, r.Request.URL, r.Response.StatusCode, http.StatusText(r.Response.StatusCode), r.JSON.raw) + return fmt.Sprintf("%s \"%s\": %d %s %s", r.Request.Method, r.Request.URL, r.Response.StatusCode, http.StatusText(r.Response.StatusCode), r.JSON.RawJSON()) } func (r *Error) DumpRequest(body bool) []byte { diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index 8f3264b..243a1a1 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -13,38 +13,22 @@ import ( "sync" "time" - "github.com/stainless-sdks/opencode-go/packages/param" + "github.com/sst/opencode-sdk-go/internal/param" ) var encoders sync.Map // map[encoderEntry]encoderFunc -func Marshal(value any, writer *multipart.Writer) error { - e := &encoder{ - dateFormat: time.RFC3339, - arrayFmt: "comma", - } +func Marshal(value interface{}, writer *multipart.Writer) error { + e := &encoder{dateFormat: time.RFC3339} return e.marshal(value, writer) } -func MarshalRoot(value any, writer *multipart.Writer) error { - e := &encoder{ - root: true, - dateFormat: time.RFC3339, - arrayFmt: "comma", - } - return e.marshal(value, writer) -} - -func MarshalWithSettings(value any, writer *multipart.Writer, arrayFormat string) error { - e := &encoder{ - arrayFmt: arrayFormat, - dateFormat: time.RFC3339, - } +func MarshalRoot(value interface{}, writer *multipart.Writer) error { + e := &encoder{root: true, dateFormat: time.RFC3339} return e.marshal(value, writer) } type encoder struct { - arrayFmt string dateFormat string root bool } @@ -63,7 +47,7 @@ type encoderEntry struct { root bool } -func (e *encoder) marshal(value any, writer *multipart.Writer) error { +func (e *encoder) marshal(value interface{}, writer *multipart.Writer) error { val := reflect.ValueOf(value) if !val.IsValid() { return nil @@ -112,7 +96,7 @@ func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { return e.newTimeTypeEncoder() } - if t.Implements(reflect.TypeOf((*io.Reader)(nil)).Elem()) { + if t.ConvertibleTo(reflect.TypeOf((*io.Reader)(nil)).Elem()) { return e.newReaderTypeEncoder() } e.root = false @@ -178,40 +162,15 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { } } -func arrayKeyEncoder(arrayFmt string) func(string, int) string { - var keyFn func(string, int) string - switch arrayFmt { - case "comma", "repeat": - keyFn = func(k string, _ int) string { return k } - case "brackets": - keyFn = func(key string, _ int) string { return key + "[]" } - case "indices:dots": - keyFn = func(k string, i int) string { - if k == "" { - return strconv.Itoa(i) - } - return k + "." + strconv.Itoa(i) - } - case "indices:brackets": - keyFn = func(k string, i int) string { - if k == "" { - return strconv.Itoa(i) - } - return k + "[" + strconv.Itoa(i) + "]" - } - } - return keyFn -} - func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { itemEncoder := e.typeEncoder(t.Elem()) - keyFn := arrayKeyEncoder(e.arrayFmt) + return func(key string, v reflect.Value, writer *multipart.Writer) error { - if keyFn == nil { - return fmt.Errorf("apiform: unsupported array format") + if key != "" { + key = key + "." } for i := 0; i < v.Len(); i++ { - err := itemEncoder(keyFn(key, i), v.Index(i), writer) + err := itemEncoder(key+strconv.Itoa(i), v.Index(i), writer) if err != nil { return err } @@ -221,14 +180,8 @@ func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { } func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { - if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) { - return e.newRichFieldTypeEncoder(t) - } - - for i := 0; i < t.NumField(); i++ { - if t.Field(i).Type == paramUnionType && t.Field(i).Anonymous { - return e.newStructUnionTypeEncoder(t) - } + if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) { + return e.newFieldTypeEncoder(t) } encoderFields := []encoderField{} @@ -264,7 +217,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx} continue } - if ptag.name == "-" || ptag.name == "" { + if ptag.name == "-" { continue } @@ -278,20 +231,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { e.dateFormat = "2006-01-02" } } - - var encoderFn encoderFunc - if ptag.omitzero { - typeEncoderFn := e.typeEncoder(field.Type) - encoderFn = func(key string, value reflect.Value, writer *multipart.Writer) error { - if value.IsZero() { - return nil - } - return typeEncoderFn(key, value, writer) - } - } else { - encoderFn = e.typeEncoder(field.Type) - } - encoderFields = append(encoderFields, encoderField{ptag, encoderFn, idx}) + encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx}) e.dateFormat = oldFormat } } @@ -326,29 +266,24 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { } } -var paramUnionType = reflect.TypeOf((*param.APIUnion)(nil)).Elem() - -func (e *encoder) newStructUnionTypeEncoder(t reflect.Type) encoderFunc { - var fieldEncoders []encoderFunc - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - if field.Type == paramUnionType && field.Anonymous { - fieldEncoders = append(fieldEncoders, nil) - continue - } - fieldEncoders = append(fieldEncoders, e.typeEncoder(field.Type)) - } +func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc { + f, _ := t.FieldByName("Value") + enc := e.typeEncoder(f.Type) return func(key string, value reflect.Value, writer *multipart.Writer) error { - for i := 0; i < t.NumField(); i++ { - if value.Field(i).Type() == paramUnionType { - continue - } - if !value.Field(i).IsZero() { - return fieldEncoders[i](key, value.Field(i), writer) - } + present := value.FieldByName("Present") + if !present.Bool() { + return nil } - return fmt.Errorf("apiform: union %s has no field set", t.String()) + null := value.FieldByName("Null") + if null.Bool() { + return nil + } + raw := value.FieldByName("Raw") + if !raw.IsNil() { + return e.typeEncoder(raw.Type())(key, raw, writer) + } + return enc(key, value.FieldByName("Value"), writer) } } @@ -377,10 +312,7 @@ func escapeQuotes(s string) string { func (e *encoder) newReaderTypeEncoder() encoderFunc { return func(key string, value reflect.Value, writer *multipart.Writer) error { - reader, ok := value.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader) - if !ok { - return nil - } + reader := value.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader) filename := "anonymous_file" contentType := "application/octet-stream" if named, ok := reader.(interface{ Filename() string }); ok { @@ -444,22 +376,8 @@ func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipar return nil } -func (e *encoder) newMapEncoder(_ reflect.Type) encoderFunc { +func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { return func(key string, value reflect.Value, writer *multipart.Writer) error { return e.encodeMapEntries(key, value, writer) } } - -func WriteExtras(writer *multipart.Writer, extras map[string]any) (err error) { - for k, v := range extras { - str, ok := v.(string) - if !ok { - break - } - err = writer.WriteField(k, str) - if err != nil { - break - } - } - return -} diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go index 132ee18..39d1460 100644 --- a/internal/apiform/form_test.go +++ b/internal/apiform/form_test.go @@ -2,8 +2,6 @@ package apiform import ( "bytes" - "github.com/stainless-sdks/opencode-go/packages/param" - "io" "mime/multipart" "strings" "testing" @@ -21,13 +19,6 @@ type Primitives struct { F []int `form:"f"` } -// These aliases are necessary to bypass the cache. -// This only relevant during testing. -type int_ int -type PrimitivesBrackets struct { - F []int_ `form:"f"` -} - type PrimitivePointers struct { A *bool `form:"a"` B *int `form:"b"` @@ -47,8 +38,8 @@ type DateTime struct { } type AdditionalProperties struct { - A bool `form:"a"` - Extras map[string]any `form:"-,extras"` + A bool `form:"a"` + Extras map[string]interface{} `form:"-,extras"` } type TypedAdditionalProperties struct { @@ -58,8 +49,8 @@ type TypedAdditionalProperties struct { type EmbeddedStructs struct { AdditionalProperties - A *int `form:"number2"` - Extras map[string]any `form:"-,extras"` + A *int `form:"number2"` + Extras map[string]interface{} `form:"-,extras"` } type Recursive struct { @@ -68,7 +59,7 @@ type Recursive struct { } type UnknownStruct struct { - Unknown any `form:"unknown"` + Unknown interface{} `form:"unknown"` } type UnionStruct struct { @@ -103,42 +94,12 @@ type UnionTime time.Time func (UnionTime) union() {} type ReaderStruct struct { - File io.Reader `form:"file"` -} - -type NamedEnum string - -const NamedEnumFoo NamedEnum = "foo" - -type StructUnionWrapper struct { - Union StructUnion `form:"union"` -} - -type StructUnion struct { - OfInt param.Opt[int64] `form:",omitzero,inline"` - OfString param.Opt[string] `form:",omitzero,inline"` - OfEnum param.Opt[NamedEnum] `form:",omitzero,inline"` - OfA UnionStructA `form:",omitzero,inline"` - OfB UnionStructB `form:",omitzero,inline"` - param.APIUnion } var tests = map[string]struct { buf string - val any + val interface{} }{ - "file": { - buf: `--xxx -Content-Disposition: form-data; name="file"; filename="anonymous_file" -Content-Type: application/octet-stream - -some file contents... ---xxx-- -`, - val: ReaderStruct{ - File: io.Reader(bytes.NewBuffer([]byte("some file contents..."))), - }, - }, "map_string": { `--xxx Content-Disposition: form-data; name="foo" @@ -164,7 +125,7 @@ Content-Disposition: form-data; name="c" false --xxx-- `, - map[string]any{"a": float64(1), "b": "str", "c": false}, + map[string]interface{}{"a": float64(1), "b": "str", "c": false}, }, "primitive_struct": { @@ -208,27 +169,6 @@ Content-Disposition: form-data; name="f.3" `, Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, }, - "primitive_struct,brackets": { - `--xxx -Content-Disposition: form-data; name="f[]" - -1 ---xxx -Content-Disposition: form-data; name="f[]" - -2 ---xxx -Content-Disposition: form-data; name="f[]" - -3 ---xxx -Content-Disposition: form-data; name="f[]" - -4 ---xxx-- -`, - PrimitivesBrackets{F: []int_{1, 2, 3, 4}}, - }, "slices": { `--xxx @@ -273,6 +213,7 @@ Content-Disposition: form-data; name="slices.0.f.3" Slice: []Primitives{{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}}, }, }, + "primitive_pointer_struct": { `--xxx Content-Disposition: form-data; name="a" @@ -360,7 +301,7 @@ true `, AdditionalProperties{ A: true, - Extras: map[string]any{ + Extras: map[string]interface{}{ "bar": "value", "foo": true, }, @@ -401,24 +342,12 @@ bar --xxx-- `, UnknownStruct{ - Unknown: map[string]any{ + Unknown: map[string]interface{}{ "foo": "bar", }, }, }, - "struct_union_integer": { - `--xxx -Content-Disposition: form-data; name="union" - -12 ---xxx-- -`, - StructUnionWrapper{ - Union: StructUnion{OfInt: param.NewOpt[int64](12)}, - }, - }, - "union_integer": { `--xxx Content-Disposition: form-data; name="union" @@ -431,30 +360,6 @@ Content-Disposition: form-data; name="union" }, }, - "struct_union_struct_discriminated_a": { - `--xxx -Content-Disposition: form-data; name="union.a" - -foo ---xxx -Content-Disposition: form-data; name="union.b" - -bar ---xxx -Content-Disposition: form-data; name="union.type" - -typeA ---xxx-- -`, - StructUnionWrapper{ - Union: StructUnion{OfA: UnionStructA{ - Type: "typeA", - A: "foo", - B: "bar", - }}, - }, - }, - "union_struct_discriminated_a": { `--xxx Content-Disposition: form-data; name="union.a" @@ -480,25 +385,6 @@ typeA }, }, - "struct_union_struct_discriminated_b": { - `--xxx -Content-Disposition: form-data; name="union.a" - -foo ---xxx -Content-Disposition: form-data; name="union.type" - -typeB ---xxx-- -`, - StructUnionWrapper{ - Union: StructUnion{OfB: UnionStructB{ - Type: "typeB", - A: "foo", - }}, - }, - }, - "union_struct_discriminated_b": { `--xxx Content-Disposition: form-data; name="union.a" @@ -537,13 +423,7 @@ func TestEncode(t *testing.T) { buf := bytes.NewBuffer(nil) writer := multipart.NewWriter(buf) writer.SetBoundary("xxx") - - var arrayFmt string = "indices:dots" - if tags := strings.Split(name, ","); len(tags) > 1 { - arrayFmt = tags[1] - } - - err := MarshalWithSettings(test.val, writer, arrayFmt) + err := Marshal(test.val, writer) if err != nil { t.Errorf("serialization of %v failed with error %v", test.val, err) } diff --git a/internal/apiform/richparam.go b/internal/apiform/richparam.go deleted file mode 100644 index 7d5fcf7..0000000 --- a/internal/apiform/richparam.go +++ /dev/null @@ -1,20 +0,0 @@ -package apiform - -import ( - "github.com/stainless-sdks/opencode-go/packages/param" - "mime/multipart" - "reflect" -) - -func (e *encoder) newRichFieldTypeEncoder(t reflect.Type) encoderFunc { - f, _ := t.FieldByName("Value") - enc := e.newPrimitiveTypeEncoder(f.Type) - return func(key string, value reflect.Value, writer *multipart.Writer) error { - if opt, ok := value.Interface().(param.Optional); ok && opt.Valid() { - return enc(key, value.FieldByIndex(f.Index), writer) - } else if ok && param.IsNull(opt) { - return writer.WriteField(key, "null") - } - return nil - } -} diff --git a/internal/apiform/tag.go b/internal/apiform/tag.go index 736fc1e..b22e054 100644 --- a/internal/apiform/tag.go +++ b/internal/apiform/tag.go @@ -14,7 +14,6 @@ type parsedStructTag struct { required bool extras bool metadata bool - omitzero bool } func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { @@ -38,8 +37,6 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool tag.extras = true case "metadata": tag.metadata = true - case "omitzero": - tag.omitzero = true } } return diff --git a/internal/apijson/decodeparam_test.go b/internal/apijson/decodeparam_test.go deleted file mode 100644 index dfe2d36..0000000 --- a/internal/apijson/decodeparam_test.go +++ /dev/null @@ -1,410 +0,0 @@ -package apijson_test - -import ( - "encoding/json" - "fmt" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/packages/param" - "reflect" - "testing" -) - -func TestOptionalDecoders(t *testing.T) { - cases := map[string]struct { - buf string - val any - }{ - - "opt_string_present": { - `"hello"`, - param.NewOpt("hello"), - }, - "opt_string_empty_present": { - `""`, - param.NewOpt(""), - }, - "opt_string_null": { - `null`, - param.Null[string](), - }, - "opt_string_null_with_whitespace": { - ` null `, - param.Null[string](), - }, - } - - for name, test := range cases { - t.Run(name, func(t *testing.T) { - result := reflect.New(reflect.TypeOf(test.val)) - if err := json.Unmarshal([]byte(test.buf), result.Interface()); err != nil { - t.Fatalf("deserialization of %v failed with error %v", result, err) - } - - if !reflect.DeepEqual(result.Elem().Interface(), test.val) { - t.Fatalf("expected '%s' to deserialize to \n%#v\nbut got\n%#v", test.buf, test.val, result.Elem().Interface()) - } - }) - } -} - -type paramObject = param.APIObject - -type BasicObject struct { - ReqInt int64 `json:"req_int,required"` - ReqFloat float64 `json:"req_float,required"` - ReqString string `json:"req_string,required"` - ReqBool bool `json:"req_bool,required"` - - OptInt param.Opt[int64] `json:"opt_int"` - OptFloat param.Opt[float64] `json:"opt_float"` - OptString param.Opt[string] `json:"opt_string"` - OptBool param.Opt[bool] `json:"opt_bool"` - - paramObject -} - -func (o *BasicObject) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, o) } - -func TestBasicObjectWithNull(t *testing.T) { - raw := `{"opt_int":null,"opt_string":null,"opt_bool":null}` - var dst BasicObject - target := BasicObject{ - OptInt: param.Null[int64](), - // OptFloat: param.Opt[float64]{}, - OptString: param.Null[string](), - OptBool: param.Null[bool](), - } - - err := json.Unmarshal([]byte(raw), &dst) - if err != nil { - t.Fatalf("failed unmarshal") - } - - if !reflect.DeepEqual(dst, target) { - t.Fatalf("failed equality check %#v", dst) - } -} - -func TestBasicObject(t *testing.T) { - raw := `{"req_int":1,"req_float":1.3,"req_string":"test","req_bool":true,"opt_int":2,"opt_float":2.0,"opt_string":"test","opt_bool":false}` - var dst BasicObject - target := BasicObject{ - ReqInt: 1, - ReqFloat: 1.3, - ReqString: "test", - ReqBool: true, - OptInt: param.NewOpt[int64](2), - OptFloat: param.NewOpt(2.0), - OptString: param.NewOpt("test"), - OptBool: param.NewOpt(false), - } - - err := json.Unmarshal([]byte(raw), &dst) - if err != nil { - t.Fatalf("failed unmarshal") - } - - if !reflect.DeepEqual(dst, target) { - t.Fatalf("failed equality check %#v", dst) - } -} - -type ComplexObject struct { - Basic BasicObject `json:"basic,required"` - Enum string `json:"enum"` - paramObject -} - -func (o *ComplexObject) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, o) } - -func init() { - apijson.RegisterFieldValidator[ComplexObject]("enum", "a", "b", "c") -} - -func TestComplexObject(t *testing.T) { - raw := `{"basic":{"req_int":1,"req_float":1.3,"req_string":"test","req_bool":true,"opt_int":2,"opt_float":2.0,"opt_string":"test","opt_bool":false},"enum":"a"}` - var dst ComplexObject - - target := ComplexObject{ - Basic: BasicObject{ - ReqInt: 1, - ReqFloat: 1.3, - ReqString: "test", - ReqBool: true, - OptInt: param.NewOpt[int64](2), - OptFloat: param.NewOpt(2.0), - OptString: param.NewOpt("test"), - OptBool: param.NewOpt(false), - }, - Enum: "a", - } - - err := json.Unmarshal([]byte(raw), &dst) - if err != nil { - t.Fatalf("failed unmarshal") - } - - if !reflect.DeepEqual(dst, target) { - t.Fatalf("failed equality check %#v", dst) - } -} - -type paramUnion = param.APIUnion - -type MemberA struct { - Name string `json:"name,required"` - Age int `json:"age,required"` -} - -type MemberB struct { - Name string `json:"name,required"` - Age string `json:"age,required"` -} - -type MemberC struct { - Name string `json:"name,required"` - Age int `json:"age,required"` - Status string `json:"status"` -} - -type MemberD struct { - Cost int `json:"cost,required"` - Status string `json:"status,required"` -} - -type MemberE struct { - Cost int `json:"cost,required"` - Status string `json:"status,required"` -} - -type MemberF struct { - D int `json:"d"` - E string `json:"e"` - F float64 `json:"f"` - G param.Opt[int] `json:"g"` -} - -type MemberG struct { - D int `json:"d"` - E string `json:"e"` - F float64 `json:"f"` - G param.Opt[bool] `json:"g"` -} - -func init() { - apijson.RegisterFieldValidator[MemberD]("status", "good", "ok", "bad") - apijson.RegisterFieldValidator[MemberE]("status", "GOOD", "OK", "BAD") -} - -type UnionStruct struct { - OfMemberA *MemberA `json:",inline"` - OfMemberB *MemberB `json:",inline"` - OfMemberC *MemberC `json:",inline"` - OfMemberD *MemberD `json:",inline"` - OfMemberE *MemberE `json:",inline"` - OfMemberF *MemberF `json:",inline"` - OfMemberG *MemberG `json:",inline"` - OfString param.Opt[string] `json:",inline"` - - paramUnion -} - -func (union *UnionStruct) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, union) -} - -func TestUnionStruct(t *testing.T) { - tests := map[string]struct { - raw string - target UnionStruct - shouldFail bool - }{ - "fail": { - raw: `1200`, - target: UnionStruct{}, - shouldFail: true, - }, - "easy": { - raw: `{"age":30}`, - target: UnionStruct{OfMemberA: &MemberA{Age: 30}}, - }, - "less-easy": { - raw: `{"age":"thirty"}`, - target: UnionStruct{OfMemberB: &MemberB{Age: "thirty"}}, - }, - "even-less-easy": { - raw: `{"age":"30"}`, - target: UnionStruct{OfMemberB: &MemberB{Age: "30"}}, - }, - "medium": { - raw: `{"name":"jacob","age":30}`, - target: UnionStruct{OfMemberA: &MemberA{ - Age: 30, - Name: "jacob", - }}, - }, - "less-medium": { - raw: `{"name":"jacob","age":"thirty"}`, - target: UnionStruct{OfMemberB: &MemberB{ - Age: "thirty", - Name: "jacob", - }}, - }, - "even-less-medium": { - raw: `{"name":"jacob","age":"30"}`, - target: UnionStruct{OfMemberB: &MemberB{ - Name: "jacob", - Age: "30", - }}, - }, - "hard": { - raw: `{"name":"jacob","age":30,"status":"active"}`, - target: UnionStruct{OfMemberC: &MemberC{ - Name: "jacob", - Age: 30, - Status: "active", - }}, - }, - "inline-string": { - raw: `"hello there"`, - target: UnionStruct{OfString: param.NewOpt("hello there")}, - }, - "enum-field": { - raw: `{"cost":100,"status":"ok"}`, - target: UnionStruct{OfMemberD: &MemberD{Cost: 100, Status: "ok"}}, - }, - "other-enum-field": { - raw: `{"cost":100,"status":"GOOD"}`, - target: UnionStruct{OfMemberE: &MemberE{Cost: 100, Status: "GOOD"}}, - }, - "tricky-extra-fields": { - raw: `{"d":12,"e":"hello","f":1.00}`, - target: UnionStruct{OfMemberF: &MemberF{D: 12, E: "hello", F: 1.00}}, - }, - "optional-fields": { - raw: `{"d":12,"e":"hello","f":1.00,"g":12}`, - target: UnionStruct{OfMemberF: &MemberF{D: 12, E: "hello", F: 1.00, G: param.NewOpt(12)}}, - }, - "optional-fields-2": { - raw: `{"d":12,"e":"hello","f":1.00,"g":false}`, - target: UnionStruct{OfMemberG: &MemberG{D: 12, E: "hello", F: 1.00, G: param.NewOpt(false)}}, - }, - } - - for name, test := range tests { - var dst UnionStruct - t.Run(name, func(t *testing.T) { - err := json.Unmarshal([]byte(test.raw), &dst) - if err != nil && !test.shouldFail { - t.Fatalf("failed unmarshal with err: %v %#v", err, dst) - } - - if !reflect.DeepEqual(dst, test.target) { - if dst.OfMemberA != nil { - fmt.Printf("%#v", dst.OfMemberA) - } - t.Fatalf("failed equality, got %#v but expected %#v", dst, test.target) - } - }) - } -} - -type ConstantA string -type ConstantB string -type ConstantC string - -func (c ConstantA) Default() string { return "A" } -func (c ConstantB) Default() string { return "B" } -func (c ConstantC) Default() string { return "C" } - -type DiscVariantA struct { - Name string `json:"name,required"` - Age int `json:"age,required"` - Type ConstantA `json:"type,required"` -} - -type DiscVariantB struct { - Name string `json:"name,required"` - Age int `json:"age,required"` - Type ConstantB `json:"type,required"` -} - -type DiscVariantC struct { - Name string `json:"name,required"` - Age float64 `json:"age,required"` - Type ConstantC `json:"type,required"` -} - -type DiscriminatedUnion struct { - OfA *DiscVariantA `json:",inline"` - OfB *DiscVariantB `json:",inline"` - OfC *DiscVariantC `json:",inline"` - - paramUnion -} - -func init() { - apijson.RegisterDiscriminatedUnion[DiscriminatedUnion]("type", map[string]reflect.Type{ - "A": reflect.TypeOf(DiscVariantA{}), - "B": reflect.TypeOf(DiscVariantB{}), - "C": reflect.TypeOf(DiscVariantC{}), - }) -} - -func (d *DiscriminatedUnion) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, d) -} - -func TestDiscriminatedUnion(t *testing.T) { - tests := map[string]struct { - raw string - target DiscriminatedUnion - shouldFail bool - }{ - "variant_a": { - raw: `{"name":"Alice","age":25,"type":"A"}`, - target: DiscriminatedUnion{OfA: &DiscVariantA{ - Name: "Alice", - Age: 25, - Type: "A", - }}, - }, - "variant_b": { - raw: `{"name":"Bob","age":30,"type":"B"}`, - target: DiscriminatedUnion{OfB: &DiscVariantB{ - Name: "Bob", - Age: 30, - Type: "B", - }}, - }, - "variant_c": { - raw: `{"name":"Charlie","age":35.5,"type":"C"}`, - target: DiscriminatedUnion{OfC: &DiscVariantC{ - Name: "Charlie", - Age: 35.5, - Type: "C", - }}, - }, - "invalid_type": { - raw: `{"name":"Unknown","age":40,"type":"D"}`, - target: DiscriminatedUnion{}, - shouldFail: true, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var dst DiscriminatedUnion - err := json.Unmarshal([]byte(test.raw), &dst) - if err != nil && !test.shouldFail { - t.Fatalf("failed unmarshal with err: %v", err) - } - if err == nil && test.shouldFail { - t.Fatalf("expected unmarshal to fail but it succeeded") - } - if !reflect.DeepEqual(dst, test.target) { - t.Fatalf("failed equality, got %#v but expected %#v", dst, test.target) - } - }) - } -} diff --git a/internal/apijson/decoder.go b/internal/apijson/decoder.go index 8410ea7..68b7ed6 100644 --- a/internal/apijson/decoder.go +++ b/internal/apijson/decoder.go @@ -1,13 +1,9 @@ -// The deserialization algorithm from apijson may be subject to improvements -// between minor versions, particularly with respect to calling [json.Unmarshal] -// into param unions. - package apijson import ( "encoding/json" + "errors" "fmt" - "github.com/stainless-sdks/opencode-go/packages/param" "reflect" "strconv" "sync" @@ -50,7 +46,6 @@ type decoderBuilder struct { type decoderState struct { strict bool exactness exactness - validator *validationEntry } // Exactness refers to how close to the type the result was if deserialization @@ -94,18 +89,6 @@ func (d *decoderBuilder) unmarshal(raw []byte, to any) error { return d.typeDecoder(value.Type())(result, value, &decoderState{strict: false, exactness: exact}) } -// unmarshalWithExactness is used for internal testing purposes. -func (d *decoderBuilder) unmarshalWithExactness(raw []byte, to any) (exactness, error) { - value := reflect.ValueOf(to).Elem() - result := gjson.ParseBytes(raw) - if !value.IsValid() { - return 0, fmt.Errorf("apijson: cannot marshal into invalid value") - } - state := decoderState{strict: false, exactness: exact} - err := d.typeDecoder(value.Type())(result, value, &state) - return state.exactness, err -} - func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc { entry := decoderEntry{ Type: t, @@ -141,24 +124,6 @@ func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc { return f } -// validatedTypeDecoder wraps the type decoder with a validator. This is helpful -// for ensuring that enum fields are correct. -func (d *decoderBuilder) validatedTypeDecoder(t reflect.Type, entry *validationEntry) decoderFunc { - dec := d.typeDecoder(t) - if entry == nil { - return dec - } - - // Thread the current validation entry through the decoder, - // but clean up in time for the next field. - return func(node gjson.Result, v reflect.Value, state *decoderState) error { - state.validator = entry - err := dec(node, v, state) - state.validator = nil - return err - } -} - func indirectUnmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error { return v.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw)) } @@ -174,11 +139,6 @@ func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc { if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { return d.newTimeTypeDecoder(t) } - - if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) { - return d.newOptTypeDecoder(t) - } - if !d.root && t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) { return unmarshalerDecoder } @@ -190,9 +150,6 @@ func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc { d.root = false if _, ok := unionRegistry[t]; ok { - if isStructUnion(t) { - return d.newStructUnionDecoder(t) - } return d.newUnionDecoder(t) } @@ -216,9 +173,6 @@ func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc { return nil } case reflect.Struct: - if isStructUnion(t) { - return d.newStructUnionDecoder(t) - } return d.newStructTypeDecoder(t) case reflect.Array: fallthrough @@ -241,6 +195,80 @@ func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc { } } +// newUnionDecoder returns a decoderFunc that deserializes into a union using an +// algorithm roughly similar to Pydantic's [smart algorithm]. +// +// Conceptually this is equivalent to choosing the best schema based on how 'exact' +// the deserialization is for each of the schemas. +// +// If there is a tie in the level of exactness, then the tie is broken +// left-to-right. +// +// [smart algorithm]: https://docs.pydantic.dev/latest/concepts/unions/#smart-mode +func (d *decoderBuilder) newUnionDecoder(t reflect.Type) decoderFunc { + unionEntry, ok := unionRegistry[t] + if !ok { + panic("apijson: couldn't find union of type " + t.String() + " in union registry") + } + decoders := []decoderFunc{} + for _, variant := range unionEntry.variants { + decoder := d.typeDecoder(variant.Type) + decoders = append(decoders, decoder) + } + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + // If there is a discriminator match, circumvent the exactness logic entirely + for idx, variant := range unionEntry.variants { + decoder := decoders[idx] + if variant.TypeFilter != n.Type { + continue + } + + if len(unionEntry.discriminatorKey) != 0 { + discriminatorValue := n.Get(unionEntry.discriminatorKey).Value() + if discriminatorValue == variant.DiscriminatorValue { + inner := reflect.New(variant.Type).Elem() + err := decoder(n, inner, state) + v.Set(inner) + return err + } + } + } + + // Set bestExactness to worse than loose + bestExactness := loose - 1 + for idx, variant := range unionEntry.variants { + decoder := decoders[idx] + if variant.TypeFilter != n.Type { + continue + } + sub := decoderState{strict: state.strict, exactness: exact} + inner := reflect.New(variant.Type).Elem() + err := decoder(n, inner, &sub) + if err != nil { + continue + } + if sub.exactness == exact { + v.Set(inner) + return nil + } + if sub.exactness > bestExactness { + v.Set(inner) + bestExactness = sub.exactness + } + } + + if bestExactness < loose { + return errors.New("apijson: was not able to coerce type as union") + } + + if guardStrict(state, bestExactness != exact) { + return errors.New("apijson: was not able to coerce type as union strictly") + } + + return nil + } +} + func (d *decoderBuilder) newMapDecoder(t reflect.Type) decoderFunc { keyType := t.Key() itemType := t.Elem() @@ -315,9 +343,7 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { decoderFields := map[string]decoderField{} anonymousDecoders := []decoderField{} extraDecoder := (*decoderField)(nil) - var inlineDecoders []decoderField - - validationEntries := validationRegistry[t] + inlineDecoder := (*decoderField)(nil) for i := 0; i < t.NumField(); i++ { idx := []int{i} @@ -325,15 +351,6 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { if !field.IsExported() { continue } - - var validator *validationEntry - for _, entry := range validationEntries { - if entry.field.Offset == field.Offset { - validator = &entry - break - } - } - // If this is an embedded struct, traverse one level deeper to extract // the fields and get their encoders as well. if field.Anonymous { @@ -356,8 +373,7 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { continue } if ptag.inline { - df := decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} - inlineDecoders = append(inlineDecoders, df) + inlineDecoder = &decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} continue } if ptag.metadata { @@ -374,13 +390,7 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { d.dateFormat = "2006-01-02" } } - - decoderFields[ptag.name] = decoderField{ - ptag, - d.validatedTypeDecoder(field.Type, validator), - idx, field.Name, - } - + decoderFields[ptag.name] = decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} d.dateFormat = oldFormat } @@ -396,13 +406,12 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { decoder.fn(node, value.FieldByIndex(decoder.idx), state) } - for _, inlineDecoder := range inlineDecoders { + if inlineDecoder != nil { var meta Field dest := value.FieldByIndex(inlineDecoder.idx) isValid := false if dest.IsValid() && node.Type != gjson.Null { - inlineState := decoderState{exactness: state.exactness, strict: true} - err = inlineDecoder.fn(node, dest, &inlineState) + err = inlineDecoder.fn(node, dest, state) if err == nil { isValid = true } @@ -414,18 +423,20 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { status: null, } } else if !isValid { - // If an inline decoder fails, unset the field and move on. - if dest.IsValid() { - dest.SetZero() + meta = Field{ + raw: node.Raw, + status: invalid, } - continue } else if isValid { meta = Field{ raw: node.Raw, status: valid, } } - setMetadataSubField(value, inlineDecoder.idx, inlineDecoder.goname, meta) + if metadata := getSubField(value, inlineDecoder.idx, inlineDecoder.goname); metadata.IsValid() { + metadata.Set(reflect.ValueOf(meta)) + } + return err } typedExtraType := reflect.Type(nil) @@ -460,12 +471,6 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { } } - // Handle null [param.Opt] - if itemNode.Type == gjson.Null && dest.IsValid() && dest.Type().Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) { - dest.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(itemNode.Raw)) - continue - } - if itemNode.Type == gjson.Null { meta = Field{ raw: itemNode.Raw, @@ -484,7 +489,9 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { } if explicit { - setMetadataSubField(value, df.idx, df.goname, meta) + if metadata := getSubField(value, df.idx, df.goname); metadata.IsValid() { + metadata.Set(reflect.ValueOf(meta)) + } } if !explicit { untypedExtraFields[fieldName] = meta @@ -503,8 +510,8 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { state.exactness = extras } - if len(untypedExtraFields) > 0 { - setMetadataExtraFields(value, []int{-1}, "ExtraFields", untypedExtraFields) + if metadata := getSubField(value, []int{-1}, "ExtraFields"); metadata.IsValid() && len(untypedExtraFields) > 0 { + metadata.Set(reflect.ValueOf(untypedExtraFields)) } return nil } @@ -522,9 +529,6 @@ func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc { if n.Type == gjson.JSON { return fmt.Errorf("apijson: failed to parse string") } - - state.validateString(v) - if guardUnknown(state, v) { return fmt.Errorf("apijson: failed string enum validation") } @@ -541,9 +545,6 @@ func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc { if n.Type == gjson.String && (n.Raw != "true" && n.Raw != "false") || n.Type == gjson.JSON { return fmt.Errorf("apijson: failed to parse bool") } - - state.validateBool(v) - if guardUnknown(state, v) { return fmt.Errorf("apijson: failed bool enum validation") } @@ -560,9 +561,6 @@ func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc { if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) { return fmt.Errorf("apijson: failed to parse int") } - - state.validateInt(v) - if guardUnknown(state, v) { return fmt.Errorf("apijson: failed int enum validation") } @@ -607,17 +605,6 @@ func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc { } } -func (d *decoderBuilder) newOptTypeDecoder(t reflect.Type) decoderFunc { - for t.Kind() == reflect.Pointer { - t = t.Elem() - } - valueField, _ := t.FieldByName("Value") - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - state.validateOptKind(n, valueField.Type) - return v.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw)) - } -} - func (d *decoderBuilder) newTimeTypeDecoder(t reflect.Type) decoderFunc { format := d.dateFormat return func(n gjson.Result, v reflect.Value, state *decoderState) error { @@ -653,7 +640,7 @@ func (d *decoderBuilder) newTimeTypeDecoder(t reflect.Type) decoderFunc { } } -func setUnexportedField(field reflect.Value, value any) { +func setUnexportedField(field reflect.Value, value interface{}) { reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(reflect.ValueOf(value)) } @@ -675,17 +662,9 @@ func canParseAsNumber(str string) bool { return err == nil } -var stringType = reflect.TypeOf(string("")) - func guardUnknown(state *decoderState, v reflect.Value) bool { if have, ok := v.Interface().(interface{ IsKnown() bool }); guardStrict(state, ok && !have.IsKnown()) { return true } - - constantString, ok := v.Interface().(interface{ Default() string }) - named := v.Type() != stringType - if guardStrict(state, ok && named && v.Equal(reflect.ValueOf(constantString.Default()))) { - return true - } return false } diff --git a/internal/apijson/decoderesp_test.go b/internal/apijson/decoderesp_test.go deleted file mode 100644 index bbec2c0..0000000 --- a/internal/apijson/decoderesp_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package apijson_test - -import ( - "encoding/json" - "github.com/stainless-sdks/opencode-go/internal/apijson" - "github.com/stainless-sdks/opencode-go/packages/respjson" - "testing" -) - -type StructWithNullExtraField struct { - Results []string `json:"results,required"` - JSON struct { - Results respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -func (r *StructWithNullExtraField) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -func TestDecodeWithNullExtraField(t *testing.T) { - raw := `{"something_else":null}` - var dst *StructWithNullExtraField - err := json.Unmarshal([]byte(raw), &dst) - if err != nil { - t.Fatalf("error: %s", err.Error()) - } -} diff --git a/internal/apijson/encoder.go b/internal/apijson/encoder.go index 8358a2f..0e5f89e 100644 --- a/internal/apijson/encoder.go +++ b/internal/apijson/encoder.go @@ -12,16 +12,18 @@ import ( "time" "github.com/tidwall/sjson" + + "github.com/sst/opencode-sdk-go/internal/param" ) var encoders sync.Map // map[encoderEntry]encoderFunc -func Marshal(value any) ([]byte, error) { +func Marshal(value interface{}) ([]byte, error) { e := &encoder{dateFormat: time.RFC3339} return e.marshal(value) } -func MarshalRoot(value any) ([]byte, error) { +func MarshalRoot(value interface{}) ([]byte, error) { e := &encoder{root: true, dateFormat: time.RFC3339} return e.marshal(value) } @@ -45,7 +47,7 @@ type encoderEntry struct { root bool } -func (e *encoder) marshal(value any) ([]byte, error) { +func (e *encoder) marshal(value interface{}) ([]byte, error) { val := reflect.ValueOf(value) if !val.IsValid() { return nil, nil @@ -200,6 +202,10 @@ func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { } func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { + if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) { + return e.newFieldTypeEncoder(t) + } + encoderFields := []encoderField{} extraEncoder := (*encoderField)(nil) @@ -375,7 +381,7 @@ func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error) return json, nil } -func (e *encoder) newMapEncoder(_ reflect.Type) encoderFunc { +func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { return func(value reflect.Value) ([]byte, error) { json := []byte("{}") var err error diff --git a/internal/apijson/enum.go b/internal/apijson/enum.go deleted file mode 100644 index 18b218a..0000000 --- a/internal/apijson/enum.go +++ /dev/null @@ -1,145 +0,0 @@ -package apijson - -import ( - "fmt" - "reflect" - "slices" - "sync" - - "github.com/tidwall/gjson" -) - -/********************/ -/* Validating Enums */ -/********************/ - -type validationEntry struct { - field reflect.StructField - required bool - legalValues struct { - strings []string - // 1 represents true, 0 represents false, -1 represents either - bools int - ints []int64 - } -} - -type validatorFunc func(reflect.Value) exactness - -var validators sync.Map -var validationRegistry = map[reflect.Type][]validationEntry{} - -func RegisterFieldValidator[T any, V string | bool | int](fieldName string, values ...V) { - var t T - parentType := reflect.TypeOf(t) - - if _, ok := validationRegistry[parentType]; !ok { - validationRegistry[parentType] = []validationEntry{} - } - - // The following checks run at initialization time, - // it is impossible for them to panic if any tests pass. - if parentType.Kind() != reflect.Struct { - panic(fmt.Sprintf("apijson: cannot initialize validator for non-struct %s", parentType.String())) - } - - var field reflect.StructField - found := false - for i := 0; i < parentType.NumField(); i++ { - ptag, ok := parseJSONStructTag(parentType.Field(i)) - if ok && ptag.name == fieldName { - field = parentType.Field(i) - found = true - break - } - } - - if !found { - panic(fmt.Sprintf("apijson: cannot find field %s in struct %s", fieldName, parentType.String())) - } - - newEntry := validationEntry{field: field} - newEntry.legalValues.bools = -1 // default to either - - switch values := any(values).(type) { - case []string: - newEntry.legalValues.strings = values - case []int: - newEntry.legalValues.ints = make([]int64, len(values)) - for i, value := range values { - newEntry.legalValues.ints[i] = int64(value) - } - case []bool: - for i, value := range values { - var next int - if value { - next = 1 - } - if i > 0 && newEntry.legalValues.bools != next { - newEntry.legalValues.bools = -1 // accept either - break - } - newEntry.legalValues.bools = next - } - } - - // Store the information necessary to create a validator, so that we can use it - // lazily create the validator function when did. - validationRegistry[parentType] = append(validationRegistry[parentType], newEntry) -} - -func (state *decoderState) validateString(v reflect.Value) { - if state.validator == nil { - return - } - if !slices.Contains(state.validator.legalValues.strings, v.String()) { - state.exactness = loose - } -} - -func (state *decoderState) validateInt(v reflect.Value) { - if state.validator == nil { - return - } - if !slices.Contains(state.validator.legalValues.ints, v.Int()) { - state.exactness = loose - } -} - -func (state *decoderState) validateBool(v reflect.Value) { - if state.validator == nil { - return - } - b := v.Bool() - if state.validator.legalValues.bools == 1 && b == false { - state.exactness = loose - } else if state.validator.legalValues.bools == 0 && b == true { - state.exactness = loose - } -} - -func (state *decoderState) validateOptKind(node gjson.Result, t reflect.Type) { - switch node.Type { - case gjson.JSON: - state.exactness = loose - case gjson.Null: - return - case gjson.False, gjson.True: - if t.Kind() != reflect.Bool { - state.exactness = loose - } - case gjson.Number: - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Float32, reflect.Float64: - return - default: - state.exactness = loose - } - case gjson.String: - if t.Kind() != reflect.String { - state.exactness = loose - } - } -} diff --git a/internal/apijson/enum_test.go b/internal/apijson/enum_test.go deleted file mode 100644 index a2aeed4..0000000 --- a/internal/apijson/enum_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package apijson - -import ( - "reflect" - "testing" -) - -type EnumStruct struct { - NormalString string `json:"normal_string"` - StringEnum string `json:"string_enum"` - NamedEnum NamedEnumType `json:"named_enum"` - - IntEnum int `json:"int_enum"` - BoolEnum bool `json:"bool_enum"` - - WeirdBoolEnum bool `json:"weird_bool_enum"` -} - -func (o *EnumStruct) UnmarshalJSON(data []byte) error { - return UnmarshalRoot(data, o) -} - -func init() { - RegisterFieldValidator[EnumStruct]("string_enum", "one", "two", "three") - RegisterFieldValidator[EnumStruct]("int_enum", 200, 404) - RegisterFieldValidator[EnumStruct]("bool_enum", false) - RegisterFieldValidator[EnumStruct]("weird_bool_enum", true, false) -} - -type NamedEnumType string - -const ( - NamedEnumOne NamedEnumType = "one" - NamedEnumTwo NamedEnumType = "two" - NamedEnumThree NamedEnumType = "three" -) - -func (e NamedEnumType) IsKnown() bool { - return e == NamedEnumOne || e == NamedEnumTwo || e == NamedEnumThree -} - -func TestEnumStructStringValidator(t *testing.T) { - cases := map[string]struct { - exactness - EnumStruct - }{ - `{"string_enum":"one"}`: {exact, EnumStruct{StringEnum: "one"}}, - `{"string_enum":"two"}`: {exact, EnumStruct{StringEnum: "two"}}, - `{"string_enum":"three"}`: {exact, EnumStruct{StringEnum: "three"}}, - `{"string_enum":"none"}`: {loose, EnumStruct{StringEnum: "none"}}, - `{"int_enum":200}`: {exact, EnumStruct{IntEnum: 200}}, - `{"int_enum":404}`: {exact, EnumStruct{IntEnum: 404}}, - `{"int_enum":500}`: {loose, EnumStruct{IntEnum: 500}}, - `{"bool_enum":false}`: {exact, EnumStruct{BoolEnum: false}}, - `{"bool_enum":true}`: {loose, EnumStruct{BoolEnum: true}}, - `{"weird_bool_enum":true}`: {exact, EnumStruct{WeirdBoolEnum: true}}, - `{"weird_bool_enum":false}`: {exact, EnumStruct{WeirdBoolEnum: false}}, - - `{"named_enum":"one"}`: {exact, EnumStruct{NamedEnum: NamedEnumOne}}, - `{"named_enum":"none"}`: {loose, EnumStruct{NamedEnum: "none"}}, - - `{"string_enum":"one","named_enum":"one"}`: {exact, EnumStruct{NamedEnum: "one", StringEnum: "one"}}, - `{"string_enum":"four","named_enum":"one"}`: { - loose, - EnumStruct{NamedEnum: "one", StringEnum: "four"}, - }, - `{"string_enum":"one","named_enum":"four"}`: { - loose, EnumStruct{NamedEnum: "four", StringEnum: "one"}, - }, - `{"wrong_key":"one"}`: {extras, EnumStruct{StringEnum: ""}}, - } - - for raw, expected := range cases { - var dst EnumStruct - - dec := decoderBuilder{root: true} - exactness, _ := dec.unmarshalWithExactness([]byte(raw), &dst) - - if !reflect.DeepEqual(dst, expected.EnumStruct) { - t.Fatalf("failed equality check %#v", dst) - } - - if exactness != expected.exactness { - t.Fatalf("exactness got %d expected %d %s", exactness, expected.exactness, raw) - } - } -} diff --git a/internal/apijson/field.go b/internal/apijson/field.go index 854d6dd..3ef207c 100644 --- a/internal/apijson/field.go +++ b/internal/apijson/field.go @@ -1,5 +1,7 @@ package apijson +import "reflect" + type status uint8 const ( @@ -21,3 +23,19 @@ func (j Field) IsNull() bool { return j.status <= null } func (j Field) IsMissing() bool { return j.status == missing } func (j Field) IsInvalid() bool { return j.status == invalid } func (j Field) Raw() string { return j.raw } + +func getSubField(root reflect.Value, index []int, name string) reflect.Value { + strct := root.FieldByIndex(index[:len(index)-1]) + if !strct.IsValid() { + panic("couldn't find encapsulating struct for field " + name) + } + meta := strct.FieldByName("JSON") + if !meta.IsValid() { + return reflect.Value{} + } + field := meta.FieldByName(name) + if !field.IsValid() { + return reflect.Value{} + } + return field +} diff --git a/internal/apijson/field_test.go b/internal/apijson/field_test.go new file mode 100644 index 0000000..2e170c7 --- /dev/null +++ b/internal/apijson/field_test.go @@ -0,0 +1,66 @@ +package apijson + +import ( + "testing" + "time" + + "github.com/sst/opencode-sdk-go/internal/param" +) + +type Struct struct { + A string `json:"a"` + B int64 `json:"b"` +} + +type FieldStruct struct { + A param.Field[string] `json:"a"` + B param.Field[int64] `json:"b"` + C param.Field[Struct] `json:"c"` + D param.Field[time.Time] `json:"d" format:"date"` + E param.Field[time.Time] `json:"e" format:"date-time"` + F param.Field[int64] `json:"f"` +} + +func TestFieldMarshal(t *testing.T) { + tests := map[string]struct { + value interface{} + expected string + }{ + "null_string": {param.Field[string]{Present: true, Null: true}, "null"}, + "null_int": {param.Field[int]{Present: true, Null: true}, "null"}, + "null_int64": {param.Field[int64]{Present: true, Null: true}, "null"}, + "null_struct": {param.Field[Struct]{Present: true, Null: true}, "null"}, + + "string": {param.Field[string]{Present: true, Value: "string"}, `"string"`}, + "int": {param.Field[int]{Present: true, Value: 123}, "123"}, + "int64": {param.Field[int64]{Present: true, Value: int64(123456789123456789)}, "123456789123456789"}, + "struct": {param.Field[Struct]{Present: true, Value: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`}, + + "string_raw": {param.Field[int]{Present: true, Raw: "string"}, `"string"`}, + "int_raw": {param.Field[int]{Present: true, Raw: 123}, "123"}, + "int64_raw": {param.Field[int]{Present: true, Raw: int64(123456789123456789)}, "123456789123456789"}, + "struct_raw": {param.Field[int]{Present: true, Raw: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`}, + + "param_struct": { + FieldStruct{ + A: param.Field[string]{Present: true, Value: "hello"}, + B: param.Field[int64]{Present: true, Value: int64(12)}, + D: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)}, + E: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)}, + }, + `{"a":"hello","b":12,"d":"2023-03-18","e":"2023-03-18T14:47:38Z"}`, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + b, err := Marshal(test.value) + if err != nil { + t.Fatalf("didn't expect error %v", err) + } + if string(b) != test.expected { + t.Fatalf("expected %s, received %s", test.expected, string(b)) + } + }) + } +} diff --git a/internal/apijson/json_test.go b/internal/apijson/json_test.go index 02904d2..e656344 100644 --- a/internal/apijson/json_test.go +++ b/internal/apijson/json_test.go @@ -39,8 +39,8 @@ type DateTime struct { } type AdditionalProperties struct { - A bool `json:"a"` - ExtraFields map[string]any `json:"-,extras"` + A bool `json:"a"` + ExtraFields map[string]interface{} `json:"-,extras"` } type TypedAdditionalProperties struct { @@ -64,8 +64,8 @@ type EmbeddedStructJSON struct { type EmbeddedStructs struct { EmbeddedStruct - A *int `json:"a"` - ExtraFields map[string]any `json:"-,extras"` + A *int `json:"a"` + ExtraFields map[string]interface{} `json:"-,extras"` JSON EmbeddedStructsJSON } @@ -86,8 +86,8 @@ type JSONFieldStruct struct { B int64 `json:"b"` C string `json:"c"` D string `json:"d"` - ExtraFields map[string]int64 `json:",extras"` - JSON JSONFieldStructJSON `json:",metadata"` + ExtraFields map[string]int64 `json:"-,extras"` + JSON JSONFieldStructJSON `json:"-,metadata"` } type JSONFieldStructJSON struct { @@ -100,7 +100,7 @@ type JSONFieldStructJSON struct { } type UnknownStruct struct { - Unknown any `json:"unknown"` + Unknown interface{} `json:"unknown"` } type UnionStruct struct { @@ -112,13 +112,13 @@ type Union interface { } type Inline struct { - InlineField Primitives `json:",inline"` - JSON InlineJSON `json:",metadata"` + InlineField Primitives `json:"-,inline"` + JSON InlineJSON `json:"-,metadata"` } type InlineArray struct { - InlineField []string `json:",inline"` - JSON InlineJSON `json:",metadata"` + InlineField []string `json:"-,inline"` + JSON InlineJSON `json:"-,metadata"` } type InlineJSON struct { @@ -150,7 +150,7 @@ type UnionTime time.Time func (UnionTime) union() {} func init() { - RegisterUnion[Union]("type", + RegisterUnion(reflect.TypeOf((*Union)(nil)).Elem(), "type", UnionVariant{ TypeFilter: gjson.String, Type: reflect.TypeOf(UnionTime{}), @@ -237,7 +237,7 @@ func (r *UnmarshalStruct) UnmarshalJSON(json []byte) error { func (ComplexUnionTypeB) complexUnion() {} func init() { - RegisterUnion[ComplexUnion]("", + RegisterUnion(reflect.TypeOf((*ComplexUnion)(nil)).Elem(), "", UnionVariant{ TypeFilter: gjson.JSON, Type: reflect.TypeOf(ComplexUnionA{}), @@ -300,7 +300,8 @@ func (r *MarshallingUnionB) UnmarshalJSON(data []byte) (err error) { } func init() { - RegisterUnion[MarshallingUnion]( + RegisterUnion( + reflect.TypeOf((*MarshallingUnion)(nil)).Elem(), "", UnionVariant{ TypeFilter: gjson.JSON, @@ -315,7 +316,7 @@ func init() { var tests = map[string]struct { buf string - val any + val interface{} }{ "true": {"true", true}, "false": {"false", false}, @@ -362,7 +363,7 @@ var tests = map[string]struct { "map_string": {`{"foo":"bar"}`, map[string]string{"foo": "bar"}}, "map_string_with_sjson_path_chars": {`{":a.b.c*:d*-1e.f":"bar"}`, map[string]string{":a.b.c*:d*-1e.f": "bar"}}, - "map_interface": {`{"a":1,"b":"str","c":false}`, map[string]any{"a": float64(1), "b": "str", "c": false}}, + "map_interface": {`{"a":1,"b":"str","c":false}`, map[string]interface{}{"a": float64(1), "b": "str", "c": false}}, "primitive_struct": { `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, @@ -400,7 +401,7 @@ var tests = map[string]struct { `{"a":true,"bar":"value","foo":true}`, AdditionalProperties{ A: true, - ExtraFields: map[string]any{ + ExtraFields: map[string]interface{}{ "bar": "value", "foo": true, }, @@ -420,7 +421,7 @@ var tests = map[string]struct { }, }, A: P(1), - ExtraFields: map[string]any{"b": "bar"}, + ExtraFields: map[string]interface{}{"b": "bar"}, JSON: EmbeddedStructsJSON{ A: Field{raw: `1`, status: valid}, ExtraFields: map[string]Field{ @@ -476,7 +477,7 @@ var tests = map[string]struct { "unknown_struct_map": { `{"unknown":{"foo":"bar"}}`, UnknownStruct{ - Unknown: map[string]any{ + Unknown: map[string]interface{}{ "foo": "bar", }, }, diff --git a/internal/apijson/port.go b/internal/apijson/port.go index b40013c..502ab77 100644 --- a/internal/apijson/port.go +++ b/internal/apijson/port.go @@ -53,7 +53,7 @@ func Port(from any, to any) error { for i := 0; i < t.NumField(); i++ { field := t.Field(i) ptag, ok := parseJSONStructTag(field) - if !ok || ptag.name == "-" || ptag.name == "" { + if !ok || ptag.name == "-" { continue } values[ptag.name] = v.Field(i) diff --git a/internal/apijson/port_test.go b/internal/apijson/port_test.go index bb01f1a..1154053 100644 --- a/internal/apijson/port_test.go +++ b/internal/apijson/port_test.go @@ -16,7 +16,7 @@ type Card struct { IsFoo bool `json:"is_foo"` IsBar bool `json:"is_bar"` Metadata Metadata `json:"metadata"` - Value any `json:"value"` + Value interface{} `json:"value"` JSON cardJSON } diff --git a/internal/apijson/registry.go b/internal/apijson/registry.go index 2a24982..119cc5f 100644 --- a/internal/apijson/registry.go +++ b/internal/apijson/registry.go @@ -8,29 +8,19 @@ import ( type UnionVariant struct { TypeFilter gjson.Type - DiscriminatorValue any + DiscriminatorValue interface{} Type reflect.Type } var unionRegistry = map[reflect.Type]unionEntry{} -var unionVariants = map[reflect.Type]any{} +var unionVariants = map[reflect.Type]interface{}{} type unionEntry struct { discriminatorKey string variants []UnionVariant } -func Discriminator[T any](value any) UnionVariant { - var zero T - return UnionVariant{ - TypeFilter: gjson.JSON, - DiscriminatorValue: value, - Type: reflect.TypeOf(zero), - } -} - -func RegisterUnion[T any](discriminator string, variants ...UnionVariant) { - typ := reflect.TypeOf((*T)(nil)).Elem() +func RegisterUnion(typ reflect.Type, discriminator string, variants ...UnionVariant) { unionRegistry[typ] = unionEntry{ discriminatorKey: discriminator, variants: variants, diff --git a/internal/apijson/subfield.go b/internal/apijson/subfield.go deleted file mode 100644 index da2460e..0000000 --- a/internal/apijson/subfield.go +++ /dev/null @@ -1,67 +0,0 @@ -package apijson - -import ( - "github.com/stainless-sdks/opencode-go/packages/respjson" - "reflect" -) - -func getSubField(root reflect.Value, index []int, name string) reflect.Value { - strct := root.FieldByIndex(index[:len(index)-1]) - if !strct.IsValid() { - panic("couldn't find encapsulating struct for field " + name) - } - meta := strct.FieldByName("JSON") - if !meta.IsValid() { - return reflect.Value{} - } - field := meta.FieldByName(name) - if !field.IsValid() { - return reflect.Value{} - } - return field -} - -func setMetadataSubField(root reflect.Value, index []int, name string, meta Field) { - target := getSubField(root, index, name) - if !target.IsValid() { - return - } - - if target.Type() == reflect.TypeOf(meta) { - target.Set(reflect.ValueOf(meta)) - } else if respMeta := meta.toRespField(); target.Type() == reflect.TypeOf(respMeta) { - target.Set(reflect.ValueOf(respMeta)) - } -} - -func setMetadataExtraFields(root reflect.Value, index []int, name string, metaExtras map[string]Field) { - target := getSubField(root, index, name) - if !target.IsValid() { - return - } - - if target.Type() == reflect.TypeOf(metaExtras) { - target.Set(reflect.ValueOf(metaExtras)) - return - } - - newMap := make(map[string]respjson.Field, len(metaExtras)) - if target.Type() == reflect.TypeOf(newMap) { - for k, v := range metaExtras { - newMap[k] = v.toRespField() - } - target.Set(reflect.ValueOf(newMap)) - } -} - -func (f Field) toRespField() respjson.Field { - if f.IsMissing() { - return respjson.Field{} - } else if f.IsNull() { - return respjson.NewField("null") - } else if f.IsInvalid() { - return respjson.NewInvalidField(f.raw) - } else { - return respjson.NewField(f.raw) - } -} diff --git a/internal/apijson/union.go b/internal/apijson/union.go deleted file mode 100644 index 2237458..0000000 --- a/internal/apijson/union.go +++ /dev/null @@ -1,202 +0,0 @@ -package apijson - -import ( - "errors" - "github.com/stainless-sdks/opencode-go/packages/param" - "reflect" - - "github.com/tidwall/gjson" -) - -var apiUnionType = reflect.TypeOf(param.APIUnion{}) - -func isStructUnion(t reflect.Type) bool { - if t.Kind() != reflect.Struct { - return false - } - for i := 0; i < t.NumField(); i++ { - if t.Field(i).Type == apiUnionType && t.Field(i).Anonymous { - return true - } - } - return false -} - -func RegisterDiscriminatedUnion[T any](key string, mappings map[string]reflect.Type) { - var t T - entry := unionEntry{ - discriminatorKey: key, - variants: []UnionVariant{}, - } - for k, typ := range mappings { - entry.variants = append(entry.variants, UnionVariant{ - DiscriminatorValue: k, - Type: typ, - }) - } - unionRegistry[reflect.TypeOf(t)] = entry -} - -func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc { - type variantDecoder struct { - decoder decoderFunc - field reflect.StructField - discriminatorValue any - } - - variants := []variantDecoder{} - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - - if field.Anonymous && field.Type == apiUnionType { - continue - } - - decoder := d.typeDecoder(field.Type) - variants = append(variants, variantDecoder{ - decoder: decoder, - field: field, - }) - } - - unionEntry, discriminated := unionRegistry[t] - for _, unionVariant := range unionEntry.variants { - for i := 0; i < len(variants); i++ { - variant := &variants[i] - if variant.field.Type.Elem() == unionVariant.Type { - variant.discriminatorValue = unionVariant.DiscriminatorValue - break - } - } - } - - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - if discriminated && n.Type == gjson.JSON && len(unionEntry.discriminatorKey) != 0 { - discriminator := n.Get(unionEntry.discriminatorKey).Value() - for _, variant := range variants { - if discriminator == variant.discriminatorValue { - inner := v.FieldByIndex(variant.field.Index) - return variant.decoder(n, inner, state) - } - } - return errors.New("apijson: was not able to find discriminated union variant") - } - - // Set bestExactness to worse than loose - bestExactness := loose - 1 - bestVariant := -1 - for i, variant := range variants { - // Pointers are used to discern JSON object variants from value variants - if n.Type != gjson.JSON && variant.field.Type.Kind() == reflect.Ptr { - continue - } - - sub := decoderState{strict: state.strict, exactness: exact} - inner := v.FieldByIndex(variant.field.Index) - err := variant.decoder(n, inner, &sub) - if err != nil { - continue - } - if sub.exactness == exact { - bestExactness = exact - bestVariant = i - break - } - if sub.exactness > bestExactness { - bestExactness = sub.exactness - bestVariant = i - } - } - - if bestExactness < loose { - return errors.New("apijson: was not able to coerce type as union") - } - - if guardStrict(state, bestExactness != exact) { - return errors.New("apijson: was not able to coerce type as union strictly") - } - - for i := 0; i < len(variants); i++ { - if i == bestVariant { - continue - } - v.FieldByIndex(variants[i].field.Index).SetZero() - } - - return nil - } -} - -// newUnionDecoder returns a decoderFunc that deserializes into a union using an -// algorithm roughly similar to Pydantic's [smart algorithm]. -// -// Conceptually this is equivalent to choosing the best schema based on how 'exact' -// the deserialization is for each of the schemas. -// -// If there is a tie in the level of exactness, then the tie is broken -// left-to-right. -// -// [smart algorithm]: https://docs.pydantic.dev/latest/concepts/unions/#smart-mode -func (d *decoderBuilder) newUnionDecoder(t reflect.Type) decoderFunc { - unionEntry, ok := unionRegistry[t] - if !ok { - panic("apijson: couldn't find union of type " + t.String() + " in union registry") - } - decoders := []decoderFunc{} - for _, variant := range unionEntry.variants { - decoder := d.typeDecoder(variant.Type) - decoders = append(decoders, decoder) - } - return func(n gjson.Result, v reflect.Value, state *decoderState) error { - // If there is a discriminator match, circumvent the exactness logic entirely - for idx, variant := range unionEntry.variants { - decoder := decoders[idx] - if variant.TypeFilter != n.Type { - continue - } - - if len(unionEntry.discriminatorKey) != 0 { - discriminatorValue := n.Get(unionEntry.discriminatorKey).Value() - if discriminatorValue == variant.DiscriminatorValue { - inner := reflect.New(variant.Type).Elem() - err := decoder(n, inner, state) - v.Set(inner) - return err - } - } - } - - // Set bestExactness to worse than loose - bestExactness := loose - 1 - for idx, variant := range unionEntry.variants { - decoder := decoders[idx] - if variant.TypeFilter != n.Type { - continue - } - sub := decoderState{strict: state.strict, exactness: exact} - inner := reflect.New(variant.Type).Elem() - err := decoder(n, inner, &sub) - if err != nil { - continue - } - if sub.exactness == exact { - v.Set(inner) - return nil - } - if sub.exactness > bestExactness { - v.Set(inner) - bestExactness = sub.exactness - } - } - - if bestExactness < loose { - return errors.New("apijson: was not able to coerce type as union") - } - - if guardStrict(state, bestExactness != exact) { - return errors.New("apijson: was not able to coerce type as union strictly") - } - - return nil - } -} diff --git a/internal/apiquery/encoder.go b/internal/apiquery/encoder.go index 398ef3c..0922c23 100644 --- a/internal/apiquery/encoder.go +++ b/internal/apiquery/encoder.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/stainless-sdks/opencode-go/packages/param" + "github.com/sst/opencode-sdk-go/internal/param" ) var encoders sync.Map // map[reflect.Type]encoderFunc @@ -20,7 +20,7 @@ type encoder struct { settings QuerySettings } -type encoderFunc func(key string, value reflect.Value) ([]Pair, error) +type encoderFunc func(key string, value reflect.Value) []Pair type encoderField struct { tag parsedStructTag @@ -61,7 +61,7 @@ func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { f encoderFunc ) wg.Add(1) - fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value) ([]Pair, error) { + fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value) []Pair { wg.Wait() return f(key, v) })) @@ -76,36 +76,28 @@ func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { return f } -func marshalerEncoder(key string, value reflect.Value) ([]Pair, error) { - s, err := value.Interface().(json.Marshaler).MarshalJSON() - if err != nil { - return nil, fmt.Errorf("apiquery: json fallback marshal error %s", err) - } - return []Pair{{key, string(s)}}, nil +func marshalerEncoder(key string, value reflect.Value) []Pair { + s, _ := value.Interface().(json.Marshaler).MarshalJSON() + return []Pair{{key, string(s)}} } func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { return e.newTimeTypeEncoder(t) } - - if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) { - return e.newRichFieldTypeEncoder(t) - } - if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) { return marshalerEncoder } - e.root = false switch t.Kind() { case reflect.Pointer: encoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) (pairs []Pair, err error) { + return func(key string, value reflect.Value) (pairs []Pair) { if !value.IsValid() || value.IsNil() { return } - return encoder(key, value.Elem()) + pairs = encoder(key, value.Elem()) + return } case reflect.Struct: return e.newStructTypeEncoder(t) @@ -123,14 +115,8 @@ func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { } func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { - if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) { - return e.newRichFieldTypeEncoder(t) - } - - for i := 0; i < t.NumField(); i++ { - if t.Field(i).Type == paramUnionType && t.Field(i).Anonymous { - return e.newStructUnionTypeEncoder(t) - } + if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) { + return e.newFieldTypeEncoder(t) } encoderFields := []encoderField{} @@ -159,7 +145,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { continue } - if (ptag.name == "-" || ptag.name == "") && !ptag.inline { + if ptag.name == "-" && !ptag.inline { continue } @@ -173,25 +159,13 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { e.dateFormat = "2006-01-02" } } - var encoderFn encoderFunc - if ptag.omitzero { - typeEncoderFn := e.typeEncoder(field.Type) - encoderFn = func(key string, value reflect.Value) ([]Pair, error) { - if value.IsZero() { - return nil, nil - } - return typeEncoderFn(key, value) - } - } else { - encoderFn = e.typeEncoder(field.Type) - } - encoderFields = append(encoderFields, encoderField{ptag, encoderFn, idx}) + encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx}) e.dateFormat = oldFormat } } collectEncoderFields(t, []int{}) - return func(key string, value reflect.Value) (pairs []Pair, err error) { + return func(key string, value reflect.Value) (pairs []Pair) { for _, ef := range encoderFields { var subkey string = e.renderKeyPath(key, ef.tag.name) if ef.tag.inline { @@ -199,62 +173,25 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { } field := value.FieldByIndex(ef.idx) - subpairs, suberr := ef.fn(subkey, field) - if suberr != nil { - err = suberr - } - pairs = append(pairs, subpairs...) + pairs = append(pairs, ef.fn(subkey, field)...) } return } } -var paramUnionType = reflect.TypeOf((*param.APIUnion)(nil)).Elem() - -func (e *encoder) newStructUnionTypeEncoder(t reflect.Type) encoderFunc { - var fieldEncoders []encoderFunc - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - if field.Type == paramUnionType && field.Anonymous { - fieldEncoders = append(fieldEncoders, nil) - continue - } - fieldEncoders = append(fieldEncoders, e.typeEncoder(field.Type)) - } - - return func(key string, value reflect.Value) (pairs []Pair, err error) { - for i := 0; i < t.NumField(); i++ { - if value.Field(i).Type() == paramUnionType { - continue - } - if !value.Field(i).IsZero() { - return fieldEncoders[i](key, value.Field(i)) - } - } - return nil, fmt.Errorf("apiquery: union %s has no field set", t.String()) - } -} - func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { keyEncoder := e.typeEncoder(t.Key()) elementEncoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) (pairs []Pair, err error) { + return func(key string, value reflect.Value) (pairs []Pair) { iter := value.MapRange() for iter.Next() { - encodedKey, err := keyEncoder("", iter.Key()) - if err != nil { - return nil, err - } + encodedKey := keyEncoder("", iter.Key()) if len(encodedKey) != 1 { - return nil, fmt.Errorf("apiquery: unexpected number of parts for encoded map key, map may contain non-primitive") + panic("Unexpected number of parts for encoded map key. Are you using a non-primitive for this map?") } subkey := encodedKey[0].value keyPath := e.renderKeyPath(key, subkey) - subpairs, suberr := elementEncoder(keyPath, iter.Value()) - if suberr != nil { - err = suberr - } - pairs = append(pairs, subpairs...) + pairs = append(pairs, elementEncoder(keyPath, iter.Value())...) } return } @@ -274,48 +211,36 @@ func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { switch e.settings.ArrayFormat { case ArrayQueryFormatComma: innerEncoder := e.typeEncoder(t.Elem()) - return func(key string, v reflect.Value) ([]Pair, error) { + return func(key string, v reflect.Value) []Pair { elements := []string{} for i := 0; i < v.Len(); i++ { - innerPairs, err := innerEncoder("", v.Index(i)) - if err != nil { - return nil, err - } - for _, pair := range innerPairs { + for _, pair := range innerEncoder("", v.Index(i)) { elements = append(elements, pair.value) } } if len(elements) == 0 { - return []Pair{}, nil + return []Pair{} } - return []Pair{{key, strings.Join(elements, ",")}}, nil + return []Pair{{key, strings.Join(elements, ",")}} } case ArrayQueryFormatRepeat: innerEncoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) (pairs []Pair, err error) { + return func(key string, value reflect.Value) (pairs []Pair) { for i := 0; i < value.Len(); i++ { - subpairs, suberr := innerEncoder(key, value.Index(i)) - if suberr != nil { - err = suberr - } - pairs = append(pairs, subpairs...) + pairs = append(pairs, innerEncoder(key, value.Index(i))...) } - return + return pairs } case ArrayQueryFormatIndices: panic("The array indices format is not supported yet") case ArrayQueryFormatBrackets: innerEncoder := e.typeEncoder(t.Elem()) - return func(key string, value reflect.Value) (pairs []Pair, err error) { - pairs = []Pair{} + return func(key string, value reflect.Value) []Pair { + pairs := []Pair{} for i := 0; i < value.Len(); i++ { - subpairs, suberr := innerEncoder(key+"[]", value.Index(i)) - if suberr != nil { - err = suberr - } - pairs = append(pairs, subpairs...) + pairs = append(pairs, innerEncoder(key+"[]", value.Index(i))...) } - return + return pairs } default: panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat)) @@ -328,46 +253,46 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { inner := t.Elem() innerEncoder := e.newPrimitiveTypeEncoder(inner) - return func(key string, v reflect.Value) ([]Pair, error) { + return func(key string, v reflect.Value) []Pair { if !v.IsValid() || v.IsNil() { - return nil, nil + return nil } return innerEncoder(key, v.Elem()) } case reflect.String: - return func(key string, v reflect.Value) ([]Pair, error) { - return []Pair{{key, v.String()}}, nil + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, v.String()}} } case reflect.Bool: - return func(key string, v reflect.Value) ([]Pair, error) { + return func(key string, v reflect.Value) []Pair { if v.Bool() { - return []Pair{{key, "true"}}, nil + return []Pair{{key, "true"}} } - return []Pair{{key, "false"}}, nil + return []Pair{{key, "false"}} } case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: - return func(key string, v reflect.Value) ([]Pair, error) { - return []Pair{{key, strconv.FormatInt(v.Int(), 10)}}, nil + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatInt(v.Int(), 10)}} } case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return func(key string, v reflect.Value) ([]Pair, error) { - return []Pair{{key, strconv.FormatUint(v.Uint(), 10)}}, nil + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatUint(v.Uint(), 10)}} } case reflect.Float32, reflect.Float64: - return func(key string, v reflect.Value) ([]Pair, error) { - return []Pair{{key, strconv.FormatFloat(v.Float(), 'f', -1, 64)}}, nil + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatFloat(v.Float(), 'f', -1, 64)}} } case reflect.Complex64, reflect.Complex128: bitSize := 64 if t.Kind() == reflect.Complex128 { bitSize = 128 } - return func(key string, v reflect.Value) ([]Pair, error) { - return []Pair{{key, strconv.FormatComplex(v.Complex(), 'f', -1, bitSize)}}, nil + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatComplex(v.Complex(), 'f', -1, bitSize)}} } default: - return func(key string, v reflect.Value) ([]Pair, error) { - return nil, nil + return func(key string, v reflect.Value) []Pair { + return nil } } } @@ -376,14 +301,15 @@ func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc { f, _ := t.FieldByName("Value") enc := e.typeEncoder(f.Type) - return func(key string, value reflect.Value) ([]Pair, error) { + return func(key string, value reflect.Value) []Pair { present := value.FieldByName("Present") if !present.Bool() { - return nil, nil + return nil } null := value.FieldByName("Null") if null.Bool() { - return nil, fmt.Errorf("apiquery: field cannot be null") + // TODO: Error? + return nil } raw := value.FieldByName("Raw") if !raw.IsNil() { @@ -393,21 +319,21 @@ func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc { } } -func (e *encoder) newTimeTypeEncoder(_ reflect.Type) encoderFunc { +func (e *encoder) newTimeTypeEncoder(t reflect.Type) encoderFunc { format := e.dateFormat - return func(key string, value reflect.Value) ([]Pair, error) { + return func(key string, value reflect.Value) []Pair { return []Pair{{ key, value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format), - }}, nil + }} } } func (e encoder) newInterfaceEncoder() encoderFunc { - return func(key string, value reflect.Value) ([]Pair, error) { + return func(key string, value reflect.Value) []Pair { value = value.Elem() if !value.IsValid() { - return nil, nil + return nil } return e.typeEncoder(value.Type())(key, value) } diff --git a/internal/apiquery/query.go b/internal/apiquery/query.go index 0f379fa..6f90e99 100644 --- a/internal/apiquery/query.go +++ b/internal/apiquery/query.go @@ -6,31 +6,26 @@ import ( "time" ) -func MarshalWithSettings(value any, settings QuerySettings) (url.Values, error) { +func MarshalWithSettings(value interface{}, settings QuerySettings) url.Values { e := encoder{time.RFC3339, true, settings} kv := url.Values{} val := reflect.ValueOf(value) if !val.IsValid() { - return nil, nil + return nil } typ := val.Type() - - pairs, err := e.typeEncoder(typ)("", val) - if err != nil { - return nil, err - } - for _, pair := range pairs { + for _, pair := range e.typeEncoder(typ)("", val) { kv.Add(pair.key, pair.value) } - return kv, nil + return kv } -func Marshal(value any) (url.Values, error) { +func Marshal(value interface{}) url.Values { return MarshalWithSettings(value, QuerySettings{}) } type Queryer interface { - URLQuery() (url.Values, error) + URLQuery() url.Values } type QuerySettings struct { diff --git a/internal/apiquery/query_test.go b/internal/apiquery/query_test.go index 00765e8..1e740d6 100644 --- a/internal/apiquery/query_test.go +++ b/internal/apiquery/query_test.go @@ -1,7 +1,6 @@ package apiquery import ( - "github.com/stainless-sdks/opencode-go/packages/param" "net/url" "testing" "time" @@ -28,8 +27,8 @@ type PrimitivePointers struct { } type Slices struct { - Slice []Primitives `query:"slices"` - Mixed []any `query:"mixed"` + Slice []Primitives `query:"slices"` + Mixed []interface{} `query:"mixed"` } type DateTime struct { @@ -38,8 +37,8 @@ type DateTime struct { } type AdditionalProperties struct { - A bool `query:"a"` - Extras map[string]any `query:"-,inline"` + A bool `query:"a"` + Extras map[string]interface{} `query:"-,inline"` } type Recursive struct { @@ -48,7 +47,7 @@ type Recursive struct { } type UnknownStruct struct { - Unknown any `query:"unknown"` + Unknown interface{} `query:"unknown"` } type UnionStruct struct { @@ -102,35 +101,9 @@ type DeeplyNested3 struct { D *string `query:"d"` } -type RichPrimitives struct { - A param.Opt[string] `query:"a"` -} - -type QueryOmitTest struct { - A param.Opt[string] `query:"a,omitzero"` - B string `query:"b,omitzero"` -} - -type NamedEnum string - -const NamedEnumFoo NamedEnum = "foo" - -type StructUnionWrapper struct { - Union StructUnion `query:"union"` -} - -type StructUnion struct { - OfInt param.Opt[int64] `query:",omitzero,inline"` - OfString param.Opt[string] `query:",omitzero,inline"` - OfEnum param.Opt[NamedEnum] `query:",omitzero,inline"` - OfA UnionStructA `query:",omitzero,inline"` - OfB UnionStructB `query:",omitzero,inline"` - param.APIUnion -} - var tests = map[string]struct { enc string - val any + val interface{} settings QuerySettings }{ "primitives": { @@ -146,7 +119,7 @@ var tests = map[string]struct { {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, }, - Mixed: []any{1, 2.3, "hello"}, + Mixed: []interface{}{1, 2.3, "hello"}, }, QuerySettings{ArrayFormat: ArrayQueryFormatBrackets}, }, @@ -154,7 +127,7 @@ var tests = map[string]struct { "slices_comma": { `mixed=1,2.3,hello`, Slices{ - Mixed: []any{1, 2.3, "hello"}, + Mixed: []interface{}{1, 2.3, "hello"}, }, QuerySettings{ArrayFormat: ArrayQueryFormatComma}, }, @@ -166,7 +139,7 @@ var tests = map[string]struct { {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, }, - Mixed: []any{1, 2.3, "hello"}, + Mixed: []interface{}{1, 2.3, "hello"}, }, QuerySettings{ArrayFormat: ArrayQueryFormatRepeat}, }, @@ -197,7 +170,7 @@ var tests = map[string]struct { `a=true&bar=value&foo=true`, AdditionalProperties{ A: true, - Extras: map[string]any{ + Extras: map[string]interface{}{ "bar": "value", "foo": true, }, @@ -228,7 +201,7 @@ var tests = map[string]struct { "unknown_struct_map_brackets": { `unknown[foo]=bar`, UnknownStruct{ - Unknown: map[string]any{ + Unknown: map[string]interface{}{ "foo": "bar", }, }, @@ -238,21 +211,13 @@ var tests = map[string]struct { "unknown_struct_map_dots": { `unknown.foo=bar`, UnknownStruct{ - Unknown: map[string]any{ + Unknown: map[string]interface{}{ "foo": "bar", }, }, QuerySettings{NestedFormat: NestedQueryFormatDots}, }, - "struct_union_string": { - `union=hello`, - StructUnionWrapper{ - Union: StructUnion{OfString: param.NewOpt("hello")}, - }, - QuerySettings{}, - }, - "union_string": { `union=hello`, UnionStruct{ @@ -261,14 +226,6 @@ var tests = map[string]struct { QuerySettings{}, }, - "struct_union_integer": { - `union=12`, - StructUnionWrapper{ - Union: StructUnion{OfInt: param.NewOpt[int64](12)}, - }, - QuerySettings{}, - }, - "union_integer": { `union=12`, UnionStruct{ @@ -277,26 +234,6 @@ var tests = map[string]struct { QuerySettings{}, }, - "struct_union_enum": { - `union=foo`, - StructUnionWrapper{ - Union: StructUnion{OfEnum: param.NewOpt[NamedEnum](NamedEnumFoo)}, - }, - QuerySettings{}, - }, - - "struct_union_struct_discriminated_a": { - `union[a]=foo&union[b]=bar&union[type]=typeA`, - StructUnionWrapper{ - Union: StructUnion{OfA: UnionStructA{ - Type: "typeA", - A: "foo", - B: "bar", - }}, - }, - QuerySettings{}, - }, - "union_struct_discriminated_a": { `union[a]=foo&union[b]=bar&union[type]=typeA`, UnionStruct{ @@ -309,17 +246,6 @@ var tests = map[string]struct { QuerySettings{}, }, - "struct_union_struct_discriminated_b": { - `union[a]=foo&union[type]=typeB`, - StructUnionWrapper{ - Union: StructUnion{OfB: UnionStructB{ - Type: "typeB", - A: "foo", - }}, - }, - QuerySettings{}, - }, - "union_struct_discriminated_b": { `union[a]=foo&union[type]=typeB`, UnionStruct{ @@ -394,38 +320,12 @@ var tests = map[string]struct { }, QuerySettings{NestedFormat: NestedQueryFormatDots}, }, - - "rich_primitives": { - `a=hello`, - RichPrimitives{ - A: param.Opt[string]{Value: "hello"}, - }, - QuerySettings{}, - }, - - "rich_primitives_omit": { - ``, - QueryOmitTest{ - A: param.Opt[string]{}, - }, - QuerySettings{}, - }, - "query_omit": { - `a=hello`, - QueryOmitTest{ - A: param.Opt[string]{Value: "hello"}, - }, - QuerySettings{}, - }, } func TestEncode(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - values, err := MarshalWithSettings(test.val, test.settings) - if err != nil { - t.Fatalf("failed to marshal url %s", err) - } + values := MarshalWithSettings(test.val, test.settings) str, _ := url.QueryUnescape(values.Encode()) if str != test.enc { t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.enc, str) diff --git a/internal/apiquery/richparam.go b/internal/apiquery/richparam.go deleted file mode 100644 index 72b3c65..0000000 --- a/internal/apiquery/richparam.go +++ /dev/null @@ -1,19 +0,0 @@ -package apiquery - -import ( - "github.com/stainless-sdks/opencode-go/packages/param" - "reflect" -) - -func (e *encoder) newRichFieldTypeEncoder(t reflect.Type) encoderFunc { - f, _ := t.FieldByName("Value") - enc := e.typeEncoder(f.Type) - return func(key string, value reflect.Value) ([]Pair, error) { - if opt, ok := value.Interface().(param.Optional); ok && opt.Valid() { - return enc(key, value.FieldByIndex(f.Index)) - } else if ok && param.IsNull(opt) { - return []Pair{{key, "null"}}, nil - } - return nil, nil - } -} diff --git a/internal/apiquery/tag.go b/internal/apiquery/tag.go index 772c40e..7ccd739 100644 --- a/internal/apiquery/tag.go +++ b/internal/apiquery/tag.go @@ -11,7 +11,6 @@ const formatStructTag = "format" type parsedStructTag struct { name string omitempty bool - omitzero bool inline bool } @@ -27,8 +26,6 @@ func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok boo tag.name = parts[0] for _, part := range parts[1:] { switch part { - case "omitzero": - tag.omitzero = true case "omitempty": tag.omitempty = true case "inline": diff --git a/internal/encoding/json/decode.go b/internal/encoding/json/decode.go deleted file mode 100644 index ba23fb9..0000000 --- a/internal/encoding/json/decode.go +++ /dev/null @@ -1,1324 +0,0 @@ -// Vendored from Go 1.24.0-pre-release -// To find alterations, check package shims, and comments beginning in SHIM(). -// -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Represents JSON data structure using native Go types: booleans, floats, -// strings, arrays, and maps. - -package json - -import ( - "encoding" - "encoding/base64" - "fmt" - "github.com/stainless-sdks/opencode-go/internal/encoding/json/shims" - "reflect" - "strconv" - "strings" - "unicode" - "unicode/utf16" - "unicode/utf8" - _ "unsafe" // for linkname -) - -// Unmarshal parses the JSON-encoded data and stores the result -// in the value pointed to by v. If v is nil or not a pointer, -// Unmarshal returns an [InvalidUnmarshalError]. -// -// Unmarshal uses the inverse of the encodings that -// [Marshal] uses, allocating maps, slices, and pointers as necessary, -// with the following additional rules: -// -// To unmarshal JSON into a pointer, Unmarshal first handles the case of -// the JSON being the JSON literal null. In that case, Unmarshal sets -// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into -// the value pointed at by the pointer. If the pointer is nil, Unmarshal -// allocates a new value for it to point to. -// -// To unmarshal JSON into a value implementing [Unmarshaler], -// Unmarshal calls that value's [Unmarshaler.UnmarshalJSON] method, including -// when the input is a JSON null. -// Otherwise, if the value implements [encoding.TextUnmarshaler] -// and the input is a JSON quoted string, Unmarshal calls -// [encoding.TextUnmarshaler.UnmarshalText] with the unquoted form of the string. -// -// To unmarshal JSON into a struct, Unmarshal matches incoming object -// keys to the keys used by [Marshal] (either the struct field name or its tag), -// preferring an exact match but also accepting a case-insensitive match. By -// default, object keys which don't have a corresponding struct field are -// ignored (see [Decoder.DisallowUnknownFields] for an alternative). -// -// To unmarshal JSON into an interface value, -// Unmarshal stores one of these in the interface value: -// -// - bool, for JSON booleans -// - float64, for JSON numbers -// - string, for JSON strings -// - []any, for JSON arrays -// - map[string]any, for JSON objects -// - nil for JSON null -// -// To unmarshal a JSON array into a slice, Unmarshal resets the slice length -// to zero and then appends each element to the slice. -// As a special case, to unmarshal an empty JSON array into a slice, -// Unmarshal replaces the slice with a new empty slice. -// -// To unmarshal a JSON array into a Go array, Unmarshal decodes -// JSON array elements into corresponding Go array elements. -// If the Go array is smaller than the JSON array, -// the additional JSON array elements are discarded. -// If the JSON array is smaller than the Go array, -// the additional Go array elements are set to zero values. -// -// To unmarshal a JSON object into a map, Unmarshal first establishes a map to -// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal -// reuses the existing map, keeping existing entries. Unmarshal then stores -// key-value pairs from the JSON object into the map. The map's key type must -// either be any string type, an integer, or implement [encoding.TextUnmarshaler]. -// -// If the JSON-encoded data contain a syntax error, Unmarshal returns a [SyntaxError]. -// -// If a JSON value is not appropriate for a given target type, -// or if a JSON number overflows the target type, Unmarshal -// skips that field and completes the unmarshaling as best it can. -// If no more serious errors are encountered, Unmarshal returns -// an [UnmarshalTypeError] describing the earliest such error. In any -// case, it's not guaranteed that all the remaining fields following -// the problematic one will be unmarshaled into the target object. -// -// The JSON null value unmarshals into an interface, map, pointer, or slice -// by setting that Go value to nil. Because null is often used in JSON to mean -// “not present,” unmarshaling a JSON null into any other Go type has no effect -// on the value and produces no error. -// -// When unmarshaling quoted strings, invalid UTF-8 or -// invalid UTF-16 surrogate pairs are not treated as an error. -// Instead, they are replaced by the Unicode replacement -// character U+FFFD. -func Unmarshal(data []byte, v any) error { - // Check for well-formedness. - // Avoids filling out half a data structure - // before discovering a JSON syntax error. - var d decodeState - err := checkValid(data, &d.scan) - if err != nil { - return err - } - - d.init(data) - return d.unmarshal(v) -} - -// Unmarshaler is the interface implemented by types -// that can unmarshal a JSON description of themselves. -// The input can be assumed to be a valid encoding of -// a JSON value. UnmarshalJSON must copy the JSON data -// if it wishes to retain the data after returning. -// -// By convention, to approximate the behavior of [Unmarshal] itself, -// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. -type Unmarshaler interface { - UnmarshalJSON([]byte) error -} - -// An UnmarshalTypeError describes a JSON value that was -// not appropriate for a value of a specific Go type. -type UnmarshalTypeError struct { - Value string // description of JSON value - "bool", "array", "number -5" - Type reflect.Type // type of Go value it could not be assigned to - Offset int64 // error occurred after reading Offset bytes - Struct string // name of the struct type containing the field - Field string // the full path from root node to the field, include embedded struct -} - -func (e *UnmarshalTypeError) Error() string { - if e.Struct != "" || e.Field != "" { - return "json: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String() - } - return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() -} - -// An UnmarshalFieldError describes a JSON object key that -// led to an unexported (and therefore unwritable) struct field. -// -// Deprecated: No longer used; kept for compatibility. -type UnmarshalFieldError struct { - Key string - Type reflect.Type - Field reflect.StructField -} - -func (e *UnmarshalFieldError) Error() string { - return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() -} - -// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal]. -// (The argument to [Unmarshal] must be a non-nil pointer.) -type InvalidUnmarshalError struct { - Type reflect.Type -} - -func (e *InvalidUnmarshalError) Error() string { - if e.Type == nil { - return "json: Unmarshal(nil)" - } - - if e.Type.Kind() != reflect.Pointer { - return "json: Unmarshal(non-pointer " + e.Type.String() + ")" - } - return "json: Unmarshal(nil " + e.Type.String() + ")" -} - -func (d *decodeState) unmarshal(v any) error { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Pointer || rv.IsNil() { - return &InvalidUnmarshalError{reflect.TypeOf(v)} - } - - d.scan.reset() - d.scanWhile(scanSkipSpace) - // We decode rv not rv.Elem because the Unmarshaler interface - // test must be applied at the top level of the value. - err := d.value(rv) - if err != nil { - return d.addErrorContext(err) - } - return d.savedError -} - -// A Number represents a JSON number literal. -type Number string - -// String returns the literal text of the number. -func (n Number) String() string { return string(n) } - -// Float64 returns the number as a float64. -func (n Number) Float64() (float64, error) { - return strconv.ParseFloat(string(n), 64) -} - -// Int64 returns the number as an int64. -func (n Number) Int64() (int64, error) { - return strconv.ParseInt(string(n), 10, 64) -} - -// An errorContext provides context for type errors during decoding. -type errorContext struct { - Struct reflect.Type - FieldStack []string -} - -// decodeState represents the state while decoding a JSON value. -type decodeState struct { - data []byte - off int // next read offset in data - opcode int // last read result - scan scanner - errorContext *errorContext - savedError error - useNumber bool - disallowUnknownFields bool -} - -// readIndex returns the position of the last byte read. -func (d *decodeState) readIndex() int { - return d.off - 1 -} - -// phasePanicMsg is used as a panic message when we end up with something that -// shouldn't happen. It can indicate a bug in the JSON decoder, or that -// something is editing the data slice while the decoder executes. -const phasePanicMsg = "JSON decoder out of sync - data changing underfoot?" - -func (d *decodeState) init(data []byte) *decodeState { - d.data = data - d.off = 0 - d.savedError = nil - if d.errorContext != nil { - d.errorContext.Struct = nil - // Reuse the allocated space for the FieldStack slice. - d.errorContext.FieldStack = d.errorContext.FieldStack[:0] - } - return d -} - -// saveError saves the first err it is called with, -// for reporting at the end of the unmarshal. -func (d *decodeState) saveError(err error) { - if d.savedError == nil { - d.savedError = d.addErrorContext(err) - } -} - -// addErrorContext returns a new error enhanced with information from d.errorContext -func (d *decodeState) addErrorContext(err error) error { - if d.errorContext != nil && (d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0) { - switch err := err.(type) { - case *UnmarshalTypeError: - err.Struct = d.errorContext.Struct.Name() - fieldStack := d.errorContext.FieldStack - if err.Field != "" { - fieldStack = append(fieldStack, err.Field) - } - err.Field = strings.Join(fieldStack, ".") - } - } - return err -} - -// skip scans to the end of what was started. -func (d *decodeState) skip() { - s, data, i := &d.scan, d.data, d.off - depth := len(s.parseState) - for { - op := s.step(s, data[i]) - i++ - if len(s.parseState) < depth { - d.off = i - d.opcode = op - return - } - } -} - -// scanNext processes the byte at d.data[d.off]. -func (d *decodeState) scanNext() { - if d.off < len(d.data) { - d.opcode = d.scan.step(&d.scan, d.data[d.off]) - d.off++ - } else { - d.opcode = d.scan.eof() - d.off = len(d.data) + 1 // mark processed EOF with len+1 - } -} - -// scanWhile processes bytes in d.data[d.off:] until it -// receives a scan code not equal to op. -func (d *decodeState) scanWhile(op int) { - s, data, i := &d.scan, d.data, d.off - for i < len(data) { - newOp := s.step(s, data[i]) - i++ - if newOp != op { - d.opcode = newOp - d.off = i - return - } - } - - d.off = len(data) + 1 // mark processed EOF with len+1 - d.opcode = d.scan.eof() -} - -// rescanLiteral is similar to scanWhile(scanContinue), but it specialises the -// common case where we're decoding a literal. The decoder scans the input -// twice, once for syntax errors and to check the length of the value, and the -// second to perform the decoding. -// -// Only in the second step do we use decodeState to tokenize literals, so we -// know there aren't any syntax errors. We can take advantage of that knowledge, -// and scan a literal's bytes much more quickly. -func (d *decodeState) rescanLiteral() { - data, i := d.data, d.off -Switch: - switch data[i-1] { - case '"': // string - for ; i < len(data); i++ { - switch data[i] { - case '\\': - i++ // escaped char - case '"': - i++ // tokenize the closing quote too - break Switch - } - } - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': // number - for ; i < len(data); i++ { - switch data[i] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '.', 'e', 'E', '+', '-': - default: - break Switch - } - } - case 't': // true - i += len("rue") - case 'f': // false - i += len("alse") - case 'n': // null - i += len("ull") - } - if i < len(data) { - d.opcode = stateEndValue(&d.scan, data[i]) - } else { - d.opcode = scanEnd - } - d.off = i + 1 -} - -// value consumes a JSON value from d.data[d.off-1:], decoding into v, and -// reads the following byte ahead. If v is invalid, the value is discarded. -// The first byte of the value has been read already. -func (d *decodeState) value(v reflect.Value) error { - switch d.opcode { - default: - panic(phasePanicMsg) - - case scanBeginArray: - if v.IsValid() { - if err := d.array(v); err != nil { - return err - } - } else { - d.skip() - } - d.scanNext() - - case scanBeginObject: - if v.IsValid() { - if err := d.object(v); err != nil { - return err - } - } else { - d.skip() - } - d.scanNext() - - case scanBeginLiteral: - // All bytes inside literal return scanContinue op code. - start := d.readIndex() - d.rescanLiteral() - - if v.IsValid() { - if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil { - return err - } - } - } - return nil -} - -type unquotedValue struct{} - -// valueQuoted is like value but decodes a -// quoted string literal or literal null into an interface value. -// If it finds anything other than a quoted string literal or null, -// valueQuoted returns unquotedValue{}. -func (d *decodeState) valueQuoted() any { - switch d.opcode { - default: - panic(phasePanicMsg) - - case scanBeginArray, scanBeginObject: - d.skip() - d.scanNext() - - case scanBeginLiteral: - v := d.literalInterface() - switch v.(type) { - case nil, string: - return v - } - } - return unquotedValue{} -} - -// indirect walks down v allocating pointers as needed, -// until it gets to a non-pointer. -// If it encounters an Unmarshaler, indirect stops and returns that. -// If decodingNull is true, indirect stops at the first settable pointer so it -// can be set to nil. -func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { - // Issue #24153 indicates that it is generally not a guaranteed property - // that you may round-trip a reflect.Value by calling Value.Addr().Elem() - // and expect the value to still be settable for values derived from - // unexported embedded struct fields. - // - // The logic below effectively does this when it first addresses the value - // (to satisfy possible pointer methods) and continues to dereference - // subsequent pointers as necessary. - // - // After the first round-trip, we set v back to the original value to - // preserve the original RW flags contained in reflect.Value. - v0 := v - haveAddr := false - - // If v is a named type and is addressable, - // start with its address, so that if the type has pointer methods, - // we find them. - if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() { - haveAddr = true - v = v.Addr() - } - for { - // Load value from interface, but only if the result will be - // usefully addressable. - if v.Kind() == reflect.Interface && !v.IsNil() { - e := v.Elem() - if e.Kind() == reflect.Pointer && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Pointer) { - haveAddr = false - v = e - continue - } - } - - if v.Kind() != reflect.Pointer { - break - } - - if decodingNull && v.CanSet() { - break - } - - // Prevent infinite loop if v is an interface pointing to its own address: - // var v any - // v = &v - if v.Elem().Kind() == reflect.Interface && v.Elem().Elem().Equal(v) { - v = v.Elem() - break - } - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - if v.Type().NumMethod() > 0 && v.CanInterface() { - if u, ok := v.Interface().(Unmarshaler); ok { - return u, nil, reflect.Value{} - } - if !decodingNull { - if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { - return nil, u, reflect.Value{} - } - } - } - - if haveAddr { - v = v0 // restore original value after round-trip Value.Addr().Elem() - haveAddr = false - } else { - v = v.Elem() - } - } - return nil, nil, v -} - -// array consumes an array from d.data[d.off-1:], decoding into v. -// The first byte of the array ('[') has been read already. -func (d *decodeState) array(v reflect.Value) error { - // Check for unmarshaler. - u, ut, pv := indirect(v, false) - if u != nil { - start := d.readIndex() - d.skip() - return u.UnmarshalJSON(d.data[start:d.off]) - } - if ut != nil { - d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) - d.skip() - return nil - } - v = pv - - // Check type of target. - switch v.Kind() { - case reflect.Interface: - if v.NumMethod() == 0 { - // Decoding into nil interface? Switch to non-reflect code. - ai := d.arrayInterface() - v.Set(reflect.ValueOf(ai)) - return nil - } - // Otherwise it's invalid. - fallthrough - default: - d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) - d.skip() - return nil - case reflect.Array, reflect.Slice: - break - } - - i := 0 - for { - // Look ahead for ] - can only happen on first iteration. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndArray { - break - } - - // Expand slice length, growing the slice if necessary. - if v.Kind() == reflect.Slice { - if i >= v.Cap() { - v.Grow(1) - } - if i >= v.Len() { - v.SetLen(i + 1) - } - } - - if i < v.Len() { - // Decode into element. - if err := d.value(v.Index(i)); err != nil { - return err - } - } else { - // Ran out of fixed array: skip. - if err := d.value(reflect.Value{}); err != nil { - return err - } - } - i++ - - // Next token must be , or ]. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode == scanEndArray { - break - } - if d.opcode != scanArrayValue { - panic(phasePanicMsg) - } - } - - if i < v.Len() { - if v.Kind() == reflect.Array { - for ; i < v.Len(); i++ { - v.Index(i).SetZero() // zero remainder of array - } - } else { - v.SetLen(i) // truncate the slice - } - } - if i == 0 && v.Kind() == reflect.Slice { - v.Set(reflect.MakeSlice(v.Type(), 0, 0)) - } - return nil -} - -var nullLiteral = []byte("null") - -// SHIM(reflect): reflect.TypeFor[T]() reflect.T -var textUnmarshalerType = shims.TypeFor[encoding.TextUnmarshaler]() - -// object consumes an object from d.data[d.off-1:], decoding into v. -// The first byte ('{') of the object has been read already. -func (d *decodeState) object(v reflect.Value) error { - // Check for unmarshaler. - u, ut, pv := indirect(v, false) - if u != nil { - start := d.readIndex() - d.skip() - return u.UnmarshalJSON(d.data[start:d.off]) - } - if ut != nil { - d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) - d.skip() - return nil - } - v = pv - t := v.Type() - - // Decoding into nil interface? Switch to non-reflect code. - if v.Kind() == reflect.Interface && v.NumMethod() == 0 { - oi := d.objectInterface() - v.Set(reflect.ValueOf(oi)) - return nil - } - - var fields structFields - - // Check type of target: - // struct or - // map[T1]T2 where T1 is string, an integer type, - // or an encoding.TextUnmarshaler - switch v.Kind() { - case reflect.Map: - // Map key must either have string kind, have an integer kind, - // or be an encoding.TextUnmarshaler. - switch t.Key().Kind() { - case reflect.String, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - default: - if !reflect.PointerTo(t.Key()).Implements(textUnmarshalerType) { - d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) - d.skip() - return nil - } - } - if v.IsNil() { - v.Set(reflect.MakeMap(t)) - } - case reflect.Struct: - fields = cachedTypeFields(t) - // ok - default: - d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) - d.skip() - return nil - } - - var mapElem reflect.Value - var origErrorContext errorContext - if d.errorContext != nil { - origErrorContext = *d.errorContext - } - - for { - // Read opening " of string key or closing }. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndObject { - // closing } - can only happen on first iteration. - break - } - if d.opcode != scanBeginLiteral { - panic(phasePanicMsg) - } - - // Read key. - start := d.readIndex() - d.rescanLiteral() - item := d.data[start:d.readIndex()] - key, ok := unquoteBytes(item) - if !ok { - panic(phasePanicMsg) - } - - // Figure out field corresponding to key. - var subv reflect.Value - destring := false // whether the value is wrapped in a string to be decoded first - - if v.Kind() == reflect.Map { - elemType := t.Elem() - if !mapElem.IsValid() { - mapElem = reflect.New(elemType).Elem() - } else { - mapElem.SetZero() - } - subv = mapElem - } else { - f := fields.byExactName[string(key)] - if f == nil { - f = fields.byFoldedName[string(foldName(key))] - } - if f != nil { - subv = v - destring = f.quoted - if d.errorContext == nil { - d.errorContext = new(errorContext) - } - for i, ind := range f.index { - if subv.Kind() == reflect.Pointer { - if subv.IsNil() { - // If a struct embeds a pointer to an unexported type, - // it is not possible to set a newly allocated value - // since the field is unexported. - // - // See https://golang.org/issue/21357 - if !subv.CanSet() { - d.saveError(fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v", subv.Type().Elem())) - // Invalidate subv to ensure d.value(subv) skips over - // the JSON value without assigning it to subv. - subv = reflect.Value{} - destring = false - break - } - subv.Set(reflect.New(subv.Type().Elem())) - } - subv = subv.Elem() - } - if i < len(f.index)-1 { - d.errorContext.FieldStack = append( - d.errorContext.FieldStack, - subv.Type().Field(ind).Name, - ) - } - subv = subv.Field(ind) - } - d.errorContext.Struct = t - d.errorContext.FieldStack = append(d.errorContext.FieldStack, f.name) - } else if d.disallowUnknownFields { - d.saveError(fmt.Errorf("json: unknown field %q", key)) - } - } - - // Read : before value. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode != scanObjectKey { - panic(phasePanicMsg) - } - d.scanWhile(scanSkipSpace) - - if destring { - switch qv := d.valueQuoted().(type) { - case nil: - if err := d.literalStore(nullLiteral, subv, false); err != nil { - return err - } - case string: - if err := d.literalStore([]byte(qv), subv, true); err != nil { - return err - } - default: - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type())) - } - } else { - if err := d.value(subv); err != nil { - return err - } - } - - // Write value back to map; - // if using struct, subv points into struct already. - if v.Kind() == reflect.Map { - kt := t.Key() - var kv reflect.Value - if reflect.PointerTo(kt).Implements(textUnmarshalerType) { - kv = reflect.New(kt) - if err := d.literalStore(item, kv, true); err != nil { - return err - } - kv = kv.Elem() - } else { - switch kt.Kind() { - case reflect.String: - kv = reflect.New(kt).Elem() - kv.SetString(string(key)) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - s := string(key) - n, err := strconv.ParseInt(s, 10, 64) - // SHIM(reflect): reflect.Type.OverflowInt(int64) bool - okt := shims.OverflowableType{Type: kt} - if err != nil || okt.OverflowInt(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) - break - } - kv = reflect.New(kt).Elem() - kv.SetInt(n) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - s := string(key) - n, err := strconv.ParseUint(s, 10, 64) - // SHIM(reflect): reflect.Type.OverflowUint(uint64) bool - okt := shims.OverflowableType{Type: kt} - if err != nil || okt.OverflowUint(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) - break - } - kv = reflect.New(kt).Elem() - kv.SetUint(n) - default: - panic("json: Unexpected key type") // should never occur - } - } - if kv.IsValid() { - v.SetMapIndex(kv, subv) - } - } - - // Next token must be , or }. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.errorContext != nil { - // Reset errorContext to its original state. - // Keep the same underlying array for FieldStack, to reuse the - // space and avoid unnecessary allocs. - d.errorContext.FieldStack = d.errorContext.FieldStack[:len(origErrorContext.FieldStack)] - d.errorContext.Struct = origErrorContext.Struct - } - if d.opcode == scanEndObject { - break - } - if d.opcode != scanObjectValue { - panic(phasePanicMsg) - } - } - return nil -} - -// convertNumber converts the number literal s to a float64 or a Number -// depending on the setting of d.useNumber. -func (d *decodeState) convertNumber(s string) (any, error) { - if d.useNumber { - return Number(s), nil - } - f, err := strconv.ParseFloat(s, 64) - if err != nil { - // SHIM(reflect): reflect.TypeFor[T]() reflect.Type - return nil, &UnmarshalTypeError{Value: "number " + s, Type: shims.TypeFor[float64](), Offset: int64(d.off)} - } - return f, nil -} - -// SHIM(reflect): TypeFor[T]() reflect.Type -var numberType = shims.TypeFor[Number]() - -// literalStore decodes a literal stored in item into v. -// -// fromQuoted indicates whether this literal came from unwrapping a -// string from the ",string" struct tag option. this is used only to -// produce more helpful error messages. -func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) error { - // Check for unmarshaler. - if len(item) == 0 { - // Empty string given. - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - return nil - } - isNull := item[0] == 'n' // null - u, ut, pv := indirect(v, isNull) - if u != nil { - return u.UnmarshalJSON(item) - } - if ut != nil { - if item[0] != '"' { - if fromQuoted { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - return nil - } - val := "number" - switch item[0] { - case 'n': - val = "null" - case 't', 'f': - val = "bool" - } - d.saveError(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.readIndex())}) - return nil - } - s, ok := unquoteBytes(item) - if !ok { - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - panic(phasePanicMsg) - } - return ut.UnmarshalText(s) - } - - v = pv - - switch c := item[0]; c { - case 'n': // null - // The main parser checks that only true and false can reach here, - // but if this was a quoted string input, it could be anything. - if fromQuoted && string(item) != "null" { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - break - } - switch v.Kind() { - case reflect.Interface, reflect.Pointer, reflect.Map, reflect.Slice: - v.SetZero() - // otherwise, ignore null for primitives/string - } - case 't', 'f': // true, false - value := item[0] == 't' - // The main parser checks that only true and false can reach here, - // but if this was a quoted string input, it could be anything. - if fromQuoted && string(item) != "true" && string(item) != "false" { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - break - } - switch v.Kind() { - default: - if fromQuoted { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())}) - } - case reflect.Bool: - v.SetBool(value) - case reflect.Interface: - if v.NumMethod() == 0 { - v.Set(reflect.ValueOf(value)) - } else { - d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())}) - } - } - - case '"': // string - s, ok := unquoteBytes(item) - if !ok { - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - panic(phasePanicMsg) - } - switch v.Kind() { - default: - d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) - case reflect.Slice: - if v.Type().Elem().Kind() != reflect.Uint8 { - d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - b := make([]byte, base64.StdEncoding.DecodedLen(len(s))) - n, err := base64.StdEncoding.Decode(b, s) - if err != nil { - d.saveError(err) - break - } - v.SetBytes(b[:n]) - case reflect.String: - t := string(s) - if v.Type() == numberType && !isValidNumber(t) { - return fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item) - } - v.SetString(t) - case reflect.Interface: - if v.NumMethod() == 0 { - v.Set(reflect.ValueOf(string(s))) - } else { - d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) - } - } - - default: // number - if c != '-' && (c < '0' || c > '9') { - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - panic(phasePanicMsg) - } - switch v.Kind() { - default: - if v.Kind() == reflect.String && v.Type() == numberType { - // s must be a valid number, because it's - // already been tokenized. - v.SetString(string(item)) - break - } - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) - case reflect.Interface: - n, err := d.convertNumber(string(item)) - if err != nil { - d.saveError(err) - break - } - if v.NumMethod() != 0 { - d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.Set(reflect.ValueOf(n)) - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n, err := strconv.ParseInt(string(item), 10, 64) - if err != nil || v.OverflowInt(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + string(item), Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.SetInt(n) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - n, err := strconv.ParseUint(string(item), 10, 64) - if err != nil || v.OverflowUint(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + string(item), Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.SetUint(n) - - case reflect.Float32, reflect.Float64: - n, err := strconv.ParseFloat(string(item), v.Type().Bits()) - if err != nil || v.OverflowFloat(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + string(item), Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.SetFloat(n) - } - } - return nil -} - -// The xxxInterface routines build up a value to be stored -// in an empty interface. They are not strictly necessary, -// but they avoid the weight of reflection in this common case. - -// valueInterface is like value but returns any. -func (d *decodeState) valueInterface() (val any) { - switch d.opcode { - default: - panic(phasePanicMsg) - case scanBeginArray: - val = d.arrayInterface() - d.scanNext() - case scanBeginObject: - val = d.objectInterface() - d.scanNext() - case scanBeginLiteral: - val = d.literalInterface() - } - return -} - -// arrayInterface is like array but returns []any. -func (d *decodeState) arrayInterface() []any { - var v = make([]any, 0) - for { - // Look ahead for ] - can only happen on first iteration. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndArray { - break - } - - v = append(v, d.valueInterface()) - - // Next token must be , or ]. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode == scanEndArray { - break - } - if d.opcode != scanArrayValue { - panic(phasePanicMsg) - } - } - return v -} - -// objectInterface is like object but returns map[string]any. -func (d *decodeState) objectInterface() map[string]any { - m := make(map[string]any) - for { - // Read opening " of string key or closing }. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndObject { - // closing } - can only happen on first iteration. - break - } - if d.opcode != scanBeginLiteral { - panic(phasePanicMsg) - } - - // Read string key. - start := d.readIndex() - d.rescanLiteral() - item := d.data[start:d.readIndex()] - key, ok := unquote(item) - if !ok { - panic(phasePanicMsg) - } - - // Read : before value. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode != scanObjectKey { - panic(phasePanicMsg) - } - d.scanWhile(scanSkipSpace) - - // Read value. - m[key] = d.valueInterface() - - // Next token must be , or }. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode == scanEndObject { - break - } - if d.opcode != scanObjectValue { - panic(phasePanicMsg) - } - } - return m -} - -// literalInterface consumes and returns a literal from d.data[d.off-1:] and -// it reads the following byte ahead. The first byte of the literal has been -// read already (that's how the caller knows it's a literal). -func (d *decodeState) literalInterface() any { - // All bytes inside literal return scanContinue op code. - start := d.readIndex() - d.rescanLiteral() - - item := d.data[start:d.readIndex()] - - switch c := item[0]; c { - case 'n': // null - return nil - - case 't', 'f': // true, false - return c == 't' - - case '"': // string - s, ok := unquote(item) - if !ok { - panic(phasePanicMsg) - } - return s - - default: // number - if c != '-' && (c < '0' || c > '9') { - panic(phasePanicMsg) - } - n, err := d.convertNumber(string(item)) - if err != nil { - d.saveError(err) - } - return n - } -} - -// getu4 decodes \uXXXX from the beginning of s, returning the hex value, -// or it returns -1. -func getu4(s []byte) rune { - if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { - return -1 - } - var r rune - for _, c := range s[2:6] { - switch { - case '0' <= c && c <= '9': - c = c - '0' - case 'a' <= c && c <= 'f': - c = c - 'a' + 10 - case 'A' <= c && c <= 'F': - c = c - 'A' + 10 - default: - return -1 - } - r = r*16 + rune(c) - } - return r -} - -// unquote converts a quoted JSON string literal s into an actual string t. -// The rules are different than for Go, so cannot use strconv.Unquote. -func unquote(s []byte) (t string, ok bool) { - s, ok = unquoteBytes(s) - t = string(s) - return -} - -// unquoteBytes should be an internal detail, -// but widely used packages access it using linkname. -// Notable members of the hall of shame include: -// - github.com/bytedance/sonic -// -// Do not remove or change the type signature. -// See go.dev/issue/67401. -// -//go:linkname unquoteBytes -func unquoteBytes(s []byte) (t []byte, ok bool) { - if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { - return - } - s = s[1 : len(s)-1] - - // Check for unusual characters. If there are none, - // then no unquoting is needed, so return a slice of the - // original bytes. - r := 0 - for r < len(s) { - c := s[r] - if c == '\\' || c == '"' || c < ' ' { - break - } - if c < utf8.RuneSelf { - r++ - continue - } - rr, size := utf8.DecodeRune(s[r:]) - if rr == utf8.RuneError && size == 1 { - break - } - r += size - } - if r == len(s) { - return s, true - } - - b := make([]byte, len(s)+2*utf8.UTFMax) - w := copy(b, s[0:r]) - for r < len(s) { - // Out of room? Can only happen if s is full of - // malformed UTF-8 and we're replacing each - // byte with RuneError. - if w >= len(b)-2*utf8.UTFMax { - nb := make([]byte, (len(b)+utf8.UTFMax)*2) - copy(nb, b[0:w]) - b = nb - } - switch c := s[r]; { - case c == '\\': - r++ - if r >= len(s) { - return - } - switch s[r] { - default: - return - case '"', '\\', '/', '\'': - b[w] = s[r] - r++ - w++ - case 'b': - b[w] = '\b' - r++ - w++ - case 'f': - b[w] = '\f' - r++ - w++ - case 'n': - b[w] = '\n' - r++ - w++ - case 'r': - b[w] = '\r' - r++ - w++ - case 't': - b[w] = '\t' - r++ - w++ - case 'u': - r-- - rr := getu4(s[r:]) - if rr < 0 { - return - } - r += 6 - if utf16.IsSurrogate(rr) { - rr1 := getu4(s[r:]) - if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { - // A valid pair; consume. - r += 6 - w += utf8.EncodeRune(b[w:], dec) - break - } - // Invalid surrogate; fall back to replacement rune. - rr = unicode.ReplacementChar - } - w += utf8.EncodeRune(b[w:], rr) - } - - // Quote, control characters are invalid. - case c == '"', c < ' ': - return - - // ASCII - case c < utf8.RuneSelf: - b[w] = c - r++ - w++ - - // Coerce to well-formed UTF-8. - default: - rr, size := utf8.DecodeRune(s[r:]) - r += size - w += utf8.EncodeRune(b[w:], rr) - } - } - return b[0:w], true -} diff --git a/internal/encoding/json/encode.go b/internal/encoding/json/encode.go deleted file mode 100644 index 21748d1..0000000 --- a/internal/encoding/json/encode.go +++ /dev/null @@ -1,1395 +0,0 @@ -// Vendored from Go 1.24.0-pre-release -// To find alterations, check package shims, and comments beginning in SHIM(). -// -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package json implements encoding and decoding of JSON as defined in -// RFC 7159. The mapping between JSON and Go values is described -// in the documentation for the Marshal and Unmarshal functions. -// -// See "JSON and Go" for an introduction to this package: -// https://golang.org/doc/articles/json_and_go.html -package json - -import ( - "bytes" - "cmp" - "encoding" - "encoding/base64" - "fmt" - "github.com/stainless-sdks/opencode-go/internal/encoding/json/sentinel" - "github.com/stainless-sdks/opencode-go/internal/encoding/json/shims" - "math" - "reflect" - "slices" - "strconv" - "strings" - "sync" - "unicode" - "unicode/utf8" - _ "unsafe" // for linkname -) - -// Marshal returns the JSON encoding of v. -// -// Marshal traverses the value v recursively. -// If an encountered value implements [Marshaler] -// and is not a nil pointer, Marshal calls [Marshaler.MarshalJSON] -// to produce JSON. If no [Marshaler.MarshalJSON] method is present but the -// value implements [encoding.TextMarshaler] instead, Marshal calls -// [encoding.TextMarshaler.MarshalText] and encodes the result as a JSON string. -// The nil pointer exception is not strictly necessary -// but mimics a similar, necessary exception in the behavior of -// [Unmarshaler.UnmarshalJSON]. -// -// Otherwise, Marshal uses the following type-dependent default encodings: -// -// Boolean values encode as JSON booleans. -// -// Floating point, integer, and [Number] values encode as JSON numbers. -// NaN and +/-Inf values will return an [UnsupportedValueError]. -// -// String values encode as JSON strings coerced to valid UTF-8, -// replacing invalid bytes with the Unicode replacement rune. -// So that the JSON will be safe to embed inside HTML