feat(api): update via SDK Studio

This commit is contained in:
stainless-app[bot] 2025-08-31 05:32:29 +00:00
parent 2f34d5d53e
commit 068a0eb025
92 changed files with 5874 additions and 13849 deletions

View file

@ -0,0 +1,3 @@
{
".": "0.0.1"
}

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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
View file

@ -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)
}

View file

@ -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
View file

@ -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
View file

@ -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)
}

View file

@ -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())
}
}

View file

@ -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...)
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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 {

2186
config.go

File diff suppressed because it is too large Load diff

View file

@ -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())
}
}

1817
event.go

File diff suppressed because it is too large Load diff

View file

@ -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
View file

@ -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,

View file

@ -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
View file

@ -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,

View file

@ -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
View file

@ -1,4 +1,4 @@
module github.com/stainless-sdks/opencode-go
module github.com/sst/opencode-sdk-go
go 1.21

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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)
}
})
}
}

View file

@ -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
}

View file

@ -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())
}
}

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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)
}
}
}

View file

@ -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
}

View 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))
}
})
}
}

View file

@ -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",
},
},

View file

@ -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)

View file

@ -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
}

View file

@ -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,

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)

View file

@ -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
}
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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] + "'"
}

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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]
}
*/

View file

@ -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)
}

View file

@ -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,
}

View file

@ -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
}

View file

@ -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
View 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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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

View file

@ -2,4 +2,4 @@
package internal
const PackageVersion = "0.0.1"
const PackageVersion = "0.0.1" // x-release-please-version

72
log.go
View file

@ -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"
)

View file

@ -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())
}
}

View file

@ -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/")
}

View file

@ -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
}

View file

@ -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))
}
})
}
}

View file

@ -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) })
}

View file

@ -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))
}
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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}
}

View file

@ -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"
)

View file

@ -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())
}
}

View 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

File diff suppressed because it is too large Load diff

View file

@ -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())
}
}

File diff suppressed because it is too large Load diff

View file

@ -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
View 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
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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())
}
}

View file

@ -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
View 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
View file

@ -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
}

View file

@ -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 {

View file

@ -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)
}