mirror of
https://github.com/anomalyco/opencode-sdk-go.git
synced 2026-07-10 00:19:00 +00:00
feat(api): update via SDK Studio
This commit is contained in:
parent
2f34d5d53e
commit
068a0eb025
92 changed files with 5874 additions and 13849 deletions
3
.release-please-manifest.json
Normal file
3
.release-please-manifest.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
".": "0.0.1"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
202
LICENSE
202
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.
|
||||
|
|
|
|||
266
README.md
266
README.md
|
|
@ -1,26 +1,34 @@
|
|||
# Opencode Go API Library
|
||||
|
||||
<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go"><img src="https://pkg.go.dev/badge/github.com/stainless-sdks/opencode-go.svg" alt="Go Reference"></a>
|
||||
<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go"><img src="https://pkg.go.dev/badge/github.com/sst/opencode-sdk-go.svg" alt="Go Reference"></a>
|
||||
|
||||
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
|
||||
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/stainless-sdks/opencode-go" // imported as opencode
|
||||
"github.com/sst/opencode-sdk-go" // imported as opencode
|
||||
)
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
Or to pin the version:
|
||||
|
||||
<!-- x-release-please-start-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'
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
## 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 <code>\`json:"...,required"\`</code>. 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 <code>\`json:"...,omitzero"\`</code>. 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
149
agent.go
149
agent.go
|
|
@ -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)
|
||||
}
|
||||
43
aliases.go
43
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
|
||||
|
|
|
|||
245
api.md
245
api.md
|
|
@ -1,168 +1,151 @@
|
|||
# Project
|
||||
# Shared Response Types
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ProjectListResponse">ProjectListResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /project">client.Project.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ProjectService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ProjectListResponse">ProjectListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#MessageAbortedError">MessageAbortedError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#ProviderAuthError">ProviderAuthError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#UnknownError">UnknownError</a>
|
||||
|
||||
# Event
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#EventListResponseUnion">EventListResponseUnion</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventListResponse">EventListResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /event">client.Event.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#EventService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#EventListResponseUnion">EventListResponseUnion</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Config
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ConfigGetResponse">ConfigGetResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ConfigListProvidersResponse">ConfigListProvidersResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /config">client.Config.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ConfigService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ConfigGetResponse">ConfigGetResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /config/providers">client.Config.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ConfigService.ListProviders">ListProviders</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ConfigListProvidersResponse">ConfigListProvidersResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Session
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#AssistantMessage">AssistantMessage</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#MessageAbortedError">MessageAbortedError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#MessageOutputLengthError">MessageOutputLengthError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#ProviderAuthError">ProviderAuthError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#UnknownError">UnknownError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionSendCommandResponse">SessionSendCommandResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionNewParams">SessionNewParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="patch /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.Update">Update</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionUpdateParams">SessionUpdateParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.Analyze">Analyze</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionAnalyzeParams">SessionAnalyzeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/children">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.GetChildren">GetChildren</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/permissions/{permissionID}">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.RespondToPermission">RespondToPermission</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, permissionID <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionRespondToPermissionParams">SessionRespondToPermissionParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.RestoreReverted">RestoreReverted</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/shell">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.RunShell">RunShell</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionRunShellParams">SessionRunShellParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/command">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.SendCommand">SendCommand</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionSendCommandParams">SessionSendCommandParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionSendCommandResponse">SessionSendCommandResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
## Share
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session/{id}/share">client.Session.Share.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionShareService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="delete /session/{id}/share">client.Session.Share.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionShareService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
## Message
|
||||
|
||||
Params Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FilePartSourceUnionParam">FilePartSourceUnionParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FilePartSourceTextParam">FilePartSourceTextParam</a>
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FilePartSourceUnion">FilePartSourceUnion</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FilePartSourceText">FilePartSourceText</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#MessageUnion">MessageUnion</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#PartUnion">PartUnion</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageNewResponse">SessionMessageNewResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageGetResponse">SessionMessageGetResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageListResponse">SessionMessageListResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session/{id}/message">client.Session.Message.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageNewParams">SessionMessageNewParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageNewResponse">SessionMessageNewResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/message/{messageID}">client.Session.Message.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageGetParams">SessionMessageGetParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageGetResponse">SessionMessageGetResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/message">client.Session.Message.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#SessionMessageListResponse">SessionMessageListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Command
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#CommandListResponse">CommandListResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /command">client.Command.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#CommandService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#CommandListResponse">CommandListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /event">client.Event.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventListResponse">EventListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Find
|
||||
|
||||
Params Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#RangeParam">RangeParam</a>
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#Range">Range</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindGetResponse">FindGetResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindGetSymbolResponse">FindGetSymbolResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Symbol">Symbol</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindTextResponse">FindTextResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /find">client.Find.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindGetParams">FindGetParams</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindGetResponse">FindGetResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /find/file">client.Find.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindService.GetFile">GetFile</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindGetFileParams">FindGetFileParams</a>) ([]<a href="https://pkg.go.dev/builtin#string">string</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /find/symbol">client.Find.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindService.GetSymbol">GetSymbol</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindGetSymbolParams">FindGetSymbolParams</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FindGetSymbolResponse">FindGetSymbolResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /find/file">client.Find.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindService.Files">Files</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindFilesParams">FindFilesParams</a>) ([]<a href="https://pkg.go.dev/builtin#string">string</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /find/symbol">client.Find.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindService.Symbols">Symbols</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindSymbolsParams">FindSymbolsParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Symbol">Symbol</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /find">client.Find.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindService.Text">Text</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindTextParams">FindTextParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindTextResponse">FindTextResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# File
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileListResponse">FileListResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileGetStatusResponse">FileGetStatusResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileReadResponse">FileReadResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileNode">FileNode</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /file">client.File.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileListParams">FileListParams</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileListResponse">FileListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /file/status">client.File.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileService.GetStatus">GetStatus</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileGetStatusResponse">FileGetStatusResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /file/content">client.File.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileService.Read">Read</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileReadParams">FileReadParams</a>) (<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#FileReadResponse">FileReadResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /file">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileListParams">FileListParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileNode">FileNode</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /file/content">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Read">Read</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadParams">FileReadParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /file/status">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Status">Status</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Log
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /log">client.Log.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#LogService.Write">Write</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#LogWriteParams">LogWriteParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Agent
|
||||
# Config
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#AgentListResponse">AgentListResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AgentConfig">AgentConfig</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#KeybindsConfig">KeybindsConfig</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#McpLocalConfig">McpLocalConfig</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#McpRemoteConfig">McpRemoteConfig</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /agent">client.Agent.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#AgentService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#AgentListResponse">AgentListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /config">client.Config.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ConfigService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Command
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Command">Command</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /command">client.Command.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#CommandService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Command">Command</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Session
|
||||
|
||||
Params Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AgentPartInputParam">AgentPartInputParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartInputParam">FilePartInputParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceUnionParam">FilePartSourceUnionParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceTextParam">FilePartSourceTextParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileSourceParam">FileSourceParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SymbolSourceParam">SymbolSourceParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPartInputParam">TextPartInputParam</a>
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AgentPart">AgentPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePart">FilePart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSource">FilePartSource</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceText">FilePartSourceText</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileSource">FileSource</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Message">Message</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Part">Part</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ReasoningPart">ReasoningPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SnapshotPart">SnapshotPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepFinishPart">StepFinishPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepStartPart">StepStartPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SymbolSource">SymbolSource</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPart">TextPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolPart">ToolPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateCompleted">ToolStateCompleted</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateError">ToolStateError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionNewParams">SessionNewParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="patch /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Update">Update</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionUpdateParams">SessionUpdateParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/children">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Children">Children</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/command">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Command">Command</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandParams">SessionCommandParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/shell">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Shell">Shell</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionShellParams">SessionShellParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
## Permissions
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Permission">Permission</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session/{id}/permissions/{permissionID}">client.Session.Permissions.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionService.Respond">Respond</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, permissionID <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionRespondParams">SessionPermissionRespondParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Tui
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /tui/append-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.AppendPrompt">AppendPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/clear-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.ClearPrompt">ClearPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/execute-command">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.ExecuteCommand">ExecuteCommand</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiExecuteCommandParams">TuiExecuteCommandParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-models">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.OpenModels">OpenModels</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-sessions">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.OpenSessions">OpenSessions</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-themes">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.OpenThemes">OpenThemes</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/show-toast">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.ShowToast">ShowToast</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiShowToastParams">TuiShowToastParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/submit-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#TuiService.SubmitPrompt">SubmitPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Auth
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="put /auth/{id}">client.Auth.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#AuthService.SetCredentials">SetCredentials</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go">opencode</a>.<a href="https://pkg.go.dev/github.com/stainless-sdks/opencode-go#AuthSetCredentialsParams">AuthSetCredentialsParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/append-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.AppendPrompt">AppendPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/clear-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ClearPrompt">ClearPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/execute-command">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ExecuteCommand">ExecuteCommand</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiExecuteCommandParams">TuiExecuteCommandParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-models">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenModels">OpenModels</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-sessions">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenSessions">OpenSessions</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-themes">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenThemes">OpenThemes</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/show-toast">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ShowToast">ShowToast</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiShowToastParams">TuiShowToastParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/submit-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.SubmitPrompt">SubmitPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
|
|
|||
121
auth.go
121
auth.go
|
|
@ -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)
|
||||
}
|
||||
47
auth_test.go
47
auth_test.go
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
61
client.go
61
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...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
55
command.go
55
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
field.go
61
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 }
|
||||
|
|
|
|||
196
file.go
196
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,
|
||||
|
|
|
|||
57
file_test.go
57
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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
419
find.go
419
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,
|
||||
|
|
|
|||
27
find_test.go
27
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
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -1,4 +1,4 @@
|
|||
module github.com/stainless-sdks/opencode-go
|
||||
module github.com/sst/opencode-sdk-go
|
||||
|
||||
go 1.21
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
66
internal/apijson/field_test.go
Normal file
66
internal/apijson/field_test.go
Normal file
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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":
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright 2013 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
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// foldName returns a folded string such that foldName(x) == foldName(y)
|
||||
// is identical to bytes.EqualFold(x, y).
|
||||
func foldName(in []byte) []byte {
|
||||
// This is inlinable to take advantage of "function outlining".
|
||||
var arr [32]byte // large enough for most JSON names
|
||||
return appendFoldedName(arr[:0], in)
|
||||
}
|
||||
|
||||
func appendFoldedName(out, in []byte) []byte {
|
||||
for i := 0; i < len(in); {
|
||||
// Handle single-byte ASCII.
|
||||
if c := in[i]; c < utf8.RuneSelf {
|
||||
if 'a' <= c && c <= 'z' {
|
||||
c -= 'a' - 'A'
|
||||
}
|
||||
out = append(out, c)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
// Handle multi-byte Unicode.
|
||||
r, n := utf8.DecodeRune(in[i:])
|
||||
out = utf8.AppendRune(out, foldRune(r))
|
||||
i += n
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// foldRune is returns the smallest rune for all runes in the same fold set.
|
||||
func foldRune(r rune) rune {
|
||||
for {
|
||||
r2 := unicode.SimpleFold(r)
|
||||
if r2 <= r {
|
||||
return r2
|
||||
}
|
||||
r = r2
|
||||
}
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
// 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
|
||||
|
||||
import "bytes"
|
||||
|
||||
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
|
||||
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
|
||||
// so that the JSON will be safe to embed inside HTML <script> tags.
|
||||
// For historical reasons, web browsers don't honor standard HTML
|
||||
// escaping within <script> tags, so an alternative JSON encoding must be used.
|
||||
func HTMLEscape(dst *bytes.Buffer, src []byte) {
|
||||
dst.Grow(len(src))
|
||||
dst.Write(appendHTMLEscape(dst.AvailableBuffer(), src))
|
||||
}
|
||||
|
||||
func appendHTMLEscape(dst, src []byte) []byte {
|
||||
// The characters can only appear in string literals,
|
||||
// so just scan the string one byte at a time.
|
||||
start := 0
|
||||
for i, c := range src {
|
||||
if c == '<' || c == '>' || c == '&' {
|
||||
dst = append(dst, src[start:i]...)
|
||||
dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
|
||||
start = i + 1
|
||||
}
|
||||
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
|
||||
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
|
||||
dst = append(dst, src[start:i]...)
|
||||
dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
|
||||
start = i + len("\u2029")
|
||||
}
|
||||
}
|
||||
return append(dst, src[start:]...)
|
||||
}
|
||||
|
||||
// Compact appends to dst the JSON-encoded src with
|
||||
// insignificant space characters elided.
|
||||
func Compact(dst *bytes.Buffer, src []byte) error {
|
||||
dst.Grow(len(src))
|
||||
b := dst.AvailableBuffer()
|
||||
b, err := appendCompact(b, src, false)
|
||||
dst.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func appendCompact(dst, src []byte, escape bool) ([]byte, error) {
|
||||
origLen := len(dst)
|
||||
scan := newScanner()
|
||||
defer freeScanner(scan)
|
||||
start := 0
|
||||
for i, c := range src {
|
||||
if escape && (c == '<' || c == '>' || c == '&') {
|
||||
if start < i {
|
||||
dst = append(dst, src[start:i]...)
|
||||
}
|
||||
dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
|
||||
start = i + 1
|
||||
}
|
||||
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
|
||||
if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
|
||||
if start < i {
|
||||
dst = append(dst, src[start:i]...)
|
||||
}
|
||||
dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
|
||||
start = i + 3
|
||||
}
|
||||
v := scan.step(scan, c)
|
||||
if v >= scanSkipSpace {
|
||||
if v == scanError {
|
||||
break
|
||||
}
|
||||
if start < i {
|
||||
dst = append(dst, src[start:i]...)
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return dst[:origLen], scan.err
|
||||
}
|
||||
if start < len(src) {
|
||||
dst = append(dst, src[start:]...)
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func appendNewline(dst []byte, prefix, indent string, depth int) []byte {
|
||||
dst = append(dst, '\n')
|
||||
dst = append(dst, prefix...)
|
||||
for i := 0; i < depth; i++ {
|
||||
dst = append(dst, indent...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// indentGrowthFactor specifies the growth factor of indenting JSON input.
|
||||
// Empirically, the growth factor was measured to be between 1.4x to 1.8x
|
||||
// for some set of compacted JSON with the indent being a single tab.
|
||||
// Specify a growth factor slightly larger than what is observed
|
||||
// to reduce probability of allocation in appendIndent.
|
||||
// A factor no higher than 2 ensures that wasted space never exceeds 50%.
|
||||
const indentGrowthFactor = 2
|
||||
|
||||
// Indent appends to dst an indented form of the JSON-encoded src.
|
||||
// Each element in a JSON object or array begins on a new,
|
||||
// indented line beginning with prefix followed by one or more
|
||||
// copies of indent according to the indentation nesting.
|
||||
// The data appended to dst does not begin with the prefix nor
|
||||
// any indentation, to make it easier to embed inside other formatted JSON data.
|
||||
// Although leading space characters (space, tab, carriage return, newline)
|
||||
// at the beginning of src are dropped, trailing space characters
|
||||
// at the end of src are preserved and copied to dst.
|
||||
// For example, if src has no trailing spaces, neither will dst;
|
||||
// if src ends in a trailing newline, so will dst.
|
||||
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
dst.Grow(indentGrowthFactor * len(src))
|
||||
b := dst.AvailableBuffer()
|
||||
b, err := appendIndent(b, src, prefix, indent)
|
||||
dst.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) {
|
||||
origLen := len(dst)
|
||||
scan := newScanner()
|
||||
defer freeScanner(scan)
|
||||
needIndent := false
|
||||
depth := 0
|
||||
for _, c := range src {
|
||||
scan.bytes++
|
||||
v := scan.step(scan, c)
|
||||
if v == scanSkipSpace {
|
||||
continue
|
||||
}
|
||||
if v == scanError {
|
||||
break
|
||||
}
|
||||
if needIndent && v != scanEndObject && v != scanEndArray {
|
||||
needIndent = false
|
||||
depth++
|
||||
dst = appendNewline(dst, prefix, indent, depth)
|
||||
}
|
||||
|
||||
// Emit semantically uninteresting bytes
|
||||
// (in particular, punctuation in strings) unmodified.
|
||||
if v == scanContinue {
|
||||
dst = append(dst, c)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add spacing around real punctuation.
|
||||
switch c {
|
||||
case '{', '[':
|
||||
// delay indent so that empty object and array are formatted as {} and [].
|
||||
needIndent = true
|
||||
dst = append(dst, c)
|
||||
case ',':
|
||||
dst = append(dst, c)
|
||||
dst = appendNewline(dst, prefix, indent, depth)
|
||||
case ':':
|
||||
dst = append(dst, c, ' ')
|
||||
case '}', ']':
|
||||
if needIndent {
|
||||
// suppress indent in empty object/array
|
||||
needIndent = false
|
||||
} else {
|
||||
depth--
|
||||
dst = appendNewline(dst, prefix, indent, depth)
|
||||
}
|
||||
dst = append(dst, c)
|
||||
default:
|
||||
dst = append(dst, c)
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return dst[:origLen], scan.err
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
|
@ -1,610 +0,0 @@
|
|||
// 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
|
||||
|
||||
// JSON value parser state machine.
|
||||
// Just about at the limit of what is reasonable to write by hand.
|
||||
// Some parts are a bit tedious, but overall it nicely factors out the
|
||||
// otherwise common code from the multiple scanning functions
|
||||
// in this package (Compact, Indent, checkValid, etc).
|
||||
//
|
||||
// This file starts with two simple examples using the scanner
|
||||
// before diving into the scanner itself.
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Valid reports whether data is a valid JSON encoding.
|
||||
func Valid(data []byte) bool {
|
||||
scan := newScanner()
|
||||
defer freeScanner(scan)
|
||||
return checkValid(data, scan) == nil
|
||||
}
|
||||
|
||||
// checkValid verifies that data is valid JSON-encoded data.
|
||||
// scan is passed in for use by checkValid to avoid an allocation.
|
||||
// checkValid returns nil or a SyntaxError.
|
||||
func checkValid(data []byte, scan *scanner) error {
|
||||
scan.reset()
|
||||
for _, c := range data {
|
||||
scan.bytes++
|
||||
if scan.step(scan, c) == scanError {
|
||||
return scan.err
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return scan.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A SyntaxError is a description of a JSON syntax error.
|
||||
// [Unmarshal] will return a SyntaxError if the JSON can't be parsed.
|
||||
type SyntaxError struct {
|
||||
msg string // description of error
|
||||
Offset int64 // error occurred after reading Offset bytes
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string { return e.msg }
|
||||
|
||||
// A scanner is a JSON scanning state machine.
|
||||
// Callers call scan.reset and then pass bytes in one at a time
|
||||
// by calling scan.step(&scan, c) for each byte.
|
||||
// The return value, referred to as an opcode, tells the
|
||||
// caller about significant parsing events like beginning
|
||||
// and ending literals, objects, and arrays, so that the
|
||||
// caller can follow along if it wishes.
|
||||
// The return value scanEnd indicates that a single top-level
|
||||
// JSON value has been completed, *before* the byte that
|
||||
// just got passed in. (The indication must be delayed in order
|
||||
// to recognize the end of numbers: is 123 a whole value or
|
||||
// the beginning of 12345e+6?).
|
||||
type scanner struct {
|
||||
// The step is a func to be called to execute the next transition.
|
||||
// Also tried using an integer constant and a single func
|
||||
// with a switch, but using the func directly was 10% faster
|
||||
// on a 64-bit Mac Mini, and it's nicer to read.
|
||||
step func(*scanner, byte) int
|
||||
|
||||
// Reached end of top-level value.
|
||||
endTop bool
|
||||
|
||||
// Stack of what we're in the middle of - array values, object keys, object values.
|
||||
parseState []int
|
||||
|
||||
// Error that happened, if any.
|
||||
err error
|
||||
|
||||
// total bytes consumed, updated by decoder.Decode (and deliberately
|
||||
// not set to zero by scan.reset)
|
||||
bytes int64
|
||||
}
|
||||
|
||||
var scannerPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &scanner{}
|
||||
},
|
||||
}
|
||||
|
||||
func newScanner() *scanner {
|
||||
scan := scannerPool.Get().(*scanner)
|
||||
// scan.reset by design doesn't set bytes to zero
|
||||
scan.bytes = 0
|
||||
scan.reset()
|
||||
return scan
|
||||
}
|
||||
|
||||
func freeScanner(scan *scanner) {
|
||||
// Avoid hanging on to too much memory in extreme cases.
|
||||
if len(scan.parseState) > 1024 {
|
||||
scan.parseState = nil
|
||||
}
|
||||
scannerPool.Put(scan)
|
||||
}
|
||||
|
||||
// These values are returned by the state transition functions
|
||||
// assigned to scanner.state and the method scanner.eof.
|
||||
// They give details about the current state of the scan that
|
||||
// callers might be interested to know about.
|
||||
// It is okay to ignore the return value of any particular
|
||||
// call to scanner.state: if one call returns scanError,
|
||||
// every subsequent call will return scanError too.
|
||||
const (
|
||||
// Continue.
|
||||
scanContinue = iota // uninteresting byte
|
||||
scanBeginLiteral // end implied by next result != scanContinue
|
||||
scanBeginObject // begin object
|
||||
scanObjectKey // just finished object key (string)
|
||||
scanObjectValue // just finished non-last object value
|
||||
scanEndObject // end object (implies scanObjectValue if possible)
|
||||
scanBeginArray // begin array
|
||||
scanArrayValue // just finished array value
|
||||
scanEndArray // end array (implies scanArrayValue if possible)
|
||||
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
||||
|
||||
// Stop.
|
||||
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
||||
scanError // hit an error, scanner.err.
|
||||
)
|
||||
|
||||
// These values are stored in the parseState stack.
|
||||
// They give the current state of a composite value
|
||||
// being scanned. If the parser is inside a nested value
|
||||
// the parseState describes the nested state, outermost at entry 0.
|
||||
const (
|
||||
parseObjectKey = iota // parsing object key (before colon)
|
||||
parseObjectValue // parsing object value (after colon)
|
||||
parseArrayValue // parsing array value
|
||||
)
|
||||
|
||||
// This limits the max nesting depth to prevent stack overflow.
|
||||
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
|
||||
const maxNestingDepth = 10000
|
||||
|
||||
// reset prepares the scanner for use.
|
||||
// It must be called before calling s.step.
|
||||
func (s *scanner) reset() {
|
||||
s.step = stateBeginValue
|
||||
s.parseState = s.parseState[0:0]
|
||||
s.err = nil
|
||||
s.endTop = false
|
||||
}
|
||||
|
||||
// eof tells the scanner that the end of input has been reached.
|
||||
// It returns a scan status just as s.step does.
|
||||
func (s *scanner) eof() int {
|
||||
if s.err != nil {
|
||||
return scanError
|
||||
}
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
s.step(s, ' ')
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
if s.err == nil {
|
||||
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
||||
}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// pushParseState pushes a new parse state p onto the parse stack.
|
||||
// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
|
||||
func (s *scanner) pushParseState(c byte, newParseState int, successState int) int {
|
||||
s.parseState = append(s.parseState, newParseState)
|
||||
if len(s.parseState) <= maxNestingDepth {
|
||||
return successState
|
||||
}
|
||||
return s.error(c, "exceeded max depth")
|
||||
}
|
||||
|
||||
// popParseState pops a parse state (already obtained) off the stack
|
||||
// and updates s.step accordingly.
|
||||
func (s *scanner) popParseState() {
|
||||
n := len(s.parseState) - 1
|
||||
s.parseState = s.parseState[0:n]
|
||||
if n == 0 {
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
} else {
|
||||
s.step = stateEndValue
|
||||
}
|
||||
}
|
||||
|
||||
func isSpace(c byte) bool {
|
||||
return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n')
|
||||
}
|
||||
|
||||
// stateBeginValueOrEmpty is the state after reading `[`.
|
||||
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
||||
if isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == ']' {
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginValue(s, c)
|
||||
}
|
||||
|
||||
// stateBeginValue is the state at the beginning of the input.
|
||||
func stateBeginValue(s *scanner, c byte) int {
|
||||
if isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
switch c {
|
||||
case '{':
|
||||
s.step = stateBeginStringOrEmpty
|
||||
return s.pushParseState(c, parseObjectKey, scanBeginObject)
|
||||
case '[':
|
||||
s.step = stateBeginValueOrEmpty
|
||||
return s.pushParseState(c, parseArrayValue, scanBeginArray)
|
||||
case '"':
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
case '-':
|
||||
s.step = stateNeg
|
||||
return scanBeginLiteral
|
||||
case '0': // beginning of 0.123
|
||||
s.step = state0
|
||||
return scanBeginLiteral
|
||||
case 't': // beginning of true
|
||||
s.step = stateT
|
||||
return scanBeginLiteral
|
||||
case 'f': // beginning of false
|
||||
s.step = stateF
|
||||
return scanBeginLiteral
|
||||
case 'n': // beginning of null
|
||||
s.step = stateN
|
||||
return scanBeginLiteral
|
||||
}
|
||||
if '1' <= c && c <= '9' { // beginning of 1234.5
|
||||
s.step = state1
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of value")
|
||||
}
|
||||
|
||||
// stateBeginStringOrEmpty is the state after reading `{`.
|
||||
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
||||
if isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '}' {
|
||||
n := len(s.parseState)
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginString(s, c)
|
||||
}
|
||||
|
||||
// stateBeginString is the state after reading `{"key": value,`.
|
||||
func stateBeginString(s *scanner, c byte) int {
|
||||
if isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '"' {
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of object key string")
|
||||
}
|
||||
|
||||
// stateEndValue is the state after completing a value,
|
||||
// such as after reading `{}` or `true` or `["x"`.
|
||||
func stateEndValue(s *scanner, c byte) int {
|
||||
n := len(s.parseState)
|
||||
if n == 0 {
|
||||
// Completed top-level before the current byte.
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
return stateEndTop(s, c)
|
||||
}
|
||||
if isSpace(c) {
|
||||
s.step = stateEndValue
|
||||
return scanSkipSpace
|
||||
}
|
||||
ps := s.parseState[n-1]
|
||||
switch ps {
|
||||
case parseObjectKey:
|
||||
if c == ':' {
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
s.step = stateBeginValue
|
||||
return scanObjectKey
|
||||
}
|
||||
return s.error(c, "after object key")
|
||||
case parseObjectValue:
|
||||
if c == ',' {
|
||||
s.parseState[n-1] = parseObjectKey
|
||||
s.step = stateBeginString
|
||||
return scanObjectValue
|
||||
}
|
||||
if c == '}' {
|
||||
s.popParseState()
|
||||
return scanEndObject
|
||||
}
|
||||
return s.error(c, "after object key:value pair")
|
||||
case parseArrayValue:
|
||||
if c == ',' {
|
||||
s.step = stateBeginValue
|
||||
return scanArrayValue
|
||||
}
|
||||
if c == ']' {
|
||||
s.popParseState()
|
||||
return scanEndArray
|
||||
}
|
||||
return s.error(c, "after array element")
|
||||
}
|
||||
return s.error(c, "")
|
||||
}
|
||||
|
||||
// stateEndTop is the state after finishing the top-level value,
|
||||
// such as after reading `{}` or `[1,2,3]`.
|
||||
// Only space characters should be seen now.
|
||||
func stateEndTop(s *scanner, c byte) int {
|
||||
if !isSpace(c) {
|
||||
// Complain about non-space byte on next call.
|
||||
s.error(c, "after top-level value")
|
||||
}
|
||||
return scanEnd
|
||||
}
|
||||
|
||||
// stateInString is the state after reading `"`.
|
||||
func stateInString(s *scanner, c byte) int {
|
||||
if c == '"' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
if c == '\\' {
|
||||
s.step = stateInStringEsc
|
||||
return scanContinue
|
||||
}
|
||||
if c < 0x20 {
|
||||
return s.error(c, "in string literal")
|
||||
}
|
||||
return scanContinue
|
||||
}
|
||||
|
||||
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
||||
func stateInStringEsc(s *scanner, c byte) int {
|
||||
switch c {
|
||||
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
case 'u':
|
||||
s.step = stateInStringEscU
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in string escape code")
|
||||
}
|
||||
|
||||
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
||||
func stateInStringEscU(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU1
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
||||
func stateInStringEscU1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU12
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
||||
func stateInStringEscU12(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU123
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
||||
func stateInStringEscU123(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateNeg is the state after reading `-` during a number.
|
||||
func stateNeg(s *scanner, c byte) int {
|
||||
if c == '0' {
|
||||
s.step = state0
|
||||
return scanContinue
|
||||
}
|
||||
if '1' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in numeric literal")
|
||||
}
|
||||
|
||||
// state1 is the state after reading a non-zero integer during a number,
|
||||
// such as after reading `1` or `100` but not `0`.
|
||||
func state1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return state0(s, c)
|
||||
}
|
||||
|
||||
// state0 is the state after reading `0` during a number.
|
||||
func state0(s *scanner, c byte) int {
|
||||
if c == '.' {
|
||||
s.step = stateDot
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateDot is the state after reading the integer and decimal point in a number,
|
||||
// such as after reading `1.`.
|
||||
func stateDot(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateDot0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "after decimal point in numeric literal")
|
||||
}
|
||||
|
||||
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
||||
// digits of a number, such as after reading `3.14`.
|
||||
func stateDot0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateE is the state after reading the mantissa and e in a number,
|
||||
// such as after reading `314e` or `0.314e`.
|
||||
func stateE(s *scanner, c byte) int {
|
||||
if c == '+' || c == '-' {
|
||||
s.step = stateESign
|
||||
return scanContinue
|
||||
}
|
||||
return stateESign(s, c)
|
||||
}
|
||||
|
||||
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
||||
// such as after reading `314e-` or `0.314e+`.
|
||||
func stateESign(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateE0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in exponent of numeric literal")
|
||||
}
|
||||
|
||||
// stateE0 is the state after reading the mantissa, e, optional sign,
|
||||
// and at least one digit of the exponent in a number,
|
||||
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
||||
func stateE0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateT is the state after reading `t`.
|
||||
func stateT(s *scanner, c byte) int {
|
||||
if c == 'r' {
|
||||
s.step = stateTr
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'r')")
|
||||
}
|
||||
|
||||
// stateTr is the state after reading `tr`.
|
||||
func stateTr(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateTru
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateTru is the state after reading `tru`.
|
||||
func stateTru(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateF is the state after reading `f`.
|
||||
func stateF(s *scanner, c byte) int {
|
||||
if c == 'a' {
|
||||
s.step = stateFa
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'a')")
|
||||
}
|
||||
|
||||
// stateFa is the state after reading `fa`.
|
||||
func stateFa(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateFal
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateFal is the state after reading `fal`.
|
||||
func stateFal(s *scanner, c byte) int {
|
||||
if c == 's' {
|
||||
s.step = stateFals
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 's')")
|
||||
}
|
||||
|
||||
// stateFals is the state after reading `fals`.
|
||||
func stateFals(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateN is the state after reading `n`.
|
||||
func stateN(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateNu
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateNu is the state after reading `nu`.
|
||||
func stateNu(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateNul
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateNul is the state after reading `nul`.
|
||||
func stateNul(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateError is the state after reaching a syntax error,
|
||||
// such as after reading `[1}` or `5.1.2`.
|
||||
func stateError(s *scanner, c byte) int {
|
||||
return scanError
|
||||
}
|
||||
|
||||
// error records an error and switches to the error state.
|
||||
func (s *scanner) error(c byte, context string) int {
|
||||
s.step = stateError
|
||||
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// quoteChar formats c as a quoted character literal.
|
||||
func quoteChar(c byte) string {
|
||||
// special cases - different from quoted strings
|
||||
if c == '\'' {
|
||||
return `'\''`
|
||||
}
|
||||
if c == '"' {
|
||||
return `'"'`
|
||||
}
|
||||
|
||||
// use quoted string with different quotation marks
|
||||
s := strconv.Quote(string(c))
|
||||
return "'" + s[1:len(s)-1] + "'"
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package sentinel
|
||||
|
||||
import (
|
||||
"github.com/stainless-sdks/opencode-go/internal/encoding/json/shims"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
x any
|
||||
ptr uintptr
|
||||
kind reflect.Kind
|
||||
}
|
||||
|
||||
var nullCache sync.Map // map[reflect.Type]cacheEntry
|
||||
|
||||
func NewNullSentinel[T any](mk func() T) T {
|
||||
t := shims.TypeFor[T]()
|
||||
entry, loaded := nullCache.Load(t) // avoid premature allocation
|
||||
if !loaded {
|
||||
x := mk()
|
||||
ptr := reflect.ValueOf(x).Pointer()
|
||||
entry, _ = nullCache.LoadOrStore(t, cacheEntry{x, ptr, t.Kind()})
|
||||
}
|
||||
return entry.(cacheEntry).x.(T)
|
||||
}
|
||||
|
||||
// for internal use only
|
||||
func IsValueNull(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
null, ok := nullCache.Load(v.Type())
|
||||
return ok && v.Pointer() == null.(cacheEntry).ptr
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsNull[T any](v T) bool {
|
||||
t := shims.TypeFor[T]()
|
||||
switch t.Kind() {
|
||||
case reflect.Map, reflect.Slice:
|
||||
null, ok := nullCache.Load(t)
|
||||
return ok && reflect.ValueOf(v).Pointer() == null.(cacheEntry).ptr
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
package sentinel_test
|
||||
|
||||
import (
|
||||
"github.com/stainless-sdks/opencode-go/internal/encoding/json/sentinel"
|
||||
"github.com/stainless-sdks/opencode-go/packages/param"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Pair struct {
|
||||
got bool
|
||||
want bool
|
||||
}
|
||||
|
||||
func TestNullSlice(t *testing.T) {
|
||||
var nilSlice []int = nil
|
||||
var nonNilSlice []int = []int{1, 2, 3}
|
||||
var nullSlice []int = param.NullSlice[[]int]()
|
||||
|
||||
cases := map[string]Pair{
|
||||
"nilSlice": {sentinel.IsNull(nilSlice), false},
|
||||
"nullSlice": {sentinel.IsNull(nullSlice), true},
|
||||
"newNullSlice": {sentinel.IsNull(param.NullSlice[[]int]()), true},
|
||||
"lenNullSlice": {len(nullSlice) == 0, true},
|
||||
"nilSliceValue": {sentinel.IsValueNull(reflect.ValueOf(nilSlice)), false},
|
||||
"nullSliceValue": {sentinel.IsValueNull(reflect.ValueOf(nullSlice)), true},
|
||||
"compareSlices": {slices.Compare(nilSlice, nullSlice) == 0, true},
|
||||
"compareNonNilSlices": {slices.Compare(nonNilSlice, nullSlice) == 0, false},
|
||||
}
|
||||
|
||||
for name, c := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, want := c.got, c.want
|
||||
if got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullMap(t *testing.T) {
|
||||
var nilMap map[string]int = nil
|
||||
var nonNilMap map[string]int = map[string]int{"a": 1, "b": 2}
|
||||
var nullMap map[string]int = param.NullMap[map[string]int]()
|
||||
|
||||
cases := map[string]Pair{
|
||||
"nilMap": {sentinel.IsNull(nilMap), false},
|
||||
"nullMap": {sentinel.IsNull(nullMap), true},
|
||||
"newNullMap": {sentinel.IsNull(param.NullMap[map[string]int]()), true},
|
||||
"lenNullMap": {len(nullMap) == 0, true},
|
||||
"nilMapValue": {sentinel.IsValueNull(reflect.ValueOf(nilMap)), false},
|
||||
"nullMapValue": {sentinel.IsValueNull(reflect.ValueOf(nullMap)), true},
|
||||
"compareMaps": {reflect.DeepEqual(nilMap, nullMap), false},
|
||||
"compareNonNilMaps": {reflect.DeepEqual(nonNilMap, nullMap), false},
|
||||
}
|
||||
|
||||
for name, test := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, want := test.got, test.want
|
||||
if got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNullRepeated(t *testing.T) {
|
||||
// Test for slices
|
||||
nullSlice1 := param.NullSlice[[]int]()
|
||||
nullSlice2 := param.NullSlice[[]int]()
|
||||
if !sentinel.IsNull(nullSlice1) {
|
||||
t.Errorf("IsNull(nullSlice1) = false, want true")
|
||||
}
|
||||
if !sentinel.IsNull(nullSlice2) {
|
||||
t.Errorf("IsNull(nullSlice2) = false, want true")
|
||||
}
|
||||
if !sentinel.IsNull(nullSlice1) || !sentinel.IsNull(nullSlice2) {
|
||||
t.Errorf("IsNull should return true for all NullSlice instances")
|
||||
}
|
||||
|
||||
// Test for maps
|
||||
nullMap1 := param.NullMap[map[string]int]()
|
||||
nullMap2 := param.NullMap[map[string]int]()
|
||||
if !sentinel.IsNull(nullMap1) {
|
||||
t.Errorf("IsNull(nullMap1) = false, want true")
|
||||
}
|
||||
if !sentinel.IsNull(nullMap2) {
|
||||
t.Errorf("IsNull(nullMap2) = false, want true")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
// This package provides shims over Go 1.2{2,3} APIs
|
||||
// which are missing from Go 1.21, and used by the Go 1.24 encoding/json package.
|
||||
//
|
||||
// Inside the vendored package, all shim code has comments that begin look like
|
||||
// // SHIM(...): ...
|
||||
package shims
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"slices"
|
||||
)
|
||||
|
||||
const EscapeHTMLByDefault = true
|
||||
|
||||
type OverflowableType struct{ reflect.Type }
|
||||
|
||||
func (t OverflowableType) OverflowInt(x int64) bool {
|
||||
k := t.Kind()
|
||||
switch k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
bitSize := t.Size() * 8
|
||||
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
|
||||
return x != trunc
|
||||
}
|
||||
panic("reflect: OverflowInt of non-int type " + t.String())
|
||||
}
|
||||
|
||||
func (t OverflowableType) OverflowUint(x uint64) bool {
|
||||
k := t.Kind()
|
||||
switch k {
|
||||
case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
bitSize := t.Size() * 8
|
||||
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
|
||||
return x != trunc
|
||||
}
|
||||
panic("reflect: OverflowUint of non-uint type " + t.String())
|
||||
}
|
||||
|
||||
// Original src code from Go 1.23 go/src/reflect/type.go (taken 1/9/25)
|
||||
/*
|
||||
|
||||
func (t *rtype) OverflowInt(x int64) bool {
|
||||
k := t.Kind()
|
||||
switch k {
|
||||
case Int, Int8, Int16, Int32, Int64:
|
||||
bitSize := t.Size() * 8
|
||||
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
|
||||
return x != trunc
|
||||
}
|
||||
panic("reflect: OverflowInt of non-int type " + t.String())
|
||||
}
|
||||
|
||||
func (t *rtype) OverflowUint(x uint64) bool {
|
||||
k := t.Kind()
|
||||
switch k {
|
||||
case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64:
|
||||
bitSize := t.Size() * 8
|
||||
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
|
||||
return x != trunc
|
||||
}
|
||||
panic("reflect: OverflowUint of non-uint type " + t.String())
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// TypeFor returns the [Type] that represents the type argument T.
|
||||
func TypeFor[T any]() reflect.Type {
|
||||
var v T
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
return t // optimize for T being a non-interface kind
|
||||
}
|
||||
return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind
|
||||
}
|
||||
|
||||
// Original src code from Go 1.23 go/src/reflect/type.go (taken 1/9/25)
|
||||
/*
|
||||
|
||||
// TypeFor returns the [Type] that represents the type argument T.
|
||||
func TypeFor[T any]() Type {
|
||||
var v T
|
||||
if t := TypeOf(v); t != nil {
|
||||
return t // optimize for T being a non-interface kind
|
||||
}
|
||||
return TypeOf((*T)(nil)).Elem() // only for an interface kind
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
type AppendableStdEncoding struct{ *base64.Encoding }
|
||||
|
||||
// AppendEncode appends the base64 encoded src to dst
|
||||
// and returns the extended buffer.
|
||||
func (enc AppendableStdEncoding) AppendEncode(dst, src []byte) []byte {
|
||||
n := enc.EncodedLen(len(src))
|
||||
dst = slices.Grow(dst, n)
|
||||
enc.Encode(dst[len(dst):][:n], src)
|
||||
return dst[:len(dst)+n]
|
||||
}
|
||||
|
||||
// Original src code from Go 1.23.4 go/src/encoding/base64/base64.go (taken 1/9/25)
|
||||
/*
|
||||
|
||||
// AppendEncode appends the base64 encoded src to dst
|
||||
// and returns the extended buffer.
|
||||
func (enc *Encoding) AppendEncode(dst, src []byte) []byte {
|
||||
n := enc.EncodedLen(len(src))
|
||||
dst = slices.Grow(dst, n)
|
||||
enc.Encode(dst[len(dst):][:n], src)
|
||||
return dst[:len(dst)+n]
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
@ -1,512 +0,0 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A Decoder reads and decodes JSON values from an input stream.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
buf []byte
|
||||
d decodeState
|
||||
scanp int // start of unread data in buf
|
||||
scanned int64 // amount of data already scanned
|
||||
scan scanner
|
||||
err error
|
||||
|
||||
tokenState int
|
||||
tokenStack []int
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may
|
||||
// read data from r beyond the JSON values requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
// UseNumber causes the Decoder to unmarshal a number into an
|
||||
// interface value as a [Number] instead of as a float64.
|
||||
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
||||
|
||||
// DisallowUnknownFields causes the Decoder to return an error when the destination
|
||||
// is a struct and the input contains object keys which do not match any
|
||||
// non-ignored, exported fields in the destination.
|
||||
func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true }
|
||||
|
||||
// Decode reads the next JSON-encoded value from its
|
||||
// input and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for [Unmarshal] for details about
|
||||
// the conversion of JSON into a Go value.
|
||||
func (dec *Decoder) Decode(v any) error {
|
||||
if dec.err != nil {
|
||||
return dec.err
|
||||
}
|
||||
|
||||
if err := dec.tokenPrepareForDecode(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !dec.tokenValueAllowed() {
|
||||
return &SyntaxError{msg: "not at beginning of value", Offset: dec.InputOffset()}
|
||||
}
|
||||
|
||||
// Read whole value into buffer.
|
||||
n, err := dec.readValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
|
||||
dec.scanp += n
|
||||
|
||||
// Don't save err from unmarshal into dec.err:
|
||||
// the connection is still usable since we read a complete JSON
|
||||
// object from it before the error happened.
|
||||
err = dec.d.unmarshal(v)
|
||||
|
||||
// fixup token streaming state
|
||||
dec.tokenValueEnd()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Buffered returns a reader of the data remaining in the Decoder's
|
||||
// buffer. The reader is valid until the next call to [Decoder.Decode].
|
||||
func (dec *Decoder) Buffered() io.Reader {
|
||||
return bytes.NewReader(dec.buf[dec.scanp:])
|
||||
}
|
||||
|
||||
// readValue reads a JSON value into dec.buf.
|
||||
// It returns the length of the encoding.
|
||||
func (dec *Decoder) readValue() (int, error) {
|
||||
dec.scan.reset()
|
||||
|
||||
scanp := dec.scanp
|
||||
var err error
|
||||
Input:
|
||||
// help the compiler see that scanp is never negative, so it can remove
|
||||
// some bounds checks below.
|
||||
for scanp >= 0 {
|
||||
|
||||
// Look in the buffer for a new value.
|
||||
for ; scanp < len(dec.buf); scanp++ {
|
||||
c := dec.buf[scanp]
|
||||
dec.scan.bytes++
|
||||
switch dec.scan.step(&dec.scan, c) {
|
||||
case scanEnd:
|
||||
// scanEnd is delayed one byte so we decrement
|
||||
// the scanner bytes count by 1 to ensure that
|
||||
// this value is correct in the next call of Decode.
|
||||
dec.scan.bytes--
|
||||
break Input
|
||||
case scanEndObject, scanEndArray:
|
||||
// scanEnd is delayed one byte.
|
||||
// We might block trying to get that byte from src,
|
||||
// so instead invent a space byte.
|
||||
if stateEndValue(&dec.scan, ' ') == scanEnd {
|
||||
scanp++
|
||||
break Input
|
||||
}
|
||||
case scanError:
|
||||
dec.err = dec.scan.err
|
||||
return 0, dec.scan.err
|
||||
}
|
||||
}
|
||||
|
||||
// Did the last read have an error?
|
||||
// Delayed until now to allow buffer scan.
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||
break Input
|
||||
}
|
||||
if nonSpace(dec.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
dec.err = err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n := scanp - dec.scanp
|
||||
err = dec.refill()
|
||||
scanp = dec.scanp + n
|
||||
}
|
||||
return scanp - dec.scanp, nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) refill() error {
|
||||
// Make room to read more into the buffer.
|
||||
// First slide down data already consumed.
|
||||
if dec.scanp > 0 {
|
||||
dec.scanned += int64(dec.scanp)
|
||||
n := copy(dec.buf, dec.buf[dec.scanp:])
|
||||
dec.buf = dec.buf[:n]
|
||||
dec.scanp = 0
|
||||
}
|
||||
|
||||
// Grow buffer if not large enough.
|
||||
const minRead = 512
|
||||
if cap(dec.buf)-len(dec.buf) < minRead {
|
||||
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
|
||||
copy(newBuf, dec.buf)
|
||||
dec.buf = newBuf
|
||||
}
|
||||
|
||||
// Read. Delay error for next iteration (after scan).
|
||||
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
|
||||
dec.buf = dec.buf[0 : len(dec.buf)+n]
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func nonSpace(b []byte) bool {
|
||||
for _, c := range b {
|
||||
if !isSpace(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// An Encoder writes JSON values to an output stream.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
err error
|
||||
escapeHTML bool
|
||||
|
||||
indentBuf []byte
|
||||
indentPrefix string
|
||||
indentValue string
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{w: w, escapeHTML: true}
|
||||
}
|
||||
|
||||
// Encode writes the JSON encoding of v to the stream,
|
||||
// with insignificant space characters elided,
|
||||
// followed by a newline character.
|
||||
//
|
||||
// See the documentation for [Marshal] for details about the
|
||||
// conversion of Go values to JSON.
|
||||
func (enc *Encoder) Encode(v any) error {
|
||||
if enc.err != nil {
|
||||
return enc.err
|
||||
}
|
||||
|
||||
e := newEncodeState()
|
||||
defer encodeStatePool.Put(e)
|
||||
|
||||
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Terminate each value with a newline.
|
||||
// This makes the output look a little nicer
|
||||
// when debugging, and some kind of space
|
||||
// is required if the encoded value was a number,
|
||||
// so that the reader knows there aren't more
|
||||
// digits coming.
|
||||
e.WriteByte('\n')
|
||||
|
||||
b := e.Bytes()
|
||||
if enc.indentPrefix != "" || enc.indentValue != "" {
|
||||
enc.indentBuf, err = appendIndent(enc.indentBuf[:0], b, enc.indentPrefix, enc.indentValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b = enc.indentBuf
|
||||
}
|
||||
if _, err = enc.w.Write(b); err != nil {
|
||||
enc.err = err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetIndent instructs the encoder to format each subsequent encoded
|
||||
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
|
||||
// Calling SetIndent("", "") disables indentation.
|
||||
func (enc *Encoder) SetIndent(prefix, indent string) {
|
||||
enc.indentPrefix = prefix
|
||||
enc.indentValue = indent
|
||||
}
|
||||
|
||||
// SetEscapeHTML specifies whether problematic HTML characters
|
||||
// should be escaped inside JSON quoted strings.
|
||||
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
|
||||
// to avoid certain safety problems that can arise when embedding JSON in HTML.
|
||||
//
|
||||
// In non-HTML settings where the escaping interferes with the readability
|
||||
// of the output, SetEscapeHTML(false) disables this behavior.
|
||||
func (enc *Encoder) SetEscapeHTML(on bool) {
|
||||
enc.escapeHTML = on
|
||||
}
|
||||
|
||||
// RawMessage is a raw encoded JSON value.
|
||||
// It implements [Marshaler] and [Unmarshaler] and can
|
||||
// be used to delay JSON decoding or precompute a JSON encoding.
|
||||
type RawMessage []byte
|
||||
|
||||
// MarshalJSON returns m as the JSON encoding of m.
|
||||
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *m to a copy of data.
|
||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Marshaler = (*RawMessage)(nil)
|
||||
var _ Unmarshaler = (*RawMessage)(nil)
|
||||
|
||||
// A Token holds a value of one of these types:
|
||||
//
|
||||
// - [Delim], for the four JSON delimiters [ ] { }
|
||||
// - bool, for JSON booleans
|
||||
// - float64, for JSON numbers
|
||||
// - [Number], for JSON numbers
|
||||
// - string, for JSON string literals
|
||||
// - nil, for JSON null
|
||||
type Token any
|
||||
|
||||
const (
|
||||
tokenTopValue = iota
|
||||
tokenArrayStart
|
||||
tokenArrayValue
|
||||
tokenArrayComma
|
||||
tokenObjectStart
|
||||
tokenObjectKey
|
||||
tokenObjectColon
|
||||
tokenObjectValue
|
||||
tokenObjectComma
|
||||
)
|
||||
|
||||
// advance tokenstate from a separator state to a value state
|
||||
func (dec *Decoder) tokenPrepareForDecode() error {
|
||||
// Note: Not calling peek before switch, to avoid
|
||||
// putting peek into the standard Decode path.
|
||||
// peek is only called when using the Token API.
|
||||
switch dec.tokenState {
|
||||
case tokenArrayComma:
|
||||
c, err := dec.peek()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != ',' {
|
||||
return &SyntaxError{"expected comma after array element", dec.InputOffset()}
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenArrayValue
|
||||
case tokenObjectColon:
|
||||
c, err := dec.peek()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != ':' {
|
||||
return &SyntaxError{"expected colon after object key", dec.InputOffset()}
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenObjectValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) tokenValueAllowed() bool {
|
||||
switch dec.tokenState {
|
||||
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dec *Decoder) tokenValueEnd() {
|
||||
switch dec.tokenState {
|
||||
case tokenArrayStart, tokenArrayValue:
|
||||
dec.tokenState = tokenArrayComma
|
||||
case tokenObjectValue:
|
||||
dec.tokenState = tokenObjectComma
|
||||
}
|
||||
}
|
||||
|
||||
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
|
||||
type Delim rune
|
||||
|
||||
func (d Delim) String() string {
|
||||
return string(d)
|
||||
}
|
||||
|
||||
// Token returns the next JSON token in the input stream.
|
||||
// At the end of the input stream, Token returns nil, [io.EOF].
|
||||
//
|
||||
// Token guarantees that the delimiters [ ] { } it returns are
|
||||
// properly nested and matched: if Token encounters an unexpected
|
||||
// delimiter in the input, it will return an error.
|
||||
//
|
||||
// The input stream consists of basic JSON values—bool, string,
|
||||
// number, and null—along with delimiters [ ] { } of type [Delim]
|
||||
// to mark the start and end of arrays and objects.
|
||||
// Commas and colons are elided.
|
||||
func (dec *Decoder) Token() (Token, error) {
|
||||
for {
|
||||
c, err := dec.peek()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch c {
|
||||
case '[':
|
||||
if !dec.tokenValueAllowed() {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||
dec.tokenState = tokenArrayStart
|
||||
return Delim('['), nil
|
||||
|
||||
case ']':
|
||||
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||
dec.tokenValueEnd()
|
||||
return Delim(']'), nil
|
||||
|
||||
case '{':
|
||||
if !dec.tokenValueAllowed() {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||
dec.tokenState = tokenObjectStart
|
||||
return Delim('{'), nil
|
||||
|
||||
case '}':
|
||||
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||
dec.tokenValueEnd()
|
||||
return Delim('}'), nil
|
||||
|
||||
case ':':
|
||||
if dec.tokenState != tokenObjectColon {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenObjectValue
|
||||
continue
|
||||
|
||||
case ',':
|
||||
if dec.tokenState == tokenArrayComma {
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenArrayValue
|
||||
continue
|
||||
}
|
||||
if dec.tokenState == tokenObjectComma {
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenObjectKey
|
||||
continue
|
||||
}
|
||||
return dec.tokenError(c)
|
||||
|
||||
case '"':
|
||||
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
|
||||
var x string
|
||||
old := dec.tokenState
|
||||
dec.tokenState = tokenTopValue
|
||||
err := dec.Decode(&x)
|
||||
dec.tokenState = old
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec.tokenState = tokenObjectColon
|
||||
return x, nil
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
if !dec.tokenValueAllowed() {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
var x any
|
||||
if err := dec.Decode(&x); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) tokenError(c byte) (Token, error) {
|
||||
var context string
|
||||
switch dec.tokenState {
|
||||
case tokenTopValue:
|
||||
context = " looking for beginning of value"
|
||||
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||
context = " looking for beginning of value"
|
||||
case tokenArrayComma:
|
||||
context = " after array element"
|
||||
case tokenObjectKey:
|
||||
context = " looking for beginning of object key string"
|
||||
case tokenObjectColon:
|
||||
context = " after object key"
|
||||
case tokenObjectComma:
|
||||
context = " after object key:value pair"
|
||||
}
|
||||
return nil, &SyntaxError{"invalid character " + quoteChar(c) + context, dec.InputOffset()}
|
||||
}
|
||||
|
||||
// More reports whether there is another element in the
|
||||
// current array or object being parsed.
|
||||
func (dec *Decoder) More() bool {
|
||||
c, err := dec.peek()
|
||||
return err == nil && c != ']' && c != '}'
|
||||
}
|
||||
|
||||
func (dec *Decoder) peek() (byte, error) {
|
||||
var err error
|
||||
for {
|
||||
for i := dec.scanp; i < len(dec.buf); i++ {
|
||||
c := dec.buf[i]
|
||||
if isSpace(c) {
|
||||
continue
|
||||
}
|
||||
dec.scanp = i
|
||||
return c, nil
|
||||
}
|
||||
// buffer has been scanned, now report any error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = dec.refill()
|
||||
}
|
||||
}
|
||||
|
||||
// InputOffset returns the input stream byte offset of the current decoder position.
|
||||
// The offset gives the location of the end of the most recently returned token
|
||||
// and the beginning of the next token.
|
||||
func (dec *Decoder) InputOffset() int64 {
|
||||
return dec.scanned + int64(dec.scanp)
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
// Copyright 2016 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
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// safeSet holds the value true if the ASCII character with the given array
|
||||
// position can be represented inside a JSON string without any further
|
||||
// escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), and the backslash character ("\").
|
||||
var safeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': true,
|
||||
'=': true,
|
||||
'>': true,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
||||
|
||||
// htmlSafeSet holds the value true if the ASCII character with the given
|
||||
// array position can be safely represented inside a JSON string, embedded
|
||||
// inside of HTML <script> tags, without any additional escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), the backslash character ("\"), HTML opening and closing
|
||||
// tags ("<" and ">"), and the ampersand ("&").
|
||||
var htmlSafeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': false,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': false,
|
||||
'=': true,
|
||||
'>': false,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright 2011 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
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "json"
|
||||
// tag, or the empty string. It does not include the leading comma.
|
||||
type tagOptions string
|
||||
|
||||
// parseTag splits a struct field's json tag into its name and
|
||||
// comma-separated options.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
tag, opt, _ := strings.Cut(tag, ",")
|
||||
return tag, tagOptions(opt)
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o tagOptions) Contains(optionName string) bool {
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
s := string(o)
|
||||
for s != "" {
|
||||
var name string
|
||||
name, s, _ = strings.Cut(s, ",")
|
||||
if name == optionName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// EDIT(begin): custom time marshaler
|
||||
package json
|
||||
|
||||
import (
|
||||
"github.com/stainless-sdks/opencode-go/internal/encoding/json/shims"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimeMarshaler interface {
|
||||
MarshalJSONWithTimeLayout(string) []byte
|
||||
}
|
||||
|
||||
func TimeLayout(fmt string) string {
|
||||
switch fmt {
|
||||
case "", "date-time":
|
||||
return time.RFC3339
|
||||
case "date":
|
||||
return time.DateOnly
|
||||
default:
|
||||
return fmt
|
||||
}
|
||||
}
|
||||
|
||||
var timeType = shims.TypeFor[time.Time]()
|
||||
|
||||
func newTimeEncoder() encoderFunc {
|
||||
return func(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
t := v.Interface().(time.Time)
|
||||
fmtted := t.Format(TimeLayout(opts.timefmt))
|
||||
stringEncoder(e, reflect.ValueOf(fmtted), opts)
|
||||
}
|
||||
}
|
||||
|
||||
// Uses continuation passing style, to add the timefmt option to k
|
||||
func continueWithTimeFmt(timefmt string, k encoderFunc) encoderFunc {
|
||||
return func(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
opts.timefmt = timefmt
|
||||
k(e, v, opts)
|
||||
}
|
||||
}
|
||||
|
||||
func timeMarshalEncoder(e *encodeState, v reflect.Value, opts encOpts) bool {
|
||||
tm, ok := v.Interface().(TimeMarshaler)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
b := tm.MarshalJSONWithTimeLayout(opts.timefmt)
|
||||
if b != nil {
|
||||
e.Grow(len(b))
|
||||
out := e.AvailableBuffer()
|
||||
out, _ = appendCompact(out, b, opts.escapeHTML)
|
||||
e.Buffer.Write(out)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// EDIT(end)
|
||||
29
internal/param/field.go
Normal file
29
internal/param/field.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package param
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type FieldLike interface{ field() }
|
||||
|
||||
// Field is a wrapper used for all values sent to the API,
|
||||
// to distinguish zero values from null or omitted fields.
|
||||
//
|
||||
// It also allows sending arbitrary deserializable values.
|
||||
//
|
||||
// To instantiate a Field, use the helpers exported from
|
||||
// the package root: `F()`, `Null()`, `Raw()`, etc.
|
||||
type Field[T any] struct {
|
||||
FieldLike
|
||||
Value T
|
||||
Null bool
|
||||
Present bool
|
||||
Raw any
|
||||
}
|
||||
|
||||
func (f Field[T]) String() string {
|
||||
if s, ok := any(f.Value).(fmt.Stringer); ok {
|
||||
return s.String()
|
||||
}
|
||||
return fmt.Sprintf("%v", f.Value)
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package paramutil
|
||||
|
||||
import (
|
||||
"github.com/stainless-sdks/opencode-go/packages/param"
|
||||
"github.com/stainless-sdks/opencode-go/packages/respjson"
|
||||
)
|
||||
|
||||
func AddrIfPresent[T comparable](v param.Opt[T]) *T {
|
||||
if v.Valid() {
|
||||
return &v.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ToOpt[T comparable](v T, meta respjson.Field) param.Opt[T] {
|
||||
if meta.Valid() {
|
||||
return param.NewOpt(v)
|
||||
} else if meta.Raw() == respjson.Null {
|
||||
return param.Null[T]()
|
||||
}
|
||||
return param.Opt[T]{}
|
||||
}
|
||||
|
||||
// Checks if the value is not omitted and not null
|
||||
func Valid(v param.ParamStruct) bool {
|
||||
if ovr, ok := v.Overrides(); ok {
|
||||
return ovr != nil
|
||||
}
|
||||
return !param.IsNull(v) && !param.IsOmitted(v)
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package paramutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stainless-sdks/opencode-go/packages/param"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var paramUnionType = reflect.TypeOf(param.APIUnion{})
|
||||
|
||||
// VariantFromUnion can be used to extract the present variant from a param union type.
|
||||
// A param union type is a struct with an embedded field of [APIUnion].
|
||||
func VariantFromUnion(u reflect.Value) (any, error) {
|
||||
if u.Kind() == reflect.Ptr {
|
||||
u = u.Elem()
|
||||
}
|
||||
|
||||
if u.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("param: cannot extract variant from non-struct union")
|
||||
}
|
||||
|
||||
isUnion := false
|
||||
nVariants := 0
|
||||
variantIdx := -1
|
||||
for i := 0; i < u.NumField(); i++ {
|
||||
if !u.Field(i).IsZero() {
|
||||
nVariants++
|
||||
variantIdx = i
|
||||
}
|
||||
if u.Field(i).Type() == paramUnionType {
|
||||
isUnion = u.Type().Field(i).Anonymous
|
||||
}
|
||||
}
|
||||
|
||||
if !isUnion {
|
||||
return nil, fmt.Errorf("param: cannot extract variant from non-union")
|
||||
}
|
||||
|
||||
if nVariants > 1 {
|
||||
return nil, fmt.Errorf("param: cannot extract variant from union with multiple variants")
|
||||
}
|
||||
|
||||
if nVariants == 0 {
|
||||
return nil, fmt.Errorf("param: cannot extract variant from union with no variants")
|
||||
}
|
||||
|
||||
return u.Field(variantIdx).Interface(), nil
|
||||
}
|
||||
|
|
@ -18,10 +18,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/stainless-sdks/opencode-go/internal"
|
||||
"github.com/stainless-sdks/opencode-go/internal/apierror"
|
||||
"github.com/stainless-sdks/opencode-go/internal/apiform"
|
||||
"github.com/stainless-sdks/opencode-go/internal/apiquery"
|
||||
"github.com/sst/opencode-sdk-go/internal"
|
||||
"github.com/sst/opencode-sdk-go/internal/apierror"
|
||||
"github.com/sst/opencode-sdk-go/internal/apiform"
|
||||
"github.com/sst/opencode-sdk-go/internal/apiquery"
|
||||
"github.com/sst/opencode-sdk-go/internal/param"
|
||||
)
|
||||
|
||||
func getDefaultHeaders() map[string]string {
|
||||
|
|
@ -87,7 +88,7 @@ type PreRequestOptionFunc func(*RequestConfig) error
|
|||
func (s RequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }
|
||||
func (s PreRequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }
|
||||
|
||||
func NewRequestConfig(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) (*RequestConfig, error) {
|
||||
func NewRequestConfig(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) (*RequestConfig, error) {
|
||||
var reader io.Reader
|
||||
|
||||
contentType := "application/json"
|
||||
|
|
@ -115,11 +116,7 @@ func NewRequestConfig(ctx context.Context, method string, u string, body any, ds
|
|||
}
|
||||
if body, ok := body.(apiquery.Queryer); ok {
|
||||
hasSerializationFunc = true
|
||||
q, err := body.URLQuery()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params := q.Encode()
|
||||
params := body.URLQuery().Encode()
|
||||
if params != "" {
|
||||
u = u + "?" + params
|
||||
}
|
||||
|
|
@ -190,6 +187,13 @@ func NewRequestConfig(ctx context.Context, method string, u string, body any, ds
|
|||
return &cfg, nil
|
||||
}
|
||||
|
||||
func UseDefaultParam[T any](dst *param.Field[T], src *T) {
|
||||
if !dst.Present && src != nil {
|
||||
dst.Value = *src
|
||||
dst.Present = true
|
||||
}
|
||||
}
|
||||
|
||||
// This interface is primarily used to describe an [*http.Client], but also
|
||||
// supports custom HTTP implementations.
|
||||
type HTTPDoer interface {
|
||||
|
|
@ -212,11 +216,10 @@ type RequestConfig struct {
|
|||
CustomHTTPDoer HTTPDoer
|
||||
HTTPClient *http.Client
|
||||
Middlewares []middleware
|
||||
APIKey string
|
||||
// If ResponseBodyInto not nil, then we will attempt to deserialize into
|
||||
// ResponseBodyInto. If Destination is a []byte, then it will return the body as
|
||||
// is.
|
||||
ResponseBodyInto any
|
||||
ResponseBodyInto interface{}
|
||||
// ResponseInto copies the \*http.Response of the corresponding request into the
|
||||
// given address
|
||||
ResponseInto **http.Response
|
||||
|
|
@ -556,7 +559,7 @@ func (cfg *RequestConfig) Execute() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func ExecuteNewRequest(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) error {
|
||||
func ExecuteNewRequest(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) error {
|
||||
cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -584,7 +587,6 @@ func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig {
|
|||
BaseURL: cfg.BaseURL,
|
||||
HTTPClient: cfg.HTTPClient,
|
||||
Middlewares: cfg.Middlewares,
|
||||
APIKey: cfg.APIKey,
|
||||
}
|
||||
|
||||
return new
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
package internal
|
||||
|
||||
const PackageVersion = "0.0.1"
|
||||
const PackageVersion = "0.0.1" // x-release-please-version
|
||||
|
|
|
|||
72
log.go
72
log.go
|
|
@ -1,72 +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/param"
|
||||
)
|
||||
|
||||
// LogService 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 [NewLogService] method instead.
|
||||
type LogService struct {
|
||||
Options []option.RequestOption
|
||||
}
|
||||
|
||||
// NewLogService 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 NewLogService(opts ...option.RequestOption) (r LogService) {
|
||||
r = LogService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
||||
// Write a log entry to the server logs
|
||||
func (r *LogService) Write(ctx context.Context, body LogWriteParams, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "log"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
type LogWriteParams struct {
|
||||
// Log level
|
||||
//
|
||||
// Any of "debug", "info", "error", "warn".
|
||||
Level LogWriteParamsLevel `json:"level,omitzero,required"`
|
||||
// Log message
|
||||
Message string `json:"message,required"`
|
||||
// Service name for the log entry
|
||||
Service string `json:"service,required"`
|
||||
// Additional metadata for the log entry
|
||||
Extra map[string]any `json:"extra,omitzero"`
|
||||
paramObj
|
||||
}
|
||||
|
||||
func (r LogWriteParams) MarshalJSON() (data []byte, err error) {
|
||||
type shadow LogWriteParams
|
||||
return param.MarshalObject(r, (*shadow)(&r))
|
||||
}
|
||||
func (r *LogWriteParams) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
// Log level
|
||||
type LogWriteParamsLevel string
|
||||
|
||||
const (
|
||||
LogWriteParamsLevelDebug LogWriteParamsLevel = "debug"
|
||||
LogWriteParamsLevelInfo LogWriteParamsLevel = "info"
|
||||
LogWriteParamsLevelError LogWriteParamsLevel = "error"
|
||||
LogWriteParamsLevelWarn LogWriteParamsLevel = "warn"
|
||||
)
|
||||
44
log_test.go
44
log_test.go
|
|
@ -1,44 +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 TestLogWriteWithOptionalParams(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.Log.Write(context.TODO(), opencode.LogWriteParams{
|
||||
Level: opencode.LogWriteParamsLevelDebug,
|
||||
Message: "message",
|
||||
Service: "service",
|
||||
Extra: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
})
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/stainless-sdks/opencode-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ import (
|
|||
// which can be supplied to clients, services, and methods. You can read more about this functional
|
||||
// options pattern in our [README].
|
||||
//
|
||||
// [README]: https://pkg.go.dev/github.com/stainless-sdks/opencode-go#readme-requestoptions
|
||||
// [README]: https://pkg.go.dev/github.com/sst/opencode-sdk-go#readme-requestoptions
|
||||
type RequestOption = requestconfig.RequestOption
|
||||
|
||||
// WithBaseURL returns a RequestOption that sets the BaseURL for the client.
|
||||
|
|
@ -168,7 +168,7 @@ func WithQueryDel(key string) RequestOption {
|
|||
// The key accepts a string as defined by the [sjson format].
|
||||
//
|
||||
// [sjson format]: https://github.com/tidwall/sjson
|
||||
func WithJSONSet(key string, value any) RequestOption {
|
||||
func WithJSONSet(key string, value interface{}) RequestOption {
|
||||
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) {
|
||||
var b []byte
|
||||
|
||||
|
|
@ -263,13 +263,5 @@ func WithRequestTimeout(dur time.Duration) RequestOption {
|
|||
// environment to be the "production" environment. An environment specifies which base URL
|
||||
// to use by default.
|
||||
func WithEnvironmentProduction() RequestOption {
|
||||
return requestconfig.WithDefaultBaseURL("https://api.example.com/")
|
||||
}
|
||||
|
||||
// WithAPIKey returns a RequestOption that sets the client setting "api_key".
|
||||
func WithAPIKey(value string) RequestOption {
|
||||
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
|
||||
r.APIKey = value
|
||||
return r.Apply(WithHeader("authorization", fmt.Sprintf("Bearer %s", r.APIKey)))
|
||||
})
|
||||
return requestconfig.WithDefaultBaseURL("http://localhost:54321/")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
package param
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
shimjson "github.com/stainless-sdks/opencode-go/internal/encoding/json"
|
||||
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
// EncodedAsDate is not be stable and shouldn't be relied upon
|
||||
type EncodedAsDate Opt[time.Time]
|
||||
|
||||
type forceOmit int
|
||||
|
||||
func (m EncodedAsDate) MarshalJSON() ([]byte, error) {
|
||||
underlying := Opt[time.Time](m)
|
||||
bytes := underlying.MarshalJSONWithTimeLayout("2006-01-02")
|
||||
if len(bytes) > 0 {
|
||||
return bytes, nil
|
||||
}
|
||||
return underlying.MarshalJSON()
|
||||
}
|
||||
|
||||
// MarshalObject uses a shimmed 'encoding/json' from Go 1.24, to support the 'omitzero' tag
|
||||
//
|
||||
// Stability for the API of MarshalObject is not guaranteed.
|
||||
func MarshalObject[T ParamStruct](f T, underlying any) ([]byte, error) {
|
||||
return MarshalWithExtras(f, underlying, f.extraFields())
|
||||
}
|
||||
|
||||
// MarshalWithExtras is used to marshal a struct with additional properties.
|
||||
//
|
||||
// Stability for the API of MarshalWithExtras is not guaranteed.
|
||||
func MarshalWithExtras[T ParamStruct, R any](f T, underlying any, extras map[string]R) ([]byte, error) {
|
||||
if f.null() {
|
||||
return []byte("null"), nil
|
||||
} else if len(extras) > 0 {
|
||||
bytes, err := shimjson.Marshal(underlying)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range extras {
|
||||
var a any = v
|
||||
if a == Omit {
|
||||
// Errors when handling ForceOmitted are ignored.
|
||||
if b, e := sjson.DeleteBytes(bytes, k); e == nil {
|
||||
bytes = b
|
||||
}
|
||||
continue
|
||||
}
|
||||
bytes, err = sjson.SetBytes(bytes, k, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bytes, nil
|
||||
} else if ovr, ok := f.Overrides(); ok {
|
||||
return shimjson.Marshal(ovr)
|
||||
} else {
|
||||
return shimjson.Marshal(underlying)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalUnion uses a shimmed 'encoding/json' from Go 1.24, to support the 'omitzero' tag
|
||||
//
|
||||
// Stability for the API of MarshalUnion is not guaranteed.
|
||||
func MarshalUnion[T ParamStruct](metadata T, variants ...any) ([]byte, error) {
|
||||
nPresent := 0
|
||||
presentIdx := -1
|
||||
for i, variant := range variants {
|
||||
if !IsOmitted(variant) {
|
||||
nPresent++
|
||||
presentIdx = i
|
||||
}
|
||||
}
|
||||
if nPresent == 0 || presentIdx == -1 {
|
||||
if ovr, ok := metadata.Overrides(); ok {
|
||||
return shimjson.Marshal(ovr)
|
||||
}
|
||||
return []byte(`null`), nil
|
||||
} else if nPresent > 1 {
|
||||
return nil, &json.MarshalerError{
|
||||
Type: typeFor[T](),
|
||||
Err: fmt.Errorf("expected union to have only one present variant, got %d", nPresent),
|
||||
}
|
||||
}
|
||||
return shimjson.Marshal(variants[presentIdx])
|
||||
}
|
||||
|
||||
// typeFor is shimmed from Go 1.23 "reflect" package
|
||||
func typeFor[T any]() reflect.Type {
|
||||
var v T
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
return t // optimize for T being a non-interface kind
|
||||
}
|
||||
return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind
|
||||
}
|
||||
|
|
@ -1,365 +0,0 @@
|
|||
package param_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stainless-sdks/opencode-go/packages/param"
|
||||
)
|
||||
|
||||
type Struct struct {
|
||||
A string `json:"a"`
|
||||
B int64 `json:"b"`
|
||||
param.APIObject
|
||||
}
|
||||
|
||||
func (r Struct) MarshalJSON() (data []byte, err error) {
|
||||
type shadow Struct
|
||||
return param.MarshalObject(r, (*shadow)(&r))
|
||||
}
|
||||
|
||||
// Note that the order of fields affects the JSON
|
||||
// key order. Changing the order of the fields in this struct
|
||||
// will fail tests unnecessarily.
|
||||
type FieldStruct struct {
|
||||
A param.Opt[string] `json:"a,omitzero"`
|
||||
B param.Opt[int64] `json:"b,omitzero"`
|
||||
C Struct `json:"c,omitzero"`
|
||||
D time.Time `json:"d,omitzero" format:"date"`
|
||||
E time.Time `json:"e,omitzero"`
|
||||
F param.Opt[time.Time] `json:"f,omitzero" format:"date"`
|
||||
G param.Opt[time.Time] `json:"g,omitzero"`
|
||||
H param.Opt[time.Time] `json:"h,omitzero" format:"date-time"`
|
||||
param.APIObject
|
||||
}
|
||||
|
||||
func (r FieldStruct) MarshalJSON() (data []byte, err error) {
|
||||
type shadow FieldStruct
|
||||
return param.MarshalObject(r, (*shadow)(&r))
|
||||
}
|
||||
|
||||
type StructWithAdditionalProperties struct {
|
||||
First string `json:"first"`
|
||||
Second int `json:"second"`
|
||||
ExtraFields map[string]any `json:"-"`
|
||||
param.APIObject
|
||||
}
|
||||
|
||||
func (s StructWithAdditionalProperties) MarshalJSON() ([]byte, error) {
|
||||
type shadow StructWithAdditionalProperties
|
||||
return param.MarshalWithExtras(s, (*shadow)(&s), s.ExtraFields)
|
||||
}
|
||||
|
||||
func TestIsNullish(t *testing.T) {
|
||||
nullTests := map[string]param.ParamNullable{
|
||||
"null_string": param.Null[string](),
|
||||
"null_int64": param.Null[int64](),
|
||||
"null_time": param.Null[time.Time](),
|
||||
"null_struct": param.NullStruct[Struct](),
|
||||
}
|
||||
|
||||
for name, test := range nullTests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if !param.IsNull(test) {
|
||||
t.Fatalf("expected %s to be null", name)
|
||||
}
|
||||
if param.IsOmitted(test) {
|
||||
t.Fatalf("expected %s to not be omitted", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
omitTests := map[string]param.ParamNullable{
|
||||
"omit_string": param.Opt[string]{},
|
||||
"omit_int64": param.Opt[int64]{},
|
||||
"omit_time": param.Opt[time.Time]{},
|
||||
"omit_struct": Struct{},
|
||||
}
|
||||
|
||||
for name, test := range omitTests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if param.IsNull(test) {
|
||||
t.Fatalf("expected %s to be null", name)
|
||||
}
|
||||
if !param.IsOmitted(test) {
|
||||
t.Fatalf("expected %s to not be omitted", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldMarshal(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
value any
|
||||
expected string
|
||||
}{
|
||||
"null_string": {param.Null[string](), "null"},
|
||||
"null_int64": {param.Null[int64](), "null"},
|
||||
"null_time": {param.Null[time.Time](), "null"},
|
||||
"null_struct": {param.NullStruct[Struct](), "null"},
|
||||
|
||||
"float_zero": {param.NewOpt(float64(0.0)), "0"},
|
||||
"string_zero": {param.NewOpt(""), `""`},
|
||||
"time_zero": {param.NewOpt(time.Time{}), `"0001-01-01T00:00:00Z"`},
|
||||
|
||||
"string": {param.Opt[string]{Value: "string"}, `"string"`},
|
||||
"int": {param.Opt[int64]{Value: 123}, "123"},
|
||||
"int64": {param.Opt[int64]{Value: int64(123456789123456789)}, "123456789123456789"},
|
||||
"struct": {Struct{A: "yo", B: 123}, `{"a":"yo","b":123}`},
|
||||
"datetime": {
|
||||
param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
|
||||
`"2023-03-18T14:47:38Z"`,
|
||||
},
|
||||
"optional_date": {
|
||||
FieldStruct{
|
||||
F: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
|
||||
},
|
||||
`{"f":"2023-03-18"}`,
|
||||
},
|
||||
"optional_time": {
|
||||
FieldStruct{
|
||||
G: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
|
||||
},
|
||||
`{"g":"2023-03-18T14:47:38Z"}`,
|
||||
},
|
||||
"optional_datetime_explicit_format": {
|
||||
FieldStruct{
|
||||
H: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
|
||||
},
|
||||
`{"h":"2023-03-18T14:47:38Z"}`,
|
||||
},
|
||||
"param_struct": {
|
||||
FieldStruct{
|
||||
A: param.Opt[string]{Value: "hello"},
|
||||
B: param.Opt[int64]{Value: int64(12)},
|
||||
D: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC),
|
||||
E: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC),
|
||||
F: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
|
||||
G: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
|
||||
H: param.Opt[time.Time]{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","f":"2023-03-18","g":"2023-03-18T14:47:38Z","h":"2023-03-18T14:47:38Z"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
b, err := json.Marshal(test.value)
|
||||
if err != nil {
|
||||
t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
|
||||
}
|
||||
if string(b) != test.expected {
|
||||
t.Fatalf("expected %s, received %s", test.expected, string(b))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdditionalProperties(t *testing.T) {
|
||||
s := StructWithAdditionalProperties{
|
||||
First: "hello",
|
||||
Second: 14,
|
||||
ExtraFields: map[string]any{
|
||||
"hi": "there",
|
||||
},
|
||||
}
|
||||
exp := `{"first":"hello","second":14,"hi":"there"}`
|
||||
|
||||
bytes, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
if string(bytes) != exp {
|
||||
t.Fatalf("expected %s, got %s", exp, string(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtraFields(t *testing.T) {
|
||||
v := Struct{
|
||||
A: "hello",
|
||||
B: 123,
|
||||
}
|
||||
v.SetExtraFields(map[string]any{
|
||||
"extra": Struct{A: "recursive"},
|
||||
"b": nil,
|
||||
})
|
||||
bytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
if string(bytes) != `{"a":"hello","b":null,"extra":{"a":"recursive","b":0}}` {
|
||||
t.Fatalf("failed to marshal: got %v", string(bytes))
|
||||
}
|
||||
if v.B != 123 {
|
||||
t.Fatalf("marshal modified field B: got %v", v.B)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtraFieldsForceOmitted(t *testing.T) {
|
||||
v := Struct{
|
||||
// Testing with the zero value.
|
||||
// A: "",
|
||||
// B: 0,
|
||||
}
|
||||
v.SetExtraFields(map[string]any{
|
||||
"b": param.Omit,
|
||||
})
|
||||
bytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
if string(bytes) != `{"a":""}` {
|
||||
t.Fatalf("failed to marshal: got %v", string(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
type UnionWithDates struct {
|
||||
OfDate param.Opt[time.Time]
|
||||
OfTime param.Opt[time.Time]
|
||||
param.APIUnion
|
||||
}
|
||||
|
||||
func (r UnionWithDates) MarshalJSON() (data []byte, err error) {
|
||||
return param.MarshalUnion(r, param.EncodedAsDate(r.OfDate), r.OfTime)
|
||||
}
|
||||
|
||||
func TestUnionDateMarshal(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
value UnionWithDates
|
||||
expected string
|
||||
}{
|
||||
"date_only": {
|
||||
UnionWithDates{
|
||||
OfDate: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 0, 0, 0, 0, time.UTC)},
|
||||
},
|
||||
`"2023-03-18"`,
|
||||
},
|
||||
"datetime_only": {
|
||||
UnionWithDates{
|
||||
OfTime: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
|
||||
},
|
||||
`"2023-03-18T14:47:38Z"`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
b, err := json.Marshal(test.value)
|
||||
if err != nil {
|
||||
t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
|
||||
}
|
||||
if string(b) != test.expected {
|
||||
t.Fatalf("expected %s, received %s", test.expected, string(b))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverride(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
value param.ParamStruct
|
||||
expected string
|
||||
}{
|
||||
"param_struct": {
|
||||
param.Override[FieldStruct](map[string]any{
|
||||
"a": "hello",
|
||||
"b": 12,
|
||||
"c": nil,
|
||||
}),
|
||||
`{"a":"hello","b":12,"c":null}`,
|
||||
},
|
||||
"param_struct_primitive": {
|
||||
param.Override[FieldStruct](12),
|
||||
`12`,
|
||||
},
|
||||
"param_struct_null": {
|
||||
param.Override[FieldStruct](nil),
|
||||
`null`,
|
||||
},
|
||||
}
|
||||
|
||||
f := FieldStruct{}
|
||||
|
||||
f.SetExtraFields(map[string]any{
|
||||
"z": "ok",
|
||||
})
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
b, err := json.Marshal(test.value)
|
||||
if err != nil {
|
||||
t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
|
||||
}
|
||||
if string(b) != test.expected {
|
||||
t.Fatalf("expected %s, received %s", test.expected, string(b))
|
||||
}
|
||||
if _, ok := test.value.Overrides(); !ok {
|
||||
t.Fatalf("expected to be overridden")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Despite implementing the interface, this struct is not an param.Optional
|
||||
// since it was defined in a different package.
|
||||
type almostOpt struct{}
|
||||
|
||||
func (almostOpt) Valid() bool { return true }
|
||||
func (almostOpt) Null() bool { return false }
|
||||
func (almostOpt) isZero() bool { return false }
|
||||
|
||||
func (almostOpt) implOpt() {}
|
||||
|
||||
func TestOptionalInterfaceAssignability(t *testing.T) {
|
||||
optInt := param.Opt[int]{}
|
||||
if _, ok := any(optInt).(param.Optional); !ok {
|
||||
t.Fatalf("failed to assign")
|
||||
}
|
||||
|
||||
notOpt := almostOpt{}
|
||||
if _, ok := any(notOpt).(param.Optional); ok {
|
||||
t.Fatalf("unexpected successful assignment")
|
||||
}
|
||||
|
||||
notOpt.implOpt() // silence the warning
|
||||
}
|
||||
|
||||
type PrimitiveUnion struct {
|
||||
OfString param.Opt[string]
|
||||
OfInt param.Opt[int]
|
||||
param.APIUnion
|
||||
}
|
||||
|
||||
func (p PrimitiveUnion) MarshalJSON() (data []byte, err error) {
|
||||
return param.MarshalUnion(p, p.OfString, p.OfInt)
|
||||
}
|
||||
|
||||
func TestOverriddenUnion(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
value PrimitiveUnion
|
||||
expected string
|
||||
}{
|
||||
"string": {
|
||||
param.Override[PrimitiveUnion](json.RawMessage(`"hello"`)),
|
||||
`"hello"`,
|
||||
},
|
||||
"int": {
|
||||
param.Override[PrimitiveUnion](json.RawMessage(`42`)),
|
||||
`42`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
b, err := json.Marshal(test.value)
|
||||
if err != nil {
|
||||
t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
|
||||
}
|
||||
if string(b) != test.expected {
|
||||
t.Fatalf("expected %s, received %s", test.expected, string(b))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package param
|
||||
|
||||
import "github.com/stainless-sdks/opencode-go/internal/encoding/json/sentinel"
|
||||
|
||||
// NullMap returns a non-nil map with a length of 0.
|
||||
// When used with [MarshalObject] or [MarshalUnion], it will be marshaled as null.
|
||||
//
|
||||
// It is unspecified behavior to mutate the map returned by [NullMap].
|
||||
func NullMap[MapT ~map[string]T, T any]() MapT {
|
||||
return sentinel.NewNullSentinel(func() MapT { return make(MapT, 1) })
|
||||
}
|
||||
|
||||
// NullSlice returns a non-nil slice with a length of 0.
|
||||
// When used with [MarshalObject] or [MarshalUnion], it will be marshaled as null.
|
||||
//
|
||||
// It is unspecified behavior to mutate the slice returned by [NullSlice].
|
||||
func NullSlice[SliceT ~[]T, T any]() SliceT {
|
||||
return sentinel.NewNullSentinel(func() SliceT { return make(SliceT, 0, 1) })
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package param_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/stainless-sdks/opencode-go/packages/param"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Nullables struct {
|
||||
Slice []int `json:"slice,omitzero"`
|
||||
Map map[string]int `json:"map,omitzero"`
|
||||
param.APIObject
|
||||
}
|
||||
|
||||
func (n Nullables) MarshalJSON() ([]byte, error) {
|
||||
type shadow Nullables
|
||||
return param.MarshalObject(n, (*shadow)(&n))
|
||||
}
|
||||
|
||||
func TestNullMarshal(t *testing.T) {
|
||||
bytes, err := json.Marshal(Nullables{})
|
||||
if err != nil {
|
||||
t.Fatalf("json error %v", err.Error())
|
||||
}
|
||||
if string(bytes) != `{}` {
|
||||
t.Fatalf("expected empty object, got %s", string(bytes))
|
||||
}
|
||||
|
||||
obj := Nullables{
|
||||
Slice: param.NullSlice[[]int](),
|
||||
Map: param.NullMap[map[string]int](),
|
||||
}
|
||||
bytes, err = json.Marshal(obj)
|
||||
|
||||
if !param.IsNull(obj.Slice) {
|
||||
t.Fatal("failed null check")
|
||||
}
|
||||
if !param.IsNull(obj.Map) {
|
||||
t.Fatal("failed null check")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("json error %v", err.Error())
|
||||
}
|
||||
exp := `{"slice":null,"map":null}`
|
||||
if string(bytes) != exp {
|
||||
t.Fatalf("expected %s, got %s", exp, string(bytes))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
package param
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
shimjson "github.com/stainless-sdks/opencode-go/internal/encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewOpt[T comparable](v T) Opt[T] {
|
||||
return Opt[T]{Value: v, status: included}
|
||||
}
|
||||
|
||||
// Null creates optional field with the JSON value "null".
|
||||
//
|
||||
// To set a struct to null, use [NullStruct].
|
||||
func Null[T comparable]() Opt[T] { return Opt[T]{status: null} }
|
||||
|
||||
type status int8
|
||||
|
||||
const (
|
||||
omitted status = iota
|
||||
null
|
||||
included
|
||||
)
|
||||
|
||||
// Opt represents an optional parameter of type T. Use
|
||||
// the [Opt.Valid] method to confirm.
|
||||
type Opt[T comparable] struct {
|
||||
Value T
|
||||
// indicates whether the field should be omitted, null, or valid
|
||||
status status
|
||||
opt
|
||||
}
|
||||
|
||||
// Valid returns true if the value is not "null" or omitted.
|
||||
//
|
||||
// To check if explicitly null, use [Opt.Null].
|
||||
func (o Opt[T]) Valid() bool {
|
||||
var empty Opt[T]
|
||||
return o.status == included || o != empty && o.status != null
|
||||
}
|
||||
|
||||
func (o Opt[T]) Or(v T) T {
|
||||
if o.Valid() {
|
||||
return o.Value
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (o Opt[T]) String() string {
|
||||
if o.null() {
|
||||
return "null"
|
||||
}
|
||||
if s, ok := any(o.Value).(fmt.Stringer); ok {
|
||||
return s.String()
|
||||
}
|
||||
return fmt.Sprintf("%v", o.Value)
|
||||
}
|
||||
|
||||
func (o Opt[T]) MarshalJSON() ([]byte, error) {
|
||||
if !o.Valid() {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return shimjson.Marshal(o.Value)
|
||||
}
|
||||
|
||||
func (o *Opt[T]) UnmarshalJSON(data []byte) error {
|
||||
if string(data) == "null" {
|
||||
o.status = null
|
||||
return nil
|
||||
}
|
||||
|
||||
var value *T
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
o.status = omitted
|
||||
return nil
|
||||
}
|
||||
|
||||
o.status = included
|
||||
o.Value = *value
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSONWithTimeLayout is necessary to bypass the internal caching performed
|
||||
// by [json.Marshal]. Prefer to use [Opt.MarshalJSON] instead.
|
||||
//
|
||||
// This function requires that the generic type parameter of [Opt] is not [time.Time].
|
||||
func (o Opt[T]) MarshalJSONWithTimeLayout(format string) []byte {
|
||||
t, ok := any(o.Value).(time.Time)
|
||||
if !ok || o.null() {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := shimjson.Marshal(t.Format(shimjson.TimeLayout(format)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (o Opt[T]) null() bool { return o.status == null }
|
||||
func (o Opt[T]) isZero() bool { return o == Opt[T]{} }
|
||||
|
||||
// opt helps limit the [Optional] interface to only types in this package
|
||||
type opt struct{}
|
||||
|
||||
func (opt) implOpt() {}
|
||||
|
||||
// This interface is useful for internal purposes.
|
||||
type Optional interface {
|
||||
Valid() bool
|
||||
null() bool
|
||||
|
||||
isZero() bool
|
||||
implOpt()
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
package param
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/stainless-sdks/opencode-go/internal/encoding/json/sentinel"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// NullStruct is used to set a struct to the JSON value null.
|
||||
// Check for null structs with [IsNull].
|
||||
//
|
||||
// Only the first type parameter should be provided,
|
||||
// the type PtrT will be inferred.
|
||||
//
|
||||
// json.Marshal(param.NullStruct[MyStruct]()) -> 'null'
|
||||
//
|
||||
// To send null to an [Opt] field use [Null].
|
||||
func NullStruct[T ParamStruct, PtrT InferPtr[T]]() T {
|
||||
var t T
|
||||
pt := PtrT(&t)
|
||||
pt.setMetadata(nil)
|
||||
return *pt
|
||||
}
|
||||
|
||||
// Override replaces the value of a struct with any type.
|
||||
//
|
||||
// Only the first type parameter should be provided,
|
||||
// the type PtrT will be inferred.
|
||||
//
|
||||
// It's often useful for providing raw JSON
|
||||
//
|
||||
// param.Override[MyStruct](json.RawMessage(`{"foo": "bar"}`))
|
||||
//
|
||||
// The public fields of the returned struct T will be unset.
|
||||
//
|
||||
// To override a specific field in a struct, use its [SetExtraFields] method.
|
||||
func Override[T ParamStruct, PtrT InferPtr[T]](v any) T {
|
||||
var t T
|
||||
pt := PtrT(&t)
|
||||
pt.setMetadata(v)
|
||||
return *pt
|
||||
}
|
||||
|
||||
// IsOmitted returns true if v is the zero value of its type.
|
||||
//
|
||||
// If IsOmitted is true, and the field uses a `json:"...,omitzero"` tag,
|
||||
// the field will be omitted from the request.
|
||||
//
|
||||
// If v is set explicitly to the JSON value "null", IsOmitted returns false.
|
||||
func IsOmitted(v any) bool {
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
if o, ok := v.(Optional); ok {
|
||||
return o.isZero()
|
||||
}
|
||||
return reflect.ValueOf(v).IsZero()
|
||||
}
|
||||
|
||||
// IsNull returns true if v was set to the JSON value null.
|
||||
//
|
||||
// To set a param to null use [NullStruct], [Null], [NullMap], or [NullSlice]
|
||||
// depending on the type of v.
|
||||
//
|
||||
// IsNull returns false if the value is omitted.
|
||||
func IsNull[T any](v T) bool {
|
||||
if nullable, ok := any(v).(ParamNullable); ok {
|
||||
return nullable.null()
|
||||
}
|
||||
|
||||
switch reflect.TypeOf(v).Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
return sentinel.IsNull(v)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ParamNullable encapsulates all structs in parameters,
|
||||
// and all [Opt] types in parameters.
|
||||
type ParamNullable interface {
|
||||
null() bool
|
||||
}
|
||||
|
||||
// ParamStruct represents the set of all structs that are
|
||||
// used in API parameters, by convention these usually end in
|
||||
// "Params" or "Param".
|
||||
type ParamStruct interface {
|
||||
Overrides() (any, bool)
|
||||
null() bool
|
||||
extraFields() map[string]any
|
||||
}
|
||||
|
||||
// This is an implementation detail and should never be explicitly set.
|
||||
type InferPtr[T ParamStruct] interface {
|
||||
setMetadata(any)
|
||||
*T
|
||||
}
|
||||
|
||||
// APIObject should be embedded in api object fields, preferably using an alias to make private
|
||||
type APIObject struct{ metadata }
|
||||
|
||||
// APIUnion should be embedded in all api unions fields, preferably using an alias to make private
|
||||
type APIUnion struct{ metadata }
|
||||
|
||||
// Overrides returns the value of the struct when it is created with
|
||||
// [Override], the second argument helps differentiate an explicit null.
|
||||
func (m metadata) Overrides() (any, bool) {
|
||||
if _, ok := m.any.(metadataExtraFields); ok {
|
||||
return nil, false
|
||||
}
|
||||
return m.any, m.any != nil
|
||||
}
|
||||
|
||||
// ExtraFields returns the extra fields added to the JSON object.
|
||||
func (m metadata) ExtraFields() map[string]any {
|
||||
if extras, ok := m.any.(metadataExtraFields); ok {
|
||||
return extras
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Omit can be used with [metadata.SetExtraFields] to ensure that a
|
||||
// required field is omitted. This is useful as an escape hatch for
|
||||
// when a required is unwanted for some unexpected reason.
|
||||
const Omit forceOmit = -1
|
||||
|
||||
// SetExtraFields adds extra fields to the JSON object.
|
||||
//
|
||||
// SetExtraFields will override any existing fields with the same key.
|
||||
// For security reasons, ensure this is only used with trusted input data.
|
||||
//
|
||||
// To intentionally omit a required field, use [Omit].
|
||||
//
|
||||
// foo.SetExtraFields(map[string]any{"bar": Omit})
|
||||
//
|
||||
// If the struct already contains the field ExtraFields, then this
|
||||
// method will have no effect.
|
||||
func (m *metadata) SetExtraFields(extraFields map[string]any) {
|
||||
m.any = metadataExtraFields(extraFields)
|
||||
}
|
||||
|
||||
// extraFields aliases [metadata.ExtraFields] to avoid name collisions.
|
||||
func (m metadata) extraFields() map[string]any { return m.ExtraFields() }
|
||||
|
||||
func (m metadata) null() bool {
|
||||
if _, ok := m.any.(metadataNull); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if msg, ok := m.any.(json.RawMessage); ok {
|
||||
return string(msg) == "null"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type metadata struct{ any }
|
||||
type metadataNull struct{}
|
||||
type metadataExtraFields map[string]any
|
||||
|
||||
func (m *metadata) setMetadata(override any) {
|
||||
if override == nil {
|
||||
m.any = metadataNull{}
|
||||
return
|
||||
}
|
||||
m.any = override
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
package respjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/stainless-sdks/opencode-go/internal/apijson"
|
||||
rj "github.com/stainless-sdks/opencode-go/packages/respjson"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type UnionOfStringIntOrObject struct {
|
||||
OfString string `json:",inline"`
|
||||
OfInt int `json:",inline"`
|
||||
Type string `json:"type"`
|
||||
Function SubFields `json:"function"`
|
||||
JSON struct {
|
||||
OfString rj.Field
|
||||
OfInt rj.Field
|
||||
Type rj.Field
|
||||
Function rj.Field
|
||||
raw string
|
||||
} `json:"-"`
|
||||
}
|
||||
|
||||
func (u UnionOfStringIntOrObject) RawJSON() string { return u.JSON.raw }
|
||||
func (r *UnionOfStringIntOrObject) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
type SubFields struct {
|
||||
OfBool bool `json:",inline"`
|
||||
Name string `json:"name,required"`
|
||||
JSON struct {
|
||||
OfBool rj.Field
|
||||
Name rj.Field
|
||||
ExtraFields map[string]rj.Field
|
||||
raw string
|
||||
} `json:"-"`
|
||||
}
|
||||
|
||||
func (r SubFields) RawJSON() string { return r.JSON.raw }
|
||||
func (r *SubFields) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func TestUnmarshalUnionString(t *testing.T) {
|
||||
rawJSON := `"123"`
|
||||
testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error {
|
||||
return map[string]error{
|
||||
"rawJSON": checkEqual(res.RawJSON(), rawJSON),
|
||||
|
||||
"string": checkEqual(res.OfString, "123"),
|
||||
"int": checkEqual(res.OfInt, 0),
|
||||
"$.type": checkEqual(res.Type, ""),
|
||||
"$.function.name": checkEqual(res.Function.Name, ""),
|
||||
|
||||
"string.meta": checkMeta(res.JSON.OfString, rawJSON, shouldBePresent),
|
||||
"int.meta": checkMeta(res.JSON.OfInt, "", shouldBeNullish),
|
||||
"$.type.meta": checkMeta(res.JSON.Type, "", shouldBeNullish),
|
||||
"$.function.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish),
|
||||
"$.function.name.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalUnionInt(t *testing.T) {
|
||||
rawJSON := `123`
|
||||
testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error {
|
||||
return map[string]error{
|
||||
"rawJSON": checkEqual(res.RawJSON(), rawJSON),
|
||||
|
||||
"string": checkEqual(res.OfString, ""),
|
||||
"int": checkEqual(res.OfInt, 123),
|
||||
"$.type": checkEqual(res.Type, ""),
|
||||
"$.function.name": checkEqual(res.Function.Name, ""),
|
||||
"$.function.bool": checkEqual(res.Function.OfBool, false),
|
||||
|
||||
"string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish),
|
||||
"int.meta": checkMeta(res.JSON.OfInt, rawJSON, shouldBePresent),
|
||||
"$.type.meta": checkMeta(res.JSON.Type, "", shouldBeNullish),
|
||||
"$.function.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish),
|
||||
"$.function.name.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish),
|
||||
}
|
||||
})
|
||||
|
||||
testUnmarshalUnion(t, `0`, func(res UnionOfStringIntOrObject) map[string]error {
|
||||
return map[string]error{
|
||||
"rawJSON": checkEqual(res.RawJSON(), "0"),
|
||||
"string": checkEqual(res.OfString, ""),
|
||||
|
||||
"int": checkEqual(res.OfInt, 0),
|
||||
"int.meta": checkMeta(res.JSON.OfInt, "0", shouldBePresent),
|
||||
"string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalUnionObject(t *testing.T) {
|
||||
rawJSON := `{"type":"auto","function":{"name":"test_fn"}}`
|
||||
testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error {
|
||||
return map[string]error{
|
||||
"rawJSON": checkEqual(res.RawJSON(), rawJSON),
|
||||
|
||||
"string": checkEqual(res.OfString, ""),
|
||||
"int": checkEqual(res.OfInt, 0),
|
||||
"$.type": checkEqual(res.Type, "auto"),
|
||||
"$.function.name": checkEqual(res.Function.Name, "test_fn"),
|
||||
"$.function.bool": checkEqual(res.Function.OfBool, false),
|
||||
|
||||
"string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish),
|
||||
"int.meta": checkMeta(res.JSON.OfInt, "", shouldBeNullish),
|
||||
"$.type.meta": checkMeta(res.JSON.Type, `"auto"`, shouldBePresent),
|
||||
"$.function.meta": checkMeta(res.JSON.Function, `{"name":"test_fn"}`, shouldBePresent),
|
||||
"$.function.name.meta": checkMeta(res.Function.JSON.Name, `"test_fn"`, shouldBePresent),
|
||||
"$.function.bool.meta": checkMeta(res.Function.JSON.OfBool, "", shouldBeNullish),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalUnionObjectWithInlineSubUnion(t *testing.T) {
|
||||
rawJSON := `{"type":"auto","function":true}`
|
||||
testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error {
|
||||
return map[string]error{
|
||||
"rawJSON": checkEqual(res.RawJSON(), rawJSON),
|
||||
|
||||
"string": checkEqual(res.OfString, ""),
|
||||
"int": checkEqual(res.OfInt, 0),
|
||||
"$.type": checkEqual(res.Type, "auto"),
|
||||
"$.function": checkEqual(res.Function.OfBool, true),
|
||||
|
||||
"string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish),
|
||||
"int.meta": checkMeta(res.JSON.OfInt, "", shouldBeNullish),
|
||||
"$.type.meta": checkMeta(res.JSON.Type, `"auto"`, shouldBePresent),
|
||||
"$.function.meta": checkMeta(res.JSON.Function, `true`, shouldBePresent),
|
||||
"$.function.name.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish),
|
||||
"$.function.bool.meta": checkMeta(res.Function.JSON.OfBool, `true`, shouldBePresent),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*********/
|
||||
/* UTILS */
|
||||
/*********/
|
||||
|
||||
func testUnmarshalUnion[T any](t *testing.T, raw string, check testChecks[T]) {
|
||||
var res T
|
||||
err := json.Unmarshal([]byte(raw), &res)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal %v", err.Error())
|
||||
}
|
||||
|
||||
for label, fail := range check(res) {
|
||||
if fail != nil {
|
||||
t.Errorf("failed check %v: %v", label, fail.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkEqual[T any](got, expected T) error {
|
||||
if reflect.DeepEqual(got, expected) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("not equal: got %v, expected %v", got, expected)
|
||||
}
|
||||
|
||||
type metaStatus int
|
||||
|
||||
const (
|
||||
shouldBePresent metaStatus = iota
|
||||
shouldBeNullish
|
||||
shouldBeInvalid
|
||||
)
|
||||
|
||||
type testChecks[T any] func(T) map[string]error
|
||||
|
||||
func checkMeta(got rj.Field, raw string, stat metaStatus) error {
|
||||
switch stat {
|
||||
case shouldBePresent:
|
||||
if !got.Valid() {
|
||||
return fmt.Errorf("expected field to be present, but got nullish")
|
||||
}
|
||||
if got.Raw() != raw {
|
||||
return fmt.Errorf("expected field to be present with raw value %v, but got %v", raw, got.Raw())
|
||||
}
|
||||
case shouldBeNullish:
|
||||
if got.Valid() {
|
||||
return fmt.Errorf("expected field to be nullish, but got %v", got.Raw())
|
||||
}
|
||||
if got.Raw() != rj.Omitted && got.Raw() != rj.Null {
|
||||
return fmt.Errorf("expected field to be nullish, but got %v", got.Raw())
|
||||
}
|
||||
case shouldBeInvalid:
|
||||
if !got.Valid() || got.Raw() == "" {
|
||||
return fmt.Errorf("expected field to be invalid, but got valid value %v", got.Raw())
|
||||
}
|
||||
if got.Raw() != raw {
|
||||
return fmt.Errorf("expected field to be invalid, but got valid value %v", got.Raw())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown metaStatus: %v", stat)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package respjson
|
||||
|
||||
// A Field provides metadata to indicate the presence of a value.
|
||||
//
|
||||
// Use [Field.Valid] to check if an optional value was null or omitted.
|
||||
//
|
||||
// A Field will always occur in the following structure, where it
|
||||
// mirrors the original field in it's parent struct:
|
||||
//
|
||||
// type ExampleObject struct {
|
||||
// Foo bool `json:"foo"`
|
||||
// Bar int `json:"bar"`
|
||||
// // ...
|
||||
//
|
||||
// // JSON provides metadata about the object.
|
||||
// JSON struct {
|
||||
// Foo Field
|
||||
// Bar Field
|
||||
// // ...
|
||||
// } `json:"-"`
|
||||
// }
|
||||
//
|
||||
// To differentiate a "nullish" value from the zero value,
|
||||
// use the [Field.Valid] method.
|
||||
//
|
||||
// if !example.JSON.Foo.Valid() {
|
||||
// println("Foo is null or omitted")
|
||||
// }
|
||||
//
|
||||
// if example.Foo {
|
||||
// println("Foo is true")
|
||||
// } else {
|
||||
// println("Foo is false")
|
||||
// }
|
||||
//
|
||||
// To differentiate if a field was omitted or the JSON value "null",
|
||||
// use the [Field.Raw] method.
|
||||
//
|
||||
// if example.JSON.Foo.Raw() == "null" {
|
||||
// println("Foo is null")
|
||||
// }
|
||||
//
|
||||
// if example.JSON.Foo.Raw() == "" {
|
||||
// println("Foo was omitted")
|
||||
// }
|
||||
//
|
||||
// Otherwise, if the field was invalid and couldn't be marshalled successfully,
|
||||
// [Field.Valid] will be false and [Field.Raw] will not be empty.
|
||||
type Field struct {
|
||||
status
|
||||
raw string
|
||||
}
|
||||
|
||||
const (
|
||||
omitted status = iota
|
||||
null
|
||||
invalid
|
||||
valid
|
||||
)
|
||||
|
||||
type status int8
|
||||
|
||||
// Valid returns true if the parent field was set.
|
||||
// Valid returns false if the value doesn't exist, is JSON null, or
|
||||
// is an unexpected type.
|
||||
func (j Field) Valid() bool { return j.status > invalid }
|
||||
|
||||
const Null string = "null"
|
||||
const Omitted string = ""
|
||||
|
||||
// Returns the raw JSON value of the field.
|
||||
func (j Field) Raw() string {
|
||||
if j.status == omitted {
|
||||
return ""
|
||||
}
|
||||
return j.raw
|
||||
}
|
||||
|
||||
func NewField(raw string) Field {
|
||||
if raw == "null" {
|
||||
return Field{status: null, raw: Null}
|
||||
}
|
||||
return Field{status: valid, raw: raw}
|
||||
}
|
||||
|
||||
func NewInvalidField(raw string) Field {
|
||||
return Field{status: invalid, raw: raw}
|
||||
}
|
||||
87
project.go
87
project.go
|
|
@ -1,87 +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"
|
||||
)
|
||||
|
||||
// ProjectService 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 [NewProjectService] method instead.
|
||||
type ProjectService struct {
|
||||
Options []option.RequestOption
|
||||
}
|
||||
|
||||
// NewProjectService 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 NewProjectService(opts ...option.RequestOption) (r ProjectService) {
|
||||
r = ProjectService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
||||
// List all projects
|
||||
func (r *ProjectService) List(ctx context.Context, opts ...option.RequestOption) (res *[]ProjectListResponse, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "project"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
type ProjectListResponse struct {
|
||||
ID string `json:"id,required"`
|
||||
Time ProjectListResponseTime `json:"time,required"`
|
||||
Worktree string `json:"worktree,required"`
|
||||
// Any of "git".
|
||||
Vcs ProjectListResponseVcs `json:"vcs"`
|
||||
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
|
||||
JSON struct {
|
||||
ID respjson.Field
|
||||
Time respjson.Field
|
||||
Worktree respjson.Field
|
||||
Vcs respjson.Field
|
||||
ExtraFields map[string]respjson.Field
|
||||
raw string
|
||||
} `json:"-"`
|
||||
}
|
||||
|
||||
// Returns the unmodified JSON received from the API
|
||||
func (r ProjectListResponse) RawJSON() string { return r.JSON.raw }
|
||||
func (r *ProjectListResponse) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
type ProjectListResponseTime struct {
|
||||
Created float64 `json:"created,required"`
|
||||
Initialized float64 `json:"initialized"`
|
||||
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
|
||||
JSON struct {
|
||||
Created respjson.Field
|
||||
Initialized respjson.Field
|
||||
ExtraFields map[string]respjson.Field
|
||||
raw string
|
||||
} `json:"-"`
|
||||
}
|
||||
|
||||
// Returns the unmodified JSON received from the API
|
||||
func (r ProjectListResponseTime) RawJSON() string { return r.JSON.raw }
|
||||
func (r *ProjectListResponseTime) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
type ProjectListResponseVcs string
|
||||
|
||||
const (
|
||||
ProjectListResponseVcsGit ProjectListResponseVcs = "git"
|
||||
)
|
||||
|
|
@ -1,37 +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 TestProjectList(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.Project.List(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())
|
||||
}
|
||||
}
|
||||
67
release-please-config.json
Normal file
67
release-please-config.json
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"packages": {
|
||||
".": {}
|
||||
},
|
||||
"$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json",
|
||||
"include-v-in-tag": true,
|
||||
"include-component-in-tag": false,
|
||||
"versioning": "prerelease",
|
||||
"prerelease": true,
|
||||
"bump-minor-pre-major": true,
|
||||
"bump-patch-for-minor-pre-major": false,
|
||||
"pull-request-header": "Automated Release PR",
|
||||
"pull-request-title-pattern": "release: ${version}",
|
||||
"changelog-sections": [
|
||||
{
|
||||
"type": "feat",
|
||||
"section": "Features"
|
||||
},
|
||||
{
|
||||
"type": "fix",
|
||||
"section": "Bug Fixes"
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"section": "Performance Improvements"
|
||||
},
|
||||
{
|
||||
"type": "revert",
|
||||
"section": "Reverts"
|
||||
},
|
||||
{
|
||||
"type": "chore",
|
||||
"section": "Chores"
|
||||
},
|
||||
{
|
||||
"type": "docs",
|
||||
"section": "Documentation"
|
||||
},
|
||||
{
|
||||
"type": "style",
|
||||
"section": "Styles"
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"section": "Refactors"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"section": "Tests",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"section": "Build System"
|
||||
},
|
||||
{
|
||||
"type": "ci",
|
||||
"section": "Continuous Integration",
|
||||
"hidden": true
|
||||
}
|
||||
],
|
||||
"release-type": "go",
|
||||
"extra-files": [
|
||||
"internal/version.go",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
2680
session.go
2680
session.go
File diff suppressed because it is too large
Load diff
252
session_test.go
252
session_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 TestSessionNewWithOptionalParams(t *testing.T) {
|
||||
|
|
@ -24,11 +24,10 @@ func TestSessionNewWithOptionalParams(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.New(context.TODO(), opencode.SessionNewParams{
|
||||
ParentID: opencode.String("parentID"),
|
||||
Title: opencode.String("title"),
|
||||
ParentID: opencode.F("parentID"),
|
||||
Title: opencode.F("title"),
|
||||
})
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
|
|
@ -39,29 +38,6 @@ func TestSessionNewWithOptionalParams(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionGet(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.Session.Get(context.TODO(), "id")
|
||||
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 TestSessionUpdateWithOptionalParams(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
|
|
@ -73,13 +49,12 @@ func TestSessionUpdateWithOptionalParams(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.Update(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionUpdateParams{
|
||||
Title: opencode.String("title"),
|
||||
Title: opencode.F("title"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -102,7 +77,6 @@ func TestSessionList(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.List(context.TODO())
|
||||
if err != nil {
|
||||
|
|
@ -125,7 +99,6 @@ func TestSessionDelete(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.Delete(context.TODO(), "id")
|
||||
if err != nil {
|
||||
|
|
@ -148,7 +121,6 @@ func TestSessionAbort(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.Abort(context.TODO(), "id")
|
||||
if err != nil {
|
||||
|
|
@ -160,7 +132,7 @@ func TestSessionAbort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionAnalyze(t *testing.T) {
|
||||
func TestSessionChatWithOptionalParams(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
|
|
@ -171,15 +143,29 @@ func TestSessionAnalyze(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.Analyze(
|
||||
_, err := client.Session.Chat(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionAnalyzeParams{
|
||||
MessageID: "messageID",
|
||||
ModelID: "modelID",
|
||||
ProviderID: "providerID",
|
||||
opencode.SessionChatParams{
|
||||
ModelID: opencode.F("modelID"),
|
||||
Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartInputParam{
|
||||
Text: opencode.F("text"),
|
||||
Type: opencode.F(opencode.TextPartInputTypeText),
|
||||
ID: opencode.F("id"),
|
||||
Synthetic: opencode.F(true),
|
||||
Time: opencode.F(opencode.TextPartInputTimeParam{
|
||||
Start: opencode.F(0.000000),
|
||||
End: opencode.F(0.000000),
|
||||
}),
|
||||
}}),
|
||||
ProviderID: opencode.F("providerID"),
|
||||
Agent: opencode.F("agent"),
|
||||
MessageID: opencode.F("msg"),
|
||||
System: opencode.F("system"),
|
||||
Tools: opencode.F(map[string]bool{
|
||||
"foo": true,
|
||||
}),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -191,7 +177,7 @@ func TestSessionAnalyze(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionGetChildren(t *testing.T) {
|
||||
func TestSessionChildren(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
|
|
@ -202,9 +188,8 @@ func TestSessionGetChildren(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.GetChildren(context.TODO(), "id")
|
||||
_, err := client.Session.Children(context.TODO(), "id")
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
|
|
@ -214,7 +199,7 @@ func TestSessionGetChildren(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionRespondToPermission(t *testing.T) {
|
||||
func TestSessionCommandWithOptionalParams(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
|
|
@ -225,14 +210,16 @@ func TestSessionRespondToPermission(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.RespondToPermission(
|
||||
_, err := client.Session.Command(
|
||||
context.TODO(),
|
||||
"permissionID",
|
||||
opencode.SessionRespondToPermissionParams{
|
||||
ID: "id",
|
||||
Response: opencode.SessionRespondToPermissionParamsResponseOnce,
|
||||
"id",
|
||||
opencode.SessionCommandParams{
|
||||
Arguments: opencode.F("arguments"),
|
||||
Command: opencode.F("command"),
|
||||
Agent: opencode.F("agent"),
|
||||
MessageID: opencode.F("msg"),
|
||||
Model: opencode.F("model"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -244,7 +231,7 @@ func TestSessionRespondToPermission(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionRestoreReverted(t *testing.T) {
|
||||
func TestSessionGet(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
|
|
@ -255,9 +242,86 @@ func TestSessionRestoreReverted(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.RestoreReverted(context.TODO(), "id")
|
||||
_, err := client.Session.Get(context.TODO(), "id")
|
||||
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 TestSessionInit(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.Session.Init(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionInitParams{
|
||||
MessageID: opencode.F("messageID"),
|
||||
ModelID: opencode.F("modelID"),
|
||||
ProviderID: opencode.F("providerID"),
|
||||
},
|
||||
)
|
||||
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 TestSessionMessage(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.Session.Message(
|
||||
context.TODO(),
|
||||
"id",
|
||||
"messageID",
|
||||
)
|
||||
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 TestSessionMessages(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.Session.Messages(context.TODO(), "id")
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
|
|
@ -278,14 +342,13 @@ func TestSessionRevertWithOptionalParams(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.Revert(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionRevertParams{
|
||||
MessageID: "msg",
|
||||
PartID: opencode.String("prt"),
|
||||
MessageID: opencode.F("msg"),
|
||||
PartID: opencode.F("prt"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -297,7 +360,7 @@ func TestSessionRevertWithOptionalParams(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionRunShell(t *testing.T) {
|
||||
func TestSessionShare(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
|
|
@ -308,16 +371,8 @@ func TestSessionRunShell(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.RunShell(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionRunShellParams{
|
||||
Agent: "agent",
|
||||
Command: "command",
|
||||
},
|
||||
)
|
||||
_, err := client.Session.Share(context.TODO(), "id")
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
|
|
@ -327,7 +382,7 @@ func TestSessionRunShell(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionSendCommandWithOptionalParams(t *testing.T) {
|
||||
func TestSessionShell(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
|
|
@ -338,17 +393,13 @@ func TestSessionSendCommandWithOptionalParams(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.SendCommand(
|
||||
_, err := client.Session.Shell(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionSendCommandParams{
|
||||
Arguments: "arguments",
|
||||
Command: "command",
|
||||
Agent: opencode.String("agent"),
|
||||
MessageID: opencode.String("msg"),
|
||||
Model: opencode.String("model"),
|
||||
opencode.SessionShellParams{
|
||||
Agent: opencode.F("agent"),
|
||||
Command: opencode.F("command"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -371,14 +422,13 @@ func TestSessionSummarize(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Session.Summarize(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionSummarizeParams{
|
||||
ModelID: "modelID",
|
||||
ProviderID: "providerID",
|
||||
ModelID: opencode.F("modelID"),
|
||||
ProviderID: opencode.F("providerID"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -389,3 +439,47 @@ func TestSessionSummarize(t *testing.T) {
|
|||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionUnrevert(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.Session.Unrevert(context.TODO(), "id")
|
||||
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 TestSessionUnshare(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.Session.Unshare(context.TODO(), "id")
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1751
sessionmessage.go
1751
sessionmessage.go
File diff suppressed because it is too large
Load diff
|
|
@ -1,113 +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 TestSessionMessageNewWithOptionalParams(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.Session.Message.New(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionMessageNewParams{
|
||||
ModelID: "modelID",
|
||||
Parts: []opencode.SessionMessageNewParamsPartUnion{{
|
||||
OfText: &opencode.SessionMessageNewParamsPartText{
|
||||
Text: "text",
|
||||
ID: opencode.String("id"),
|
||||
Synthetic: opencode.Bool(true),
|
||||
Time: opencode.SessionMessageNewParamsPartTextTime{
|
||||
Start: 0,
|
||||
End: opencode.Float(0),
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProviderID: "providerID",
|
||||
Agent: opencode.String("agent"),
|
||||
MessageID: opencode.String("msg"),
|
||||
System: opencode.String("system"),
|
||||
Tools: map[string]bool{
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
)
|
||||
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 TestSessionMessageGet(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.Session.Message.Get(
|
||||
context.TODO(),
|
||||
"messageID",
|
||||
opencode.SessionMessageGetParams{
|
||||
ID: "id",
|
||||
},
|
||||
)
|
||||
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 TestSessionMessageList(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.Session.Message.List(context.TODO(), "id")
|
||||
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())
|
||||
}
|
||||
}
|
||||
130
sessionpermission.go
Normal file
130
sessionpermission.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||
"github.com/sst/opencode-sdk-go/internal/param"
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
// SessionPermissionService 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 [NewSessionPermissionService] method instead.
|
||||
type SessionPermissionService struct {
|
||||
Options []option.RequestOption
|
||||
}
|
||||
|
||||
// NewSessionPermissionService 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 NewSessionPermissionService(opts ...option.RequestOption) (r *SessionPermissionService) {
|
||||
r = &SessionPermissionService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
||||
// Respond to a permission request
|
||||
func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, body SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
if permissionID == "" {
|
||||
err = errors.New("missing required permissionID parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/permissions/%s", id, permissionID)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
ID string `json:"id,required"`
|
||||
MessageID string `json:"messageID,required"`
|
||||
Metadata map[string]interface{} `json:"metadata,required"`
|
||||
SessionID string `json:"sessionID,required"`
|
||||
Time PermissionTime `json:"time,required"`
|
||||
Title string `json:"title,required"`
|
||||
Type string `json:"type,required"`
|
||||
CallID string `json:"callID"`
|
||||
Pattern string `json:"pattern"`
|
||||
JSON permissionJSON `json:"-"`
|
||||
}
|
||||
|
||||
// permissionJSON contains the JSON metadata for the struct [Permission]
|
||||
type permissionJSON struct {
|
||||
ID apijson.Field
|
||||
MessageID apijson.Field
|
||||
Metadata apijson.Field
|
||||
SessionID apijson.Field
|
||||
Time apijson.Field
|
||||
Title apijson.Field
|
||||
Type apijson.Field
|
||||
CallID apijson.Field
|
||||
Pattern apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *Permission) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r permissionJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type PermissionTime struct {
|
||||
Created float64 `json:"created,required"`
|
||||
JSON permissionTimeJSON `json:"-"`
|
||||
}
|
||||
|
||||
// permissionTimeJSON contains the JSON metadata for the struct [PermissionTime]
|
||||
type permissionTimeJSON struct {
|
||||
Created apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *PermissionTime) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r permissionTimeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionPermissionRespondParams struct {
|
||||
Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
|
||||
}
|
||||
|
||||
func (r SessionPermissionRespondParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type SessionPermissionRespondParamsResponse string
|
||||
|
||||
const (
|
||||
SessionPermissionRespondParamsResponseOnce SessionPermissionRespondParamsResponse = "once"
|
||||
SessionPermissionRespondParamsResponseAlways SessionPermissionRespondParamsResponse = "always"
|
||||
SessionPermissionRespondParamsResponseReject SessionPermissionRespondParamsResponse = "reject"
|
||||
)
|
||||
|
||||
func (r SessionPermissionRespondParamsResponse) IsKnown() bool {
|
||||
switch r {
|
||||
case SessionPermissionRespondParamsResponseOnce, SessionPermissionRespondParamsResponseAlways, SessionPermissionRespondParamsResponseReject:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -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 TestAgentList(t *testing.T) {
|
||||
func TestSessionPermissionRespond(t *testing.T) {
|
||||
t.Skip("Prism tests are disabled")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
|
|
@ -24,9 +24,15 @@ func TestAgentList(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Agent.List(context.TODO())
|
||||
_, err := client.Session.Permissions.Respond(
|
||||
context.TODO(),
|
||||
"id",
|
||||
"permissionID",
|
||||
opencode.SessionPermissionRespondParams{
|
||||
Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
|
|
@ -1,56 +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/requestconfig"
|
||||
"github.com/stainless-sdks/opencode-go/option"
|
||||
)
|
||||
|
||||
// SessionShareService 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 [NewSessionShareService] method instead.
|
||||
type SessionShareService struct {
|
||||
Options []option.RequestOption
|
||||
}
|
||||
|
||||
// NewSessionShareService 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 NewSessionShareService(opts ...option.RequestOption) (r SessionShareService) {
|
||||
r = SessionShareService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
||||
// Share a session
|
||||
func (r *SessionShareService) New(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/share", id)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Unshare the session
|
||||
func (r *SessionShareService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/share", id)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
|
@ -1,60 +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 TestSessionShareNew(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.Session.Share.New(context.TODO(), "id")
|
||||
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 TestSessionShareDelete(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.Session.Share.Delete(context.TODO(), "id")
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package constant
|
||||
|
||||
import (
|
||||
shimjson "github.com/stainless-sdks/opencode-go/internal/encoding/json"
|
||||
)
|
||||
|
||||
type Constant[T any] interface {
|
||||
Default() T
|
||||
}
|
||||
|
||||
// ValueOf gives the default value of a constant from its type. It's helpful when
|
||||
// constructing constants as variants in a one-of. Note that empty structs are
|
||||
// marshalled by default. Usage: constant.ValueOf[constant.Foo]()
|
||||
func ValueOf[T Constant[T]]() T {
|
||||
var t T
|
||||
return t.Default()
|
||||
}
|
||||
|
||||
type Agent string // Always "agent"
|
||||
type API string // Always "api"
|
||||
type Assistant string // Always "assistant"
|
||||
type Completed string // Always "completed"
|
||||
type Error string // Always "error"
|
||||
type File string // Always "file"
|
||||
type FileEdited string // Always "file.edited"
|
||||
type InstallationUpdated string // Always "installation.updated"
|
||||
type Local string // Always "local"
|
||||
type LspClientDiagnostics string // Always "lsp.client.diagnostics"
|
||||
type MessagePartRemoved string // Always "message.part.removed"
|
||||
type MessagePartUpdated string // Always "message.part.updated"
|
||||
type MessageRemoved string // Always "message.removed"
|
||||
type MessageUpdated string // Always "message.updated"
|
||||
type MessageAbortedError string // Always "MessageAbortedError"
|
||||
type MessageOutputLengthError string // Always "MessageOutputLengthError"
|
||||
type OAuth string // Always "oauth"
|
||||
type Patch string // Always "patch"
|
||||
type Pending string // Always "pending"
|
||||
type PermissionReplied string // Always "permission.replied"
|
||||
type PermissionUpdated string // Always "permission.updated"
|
||||
type ProviderAuthError string // Always "ProviderAuthError"
|
||||
type Reasoning string // Always "reasoning"
|
||||
type Remote string // Always "remote"
|
||||
type Running string // Always "running"
|
||||
type ServerConnected string // Always "server.connected"
|
||||
type SessionDeleted string // Always "session.deleted"
|
||||
type SessionError string // Always "session.error"
|
||||
type SessionIdle string // Always "session.idle"
|
||||
type SessionUpdated string // Always "session.updated"
|
||||
type Snapshot string // Always "snapshot"
|
||||
type StepFinish string // Always "step-finish"
|
||||
type StepStart string // Always "step-start"
|
||||
type Symbol string // Always "symbol"
|
||||
type Text string // Always "text"
|
||||
type Tool string // Always "tool"
|
||||
type UnknownError string // Always "UnknownError"
|
||||
type User string // Always "user"
|
||||
type Wellknown string // Always "wellknown"
|
||||
|
||||
func (c Agent) Default() Agent { return "agent" }
|
||||
func (c API) Default() API { return "api" }
|
||||
func (c Assistant) Default() Assistant { return "assistant" }
|
||||
func (c Completed) Default() Completed { return "completed" }
|
||||
func (c Error) Default() Error { return "error" }
|
||||
func (c File) Default() File { return "file" }
|
||||
func (c FileEdited) Default() FileEdited { return "file.edited" }
|
||||
func (c InstallationUpdated) Default() InstallationUpdated { return "installation.updated" }
|
||||
func (c Local) Default() Local { return "local" }
|
||||
func (c LspClientDiagnostics) Default() LspClientDiagnostics { return "lsp.client.diagnostics" }
|
||||
func (c MessagePartRemoved) Default() MessagePartRemoved { return "message.part.removed" }
|
||||
func (c MessagePartUpdated) Default() MessagePartUpdated { return "message.part.updated" }
|
||||
func (c MessageRemoved) Default() MessageRemoved { return "message.removed" }
|
||||
func (c MessageUpdated) Default() MessageUpdated { return "message.updated" }
|
||||
func (c MessageAbortedError) Default() MessageAbortedError { return "MessageAbortedError" }
|
||||
func (c MessageOutputLengthError) Default() MessageOutputLengthError {
|
||||
return "MessageOutputLengthError"
|
||||
}
|
||||
func (c OAuth) Default() OAuth { return "oauth" }
|
||||
func (c Patch) Default() Patch { return "patch" }
|
||||
func (c Pending) Default() Pending { return "pending" }
|
||||
func (c PermissionReplied) Default() PermissionReplied { return "permission.replied" }
|
||||
func (c PermissionUpdated) Default() PermissionUpdated { return "permission.updated" }
|
||||
func (c ProviderAuthError) Default() ProviderAuthError { return "ProviderAuthError" }
|
||||
func (c Reasoning) Default() Reasoning { return "reasoning" }
|
||||
func (c Remote) Default() Remote { return "remote" }
|
||||
func (c Running) Default() Running { return "running" }
|
||||
func (c ServerConnected) Default() ServerConnected { return "server.connected" }
|
||||
func (c SessionDeleted) Default() SessionDeleted { return "session.deleted" }
|
||||
func (c SessionError) Default() SessionError { return "session.error" }
|
||||
func (c SessionIdle) Default() SessionIdle { return "session.idle" }
|
||||
func (c SessionUpdated) Default() SessionUpdated { return "session.updated" }
|
||||
func (c Snapshot) Default() Snapshot { return "snapshot" }
|
||||
func (c StepFinish) Default() StepFinish { return "step-finish" }
|
||||
func (c StepStart) Default() StepStart { return "step-start" }
|
||||
func (c Symbol) Default() Symbol { return "symbol" }
|
||||
func (c Text) Default() Text { return "text" }
|
||||
func (c Tool) Default() Tool { return "tool" }
|
||||
func (c UnknownError) Default() UnknownError { return "UnknownError" }
|
||||
func (c User) Default() User { return "user" }
|
||||
func (c Wellknown) Default() Wellknown { return "wellknown" }
|
||||
|
||||
func (c Agent) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c API) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Assistant) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Completed) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Error) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c File) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c FileEdited) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c InstallationUpdated) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Local) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c LspClientDiagnostics) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c MessagePartRemoved) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c MessagePartUpdated) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c MessageRemoved) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c MessageUpdated) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c MessageAbortedError) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c MessageOutputLengthError) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c OAuth) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Patch) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Pending) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c PermissionReplied) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c PermissionUpdated) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c ProviderAuthError) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Reasoning) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Remote) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Running) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c ServerConnected) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c SessionDeleted) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c SessionError) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c SessionIdle) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c SessionUpdated) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Snapshot) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c StepFinish) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c StepStart) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Symbol) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Text) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Tool) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c UnknownError) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c User) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
func (c Wellknown) MarshalJSON() ([]byte, error) { return marshalString(c) }
|
||||
|
||||
type constant[T any] interface {
|
||||
Constant[T]
|
||||
*T
|
||||
}
|
||||
|
||||
func marshalString[T ~string, PT constant[T]](v T) ([]byte, error) {
|
||||
var zero T
|
||||
if v == zero {
|
||||
v = PT(&v).Default()
|
||||
}
|
||||
return shimjson.Marshal(string(v))
|
||||
}
|
||||
173
shared/shared.go
Normal file
173
shared/shared.go
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package shared
|
||||
|
||||
import (
|
||||
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||
)
|
||||
|
||||
type MessageAbortedError struct {
|
||||
Data interface{} `json:"data,required"`
|
||||
Name MessageAbortedErrorName `json:"name,required"`
|
||||
JSON messageAbortedErrorJSON `json:"-"`
|
||||
}
|
||||
|
||||
// messageAbortedErrorJSON contains the JSON metadata for the struct
|
||||
// [MessageAbortedError]
|
||||
type messageAbortedErrorJSON struct {
|
||||
Data apijson.Field
|
||||
Name apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *MessageAbortedError) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r messageAbortedErrorJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r MessageAbortedError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
|
||||
|
||||
func (r MessageAbortedError) ImplementsAssistantMessageError() {}
|
||||
|
||||
type MessageAbortedErrorName string
|
||||
|
||||
const (
|
||||
MessageAbortedErrorNameMessageAbortedError MessageAbortedErrorName = "MessageAbortedError"
|
||||
)
|
||||
|
||||
func (r MessageAbortedErrorName) IsKnown() bool {
|
||||
switch r {
|
||||
case MessageAbortedErrorNameMessageAbortedError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ProviderAuthError struct {
|
||||
Data ProviderAuthErrorData `json:"data,required"`
|
||||
Name ProviderAuthErrorName `json:"name,required"`
|
||||
JSON providerAuthErrorJSON `json:"-"`
|
||||
}
|
||||
|
||||
// providerAuthErrorJSON contains the JSON metadata for the struct
|
||||
// [ProviderAuthError]
|
||||
type providerAuthErrorJSON struct {
|
||||
Data apijson.Field
|
||||
Name apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ProviderAuthError) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r providerAuthErrorJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r ProviderAuthError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
|
||||
|
||||
func (r ProviderAuthError) ImplementsAssistantMessageError() {}
|
||||
|
||||
type ProviderAuthErrorData struct {
|
||||
Message string `json:"message,required"`
|
||||
ProviderID string `json:"providerID,required"`
|
||||
JSON providerAuthErrorDataJSON `json:"-"`
|
||||
}
|
||||
|
||||
// providerAuthErrorDataJSON contains the JSON metadata for the struct
|
||||
// [ProviderAuthErrorData]
|
||||
type providerAuthErrorDataJSON struct {
|
||||
Message apijson.Field
|
||||
ProviderID apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ProviderAuthErrorData) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r providerAuthErrorDataJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ProviderAuthErrorName string
|
||||
|
||||
const (
|
||||
ProviderAuthErrorNameProviderAuthError ProviderAuthErrorName = "ProviderAuthError"
|
||||
)
|
||||
|
||||
func (r ProviderAuthErrorName) IsKnown() bool {
|
||||
switch r {
|
||||
case ProviderAuthErrorNameProviderAuthError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type UnknownError struct {
|
||||
Data UnknownErrorData `json:"data,required"`
|
||||
Name UnknownErrorName `json:"name,required"`
|
||||
JSON unknownErrorJSON `json:"-"`
|
||||
}
|
||||
|
||||
// unknownErrorJSON contains the JSON metadata for the struct [UnknownError]
|
||||
type unknownErrorJSON struct {
|
||||
Data apijson.Field
|
||||
Name apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *UnknownError) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r unknownErrorJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r UnknownError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
|
||||
|
||||
func (r UnknownError) ImplementsAssistantMessageError() {}
|
||||
|
||||
type UnknownErrorData struct {
|
||||
Message string `json:"message,required"`
|
||||
JSON unknownErrorDataJSON `json:"-"`
|
||||
}
|
||||
|
||||
// unknownErrorDataJSON contains the JSON metadata for the struct
|
||||
// [UnknownErrorData]
|
||||
type unknownErrorDataJSON struct {
|
||||
Message apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *UnknownErrorData) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r unknownErrorDataJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type UnknownErrorName string
|
||||
|
||||
const (
|
||||
UnknownErrorNameUnknownError UnknownErrorName = "UnknownError"
|
||||
)
|
||||
|
||||
func (r UnknownErrorName) IsKnown() bool {
|
||||
switch r {
|
||||
case UnknownErrorNameUnknownError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
52
tui.go
52
tui.go
|
|
@ -6,10 +6,10 @@ 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/param"
|
||||
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||
"github.com/sst/opencode-sdk-go/internal/param"
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
// TuiService contains methods and other services that help with interacting with
|
||||
|
|
@ -25,8 +25,8 @@ type TuiService struct {
|
|||
// NewTuiService 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 NewTuiService(opts ...option.RequestOption) (r TuiService) {
|
||||
r = TuiService{}
|
||||
func NewTuiService(opts ...option.RequestOption) (r *TuiService) {
|
||||
r = &TuiService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
|
@ -104,45 +104,29 @@ func (r *TuiService) SubmitPrompt(ctx context.Context, opts ...option.RequestOpt
|
|||
}
|
||||
|
||||
type TuiAppendPromptParams struct {
|
||||
Text string `json:"text,required"`
|
||||
paramObj
|
||||
Text param.Field[string] `json:"text,required"`
|
||||
}
|
||||
|
||||
func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) {
|
||||
type shadow TuiAppendPromptParams
|
||||
return param.MarshalObject(r, (*shadow)(&r))
|
||||
}
|
||||
func (r *TuiAppendPromptParams) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type TuiExecuteCommandParams struct {
|
||||
Command string `json:"command,required"`
|
||||
paramObj
|
||||
Command param.Field[string] `json:"command,required"`
|
||||
}
|
||||
|
||||
func (r TuiExecuteCommandParams) MarshalJSON() (data []byte, err error) {
|
||||
type shadow TuiExecuteCommandParams
|
||||
return param.MarshalObject(r, (*shadow)(&r))
|
||||
}
|
||||
func (r *TuiExecuteCommandParams) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type TuiShowToastParams struct {
|
||||
Message string `json:"message,required"`
|
||||
// Any of "info", "success", "warning", "error".
|
||||
Variant TuiShowToastParamsVariant `json:"variant,omitzero,required"`
|
||||
Title param.Opt[string] `json:"title,omitzero"`
|
||||
paramObj
|
||||
Message param.Field[string] `json:"message,required"`
|
||||
Variant param.Field[TuiShowToastParamsVariant] `json:"variant,required"`
|
||||
Title param.Field[string] `json:"title"`
|
||||
}
|
||||
|
||||
func (r TuiShowToastParams) MarshalJSON() (data []byte, err error) {
|
||||
type shadow TuiShowToastParams
|
||||
return param.MarshalObject(r, (*shadow)(&r))
|
||||
}
|
||||
func (r *TuiShowToastParams) UnmarshalJSON(data []byte) error {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type TuiShowToastParamsVariant string
|
||||
|
|
@ -153,3 +137,11 @@ const (
|
|||
TuiShowToastParamsVariantWarning TuiShowToastParamsVariant = "warning"
|
||||
TuiShowToastParamsVariantError TuiShowToastParamsVariant = "error"
|
||||
)
|
||||
|
||||
func (r TuiShowToastParamsVariant) IsKnown() bool {
|
||||
switch r {
|
||||
case TuiShowToastParamsVariantInfo, TuiShowToastParamsVariantSuccess, TuiShowToastParamsVariantWarning, TuiShowToastParamsVariantError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
25
tui_test.go
25
tui_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 TestTuiAppendPrompt(t *testing.T) {
|
||||
|
|
@ -24,10 +24,9 @@ func TestTuiAppendPrompt(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.AppendPrompt(context.TODO(), opencode.TuiAppendPromptParams{
|
||||
Text: "text",
|
||||
Text: opencode.F("text"),
|
||||
})
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
|
|
@ -49,7 +48,6 @@ func TestTuiClearPrompt(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.ClearPrompt(context.TODO())
|
||||
if err != nil {
|
||||
|
|
@ -72,10 +70,9 @@ func TestTuiExecuteCommand(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.ExecuteCommand(context.TODO(), opencode.TuiExecuteCommandParams{
|
||||
Command: "command",
|
||||
Command: opencode.F("command"),
|
||||
})
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
|
|
@ -97,7 +94,6 @@ func TestTuiOpenHelp(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.OpenHelp(context.TODO())
|
||||
if err != nil {
|
||||
|
|
@ -120,7 +116,6 @@ func TestTuiOpenModels(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.OpenModels(context.TODO())
|
||||
if err != nil {
|
||||
|
|
@ -143,7 +138,6 @@ func TestTuiOpenSessions(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.OpenSessions(context.TODO())
|
||||
if err != nil {
|
||||
|
|
@ -166,7 +160,6 @@ func TestTuiOpenThemes(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.OpenThemes(context.TODO())
|
||||
if err != nil {
|
||||
|
|
@ -189,12 +182,11 @@ func TestTuiShowToastWithOptionalParams(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.ShowToast(context.TODO(), opencode.TuiShowToastParams{
|
||||
Message: "message",
|
||||
Variant: opencode.TuiShowToastParamsVariantInfo,
|
||||
Title: opencode.String("title"),
|
||||
Message: opencode.F("message"),
|
||||
Variant: opencode.F(opencode.TuiShowToastParamsVariantInfo),
|
||||
Title: opencode.F("title"),
|
||||
})
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
|
|
@ -216,7 +208,6 @@ func TestTuiSubmitPrompt(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
_, err := client.Tui.SubmitPrompt(context.TODO())
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,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 TestUsage(t *testing.T) {
|
||||
|
|
@ -22,11 +22,11 @@ func TestUsage(t *testing.T) {
|
|||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
option.WithAPIKey("My API Key"),
|
||||
)
|
||||
projects, err := client.Project.List(context.TODO())
|
||||
sessions, err := client.Session.List(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Logf("%+v\n", projects)
|
||||
t.Logf("%+v\n", sessions)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue