remove outdated SDKs

This commit is contained in:
Dax Raad 2025-12-07 15:52:27 -05:00
parent 93b0abfce9
commit 13b2cf50ae
306 changed files with 0 additions and 45919 deletions

View file

@ -1,7 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
"name": "Development",
"image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm",
"postCreateCommand": "go mod tidy"
}

View file

@ -1,49 +0,0 @@
name: CI
on:
push:
branches-ignore:
- "generated"
- "codegen/**"
- "integrated/**"
- "stl-preview-head/**"
- "stl-preview-base/**"
pull_request:
branches-ignore:
- "stl-preview-head/**"
- "stl-preview-base/**"
jobs:
lint:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/opencode-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
- name: Setup go
uses: actions/setup-go@v5
with:
go-version-file: ./go.mod
- name: Run lints
run: ./scripts/lint
test:
timeout-minutes: 10
name: test
runs-on: ${{ github.repository == 'stainless-sdks/opencode-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
- name: Setup go
uses: actions/setup-go@v5
with:
go-version-file: ./go.mod
- name: Bootstrap
run: ./scripts/bootstrap
- name: Run tests
run: ./scripts/test

View file

@ -1,4 +0,0 @@
.prism.log
codegen.log
Brewfile.lock.json
.idea/

View file

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

View file

@ -1,4 +0,0 @@
configured_endpoints: 43
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-92f9d0f8daee2ea7458f8b9f1d7a7f941ff932442ad944bc7576254d5978b6d5.yml
openapi_spec_hash: 5b785c4ff6fb69039915f0e746abdaf9
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff

View file

@ -1 +0,0 @@
brew "go"

View file

@ -1,222 +0,0 @@
# Changelog
## 0.18.0 (2025-10-10)
Full Changelog: [v0.17.0...v0.18.0](https://github.com/sst/opencode-sdk-go/compare/v0.17.0...v0.18.0)
### Features
- **api:** api update ([0a7f5e7](https://github.com/sst/opencode-sdk-go/commit/0a7f5e710911506512a132ba39e0593c412beb77))
## 0.17.0 (2025-10-07)
Full Changelog: [v0.16.2...v0.17.0](https://github.com/sst/opencode-sdk-go/compare/v0.16.2...v0.17.0)
### Features
- **api:** api update ([84a3df5](https://github.com/sst/opencode-sdk-go/commit/84a3df50a7ff3d87e5593e4f29dfb5d561f71cc3))
## 0.16.2 (2025-09-26)
Full Changelog: [v0.16.1...v0.16.2](https://github.com/sst/opencode-sdk-go/compare/v0.16.1...v0.16.2)
### Bug Fixes
- bugfix for setting JSON keys with special characters ([ac9a36f](https://github.com/sst/opencode-sdk-go/commit/ac9a36feb1c185ebf766d76909d0b86ac805e8a6))
## 0.16.1 (2025-09-20)
Full Changelog: [v0.16.0...v0.16.1](https://github.com/sst/opencode-sdk-go/compare/v0.16.0...v0.16.1)
### Bug Fixes
- use slices.Concat instead of sometimes modifying r.Options ([12e8b40](https://github.com/sst/opencode-sdk-go/commit/12e8b40809071095b0abb9b8031686353c8ac149))
### Chores
- bump minimum go version to 1.22 ([1a61c5c](https://github.com/sst/opencode-sdk-go/commit/1a61c5cc7e8f68cc1b0c219738cab530cb6aa3a2))
- do not install brew dependencies in ./scripts/bootstrap by default ([f6d3eaf](https://github.com/sst/opencode-sdk-go/commit/f6d3eafffc20e124bbfae6ac5ddc1b1122ad3e27))
- update more docs for 1.22 ([a3d0b0f](https://github.com/sst/opencode-sdk-go/commit/a3d0b0f26ed92ce1a6433f5bcf37a6436d268ba5))
## 0.16.0 (2025-09-17)
Full Changelog: [v0.15.0...v0.16.0](https://github.com/sst/opencode-sdk-go/compare/v0.15.0...v0.16.0)
### Features
- **api:** api update ([46e978e](https://github.com/sst/opencode-sdk-go/commit/46e978e43aee733d5c1c09dc5be6d8ac2a752427))
## 0.15.0 (2025-09-16)
Full Changelog: [v0.14.0...v0.15.0](https://github.com/sst/opencode-sdk-go/compare/v0.14.0...v0.15.0)
### Features
- **api:** api update ([397048f](https://github.com/sst/opencode-sdk-go/commit/397048faca7a1de7a028edd2254a0ad7797b151f))
## 0.14.0 (2025-09-14)
Full Changelog: [v0.13.0...v0.14.0](https://github.com/sst/opencode-sdk-go/compare/v0.13.0...v0.14.0)
### Features
- **api:** api update ([dad0bc3](https://github.com/sst/opencode-sdk-go/commit/dad0bc3da99f20a0d002a6b94e049fb70f8e6a77))
## 0.13.0 (2025-09-14)
Full Changelog: [v0.12.0...v0.13.0](https://github.com/sst/opencode-sdk-go/compare/v0.12.0...v0.13.0)
### Features
- **api:** api update ([80da4fb](https://github.com/sst/opencode-sdk-go/commit/80da4fb4ea9c6afb51a7e7135d9f5560ce6f2a6c))
## 0.12.0 (2025-09-14)
Full Changelog: [v0.11.0...v0.12.0](https://github.com/sst/opencode-sdk-go/compare/v0.11.0...v0.12.0)
### Features
- **api:** api update ([7e3808b](https://github.com/sst/opencode-sdk-go/commit/7e3808ba349dc653174b32b48a1120c18d2975c2))
## 0.11.0 (2025-09-14)
Full Changelog: [v0.10.0...v0.11.0](https://github.com/sst/opencode-sdk-go/compare/v0.10.0...v0.11.0)
### Features
- **api:** api update ([a3d37f5](https://github.com/sst/opencode-sdk-go/commit/a3d37f5671545866547d351fc21b49809cc8b3c2))
## 0.10.0 (2025-09-11)
Full Changelog: [v0.9.0...v0.10.0](https://github.com/sst/opencode-sdk-go/compare/v0.9.0...v0.10.0)
### Features
- **api:** api update ([0dc01f6](https://github.com/sst/opencode-sdk-go/commit/0dc01f6695c9b8400a4dc92166c5002bb120cf50))
## 0.9.0 (2025-09-10)
Full Changelog: [v0.8.0...v0.9.0](https://github.com/sst/opencode-sdk-go/compare/v0.8.0...v0.9.0)
### Features
- **api:** api update ([2d3a28d](https://github.com/sst/opencode-sdk-go/commit/2d3a28df5657845aa4d73087e1737d1fc8c3ce1c))
## 0.8.0 (2025-09-01)
Full Changelog: [v0.7.0...v0.8.0](https://github.com/sst/opencode-sdk-go/compare/v0.7.0...v0.8.0)
### Features
- **api:** api update ([ae87a71](https://github.com/sst/opencode-sdk-go/commit/ae87a71949994590ace8285a39f0991ef34b664d))
## 0.7.0 (2025-09-01)
Full Changelog: [v0.6.0...v0.7.0](https://github.com/sst/opencode-sdk-go/compare/v0.6.0...v0.7.0)
### Features
- **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4))
## 0.6.0 (2025-09-01)
Full Changelog: [v0.5.0...v0.6.0](https://github.com/sst/opencode-sdk-go/compare/v0.5.0...v0.6.0)
### Features
- **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8))
## 0.5.0 (2025-08-31)
Full Changelog: [v0.4.0...v0.5.0](https://github.com/sst/opencode-sdk-go/compare/v0.4.0...v0.5.0)
### Features
- **api:** api update ([44b281d](https://github.com/sst/opencode-sdk-go/commit/44b281d0bb39c5022a984ac9d0fca1529ccc0604))
## 0.4.0 (2025-08-31)
Full Changelog: [v0.3.0...v0.4.0](https://github.com/sst/opencode-sdk-go/compare/v0.3.0...v0.4.0)
### Features
- **api:** api update ([fa9d6ec](https://github.com/sst/opencode-sdk-go/commit/fa9d6ec6472e62f4f6605d0a71a7aa8bf8a24559))
## 0.3.0 (2025-08-31)
Full Changelog: [v0.2.0...v0.3.0](https://github.com/sst/opencode-sdk-go/compare/v0.2.0...v0.3.0)
### Features
- **api:** api update ([aae1c06](https://github.com/sst/opencode-sdk-go/commit/aae1c06bb5a93a1cd9c589846a84b3f16246f5da))
## 0.2.0 (2025-08-31)
Full Changelog: [v0.1.0...v0.2.0](https://github.com/sst/opencode-sdk-go/compare/v0.1.0...v0.2.0)
### Features
- **api:** api update ([1472790](https://github.com/sst/opencode-sdk-go/commit/1472790542515f47bd46e2a9e28d8afea024cf9c))
## 0.1.0 (2025-08-31)
Full Changelog: [v0.0.1...v0.1.0](https://github.com/sst/opencode-sdk-go/compare/v0.0.1...v0.1.0)
### Features
- **api:** api update ([3f03ddd](https://github.com/sst/opencode-sdk-go/commit/3f03dddd5ec0de98f99ce48679077dcae9ceffd6))
- **api:** api update ([e9f79c4](https://github.com/sst/opencode-sdk-go/commit/e9f79c4792b21ef64ab0431ffd76f5a71e04d182))
- **api:** api update ([139a686](https://github.com/sst/opencode-sdk-go/commit/139a6862d2f0ab0c8ea791663d736868be3e96e6))
- **api:** api update ([2ed0800](https://github.com/sst/opencode-sdk-go/commit/2ed0800b2c5b99877e9f7fde669a6c005fad6b77))
- **api:** api update ([88a87a4](https://github.com/sst/opencode-sdk-go/commit/88a87a458f56ce0c18b502c73da933f614f56e8b))
- **api:** api update ([0e5d65b](https://github.com/sst/opencode-sdk-go/commit/0e5d65b571e7b30dc6347e6730098878ebba3a42))
- **api:** api update ([ba381f1](https://github.com/sst/opencode-sdk-go/commit/ba381f1e07aad24e9824df7d53befae2a644f69f))
- **api:** api update ([3f429f5](https://github.com/sst/opencode-sdk-go/commit/3f429f5b4be5607433ef5fdc0d5bf67fe590d039))
- **api:** api update ([9f34787](https://github.com/sst/opencode-sdk-go/commit/9f347876b35b7f898060c1a5f71c322e95978e3e))
- **api:** api update ([379c8e0](https://github.com/sst/opencode-sdk-go/commit/379c8e00197e13aebaf2f2d61277b125f1f90011))
- **api:** api update ([550511c](https://github.com/sst/opencode-sdk-go/commit/550511c4c5b5055ac8ff22b7b11731331bd9d088))
- **api:** api update ([547f0c2](https://github.com/sst/opencode-sdk-go/commit/547f0c262f2df1ce83eaa7267d68be64bb29b841))
- **api:** api update ([b7b0720](https://github.com/sst/opencode-sdk-go/commit/b7b07204bff314da24b1819c128835a43ef64065))
- **api:** api update ([7250ffc](https://github.com/sst/opencode-sdk-go/commit/7250ffcba262b916c958ddecc2a42927982db39f))
- **api:** api update ([17fbab7](https://github.com/sst/opencode-sdk-go/commit/17fbab73111a3eae488737c69b12370bc69c65f7))
- **api:** api update ([1270b5c](https://github.com/sst/opencode-sdk-go/commit/1270b5cd81e6ac769dcd92ade6d877891bf51bd5))
- **api:** api update ([a238d4a](https://github.com/sst/opencode-sdk-go/commit/a238d4abd6ed7d15f3547d27a4b6ecf4aec8431e))
- **api:** api update ([7475655](https://github.com/sst/opencode-sdk-go/commit/7475655aca577fe4f807c2f02f92171f6a358e9c))
- **api:** api update ([429d258](https://github.com/sst/opencode-sdk-go/commit/429d258bb56e9cdeb1528be3944bf5537ac26a96))
- **api:** api update ([f250915](https://github.com/sst/opencode-sdk-go/commit/f2509157eaf1b453e741ee9482127cad2e3ace25))
- **api:** api update ([5efc987](https://github.com/sst/opencode-sdk-go/commit/5efc987353801d1e772c20edf162b1c75da32743))
- **api:** api update ([98a8350](https://github.com/sst/opencode-sdk-go/commit/98a83504f7cfc361e83314c3e79a4e9ff53f0560))
- **api:** api update ([6da8bf8](https://github.com/sst/opencode-sdk-go/commit/6da8bf8bfe91d45991fb580753d77c5534fc0b1b))
- **api:** api update ([f8c7148](https://github.com/sst/opencode-sdk-go/commit/f8c7148ae56143823186e2675a78e82676154956))
- **api:** manual updates ([7cf038f](https://github.com/sst/opencode-sdk-go/commit/7cf038ffae5da1b77e1cef11b5fa166a53b467f2))
- **api:** update via SDK Studio ([068a0eb](https://github.com/sst/opencode-sdk-go/commit/068a0eb025010da0c8d86fa1bb496a39dbedcef9))
- **api:** update via SDK Studio ([ca651ed](https://github.com/sst/opencode-sdk-go/commit/ca651edaf71d1f3678f929287474f5bc4f1aad10))
- **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
- **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
- **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
- **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
- **api:** update via SDK Studio ([51315fa](https://github.com/sst/opencode-sdk-go/commit/51315fa2eae424743ea79701e67d44447c44144d))
- **api:** update via SDK Studio ([af07955](https://github.com/sst/opencode-sdk-go/commit/af0795543240aefaf04fc7663a348825541c79ed))
- **api:** update via SDK Studio ([5e3468a](https://github.com/sst/opencode-sdk-go/commit/5e3468a0aaa5ed3b13e019c3a24e0ba9147d1675))
- **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
- **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
- **client:** expand max streaming buffer size ([76303e5](https://github.com/sst/opencode-sdk-go/commit/76303e51067e78e732af26ced9d83b8bad7655c3))
- **client:** support optional json html escaping ([449748f](https://github.com/sst/opencode-sdk-go/commit/449748f35a1d8cb6f91dc36d25bf9489f4f371bd))
### Bug Fixes
- **client:** process custom base url ahead of time ([9b360d6](https://github.com/sst/opencode-sdk-go/commit/9b360d642cf6f302104308af5622e17099899e5f))
- **client:** resolve lint errors in streaming tests ([4d36cb0](https://github.com/sst/opencode-sdk-go/commit/4d36cb09fc9d436734d5dab1c499acaa88568ff7))
- close body before retrying ([4da3f7f](https://github.com/sst/opencode-sdk-go/commit/4da3f7f372bad222a189ba3eabcfde3373166ae5))
- don't try to deserialize as json when ResponseBodyInto is []byte ([595291f](https://github.com/sst/opencode-sdk-go/commit/595291f6dba6af472f160b9f8e3d145002f43a4a))
### Chores
- **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
- **internal:** codegen related update ([6a22ce6](https://github.com/sst/opencode-sdk-go/commit/6a22ce6df155f5003e80b8a75686a9e513a5568a))
- **internal:** fix lint script for tests ([391c482](https://github.com/sst/opencode-sdk-go/commit/391c482148ed0a77c4ad52807abeb2d540b56797))
- **internal:** update comment in script ([b7f1c3e](https://github.com/sst/opencode-sdk-go/commit/b7f1c3e16935c71e243004b8f321d661cd8e9474))
- lint tests ([616796b](https://github.com/sst/opencode-sdk-go/commit/616796b761704bde6be5c6c2428f28c79c7f05ff))
- lint tests in subpackages ([50c82ff](https://github.com/sst/opencode-sdk-go/commit/50c82ff0757c973834b68adc22566b70f767b611))
- sync repo ([2f34d5d](https://github.com/sst/opencode-sdk-go/commit/2f34d5d53e56e9cdc3df99be7ee7efc83dd977a3))
- update @stainless-api/prism-cli to v5.15.0 ([2f24852](https://github.com/sst/opencode-sdk-go/commit/2f2485216d4f4891d1fbfbc23ff8410c2f35152a))

View file

@ -1,66 +0,0 @@
## Setting up the environment
To set up the repository, run:
```sh
$ ./scripts/bootstrap
$ ./scripts/build
```
This will install all the required dependencies and build the SDK.
You can also [install go 1.22+ manually](https://go.dev/doc/install).
## Modifying/Adding code
Most of the SDK is generated code. Modifications to code will be persisted between generations, but may
result in merge conflicts between manual patches and changes from the generator. The generator will never
modify the contents of the `lib/` and `examples/` directories.
## Adding and running examples
All files in the `examples/` directory are not modified by the generator and can be freely edited or added to.
```go
# add an example to examples/<your-example>/main.go
package main
func main() {
// ...
}
```
```sh
$ go run ./examples/<your-example>
```
## Using the repository from source
To use a local version of this library from source in another project, edit the `go.mod` with a replace
directive. This can be done through the CLI with the following:
```sh
$ go mod edit -replace github.com/sst/opencode-sdk-go=/path/to/opencode-sdk-go
```
## Running tests
Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
```sh
# you will need npm installed
$ npx prism mock path/to/your/openapi.yml
```
```sh
$ ./scripts/test
```
## Formatting
This library uses the standard gofmt code formatter:
```sh
$ ./scripts/format
```

View file

@ -1,7 +0,0 @@
Copyright 2025 opencode
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.

View file

@ -1,363 +0,0 @@
# Opencode Go API Library
<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](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/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/sst/opencode-sdk-go@v0.18.0'
```
<!-- x-release-please-end -->
## Requirements
This library requires Go 1.22+.
## Usage
The full API of this library can be found in [api.md](api.md).
```go
package main
import (
"context"
"fmt"
"github.com/sst/opencode-sdk-go"
)
func main() {
client := opencode.NewClient()
sessions, err := client.Session.List(context.TODO(), opencode.SessionListParams{})
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", sessions)
}
```
### Request fields
All request parameters are wrapped in a generic `Field` type,
which we use to distinguish zero values from null or omitted fields.
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.
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
params := FooParams{
Name: opencode.F("hello"),
// Explicitly send `"description": null`
Description: opencode.Null[string](),
Point: opencode.F(opencode.Point{
X: opencode.Int(0),
Y: opencode.Int(1),
// 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 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
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
}
}
```
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.
```go
body := res.JSON.ExtraFields["my_unexpected_field"].Raw()
```
### RequestOptions
This library uses the functional options pattern. Functions defined in the
`option` package return a `RequestOption`, which is a closure that mutates a
`RequestConfig`. These options can be supplied to the client or at individual
requests. For example:
```go
client := opencode.NewClient(
// Adds a header to every request made by the client
option.WithHeader("X-Some-Header", "custom_header_info"),
)
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
option.WithJSONSet("some.json.path", map[string]string{"my": "object"}),
)
```
See the [full list of request options](https://pkg.go.dev/github.com/sst/opencode-sdk-go/option).
### Pagination
This library provides some conveniences for working with paginated list endpoints.
You can use `.ListAutoPaging()` methods to iterate through items across all pages:
Or you can use simple `.List()` methods to fetch a single page and receive a standard response object
with additional helper methods like `.GetNextPage()`, e.g.:
### Errors
When the API returns a non-success status code, we return an error with type
`*opencode.Error`. This contains the `StatusCode`, `*http.Request`, and
`*http.Response` values of the request, as well as the JSON of the error body
(much like other response objects in the SDK).
To handle errors, we recommend that you use the `errors.As` pattern:
```go
_, err := client.Session.List(context.TODO(), opencode.SessionListParams{})
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 "/session": 400 Bad Request { ... }
}
```
When other errors occur, they are returned unwrapped; for example,
if HTTP transport fails, you might receive `*url.Error` wrapping `*net.OpError`.
### Timeouts
Requests do not time out by default; use context to configure a timeout for a request lifecycle.
Note that if a request is [retried](#retries), the context timeout does not start over.
To set a per-retry timeout, use `option.WithRequestTimeout()`.
```go
// This sets the timeout for the request, including all the retries.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
client.Session.List(
ctx,
opencode.SessionListParams{},
// This sets the per-retry timeout
option.WithRequestTimeout(20*time.Second),
)
```
### File uploads
Request parameters that correspond to file uploads in multipart requests are typed as
`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.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
Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
We retry by default all connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit,
and >=500 Internal errors.
You can use the `WithMaxRetries` option to configure or disable this:
```go
// Configure the default for all requests:
client := opencode.NewClient(
option.WithMaxRetries(0), // default is 2
)
// Override per-request:
client.Session.List(
context.TODO(),
opencode.SessionListParams{},
option.WithMaxRetries(5),
)
```
### Accessing raw response data (e.g. response headers)
You can access the raw HTTP response data by using the `option.WithResponseInto()` request option. This is useful when
you need to examine response headers, status codes, or other details.
```go
// Create a variable to store the HTTP response
var response *http.Response
sessions, err := client.Session.List(
context.TODO(),
opencode.SessionListParams{},
option.WithResponseInto(&response),
)
if err != nil {
// handle error
}
fmt.Printf("%+v\n", sessions)
fmt.Printf("Status Code: %d\n", response.StatusCode)
fmt.Printf("Headers: %+#v\n", response.Header)
```
### Making custom/undocumented requests
This library is typed for convenient access to the documented API. If you need to access undocumented
endpoints, params, or response properties, the library can still be used.
#### Undocumented endpoints
To make requests to undocumented endpoints, you can use `client.Get`, `client.Post`, and other HTTP verbs.
`RequestOptions` on the client, such as retries, will be respected when making these requests.
```go
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]interface{}
// result can be an []byte, *http.Response, a encoding/json deserializable object,
// or a model defined in this library.
result *http.Response
)
err := client.Post(context.Background(), "/unspecified", params, &result)
if err != nil {
}
```
#### Undocumented request params
To make requests using undocumented parameters, you may use either the `option.WithQuerySet()`
or the `option.WithJSONSet()` methods.
```go
params := FooNewParams{
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"))
```
#### Undocumented response properties
To access undocumented response properties, you may either access the raw JSON of the response as a string
with `result.JSON.RawJSON()`, or get the raw JSON of a particular field on the result with
`result.JSON.Foo.Raw()`.
Any fields that are not present on the response struct will be saved and can be accessed by `result.JSON.ExtraFields()` which returns the extra fields as a `map[string]Field`.
### Middleware
We provide `option.WithMiddleware` which applies the given
middleware to requests.
```go
func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) {
// Before the request
start := time.Now()
LogReq(req)
// Forward the request to the next handler
res, err = next(req)
// Handle stuff after the request
end := time.Now()
LogRes(res, err, start - end)
return res, err
}
client := opencode.NewClient(
option.WithMiddleware(Logger),
)
```
When multiple middlewares are provided as variadic arguments, the middlewares
are applied left to right. If `option.WithMiddleware` is given
multiple times, for example first in the client then the method, the
middleware in the client will run first and the middleware given in the method
will run next.
You may also replace the default `http.Client` with
`option.WithHTTPClient(client)`. Only one http client is
accepted (this overwrites any previous client) and receives requests after any
middleware has been applied.
## Semantic versioning
This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
2. Changes that we do not expect to impact the vast majority of users in practice.
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/sst/opencode-sdk-go/issues) with questions, bugs, or suggestions.
## Contributing
See [the contributing documentation](./CONTRIBUTING.md).

View file

@ -1,27 +0,0 @@
# Security Policy
## Reporting Security Issues
This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
To report a security issue, please contact the Stainless team at security@stainless.com.
## Responsible Disclosure
We appreciate the efforts of security researchers and individuals who help us maintain the security of
SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible
disclosure practices by allowing us a reasonable amount of time to investigate and address the issue
before making any information public.
## Reporting Non-SDK Related Security Issues
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.

View file

@ -1,207 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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"
)
// 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, query AgentListParams, opts ...option.RequestOption) (res *[]Agent, err error) {
opts = slices.Concat(r.Options, opts)
path := "agent"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
type Agent struct {
BuiltIn bool `json:"builtIn,required"`
Mode AgentMode `json:"mode,required"`
Name string `json:"name,required"`
Options map[string]interface{} `json:"options,required"`
Permission AgentPermission `json:"permission,required"`
Tools map[string]bool `json:"tools,required"`
Color string `json:"color"`
Description string `json:"description"`
Model AgentModel `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
TopP float64 `json:"topP"`
JSON agentJSON `json:"-"`
}
// agentJSON contains the JSON metadata for the struct [Agent]
type agentJSON struct {
BuiltIn apijson.Field
Mode apijson.Field
Name apijson.Field
Options apijson.Field
Permission apijson.Field
Tools apijson.Field
Color apijson.Field
Description apijson.Field
Model apijson.Field
Prompt apijson.Field
Temperature apijson.Field
TopP apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *Agent) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r agentJSON) RawJSON() string {
return r.raw
}
type AgentMode string
const (
AgentModeSubagent AgentMode = "subagent"
AgentModePrimary AgentMode = "primary"
AgentModeAll AgentMode = "all"
)
func (r AgentMode) IsKnown() bool {
switch r {
case AgentModeSubagent, AgentModePrimary, AgentModeAll:
return true
}
return false
}
type AgentPermission struct {
Bash map[string]AgentPermissionBash `json:"bash,required"`
Edit AgentPermissionEdit `json:"edit,required"`
Webfetch AgentPermissionWebfetch `json:"webfetch"`
JSON agentPermissionJSON `json:"-"`
}
// agentPermissionJSON contains the JSON metadata for the struct [AgentPermission]
type agentPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *AgentPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r agentPermissionJSON) RawJSON() string {
return r.raw
}
type AgentPermissionBash string
const (
AgentPermissionBashAsk AgentPermissionBash = "ask"
AgentPermissionBashAllow AgentPermissionBash = "allow"
AgentPermissionBashDeny AgentPermissionBash = "deny"
)
func (r AgentPermissionBash) IsKnown() bool {
switch r {
case AgentPermissionBashAsk, AgentPermissionBashAllow, AgentPermissionBashDeny:
return true
}
return false
}
type AgentPermissionEdit string
const (
AgentPermissionEditAsk AgentPermissionEdit = "ask"
AgentPermissionEditAllow AgentPermissionEdit = "allow"
AgentPermissionEditDeny AgentPermissionEdit = "deny"
)
func (r AgentPermissionEdit) IsKnown() bool {
switch r {
case AgentPermissionEditAsk, AgentPermissionEditAllow, AgentPermissionEditDeny:
return true
}
return false
}
type AgentPermissionWebfetch string
const (
AgentPermissionWebfetchAsk AgentPermissionWebfetch = "ask"
AgentPermissionWebfetchAllow AgentPermissionWebfetch = "allow"
AgentPermissionWebfetchDeny AgentPermissionWebfetch = "deny"
)
func (r AgentPermissionWebfetch) IsKnown() bool {
switch r {
case AgentPermissionWebfetchAsk, AgentPermissionWebfetchAllow, AgentPermissionWebfetchDeny:
return true
}
return false
}
type AgentModel struct {
ModelID string `json:"modelID,required"`
ProviderID string `json:"providerID,required"`
JSON agentModelJSON `json:"-"`
}
// agentModelJSON contains the JSON metadata for the struct [AgentModel]
type agentModelJSON struct {
ModelID apijson.Field
ProviderID apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *AgentModel) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r agentModelJSON) RawJSON() string {
return r.raw
}
type AgentListParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [AgentListParams]'s query parameters as `url.Values`.
func (r AgentListParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}

View file

@ -1,38 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestAgentListWithOptionalParams(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.Agent.List(context.TODO(), opencode.AgentListParams{
Directory: opencode.F("directory"),
})
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,46 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"github.com/sst/opencode-sdk-go/internal/apierror"
"github.com/sst/opencode-sdk-go/shared"
)
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 MessageAbortedErrorData = shared.MessageAbortedErrorData
// 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

View file

@ -1,194 +0,0 @@
# Shared Response Types
- <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/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/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>, 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#EventListParams">EventListParams</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>
# Path
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#Path">Path</a>
Methods:
- <code title="get /path">client.Path.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#PathService.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/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#PathGetParams">PathGetParams</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#Path">Path</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# App
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#Model">Model</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#Provider">Provider</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#AppProvidersResponse">AppProvidersResponse</a>
Methods:
- <code title="post /log">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Log">Log</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, params <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#AppLogParams">AppLogParams</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 /config/providers">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Providers">Providers</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#AppProvidersParams">AppProvidersParams</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#AppProvidersResponse">AppProvidersResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# Agent
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#Agent">Agent</a>
Methods:
- <code title="get /agent">client.Agent.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-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>, 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#AgentListParams">AgentListParams</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#Agent">Agent</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# Find
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#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/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/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/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>, 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#FileStatusParams">FileStatusParams</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>
# Config
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#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 /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>, 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#ConfigGetParams">ConfigGetParams</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>, 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#CommandListParams">CommandListParams</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>
# Project
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#Project">Project</a>
Methods:
- <code title="get /project">client.Project.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-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>, 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#ProjectListParams">ProjectListParams</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#Project">Project</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /project/current">client.Project.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ProjectService.Current">Current</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#ProjectCurrentParams">ProjectCurrentParams</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#Project">Project</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#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>
- <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#SessionPromptResponse">SessionPromptResponse</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>, params <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>, params <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>, 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#SessionListParams">SessionListParams</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>, 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#SessionDeleteParams">SessionDeleteParams</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>, 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#SessionAbortParams">SessionAbortParams</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/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>, 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#SessionChildrenParams">SessionChildrenParams</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>, params <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>, 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#SessionGetParams">SessionGetParams</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>, params <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>, 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#SessionMessageParams">SessionMessageParams</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>, 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#SessionMessagesParams">SessionMessagesParams</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}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Prompt">Prompt</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>, params <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#SessionPromptParams">SessionPromptParams</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#SessionPromptResponse">SessionPromptResponse</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>, params <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>, 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#SessionShareParams">SessionShareParams</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>, params <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>, params <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>, 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#SessionUnrevertParams">SessionUnrevertParams</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>, 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#SessionUnshareParams">SessionUnshareParams</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>, params <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/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>, params <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>, 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#TuiClearPromptParams">TuiClearPromptParams</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>, params <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>, 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#TuiOpenHelpParams">TuiOpenHelpParams</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>, 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#TuiOpenModelsParams">TuiOpenModelsParams</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>, 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#TuiOpenSessionsParams">TuiOpenSessionsParams</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>, 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#TuiOpenThemesParams">TuiOpenThemesParams</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>, params <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>, 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#TuiSubmitPromptParams">TuiSubmitPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>

View file

@ -1,345 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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"
)
// AppService 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 [NewAppService] method instead.
type AppService struct {
Options []option.RequestOption
}
// NewAppService 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 NewAppService(opts ...option.RequestOption) (r *AppService) {
r = &AppService{}
r.Options = opts
return
}
// Write a log entry to the server logs
func (r *AppService) Log(ctx context.Context, params AppLogParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "log"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// List all providers
func (r *AppService) Providers(ctx context.Context, query AppProvidersParams, opts ...option.RequestOption) (res *AppProvidersResponse, err error) {
opts = slices.Concat(r.Options, opts)
path := "config/providers"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
type Model struct {
ID string `json:"id,required"`
Attachment bool `json:"attachment,required"`
Cost ModelCost `json:"cost,required"`
Limit ModelLimit `json:"limit,required"`
Name string `json:"name,required"`
Options map[string]interface{} `json:"options,required"`
Reasoning bool `json:"reasoning,required"`
ReleaseDate string `json:"release_date,required"`
Temperature bool `json:"temperature,required"`
ToolCall bool `json:"tool_call,required"`
Experimental bool `json:"experimental"`
Modalities ModelModalities `json:"modalities"`
Provider ModelProvider `json:"provider"`
Status ModelStatus `json:"status"`
JSON modelJSON `json:"-"`
}
// modelJSON contains the JSON metadata for the struct [Model]
type modelJSON struct {
ID apijson.Field
Attachment apijson.Field
Cost apijson.Field
Limit apijson.Field
Name apijson.Field
Options apijson.Field
Reasoning apijson.Field
ReleaseDate apijson.Field
Temperature apijson.Field
ToolCall apijson.Field
Experimental apijson.Field
Modalities apijson.Field
Provider apijson.Field
Status apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *Model) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r modelJSON) RawJSON() string {
return r.raw
}
type ModelCost struct {
Input float64 `json:"input,required"`
Output float64 `json:"output,required"`
CacheRead float64 `json:"cache_read"`
CacheWrite float64 `json:"cache_write"`
JSON modelCostJSON `json:"-"`
}
// modelCostJSON contains the JSON metadata for the struct [ModelCost]
type modelCostJSON struct {
Input apijson.Field
Output apijson.Field
CacheRead apijson.Field
CacheWrite apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ModelCost) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r modelCostJSON) RawJSON() string {
return r.raw
}
type ModelLimit struct {
Context float64 `json:"context,required"`
Output float64 `json:"output,required"`
JSON modelLimitJSON `json:"-"`
}
// modelLimitJSON contains the JSON metadata for the struct [ModelLimit]
type modelLimitJSON struct {
Context apijson.Field
Output apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ModelLimit) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r modelLimitJSON) RawJSON() string {
return r.raw
}
type ModelModalities struct {
Input []ModelModalitiesInput `json:"input,required"`
Output []ModelModalitiesOutput `json:"output,required"`
JSON modelModalitiesJSON `json:"-"`
}
// modelModalitiesJSON contains the JSON metadata for the struct [ModelModalities]
type modelModalitiesJSON struct {
Input apijson.Field
Output apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ModelModalities) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r modelModalitiesJSON) RawJSON() string {
return r.raw
}
type ModelModalitiesInput string
const (
ModelModalitiesInputText ModelModalitiesInput = "text"
ModelModalitiesInputAudio ModelModalitiesInput = "audio"
ModelModalitiesInputImage ModelModalitiesInput = "image"
ModelModalitiesInputVideo ModelModalitiesInput = "video"
ModelModalitiesInputPdf ModelModalitiesInput = "pdf"
)
func (r ModelModalitiesInput) IsKnown() bool {
switch r {
case ModelModalitiesInputText, ModelModalitiesInputAudio, ModelModalitiesInputImage, ModelModalitiesInputVideo, ModelModalitiesInputPdf:
return true
}
return false
}
type ModelModalitiesOutput string
const (
ModelModalitiesOutputText ModelModalitiesOutput = "text"
ModelModalitiesOutputAudio ModelModalitiesOutput = "audio"
ModelModalitiesOutputImage ModelModalitiesOutput = "image"
ModelModalitiesOutputVideo ModelModalitiesOutput = "video"
ModelModalitiesOutputPdf ModelModalitiesOutput = "pdf"
)
func (r ModelModalitiesOutput) IsKnown() bool {
switch r {
case ModelModalitiesOutputText, ModelModalitiesOutputAudio, ModelModalitiesOutputImage, ModelModalitiesOutputVideo, ModelModalitiesOutputPdf:
return true
}
return false
}
type ModelProvider struct {
Npm string `json:"npm,required"`
JSON modelProviderJSON `json:"-"`
}
// modelProviderJSON contains the JSON metadata for the struct [ModelProvider]
type modelProviderJSON struct {
Npm apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ModelProvider) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r modelProviderJSON) RawJSON() string {
return r.raw
}
type ModelStatus string
const (
ModelStatusAlpha ModelStatus = "alpha"
ModelStatusBeta ModelStatus = "beta"
)
func (r ModelStatus) IsKnown() bool {
switch r {
case ModelStatusAlpha, ModelStatusBeta:
return true
}
return false
}
type Provider struct {
ID string `json:"id,required"`
Env []string `json:"env,required"`
Models map[string]Model `json:"models,required"`
Name string `json:"name,required"`
API string `json:"api"`
Npm string `json:"npm"`
JSON providerJSON `json:"-"`
}
// providerJSON contains the JSON metadata for the struct [Provider]
type providerJSON struct {
ID apijson.Field
Env apijson.Field
Models apijson.Field
Name apijson.Field
API apijson.Field
Npm apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *Provider) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r providerJSON) RawJSON() string {
return r.raw
}
type AppProvidersResponse struct {
Default map[string]string `json:"default,required"`
Providers []Provider `json:"providers,required"`
JSON appProvidersResponseJSON `json:"-"`
}
// appProvidersResponseJSON contains the JSON metadata for the struct
// [AppProvidersResponse]
type appProvidersResponseJSON struct {
Default apijson.Field
Providers apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *AppProvidersResponse) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r appProvidersResponseJSON) RawJSON() string {
return r.raw
}
type AppLogParams struct {
// Log level
Level param.Field[AppLogParamsLevel] `json:"level,required"`
// Log message
Message param.Field[string] `json:"message,required"`
// Service name for the log entry
Service param.Field[string] `json:"service,required"`
Directory param.Field[string] `query:"directory"`
// Additional metadata for the log entry
Extra param.Field[map[string]interface{}] `json:"extra"`
}
func (r AppLogParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
// URLQuery serializes [AppLogParams]'s query parameters as `url.Values`.
func (r AppLogParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
// Log level
type AppLogParamsLevel string
const (
AppLogParamsLevelDebug AppLogParamsLevel = "debug"
AppLogParamsLevelInfo AppLogParamsLevel = "info"
AppLogParamsLevelError AppLogParamsLevel = "error"
AppLogParamsLevelWarn AppLogParamsLevel = "warn"
)
func (r AppLogParamsLevel) IsKnown() bool {
switch r {
case AppLogParamsLevelDebug, AppLogParamsLevelInfo, AppLogParamsLevelError, AppLogParamsLevelWarn:
return true
}
return false
}
type AppProvidersParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [AppProvidersParams]'s query parameters as `url.Values`.
func (r AppProvidersParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}

View file

@ -1,68 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestAppLogWithOptionalParams(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.App.Log(context.TODO(), opencode.AppLogParams{
Level: opencode.F(opencode.AppLogParamsLevelDebug),
Message: opencode.F("message"),
Service: opencode.F("service"),
Directory: opencode.F("directory"),
Extra: opencode.F(map[string]interface{}{
"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())
}
}
func TestAppProvidersWithOptionalParams(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.App.Providers(context.TODO(), opencode.AppProvidersParams{
Directory: opencode.F("directory"),
})
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,134 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"os"
"slices"
"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
// interacting with the opencode API. You should not instantiate this client
// directly, and instead use the [NewClient] method instead.
type Client struct {
Options []option.RequestOption
Event *EventService
Path *PathService
App *AppService
Agent *AgentService
Find *FindService
File *FileService
Config *ConfigService
Command *CommandService
Project *ProjectService
Session *SessionService
Tui *TuiService
}
// 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))
}
return defaults
}
// NewClient generates a new client with the default option read from the
// 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.Event = NewEventService(opts...)
r.Path = NewPathService(opts...)
r.App = NewAppService(opts...)
r.Agent = NewAgentService(opts...)
r.Find = NewFindService(opts...)
r.File = NewFileService(opts...)
r.Config = NewConfigService(opts...)
r.Command = NewCommandService(opts...)
r.Project = NewProjectService(opts...)
r.Session = NewSessionService(opts...)
r.Tui = NewTuiService(opts...)
return
}
// Execute makes a request with the given context, method, URL, request params,
// response, and request options. This is useful for hitting undocumented endpoints
// while retaining the base URL, auth, retries, and other options from the client.
//
// If a byte slice or an [io.Reader] is supplied to params, it will be used as-is
// for the request body.
//
// The params is by default serialized into the body using [encoding/json]. If your
// type implements a MarshalJSON function, it will be used instead to serialize the
// request. If a URLQuery method is implemented, the returned [url.Values] will be
// used as query strings to the url.
//
// If your params struct uses [param.Field], you must provide either [MarshalJSON],
// [URLQuery], and/or [MarshalForm] functions. It is undefined behavior to use a
// struct uses [param.Field] without specifying how it is serialized.
//
// Any "…Params" object defined in this library can be used as the request
// argument. Note that 'path' arguments will not be forwarded into the url.
//
// The response body will be deserialized into the res variable, depending on its
// type:
//
// - A pointer to a [*http.Response] is populated by the raw response.
// - A pointer to a byte array will be populated with the contents of the request
// body.
// - A pointer to any other type uses this library's default JSON decoding, which
// respects UnmarshalJSON if it is defined on the type.
// - A nil value will not read the response body.
//
// For even greater flexibility, see [option.WithResponseInto] and
// [option.WithResponseBodyInto].
func (r *Client) Execute(ctx context.Context, method string, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
opts = slices.Concat(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 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 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 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 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 interface{}, res interface{}, opts ...option.RequestOption) error {
return r.Execute(ctx, http.MethodDelete, path, params, res, opts...)
}

View file

@ -1,336 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode_test
import (
"context"
"fmt"
"io"
"net/http"
"reflect"
"testing"
"time"
"github.com/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal"
"github.com/sst/opencode-sdk-go/option"
)
type closureTransport struct {
fn func(req *http.Request) (*http.Response, error)
}
func (t *closureTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.fn(req)
}
func TestUserAgentHeader(t *testing.T) {
var userAgent string
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
userAgent = req.Header.Get("User-Agent")
return &http.Response{
StatusCode: http.StatusOK,
}, nil
},
},
}),
)
client.Session.List(context.Background(), opencode.SessionListParams{})
if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) {
t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent)
}
}
func TestRetryAfter(t *testing.T) {
retryCountHeaders := make([]string, 0)
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
return &http.Response{
StatusCode: http.StatusTooManyRequests,
Header: http.Header{
http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
},
}, nil
},
},
}),
)
_, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
attempts := len(retryCountHeaders)
if attempts != 3 {
t.Errorf("Expected %d attempts, got %d", 3, attempts)
}
expectedRetryCountHeaders := []string{"0", "1", "2"}
if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
}
}
func TestDeleteRetryCountHeader(t *testing.T) {
retryCountHeaders := make([]string, 0)
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
return &http.Response{
StatusCode: http.StatusTooManyRequests,
Header: http.Header{
http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
},
}, nil
},
},
}),
option.WithHeaderDel("X-Stainless-Retry-Count"),
)
_, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
expectedRetryCountHeaders := []string{"", "", ""}
if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
}
}
func TestOverwriteRetryCountHeader(t *testing.T) {
retryCountHeaders := make([]string, 0)
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
return &http.Response{
StatusCode: http.StatusTooManyRequests,
Header: http.Header{
http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
},
}, nil
},
},
}),
option.WithHeader("X-Stainless-Retry-Count", "42"),
)
_, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
expectedRetryCountHeaders := []string{"42", "42", "42"}
if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
}
}
func TestRetryAfterMs(t *testing.T) {
attempts := 0
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
attempts++
return &http.Response{
StatusCode: http.StatusTooManyRequests,
Header: http.Header{
http.CanonicalHeaderKey("Retry-After-Ms"): []string{"100"},
},
}, nil
},
},
}),
)
_, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
if want := 3; attempts != want {
t.Errorf("Expected %d attempts, got %d", want, attempts)
}
}
func TestContextCancel(t *testing.T) {
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
<-req.Context().Done()
return nil, req.Context().Err()
},
},
}),
)
cancelCtx, cancel := context.WithCancel(context.Background())
cancel()
_, err := client.Session.List(cancelCtx, opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
}
func TestContextCancelDelay(t *testing.T) {
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
<-req.Context().Done()
return nil, req.Context().Err()
},
},
}),
)
cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
defer cancel()
_, err := client.Session.List(cancelCtx, opencode.SessionListParams{})
if err == nil {
t.Error("expected there to be a cancel error")
}
}
func TestContextDeadline(t *testing.T) {
testTimeout := time.After(3 * time.Second)
testDone := make(chan struct{})
deadline := time.Now().Add(100 * time.Millisecond)
deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
go func() {
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
<-req.Context().Done()
return nil, req.Context().Err()
},
},
}),
)
_, err := client.Session.List(deadlineCtx, opencode.SessionListParams{})
if err == nil {
t.Error("expected there to be a deadline error")
}
close(testDone)
}()
select {
case <-testTimeout:
t.Fatal("client didn't finish in time")
case <-testDone:
if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
}
}
}
func TestContextDeadlineStreaming(t *testing.T) {
testTimeout := time.After(3 * time.Second)
testDone := make(chan struct{})
deadline := time.Now().Add(100 * time.Millisecond)
deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
go func() {
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
Status: "200 OK",
Body: io.NopCloser(
io.Reader(readerFunc(func([]byte) (int, error) {
<-req.Context().Done()
return 0, req.Context().Err()
})),
),
}, nil
},
},
}),
)
stream := client.Event.ListStreaming(deadlineCtx, opencode.EventListParams{})
for stream.Next() {
_ = stream.Current()
}
if stream.Err() == nil {
t.Error("expected there to be a deadline error")
}
close(testDone)
}()
select {
case <-testTimeout:
t.Fatal("client didn't finish in time")
case <-testDone:
if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
}
}
}
func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) {
testTimeout := time.After(3 * time.Second)
testDone := make(chan struct{})
deadline := time.Now().Add(100 * time.Millisecond)
go func() {
client := opencode.NewClient(
option.WithHTTPClient(&http.Client{
Transport: &closureTransport{
fn: func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
Status: "200 OK",
Body: io.NopCloser(
io.Reader(readerFunc(func([]byte) (int, error) {
<-req.Context().Done()
return 0, req.Context().Err()
})),
),
}, nil
},
},
}),
)
stream := client.Event.ListStreaming(
context.Background(),
opencode.EventListParams{},
option.WithRequestTimeout((100 * time.Millisecond)),
)
for stream.Next() {
_ = stream.Current()
}
if stream.Err() == nil {
t.Error("expected there to be a deadline error")
}
close(testDone)
}()
select {
case <-testTimeout:
t.Fatal("client didn't finish in time")
case <-testDone:
if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
}
}
}
type readerFunc func([]byte) (int, error)
func (f readerFunc) Read(p []byte) (int, error) { return f(p) }
func (f readerFunc) Close() error { return nil }

View file

@ -1,85 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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"
)
// CommandService 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 [NewCommandService] method instead.
type CommandService struct {
Options []option.RequestOption
}
// 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{}
r.Options = opts
return
}
// List all commands
func (r *CommandService) List(ctx context.Context, query CommandListParams, opts ...option.RequestOption) (res *[]Command, err error) {
opts = slices.Concat(r.Options, opts)
path := "command"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
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"`
Subtask bool `json:"subtask"`
JSON commandJSON `json:"-"`
}
// 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
Subtask 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
}
type CommandListParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [CommandListParams]'s query parameters as `url.Values`.
func (r CommandListParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}

View file

@ -1,38 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestCommandListWithOptionalParams(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.Command.List(context.TODO(), opencode.CommandListParams{
Directory: opencode.F("directory"),
})
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,38 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestConfigGetWithOptionalParams(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.Config.Get(context.TODO(), opencode.ConfigGetParams{
Directory: opencode.F("directory"),
})
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,4 +0,0 @@
File generated from our OpenAPI spec by Stainless.
This directory can be used to store example files demonstrating usage of this SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

View file

@ -1,50 +0,0 @@
package opencode
import (
"github.com/sst/opencode-sdk-go/internal/param"
"io"
)
// 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} }
// 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} }
// 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} }
// 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 {
io.Reader
name string
contentType string
}
func (f *file) ContentType() string { return f.contentType }
func (f *file) Filename() string { return f.name }

View file

@ -1,301 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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
// 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 [NewFileService] method instead.
type FileService struct {
Options []option.RequestOption
}
// 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{}
r.Options = opts
return
}
// List files and directories
func (r *FileService) List(ctx context.Context, query FileListParams, opts ...option.RequestOption) (res *[]FileNode, err error) {
opts = slices.Concat(r.Options, opts)
path := "file"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Read a file
func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...option.RequestOption) (res *FileReadResponse, err error) {
opts = slices.Concat(r.Options, opts)
path := "file/content"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Get file status
func (r *FileService) Status(ctx context.Context, query FileStatusParams, opts ...option.RequestOption) (res *[]File, err error) {
opts = slices.Concat(r.Options, opts)
path := "file/status"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
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)
}
func (r fileJSON) RawJSON() string {
return r.raw
}
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 {
Absolute string `json:"absolute,required"`
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 {
Absolute apijson.Field
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)
}
func (r fileNodeJSON) RawJSON() string {
return r.raw
}
type FileNodeType string
const (
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"`
Type FileReadResponseType `json:"type,required"`
Diff string `json:"diff"`
Encoding FileReadResponseEncoding `json:"encoding"`
MimeType string `json:"mimeType"`
Patch FileReadResponsePatch `json:"patch"`
JSON fileReadResponseJSON `json:"-"`
}
// fileReadResponseJSON contains the JSON metadata for the struct
// [FileReadResponse]
type fileReadResponseJSON struct {
Content apijson.Field
Type apijson.Field
Diff apijson.Field
Encoding apijson.Field
MimeType apijson.Field
Patch 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 (
FileReadResponseTypeText FileReadResponseType = "text"
)
func (r FileReadResponseType) IsKnown() bool {
switch r {
case FileReadResponseTypeText:
return true
}
return false
}
type FileReadResponseEncoding string
const (
FileReadResponseEncodingBase64 FileReadResponseEncoding = "base64"
)
func (r FileReadResponseEncoding) IsKnown() bool {
switch r {
case FileReadResponseEncodingBase64:
return true
}
return false
}
type FileReadResponsePatch struct {
Hunks []FileReadResponsePatchHunk `json:"hunks,required"`
NewFileName string `json:"newFileName,required"`
OldFileName string `json:"oldFileName,required"`
Index string `json:"index"`
NewHeader string `json:"newHeader"`
OldHeader string `json:"oldHeader"`
JSON fileReadResponsePatchJSON `json:"-"`
}
// fileReadResponsePatchJSON contains the JSON metadata for the struct
// [FileReadResponsePatch]
type fileReadResponsePatchJSON struct {
Hunks apijson.Field
NewFileName apijson.Field
OldFileName apijson.Field
Index apijson.Field
NewHeader apijson.Field
OldHeader apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *FileReadResponsePatch) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r fileReadResponsePatchJSON) RawJSON() string {
return r.raw
}
type FileReadResponsePatchHunk struct {
Lines []string `json:"lines,required"`
NewLines float64 `json:"newLines,required"`
NewStart float64 `json:"newStart,required"`
OldLines float64 `json:"oldLines,required"`
OldStart float64 `json:"oldStart,required"`
JSON fileReadResponsePatchHunkJSON `json:"-"`
}
// fileReadResponsePatchHunkJSON contains the JSON metadata for the struct
// [FileReadResponsePatchHunk]
type fileReadResponsePatchHunkJSON struct {
Lines apijson.Field
NewLines apijson.Field
NewStart apijson.Field
OldLines apijson.Field
OldStart apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *FileReadResponsePatchHunk) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r fileReadResponsePatchHunkJSON) RawJSON() string {
return r.raw
}
type FileListParams struct {
Path param.Field[string] `query:"path,required"`
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [FileListParams]'s query parameters as `url.Values`.
func (r FileListParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type FileReadParams struct {
Path param.Field[string] `query:"path,required"`
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [FileReadParams]'s query parameters as `url.Values`.
func (r FileReadParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type FileStatusParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [FileStatusParams]'s query parameters as `url.Values`.
func (r FileStatusParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}

View file

@ -1,88 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestFileListWithOptionalParams(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.List(context.TODO(), opencode.FileListParams{
Path: opencode.F("path"),
Directory: opencode.F("directory"),
})
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 TestFileReadWithOptionalParams(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.Read(context.TODO(), opencode.FileReadParams{
Path: opencode.F("path"),
Directory: opencode.F("directory"),
})
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 TestFileStatusWithOptionalParams(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(), opencode.FileStatusParams{
Directory: opencode.F("directory"),
})
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,330 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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
// 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 [NewFindService] method instead.
type FindService struct {
Options []option.RequestOption
}
// 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{}
r.Options = opts
return
}
// Find files
func (r *FindService) Files(ctx context.Context, query FindFilesParams, opts ...option.RequestOption) (res *[]string, err error) {
opts = slices.Concat(r.Options, opts)
path := "find/file"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Find workspace symbols
func (r *FindService) Symbols(ctx context.Context, query FindSymbolsParams, opts ...option.RequestOption) (res *[]Symbol, err error) {
opts = slices.Concat(r.Options, opts)
path := "find/symbol"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Find text in files
func (r *FindService) Text(ctx context.Context, query FindTextParams, opts ...option.RequestOption) (res *[]FindTextResponse, err error) {
opts = slices.Concat(r.Options, opts)
path := "find"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
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)
}
func (r symbolJSON) RawJSON() string {
return r.raw
}
type SymbolLocation struct {
Range SymbolLocationRange `json:"range,required"`
Uri string `json:"uri,required"`
JSON symbolLocationJSON `json:"-"`
}
// 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)
}
func (r symbolLocationJSON) RawJSON() string {
return r.raw
}
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)
}
func (r symbolLocationRangeJSON) RawJSON() string {
return r.raw
}
type SymbolLocationRangeEnd struct {
Character float64 `json:"character,required"`
Line float64 `json:"line,required"`
JSON symbolLocationRangeEndJSON `json:"-"`
}
// 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)
}
func (r symbolLocationRangeEndJSON) RawJSON() string {
return r.raw
}
type SymbolLocationRangeStart struct {
Character float64 `json:"character,required"`
Line float64 `json:"line,required"`
JSON symbolLocationRangeStartJSON `json:"-"`
}
// 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)
}
func (r symbolLocationRangeStartJSON) RawJSON() string {
return r.raw
}
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:"-"`
}
// 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)
}
func (r findTextResponseJSON) RawJSON() string {
return r.raw
}
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)
}
func (r findTextResponseLinesJSON) RawJSON() string {
return r.raw
}
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)
}
func (r findTextResponsePathJSON) RawJSON() string {
return r.raw
}
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)
}
func (r findTextResponseSubmatchJSON) RawJSON() string {
return r.raw
}
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)
}
func (r findTextResponseSubmatchesMatchJSON) RawJSON() string {
return r.raw
}
type FindFilesParams struct {
Query param.Field[string] `query:"query,required"`
Directory param.Field[string] `query:"directory"`
}
// 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 FindSymbolsParams struct {
Query param.Field[string] `query:"query,required"`
Directory param.Field[string] `query:"directory"`
}
// 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 FindTextParams struct {
Pattern param.Field[string] `query:"pattern,required"`
Directory param.Field[string] `query:"directory"`
}
// 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

@ -1,89 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestFindFilesWithOptionalParams(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.Find.Files(context.TODO(), opencode.FindFilesParams{
Query: opencode.F("query"),
Directory: opencode.F("directory"),
})
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 TestFindSymbolsWithOptionalParams(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.Find.Symbols(context.TODO(), opencode.FindSymbolsParams{
Query: opencode.F("query"),
Directory: opencode.F("directory"),
})
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 TestFindTextWithOptionalParams(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.Find.Text(context.TODO(), opencode.FindTextParams{
Pattern: opencode.F("pattern"),
Directory: opencode.F("directory"),
})
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,13 +0,0 @@
module github.com/sst/opencode-sdk-go
go 1.22
require (
github.com/tidwall/gjson v1.14.4
github.com/tidwall/sjson v1.2.5
)
require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
)

View file

@ -1,10 +0,0 @@
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=

View file

@ -1,53 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package apierror
import (
"fmt"
"net/http"
"net/http/httputil"
"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 errorJSON `json:"-"`
StatusCode int
Request *http.Request
Response *http.Response
}
// 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 \"%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 {
if r.Request.GetBody != nil {
r.Request.Body, _ = r.Request.GetBody()
}
out, _ := httputil.DumpRequestOut(r.Request, body)
return out
}
func (r *Error) DumpResponse(body bool) []byte {
out, _ := httputil.DumpResponse(r.Response, body)
return out
}

View file

@ -1,383 +0,0 @@
package apiform
import (
"fmt"
"io"
"mime/multipart"
"net/textproto"
"path"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/sst/opencode-sdk-go/internal/param"
)
var encoders sync.Map // map[encoderEntry]encoderFunc
func Marshal(value interface{}, writer *multipart.Writer) error {
e := &encoder{dateFormat: time.RFC3339}
return e.marshal(value, writer)
}
func MarshalRoot(value interface{}, writer *multipart.Writer) error {
e := &encoder{root: true, dateFormat: time.RFC3339}
return e.marshal(value, writer)
}
type encoder struct {
dateFormat string
root bool
}
type encoderFunc func(key string, value reflect.Value, writer *multipart.Writer) error
type encoderField struct {
tag parsedStructTag
fn encoderFunc
idx []int
}
type encoderEntry struct {
reflect.Type
dateFormat string
root bool
}
func (e *encoder) marshal(value interface{}, writer *multipart.Writer) error {
val := reflect.ValueOf(value)
if !val.IsValid() {
return nil
}
typ := val.Type()
enc := e.typeEncoder(typ)
return enc("", val, writer)
}
func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
dateFormat: e.dateFormat,
root: e.root,
}
if fi, ok := encoders.Load(entry); ok {
return fi.(encoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f encoderFunc
)
wg.Add(1)
fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value, writer *multipart.Writer) error {
wg.Wait()
return f(key, v, writer)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = e.newTypeEncoder(t)
wg.Done()
encoders.Store(entry, f)
return f
}
func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return e.newTimeTypeEncoder()
}
if t.ConvertibleTo(reflect.TypeOf((*io.Reader)(nil)).Elem()) {
return e.newReaderTypeEncoder()
}
e.root = false
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerEncoder := e.typeEncoder(inner)
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if !v.IsValid() || v.IsNil() {
return nil
}
return innerEncoder(key, v.Elem(), writer)
}
case reflect.Struct:
return e.newStructTypeEncoder(t)
case reflect.Slice, reflect.Array:
return e.newArrayTypeEncoder(t)
case reflect.Map:
return e.newMapEncoder(t)
case reflect.Interface:
return e.newInterfaceEncoder()
default:
return e.newPrimitiveTypeEncoder(t)
}
}
func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
switch t.Kind() {
// Note that we could use `gjson` to encode these types but it would complicate our
// code more and this current code shouldn't cause any issues
case reflect.String:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, v.String())
}
case reflect.Bool:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if v.Bool() {
return writer.WriteField(key, "true")
}
return writer.WriteField(key, "false")
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatInt(v.Int(), 10))
}
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatUint(v.Uint(), 10))
}
case reflect.Float32:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 32))
}
case reflect.Float64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
}
default:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return fmt.Errorf("unknown type received at primitive encoder: %s", t.String())
}
}
}
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
itemEncoder := e.typeEncoder(t.Elem())
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if key != "" {
key = key + "."
}
for i := 0; i < v.Len(); i++ {
err := itemEncoder(key+strconv.Itoa(i), v.Index(i), writer)
if err != nil {
return err
}
}
return nil
}
}
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)
// This helper allows us to recursively collect field encoders into a flat
// array. The parameter `index` keeps track of the access patterns necessary
// to get to some field.
var collectEncoderFields func(r reflect.Type, index []int)
collectEncoderFields = func(r reflect.Type, index []int) {
for i := 0; i < r.NumField(); i++ {
idx := append(index, i)
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the field and get their encoders as well.
if field.Anonymous {
collectEncoderFields(field.Type, idx)
continue
}
// If json tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseFormStructTag(field)
if !ok {
continue
}
// We only want to support unexported field if they're tagged with
// `extras` because that field shouldn't be part of the public API. We
// also want to only keep the top level extras
if ptag.extras && len(index) == 0 {
extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx}
continue
}
if ptag.name == "-" {
continue
}
dateFormat, ok := parseFormatStructTag(field)
oldFormat := e.dateFormat
if ok {
switch dateFormat {
case "date-time":
e.dateFormat = time.RFC3339
case "date":
e.dateFormat = "2006-01-02"
}
}
encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx})
e.dateFormat = oldFormat
}
}
collectEncoderFields(t, []int{})
// Ensure deterministic output by sorting by lexicographic order
sort.Slice(encoderFields, func(i, j int) bool {
return encoderFields[i].tag.name < encoderFields[j].tag.name
})
return func(key string, value reflect.Value, writer *multipart.Writer) error {
if key != "" {
key = key + "."
}
for _, ef := range encoderFields {
field := value.FieldByIndex(ef.idx)
err := ef.fn(key+ef.tag.name, field, writer)
if err != nil {
return err
}
}
if extraEncoder != nil {
err := e.encodeMapEntries(key, value.FieldByIndex(extraEncoder.idx), writer)
if err != nil {
return err
}
}
return nil
}
}
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 {
present := value.FieldByName("Present")
if !present.Bool() {
return nil
}
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)
}
}
func (e *encoder) newTimeTypeEncoder() encoderFunc {
format := e.dateFormat
return func(key string, value reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format))
}
}
func (e encoder) newInterfaceEncoder() encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
value = value.Elem()
if !value.IsValid() {
return nil
}
return e.typeEncoder(value.Type())(key, value, writer)
}
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
func (e *encoder) newReaderTypeEncoder() encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
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 {
filename = named.Filename()
} else if named, ok := reader.(interface{ Name() string }); ok {
filename = path.Base(named.Name())
}
if typed, ok := reader.(interface{ ContentType() string }); ok {
contentType = typed.ContentType()
}
// Below is taken almost 1-for-1 from [multipart.CreateFormFile]
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(key), escapeQuotes(filename)))
h.Set("Content-Type", contentType)
filewriter, err := writer.CreatePart(h)
if err != nil {
return err
}
_, err = io.Copy(filewriter, reader)
return err
}
}
// Given a []byte of json (may either be an empty object or an object that already contains entries)
// encode all of the entries in the map to the json byte array.
func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipart.Writer) error {
type mapPair struct {
key string
value reflect.Value
}
if key != "" {
key = key + "."
}
pairs := []mapPair{}
iter := v.MapRange()
for iter.Next() {
if iter.Key().Type().Kind() == reflect.String {
pairs = append(pairs, mapPair{key: iter.Key().String(), value: iter.Value()})
} else {
return fmt.Errorf("cannot encode a map with a non string key")
}
}
// Ensure deterministic output
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].key < pairs[j].key
})
elementEncoder := e.typeEncoder(v.Type().Elem())
for _, p := range pairs {
err := elementEncoder(key+string(p.key), p.value, writer)
if err != nil {
return err
}
}
return nil
}
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)
}
}

View file

@ -1,5 +0,0 @@
package apiform
type Marshaler interface {
MarshalMultipart() ([]byte, string, error)
}

View file

@ -1,440 +0,0 @@
package apiform
import (
"bytes"
"mime/multipart"
"strings"
"testing"
"time"
)
func P[T any](v T) *T { return &v }
type Primitives struct {
A bool `form:"a"`
B int `form:"b"`
C uint `form:"c"`
D float64 `form:"d"`
E float32 `form:"e"`
F []int `form:"f"`
}
type PrimitivePointers struct {
A *bool `form:"a"`
B *int `form:"b"`
C *uint `form:"c"`
D *float64 `form:"d"`
E *float32 `form:"e"`
F *[]int `form:"f"`
}
type Slices struct {
Slice []Primitives `form:"slices"`
}
type DateTime struct {
Date time.Time `form:"date" format:"date"`
DateTime time.Time `form:"date-time" format:"date-time"`
}
type AdditionalProperties struct {
A bool `form:"a"`
Extras map[string]interface{} `form:"-,extras"`
}
type TypedAdditionalProperties struct {
A bool `form:"a"`
Extras map[string]int `form:"-,extras"`
}
type EmbeddedStructs struct {
AdditionalProperties
A *int `form:"number2"`
Extras map[string]interface{} `form:"-,extras"`
}
type Recursive struct {
Name string `form:"name"`
Child *Recursive `form:"child"`
}
type UnknownStruct struct {
Unknown interface{} `form:"unknown"`
}
type UnionStruct struct {
Union Union `form:"union" format:"date"`
}
type Union interface {
union()
}
type UnionInteger int64
func (UnionInteger) union() {}
type UnionStructA struct {
Type string `form:"type"`
A string `form:"a"`
B string `form:"b"`
}
func (UnionStructA) union() {}
type UnionStructB struct {
Type string `form:"type"`
A string `form:"a"`
}
func (UnionStructB) union() {}
type UnionTime time.Time
func (UnionTime) union() {}
type ReaderStruct struct {
}
var tests = map[string]struct {
buf string
val interface{}
}{
"map_string": {
`--xxx
Content-Disposition: form-data; name="foo"
bar
--xxx--
`,
map[string]string{"foo": "bar"},
},
"map_interface": {
`--xxx
Content-Disposition: form-data; name="a"
1
--xxx
Content-Disposition: form-data; name="b"
str
--xxx
Content-Disposition: form-data; name="c"
false
--xxx--
`,
map[string]interface{}{"a": float64(1), "b": "str", "c": false},
},
"primitive_struct": {
`--xxx
Content-Disposition: form-data; name="a"
false
--xxx
Content-Disposition: form-data; name="b"
237628372683
--xxx
Content-Disposition: form-data; name="c"
654
--xxx
Content-Disposition: form-data; name="d"
9999.43
--xxx
Content-Disposition: form-data; name="e"
43.76
--xxx
Content-Disposition: form-data; name="f.0"
1
--xxx
Content-Disposition: form-data; name="f.1"
2
--xxx
Content-Disposition: form-data; name="f.2"
3
--xxx
Content-Disposition: form-data; name="f.3"
4
--xxx--
`,
Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
},
"slices": {
`--xxx
Content-Disposition: form-data; name="slices.0.a"
false
--xxx
Content-Disposition: form-data; name="slices.0.b"
237628372683
--xxx
Content-Disposition: form-data; name="slices.0.c"
654
--xxx
Content-Disposition: form-data; name="slices.0.d"
9999.43
--xxx
Content-Disposition: form-data; name="slices.0.e"
43.76
--xxx
Content-Disposition: form-data; name="slices.0.f.0"
1
--xxx
Content-Disposition: form-data; name="slices.0.f.1"
2
--xxx
Content-Disposition: form-data; name="slices.0.f.2"
3
--xxx
Content-Disposition: form-data; name="slices.0.f.3"
4
--xxx--
`,
Slices{
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"
false
--xxx
Content-Disposition: form-data; name="b"
237628372683
--xxx
Content-Disposition: form-data; name="c"
654
--xxx
Content-Disposition: form-data; name="d"
9999.43
--xxx
Content-Disposition: form-data; name="e"
43.76
--xxx
Content-Disposition: form-data; name="f.0"
1
--xxx
Content-Disposition: form-data; name="f.1"
2
--xxx
Content-Disposition: form-data; name="f.2"
3
--xxx
Content-Disposition: form-data; name="f.3"
4
--xxx
Content-Disposition: form-data; name="f.4"
5
--xxx--
`,
PrimitivePointers{
A: P(false),
B: P(237628372683),
C: P(uint(654)),
D: P(9999.43),
E: P(float32(43.76)),
F: &[]int{1, 2, 3, 4, 5},
},
},
"datetime_struct": {
`--xxx
Content-Disposition: form-data; name="date"
2006-01-02
--xxx
Content-Disposition: form-data; name="date-time"
2006-01-02T15:04:05Z
--xxx--
`,
DateTime{
Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC),
DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC),
},
},
"additional_properties": {
`--xxx
Content-Disposition: form-data; name="a"
true
--xxx
Content-Disposition: form-data; name="bar"
value
--xxx
Content-Disposition: form-data; name="foo"
true
--xxx--
`,
AdditionalProperties{
A: true,
Extras: map[string]interface{}{
"bar": "value",
"foo": true,
},
},
},
"recursive_struct": {
`--xxx
Content-Disposition: form-data; name="child.name"
Alex
--xxx
Content-Disposition: form-data; name="name"
Robert
--xxx--
`,
Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}},
},
"unknown_struct_number": {
`--xxx
Content-Disposition: form-data; name="unknown"
12
--xxx--
`,
UnknownStruct{
Unknown: 12.,
},
},
"unknown_struct_map": {
`--xxx
Content-Disposition: form-data; name="unknown.foo"
bar
--xxx--
`,
UnknownStruct{
Unknown: map[string]interface{}{
"foo": "bar",
},
},
},
"union_integer": {
`--xxx
Content-Disposition: form-data; name="union"
12
--xxx--
`,
UnionStruct{
Union: UnionInteger(12),
},
},
"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--
`,
UnionStruct{
Union: UnionStructA{
Type: "typeA",
A: "foo",
B: "bar",
},
},
},
"union_struct_discriminated_b": {
`--xxx
Content-Disposition: form-data; name="union.a"
foo
--xxx
Content-Disposition: form-data; name="union.type"
typeB
--xxx--
`,
UnionStruct{
Union: UnionStructB{
Type: "typeB",
A: "foo",
},
},
},
"union_struct_time": {
`--xxx
Content-Disposition: form-data; name="union"
2010-05-23
--xxx--
`,
UnionStruct{
Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)),
},
},
}
func TestEncode(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
buf := bytes.NewBuffer(nil)
writer := multipart.NewWriter(buf)
writer.SetBoundary("xxx")
err := Marshal(test.val, writer)
if err != nil {
t.Errorf("serialization of %v failed with error %v", test.val, err)
}
err = writer.Close()
if err != nil {
t.Errorf("serialization of %v failed with error %v", test.val, err)
}
raw := buf.Bytes()
if string(raw) != strings.ReplaceAll(test.buf, "\n", "\r\n") {
t.Errorf("expected %+#v to serialize to '%s' but got '%s'", test.val, test.buf, string(raw))
}
})
}
}

View file

@ -1,48 +0,0 @@
package apiform
import (
"reflect"
"strings"
)
const jsonStructTag = "json"
const formStructTag = "form"
const formatStructTag = "format"
type parsedStructTag struct {
name string
required bool
extras bool
metadata bool
}
func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(formStructTag)
if !ok {
raw, ok = field.Tag.Lookup(jsonStructTag)
}
if !ok {
return
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
return tag, false
}
tag.name = parts[0]
for _, part := range parts[1:] {
switch part {
case "required":
tag.required = true
case "extras":
tag.extras = true
case "metadata":
tag.metadata = true
}
}
return
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
return
}

View file

@ -1,670 +0,0 @@
package apijson
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"sync"
"time"
"unsafe"
"github.com/tidwall/gjson"
)
// decoders is a synchronized map with roughly the following type:
// map[reflect.Type]decoderFunc
var decoders sync.Map
// Unmarshal is similar to [encoding/json.Unmarshal] and parses the JSON-encoded
// data and stores it in the given pointer.
func Unmarshal(raw []byte, to any) error {
d := &decoderBuilder{dateFormat: time.RFC3339}
return d.unmarshal(raw, to)
}
// UnmarshalRoot is like Unmarshal, but doesn't try to call MarshalJSON on the
// root element. Useful if a struct's UnmarshalJSON is overrode to use the
// behavior of this encoder versus the standard library.
func UnmarshalRoot(raw []byte, to any) error {
d := &decoderBuilder{dateFormat: time.RFC3339, root: true}
return d.unmarshal(raw, to)
}
// decoderBuilder contains the 'compile-time' state of the decoder.
type decoderBuilder struct {
// Whether or not this is the first element and called by [UnmarshalRoot], see
// the documentation there to see why this is necessary.
root bool
// The dateFormat (a format string for [time.Format]) which is chosen by the
// last struct tag that was seen.
dateFormat string
}
// decoderState contains the 'run-time' state of the decoder.
type decoderState struct {
strict bool
exactness exactness
}
// Exactness refers to how close to the type the result was if deserialization
// was successful. This is useful in deserializing unions, where you want to try
// each entry, first with strict, then with looser validation, without actually
// having to do a lot of redundant work by marshalling twice (or maybe even more
// times).
type exactness int8
const (
// Some values had to fudged a bit, for example by converting a string to an
// int, or an enum with extra values.
loose exactness = iota
// There are some extra arguments, but other wise it matches the union.
extras
// Exactly right.
exact
)
type decoderFunc func(node gjson.Result, value reflect.Value, state *decoderState) error
type decoderField struct {
tag parsedStructTag
fn decoderFunc
idx []int
goname string
}
type decoderEntry struct {
reflect.Type
dateFormat string
root bool
}
func (d *decoderBuilder) unmarshal(raw []byte, to any) error {
value := reflect.ValueOf(to).Elem()
result := gjson.ParseBytes(raw)
if !value.IsValid() {
return fmt.Errorf("apijson: cannot marshal into invalid value")
}
return d.typeDecoder(value.Type())(result, value, &decoderState{strict: false, exactness: exact})
}
func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc {
entry := decoderEntry{
Type: t,
dateFormat: d.dateFormat,
root: d.root,
}
if fi, ok := decoders.Load(entry); ok {
return fi.(decoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f decoderFunc
)
wg.Add(1)
fi, loaded := decoders.LoadOrStore(entry, decoderFunc(func(node gjson.Result, v reflect.Value, state *decoderState) error {
wg.Wait()
return f(node, v, state)
}))
if loaded {
return fi.(decoderFunc)
}
// Compute the real decoder and replace the indirect func with it.
f = d.newTypeDecoder(t)
wg.Done()
decoders.Store(entry, f)
return f
}
func indirectUnmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error {
return v.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw))
}
func unmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error {
if v.Kind() == reflect.Pointer && v.CanSet() {
v.Set(reflect.New(v.Type().Elem()))
}
return v.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw))
}
func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return d.newTimeTypeDecoder(t)
}
if !d.root && t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) {
return unmarshalerDecoder
}
if !d.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) {
if _, ok := unionVariants[t]; !ok {
return indirectUnmarshalerDecoder
}
}
d.root = false
if _, ok := unionRegistry[t]; ok {
return d.newUnionDecoder(t)
}
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerDecoder := d.typeDecoder(inner)
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
if !v.IsValid() {
return fmt.Errorf("apijson: unexpected invalid reflection value %+#v", v)
}
newValue := reflect.New(inner).Elem()
err := innerDecoder(n, newValue, state)
if err != nil {
return err
}
v.Set(newValue.Addr())
return nil
}
case reflect.Struct:
return d.newStructTypeDecoder(t)
case reflect.Array:
fallthrough
case reflect.Slice:
return d.newArrayTypeDecoder(t)
case reflect.Map:
return d.newMapDecoder(t)
case reflect.Interface:
return func(node gjson.Result, value reflect.Value, state *decoderState) error {
if !value.IsValid() {
return fmt.Errorf("apijson: unexpected invalid value %+#v", value)
}
if node.Value() != nil && value.CanSet() {
value.Set(reflect.ValueOf(node.Value()))
}
return nil
}
default:
return d.newPrimitiveTypeDecoder(t)
}
}
// 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(EscapeSJSONKey(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()
itemDecoder := d.typeDecoder(itemType)
return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) {
mapValue := reflect.MakeMapWithSize(t, len(node.Map()))
node.ForEach(func(key, value gjson.Result) bool {
// It's fine for us to just use `ValueOf` here because the key types will
// always be primitive types so we don't need to decode it using the standard pattern
keyValue := reflect.ValueOf(key.Value())
if !keyValue.IsValid() {
if err == nil {
err = fmt.Errorf("apijson: received invalid key type %v", keyValue.String())
}
return false
}
if keyValue.Type() != keyType {
if err == nil {
err = fmt.Errorf("apijson: expected key type %v but got %v", keyType, keyValue.Type())
}
return false
}
itemValue := reflect.New(itemType).Elem()
itemerr := itemDecoder(value, itemValue, state)
if itemerr != nil {
if err == nil {
err = itemerr
}
return false
}
mapValue.SetMapIndex(keyValue, itemValue)
return true
})
if err != nil {
return err
}
value.Set(mapValue)
return nil
}
}
func (d *decoderBuilder) newArrayTypeDecoder(t reflect.Type) decoderFunc {
itemDecoder := d.typeDecoder(t.Elem())
return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) {
if !node.IsArray() {
return fmt.Errorf("apijson: could not deserialize to an array")
}
arrayNode := node.Array()
arrayValue := reflect.MakeSlice(reflect.SliceOf(t.Elem()), len(arrayNode), len(arrayNode))
for i, itemNode := range arrayNode {
err = itemDecoder(itemNode, arrayValue.Index(i), state)
if err != nil {
return err
}
}
value.Set(arrayValue)
return nil
}
}
func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc {
// map of json field name to struct field decoders
decoderFields := map[string]decoderField{}
anonymousDecoders := []decoderField{}
extraDecoder := (*decoderField)(nil)
inlineDecoder := (*decoderField)(nil)
for i := 0; i < t.NumField(); i++ {
idx := []int{i}
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the fields and get their encoders as well.
if field.Anonymous {
anonymousDecoders = append(anonymousDecoders, decoderField{
fn: d.typeDecoder(field.Type),
idx: idx[:],
})
continue
}
// If json tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseJSONStructTag(field)
if !ok {
continue
}
// We only want to support unexported fields if they're tagged with
// `extras` because that field shouldn't be part of the public API.
if ptag.extras {
extraDecoder = &decoderField{ptag, d.typeDecoder(field.Type.Elem()), idx, field.Name}
continue
}
if ptag.inline {
inlineDecoder = &decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name}
continue
}
if ptag.metadata {
continue
}
oldFormat := d.dateFormat
dateFormat, ok := parseFormatStructTag(field)
if ok {
switch dateFormat {
case "date-time":
d.dateFormat = time.RFC3339
case "date":
d.dateFormat = "2006-01-02"
}
}
decoderFields[ptag.name] = decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name}
d.dateFormat = oldFormat
}
return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) {
if field := value.FieldByName("JSON"); field.IsValid() {
if raw := field.FieldByName("raw"); raw.IsValid() {
setUnexportedField(raw, node.Raw)
}
}
for _, decoder := range anonymousDecoders {
// ignore errors
decoder.fn(node, value.FieldByIndex(decoder.idx), state)
}
if inlineDecoder != nil {
var meta Field
dest := value.FieldByIndex(inlineDecoder.idx)
isValid := false
if dest.IsValid() && node.Type != gjson.Null {
err = inlineDecoder.fn(node, dest, state)
if err == nil {
isValid = true
}
}
if node.Type == gjson.Null {
meta = Field{
raw: node.Raw,
status: null,
}
} else if !isValid {
meta = Field{
raw: node.Raw,
status: invalid,
}
} else if isValid {
meta = Field{
raw: node.Raw,
status: valid,
}
}
if metadata := getSubField(value, inlineDecoder.idx, inlineDecoder.goname); metadata.IsValid() {
metadata.Set(reflect.ValueOf(meta))
}
return err
}
typedExtraType := reflect.Type(nil)
typedExtraFields := reflect.Value{}
if extraDecoder != nil {
typedExtraType = value.FieldByIndex(extraDecoder.idx).Type()
typedExtraFields = reflect.MakeMap(typedExtraType)
}
untypedExtraFields := map[string]Field{}
for fieldName, itemNode := range node.Map() {
df, explicit := decoderFields[fieldName]
var (
dest reflect.Value
fn decoderFunc
meta Field
)
if explicit {
fn = df.fn
dest = value.FieldByIndex(df.idx)
}
if !explicit && extraDecoder != nil {
dest = reflect.New(typedExtraType.Elem()).Elem()
fn = extraDecoder.fn
}
isValid := false
if dest.IsValid() && itemNode.Type != gjson.Null {
err = fn(itemNode, dest, state)
if err == nil {
isValid = true
}
}
if itemNode.Type == gjson.Null {
meta = Field{
raw: itemNode.Raw,
status: null,
}
} else if !isValid {
meta = Field{
raw: itemNode.Raw,
status: invalid,
}
} else if isValid {
meta = Field{
raw: itemNode.Raw,
status: valid,
}
}
if explicit {
if metadata := getSubField(value, df.idx, df.goname); metadata.IsValid() {
metadata.Set(reflect.ValueOf(meta))
}
}
if !explicit {
untypedExtraFields[fieldName] = meta
}
if !explicit && extraDecoder != nil {
typedExtraFields.SetMapIndex(reflect.ValueOf(fieldName), dest)
}
}
if extraDecoder != nil && typedExtraFields.Len() > 0 {
value.FieldByIndex(extraDecoder.idx).Set(typedExtraFields)
}
// Set exactness to 'extras' if there are untyped, extra fields.
if len(untypedExtraFields) > 0 && state.exactness > extras {
state.exactness = extras
}
if metadata := getSubField(value, []int{-1}, "ExtraFields"); metadata.IsValid() && len(untypedExtraFields) > 0 {
metadata.Set(reflect.ValueOf(untypedExtraFields))
}
return nil
}
}
func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc {
switch t.Kind() {
case reflect.String:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetString(n.String())
if guardStrict(state, n.Type != gjson.String) {
return fmt.Errorf("apijson: failed to parse string strictly")
}
// Everything that is not an object can be loosely stringified.
if n.Type == gjson.JSON {
return fmt.Errorf("apijson: failed to parse string")
}
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed string enum validation")
}
return nil
}
case reflect.Bool:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetBool(n.Bool())
if guardStrict(state, n.Type != gjson.True && n.Type != gjson.False) {
return fmt.Errorf("apijson: failed to parse bool strictly")
}
// Numbers and strings that are either 'true' or 'false' can be loosely
// deserialized as bool.
if n.Type == gjson.String && (n.Raw != "true" && n.Raw != "false") || n.Type == gjson.JSON {
return fmt.Errorf("apijson: failed to parse bool")
}
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed bool enum validation")
}
return nil
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetInt(n.Int())
if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num))) {
return fmt.Errorf("apijson: failed to parse int strictly")
}
// Numbers, booleans, and strings that maybe look like numbers can be
// loosely deserialized as numbers.
if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) {
return fmt.Errorf("apijson: failed to parse int")
}
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed int enum validation")
}
return nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetUint(n.Uint())
if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num)) || n.Num < 0) {
return fmt.Errorf("apijson: failed to parse uint strictly")
}
// Numbers, booleans, and strings that maybe look like numbers can be
// loosely deserialized as uint.
if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) {
return fmt.Errorf("apijson: failed to parse uint")
}
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed uint enum validation")
}
return nil
}
case reflect.Float32, reflect.Float64:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetFloat(n.Float())
if guardStrict(state, n.Type != gjson.Number) {
return fmt.Errorf("apijson: failed to parse float strictly")
}
// Numbers, booleans, and strings that maybe look like numbers can be
// loosely deserialized as floats.
if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) {
return fmt.Errorf("apijson: failed to parse float")
}
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed float enum validation")
}
return nil
}
default:
return func(node gjson.Result, v reflect.Value, state *decoderState) error {
return fmt.Errorf("unknown type received at primitive decoder: %s", t.String())
}
}
}
func (d *decoderBuilder) newTimeTypeDecoder(t reflect.Type) decoderFunc {
format := d.dateFormat
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
parsed, err := time.Parse(format, n.Str)
if err == nil {
v.Set(reflect.ValueOf(parsed).Convert(t))
return nil
}
if guardStrict(state, true) {
return err
}
layouts := []string{
"2006-01-02",
"2006-01-02T15:04:05Z07:00",
"2006-01-02T15:04:05Z0700",
"2006-01-02T15:04:05",
"2006-01-02 15:04:05Z07:00",
"2006-01-02 15:04:05Z0700",
"2006-01-02 15:04:05",
}
for _, layout := range layouts {
parsed, err := time.Parse(layout, n.Str)
if err == nil {
v.Set(reflect.ValueOf(parsed).Convert(t))
return nil
}
}
return fmt.Errorf("unable to leniently parse date-time string: %s", n.Str)
}
}
func setUnexportedField(field reflect.Value, value interface{}) {
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(reflect.ValueOf(value))
}
func guardStrict(state *decoderState, cond bool) bool {
if !cond {
return false
}
if state.strict {
return true
}
state.exactness = loose
return false
}
func canParseAsNumber(str string) bool {
_, err := strconv.ParseFloat(str, 64)
return err == nil
}
func guardUnknown(state *decoderState, v reflect.Value) bool {
if have, ok := v.Interface().(interface{ IsKnown() bool }); guardStrict(state, ok && !have.IsKnown()) {
return true
}
return false
}

View file

@ -1,398 +0,0 @@
package apijson
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/tidwall/sjson"
"github.com/sst/opencode-sdk-go/internal/param"
)
var encoders sync.Map // map[encoderEntry]encoderFunc
// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have
// special characters that sjson interprets as a path.
var EscapeSJSONKey = strings.NewReplacer("\\", "\\\\", "|", "\\|", "#", "\\#", "@", "\\@", "*", "\\*", ".", "\\.", ":", "\\:", "?", "\\?").Replace
func Marshal(value interface{}) ([]byte, error) {
e := &encoder{dateFormat: time.RFC3339}
return e.marshal(value)
}
func MarshalRoot(value interface{}) ([]byte, error) {
e := &encoder{root: true, dateFormat: time.RFC3339}
return e.marshal(value)
}
type encoder struct {
dateFormat string
root bool
}
type encoderFunc func(value reflect.Value) ([]byte, error)
type encoderField struct {
tag parsedStructTag
fn encoderFunc
idx []int
}
type encoderEntry struct {
reflect.Type
dateFormat string
root bool
}
func (e *encoder) marshal(value interface{}) ([]byte, error) {
val := reflect.ValueOf(value)
if !val.IsValid() {
return nil, nil
}
typ := val.Type()
enc := e.typeEncoder(typ)
return enc(val)
}
func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
dateFormat: e.dateFormat,
root: e.root,
}
if fi, ok := encoders.Load(entry); ok {
return fi.(encoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f encoderFunc
)
wg.Add(1)
fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(v reflect.Value) ([]byte, error) {
wg.Wait()
return f(v)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = e.newTypeEncoder(t)
wg.Done()
encoders.Store(entry, f)
return f
}
func marshalerEncoder(v reflect.Value) ([]byte, error) {
return v.Interface().(json.Marshaler).MarshalJSON()
}
func indirectMarshalerEncoder(v reflect.Value) ([]byte, error) {
return v.Addr().Interface().(json.Marshaler).MarshalJSON()
}
func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return e.newTimeTypeEncoder()
}
if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) {
return marshalerEncoder
}
if !e.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) {
return indirectMarshalerEncoder
}
e.root = false
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerEncoder := e.typeEncoder(inner)
return func(v reflect.Value) ([]byte, error) {
if !v.IsValid() || v.IsNil() {
return nil, nil
}
return innerEncoder(v.Elem())
}
case reflect.Struct:
return e.newStructTypeEncoder(t)
case reflect.Array:
fallthrough
case reflect.Slice:
return e.newArrayTypeEncoder(t)
case reflect.Map:
return e.newMapEncoder(t)
case reflect.Interface:
return e.newInterfaceEncoder()
default:
return e.newPrimitiveTypeEncoder(t)
}
}
func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
switch t.Kind() {
// Note that we could use `gjson` to encode these types but it would complicate our
// code more and this current code shouldn't cause any issues
case reflect.String:
return func(v reflect.Value) ([]byte, error) {
return json.Marshal(v.Interface())
}
case reflect.Bool:
return func(v reflect.Value) ([]byte, error) {
if v.Bool() {
return []byte("true"), nil
}
return []byte("false"), nil
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatInt(v.Int(), 10)), nil
}
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatUint(v.Uint(), 10)), nil
}
case reflect.Float32:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 32)), nil
}
case reflect.Float64:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 64)), nil
}
default:
return func(v reflect.Value) ([]byte, error) {
return nil, fmt.Errorf("unknown type received at primitive encoder: %s", t.String())
}
}
}
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
itemEncoder := e.typeEncoder(t.Elem())
return func(value reflect.Value) ([]byte, error) {
json := []byte("[]")
for i := 0; i < value.Len(); i++ {
var value, err = itemEncoder(value.Index(i))
if err != nil {
return nil, err
}
if value == nil {
// Assume that empty items should be inserted as `null` so that the output array
// will be the same length as the input array
value = []byte("null")
}
json, err = sjson.SetRawBytes(json, "-1", value)
if err != nil {
return nil, err
}
}
return json, nil
}
}
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)
// This helper allows us to recursively collect field encoders into a flat
// array. The parameter `index` keeps track of the access patterns necessary
// to get to some field.
var collectEncoderFields func(r reflect.Type, index []int)
collectEncoderFields = func(r reflect.Type, index []int) {
for i := 0; i < r.NumField(); i++ {
idx := append(index, i)
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the field and get their encoders as well.
if field.Anonymous {
collectEncoderFields(field.Type, idx)
continue
}
// If json tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseJSONStructTag(field)
if !ok {
continue
}
// We only want to support unexported field if they're tagged with
// `extras` because that field shouldn't be part of the public API. We
// also want to only keep the top level extras
if ptag.extras && len(index) == 0 {
extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx}
continue
}
if ptag.name == "-" {
continue
}
dateFormat, ok := parseFormatStructTag(field)
oldFormat := e.dateFormat
if ok {
switch dateFormat {
case "date-time":
e.dateFormat = time.RFC3339
case "date":
e.dateFormat = "2006-01-02"
}
}
encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx})
e.dateFormat = oldFormat
}
}
collectEncoderFields(t, []int{})
// Ensure deterministic output by sorting by lexicographic order
sort.Slice(encoderFields, func(i, j int) bool {
return encoderFields[i].tag.name < encoderFields[j].tag.name
})
return func(value reflect.Value) (json []byte, err error) {
json = []byte("{}")
for _, ef := range encoderFields {
field := value.FieldByIndex(ef.idx)
encoded, err := ef.fn(field)
if err != nil {
return nil, err
}
if encoded == nil {
continue
}
json, err = sjson.SetRawBytes(json, EscapeSJSONKey(ef.tag.name), encoded)
if err != nil {
return nil, err
}
}
if extraEncoder != nil {
json, err = e.encodeMapEntries(json, value.FieldByIndex(extraEncoder.idx))
if err != nil {
return nil, err
}
}
return
}
}
func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc {
f, _ := t.FieldByName("Value")
enc := e.typeEncoder(f.Type)
return func(value reflect.Value) (json []byte, err error) {
present := value.FieldByName("Present")
if !present.Bool() {
return nil, nil
}
null := value.FieldByName("Null")
if null.Bool() {
return []byte("null"), nil
}
raw := value.FieldByName("Raw")
if !raw.IsNil() {
return e.typeEncoder(raw.Type())(raw)
}
return enc(value.FieldByName("Value"))
}
}
func (e *encoder) newTimeTypeEncoder() encoderFunc {
format := e.dateFormat
return func(value reflect.Value) (json []byte, err error) {
return []byte(`"` + value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format) + `"`), nil
}
}
func (e encoder) newInterfaceEncoder() encoderFunc {
return func(value reflect.Value) ([]byte, error) {
value = value.Elem()
if !value.IsValid() {
return nil, nil
}
return e.typeEncoder(value.Type())(value)
}
}
// Given a []byte of json (may either be an empty object or an object that already contains entries)
// encode all of the entries in the map to the json byte array.
func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error) {
type mapPair struct {
key []byte
value reflect.Value
}
pairs := []mapPair{}
keyEncoder := e.typeEncoder(v.Type().Key())
iter := v.MapRange()
for iter.Next() {
var encodedKeyString string
if iter.Key().Type().Kind() == reflect.String {
encodedKeyString = iter.Key().String()
} else {
var err error
encodedKeyBytes, err := keyEncoder(iter.Key())
if err != nil {
return nil, err
}
encodedKeyString = string(encodedKeyBytes)
}
encodedKey := []byte(encodedKeyString)
pairs = append(pairs, mapPair{key: encodedKey, value: iter.Value()})
}
// Ensure deterministic output
sort.Slice(pairs, func(i, j int) bool {
return bytes.Compare(pairs[i].key, pairs[j].key) < 0
})
elementEncoder := e.typeEncoder(v.Type().Elem())
for _, p := range pairs {
encodedValue, err := elementEncoder(p.value)
if err != nil {
return nil, err
}
if len(encodedValue) == 0 {
continue
}
json, err = sjson.SetRawBytes(json, EscapeSJSONKey(string(p.key)), encodedValue)
if err != nil {
return nil, err
}
}
return json, nil
}
func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc {
return func(value reflect.Value) ([]byte, error) {
json := []byte("{}")
var err error
json, err = e.encodeMapEntries(json, value)
if err != nil {
return nil, err
}
return json, nil
}
}

View file

@ -1,41 +0,0 @@
package apijson
import "reflect"
type status uint8
const (
missing status = iota
null
invalid
valid
)
type Field struct {
raw string
status status
}
// Returns true if the field is explicitly `null` _or_ if it is not present at all (ie, missing).
// To check if the field's key is present in the JSON with an explicit null value,
// you must check `f.IsNull() && !f.IsMissing()`.
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

@ -1,66 +0,0 @@
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

@ -1,617 +0,0 @@
package apijson
import (
"reflect"
"strings"
"testing"
"time"
"github.com/tidwall/gjson"
)
func P[T any](v T) *T { return &v }
type Primitives struct {
A bool `json:"a"`
B int `json:"b"`
C uint `json:"c"`
D float64 `json:"d"`
E float32 `json:"e"`
F []int `json:"f"`
}
type PrimitivePointers struct {
A *bool `json:"a"`
B *int `json:"b"`
C *uint `json:"c"`
D *float64 `json:"d"`
E *float32 `json:"e"`
F *[]int `json:"f"`
}
type Slices struct {
Slice []Primitives `json:"slices"`
}
type DateTime struct {
Date time.Time `json:"date" format:"date"`
DateTime time.Time `json:"date-time" format:"date-time"`
}
type AdditionalProperties struct {
A bool `json:"a"`
ExtraFields map[string]interface{} `json:"-,extras"`
}
type TypedAdditionalProperties struct {
A bool `json:"a"`
ExtraFields map[string]int `json:"-,extras"`
}
type EmbeddedStruct struct {
A bool `json:"a"`
B string `json:"b"`
JSON EmbeddedStructJSON
}
type EmbeddedStructJSON struct {
A Field
B Field
ExtraFields map[string]Field
raw string
}
type EmbeddedStructs struct {
EmbeddedStruct
A *int `json:"a"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON EmbeddedStructsJSON
}
type EmbeddedStructsJSON struct {
A Field
ExtraFields map[string]Field
raw string
}
type Recursive struct {
Name string `json:"name"`
Child *Recursive `json:"child"`
}
type JSONFieldStruct struct {
A bool `json:"a"`
B int64 `json:"b"`
C string `json:"c"`
D string `json:"d"`
ExtraFields map[string]int64 `json:"-,extras"`
JSON JSONFieldStructJSON `json:"-,metadata"`
}
type JSONFieldStructJSON struct {
A Field
B Field
C Field
D Field
ExtraFields map[string]Field
raw string
}
type UnknownStruct struct {
Unknown interface{} `json:"unknown"`
}
type UnionStruct struct {
Union Union `json:"union" format:"date"`
}
type Union interface {
union()
}
type Inline struct {
InlineField Primitives `json:"-,inline"`
JSON InlineJSON `json:"-,metadata"`
}
type InlineArray struct {
InlineField []string `json:"-,inline"`
JSON InlineJSON `json:"-,metadata"`
}
type InlineJSON struct {
InlineField Field
raw string
}
type UnionInteger int64
func (UnionInteger) union() {}
type UnionStructA struct {
Type string `json:"type"`
A string `json:"a"`
B string `json:"b"`
}
func (UnionStructA) union() {}
type UnionStructB struct {
Type string `json:"type"`
A string `json:"a"`
}
func (UnionStructB) union() {}
type UnionTime time.Time
func (UnionTime) union() {}
func init() {
RegisterUnion(reflect.TypeOf((*Union)(nil)).Elem(), "type",
UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(UnionTime{}),
},
UnionVariant{
TypeFilter: gjson.Number,
Type: reflect.TypeOf(UnionInteger(0)),
},
UnionVariant{
TypeFilter: gjson.JSON,
DiscriminatorValue: "typeA",
Type: reflect.TypeOf(UnionStructA{}),
},
UnionVariant{
TypeFilter: gjson.JSON,
DiscriminatorValue: "typeB",
Type: reflect.TypeOf(UnionStructB{}),
},
)
}
type ComplexUnionStruct struct {
Union ComplexUnion `json:"union"`
}
type ComplexUnion interface {
complexUnion()
}
type ComplexUnionA struct {
Boo string `json:"boo"`
Foo bool `json:"foo"`
}
func (ComplexUnionA) complexUnion() {}
type ComplexUnionB struct {
Boo bool `json:"boo"`
Foo string `json:"foo"`
}
func (ComplexUnionB) complexUnion() {}
type ComplexUnionC struct {
Boo int64 `json:"boo"`
}
func (ComplexUnionC) complexUnion() {}
type ComplexUnionTypeA struct {
Baz int64 `json:"baz"`
Type TypeA `json:"type"`
}
func (ComplexUnionTypeA) complexUnion() {}
type TypeA string
func (t TypeA) IsKnown() bool {
return t == "a"
}
type ComplexUnionTypeB struct {
Baz int64 `json:"baz"`
Type TypeB `json:"type"`
}
type TypeB string
func (t TypeB) IsKnown() bool {
return t == "b"
}
type UnmarshalStruct struct {
Foo string `json:"foo"`
prop bool `json:"-"`
}
func (r *UnmarshalStruct) UnmarshalJSON(json []byte) error {
r.prop = true
return UnmarshalRoot(json, r)
}
func (ComplexUnionTypeB) complexUnion() {}
func init() {
RegisterUnion(reflect.TypeOf((*ComplexUnion)(nil)).Elem(), "",
UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ComplexUnionA{}),
},
UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ComplexUnionB{}),
},
UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ComplexUnionC{}),
},
UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ComplexUnionTypeA{}),
},
UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ComplexUnionTypeB{}),
},
)
}
type MarshallingUnionStruct struct {
Union MarshallingUnion
}
func (r *MarshallingUnionStruct) UnmarshalJSON(data []byte) (err error) {
*r = MarshallingUnionStruct{}
err = UnmarshalRoot(data, &r.Union)
return
}
func (r MarshallingUnionStruct) MarshalJSON() (data []byte, err error) {
return MarshalRoot(r.Union)
}
type MarshallingUnion interface {
marshallingUnion()
}
type MarshallingUnionA struct {
Boo string `json:"boo"`
}
func (MarshallingUnionA) marshallingUnion() {}
func (r *MarshallingUnionA) UnmarshalJSON(data []byte) (err error) {
return UnmarshalRoot(data, r)
}
type MarshallingUnionB struct {
Foo string `json:"foo"`
}
func (MarshallingUnionB) marshallingUnion() {}
func (r *MarshallingUnionB) UnmarshalJSON(data []byte) (err error) {
return UnmarshalRoot(data, r)
}
func init() {
RegisterUnion(
reflect.TypeOf((*MarshallingUnion)(nil)).Elem(),
"",
UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(MarshallingUnionA{}),
},
UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(MarshallingUnionB{}),
},
)
}
var tests = map[string]struct {
buf string
val interface{}
}{
"true": {"true", true},
"false": {"false", false},
"int": {"1", 1},
"int_bigger": {"12324", 12324},
"int_string_coerce": {`"65"`, 65},
"int_boolean_coerce": {"true", 1},
"int64": {"1", int64(1)},
"int64_huge": {"123456789123456789", int64(123456789123456789)},
"uint": {"1", uint(1)},
"uint_bigger": {"12324", uint(12324)},
"uint_coerce": {`"65"`, uint(65)},
"float_1.54": {"1.54", float32(1.54)},
"float_1.89": {"1.89", float64(1.89)},
"string": {`"str"`, "str"},
"string_int_coerce": {`12`, "12"},
"array_string": {`["foo","bar"]`, []string{"foo", "bar"}},
"array_int": {`[1,2]`, []int{1, 2}},
"array_int_coerce": {`["1",2]`, []int{1, 2}},
"ptr_true": {"true", P(true)},
"ptr_false": {"false", P(false)},
"ptr_int": {"1", P(1)},
"ptr_int_bigger": {"12324", P(12324)},
"ptr_int_string_coerce": {`"65"`, P(65)},
"ptr_int_boolean_coerce": {"true", P(1)},
"ptr_int64": {"1", P(int64(1))},
"ptr_int64_huge": {"123456789123456789", P(int64(123456789123456789))},
"ptr_uint": {"1", P(uint(1))},
"ptr_uint_bigger": {"12324", P(uint(12324))},
"ptr_uint_coerce": {`"65"`, P(uint(65))},
"ptr_float_1.54": {"1.54", P(float32(1.54))},
"ptr_float_1.89": {"1.89", P(float64(1.89))},
"date_time": {`"2007-03-01T13:00:00Z"`, time.Date(2007, time.March, 1, 13, 0, 0, 0, time.UTC)},
"date_time_nano_coerce": {`"2007-03-01T13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)},
"date_time_missing_t_coerce": {`"2007-03-01 13:03:05Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)},
"date_time_missing_timezone_coerce": {`"2007-03-01T13:03:05"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)},
// note: using -1200 to minimize probability of conflicting with the local timezone of the test runner
// see https://en.wikipedia.org/wiki/UTC%E2%88%9212:00
"date_time_missing_timezone_colon_coerce": {`"2007-03-01T13:03:05-1200"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.FixedZone("", -12*60*60))},
"date_time_nano_missing_t_coerce": {`"2007-03-01 13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)},
"map_string": {`{"foo":"bar"}`, map[string]string{"foo": "bar"}},
"map_string_with_sjson_path_chars": {`{":a.b.c*:d*-1e.f@g?h":"bar"}`, map[string]string{":a.b.c*:d*-1e.f@g?h": "bar"}},
"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]}`,
Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
},
"slices": {
`{"slices":[{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}]}`,
Slices{
Slice: []Primitives{{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}},
},
},
"primitive_pointer_struct": {
`{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4,5]}`,
PrimitivePointers{
A: P(false),
B: P(237628372683),
C: P(uint(654)),
D: P(9999.43),
E: P(float32(43.76)),
F: &[]int{1, 2, 3, 4, 5},
},
},
"datetime_struct": {
`{"date":"2006-01-02","date-time":"2006-01-02T15:04:05Z"}`,
DateTime{
Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC),
DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC),
},
},
"additional_properties": {
`{"a":true,"bar":"value","foo":true}`,
AdditionalProperties{
A: true,
ExtraFields: map[string]interface{}{
"bar": "value",
"foo": true,
},
},
},
"embedded_struct": {
`{"a":1,"b":"bar"}`,
EmbeddedStructs{
EmbeddedStruct: EmbeddedStruct{
A: true,
B: "bar",
JSON: EmbeddedStructJSON{
A: Field{raw: `1`, status: valid},
B: Field{raw: `"bar"`, status: valid},
raw: `{"a":1,"b":"bar"}`,
},
},
A: P(1),
ExtraFields: map[string]interface{}{"b": "bar"},
JSON: EmbeddedStructsJSON{
A: Field{raw: `1`, status: valid},
ExtraFields: map[string]Field{
"b": {raw: `"bar"`, status: valid},
},
raw: `{"a":1,"b":"bar"}`,
},
},
},
"recursive_struct": {
`{"child":{"name":"Alex"},"name":"Robert"}`,
Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}},
},
"metadata_coerce": {
`{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`,
JSONFieldStruct{
A: false,
B: 12,
C: "",
JSON: JSONFieldStructJSON{
raw: `{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`,
A: Field{raw: `"12"`, status: invalid},
B: Field{raw: `"12"`, status: valid},
C: Field{raw: "null", status: null},
D: Field{raw: "", status: missing},
ExtraFields: map[string]Field{
"extra_typed": {
raw: "12",
status: valid,
},
"extra_untyped": {
raw: `{"foo":"bar"}`,
status: invalid,
},
},
},
ExtraFields: map[string]int64{
"extra_typed": 12,
"extra_untyped": 0,
},
},
},
"unknown_struct_number": {
`{"unknown":12}`,
UnknownStruct{
Unknown: 12.,
},
},
"unknown_struct_map": {
`{"unknown":{"foo":"bar"}}`,
UnknownStruct{
Unknown: map[string]interface{}{
"foo": "bar",
},
},
},
"union_integer": {
`{"union":12}`,
UnionStruct{
Union: UnionInteger(12),
},
},
"union_struct_discriminated_a": {
`{"union":{"a":"foo","b":"bar","type":"typeA"}}`,
UnionStruct{
Union: UnionStructA{
Type: "typeA",
A: "foo",
B: "bar",
},
},
},
"union_struct_discriminated_b": {
`{"union":{"a":"foo","type":"typeB"}}`,
UnionStruct{
Union: UnionStructB{
Type: "typeB",
A: "foo",
},
},
},
"union_struct_time": {
`{"union":"2010-05-23"}`,
UnionStruct{
Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)),
},
},
"complex_union_a": {
`{"union":{"boo":"12","foo":true}}`,
ComplexUnionStruct{Union: ComplexUnionA{Boo: "12", Foo: true}},
},
"complex_union_b": {
`{"union":{"boo":true,"foo":"12"}}`,
ComplexUnionStruct{Union: ComplexUnionB{Boo: true, Foo: "12"}},
},
"complex_union_c": {
`{"union":{"boo":12}}`,
ComplexUnionStruct{Union: ComplexUnionC{Boo: 12}},
},
"complex_union_type_a": {
`{"union":{"baz":12,"type":"a"}}`,
ComplexUnionStruct{Union: ComplexUnionTypeA{Baz: 12, Type: TypeA("a")}},
},
"complex_union_type_b": {
`{"union":{"baz":12,"type":"b"}}`,
ComplexUnionStruct{Union: ComplexUnionTypeB{Baz: 12, Type: TypeB("b")}},
},
"marshalling_union_a": {
`{"boo":"hello"}`,
MarshallingUnionStruct{Union: MarshallingUnionA{Boo: "hello"}},
},
"marshalling_union_b": {
`{"foo":"hi"}`,
MarshallingUnionStruct{Union: MarshallingUnionB{Foo: "hi"}},
},
"unmarshal": {
`{"foo":"hello"}`,
&UnmarshalStruct{Foo: "hello", prop: true},
},
"array_of_unmarshal": {
`[{"foo":"hello"}]`,
[]UnmarshalStruct{{Foo: "hello", prop: true}},
},
"inline_coerce": {
`{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`,
Inline{
InlineField: Primitives{A: false, B: 237628372683, C: 0x28e, D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
JSON: InlineJSON{
InlineField: Field{raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", status: 3},
raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}",
},
},
},
"inline_array_coerce": {
`["Hello","foo","bar"]`,
InlineArray{
InlineField: []string{"Hello", "foo", "bar"},
JSON: InlineJSON{
InlineField: Field{raw: `["Hello","foo","bar"]`, status: 3},
raw: `["Hello","foo","bar"]`,
},
},
},
}
func TestDecode(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
result := reflect.New(reflect.TypeOf(test.val))
if err := 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())
}
})
}
}
func TestEncode(t *testing.T) {
for name, test := range tests {
if strings.HasSuffix(name, "_coerce") {
continue
}
t.Run(name, func(t *testing.T) {
raw, err := Marshal(test.val)
if err != nil {
t.Fatalf("serialization of %v failed with error %v", test.val, err)
}
if string(raw) != test.buf {
t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.buf, string(raw))
}
})
}
}

View file

@ -1,120 +0,0 @@
package apijson
import (
"fmt"
"reflect"
)
// Port copies over values from one struct to another struct.
func Port(from any, to any) error {
toVal := reflect.ValueOf(to)
fromVal := reflect.ValueOf(from)
if toVal.Kind() != reflect.Ptr || toVal.IsNil() {
return fmt.Errorf("destination must be a non-nil pointer")
}
for toVal.Kind() == reflect.Ptr {
toVal = toVal.Elem()
}
toType := toVal.Type()
for fromVal.Kind() == reflect.Ptr {
fromVal = fromVal.Elem()
}
fromType := fromVal.Type()
if toType.Kind() != reflect.Struct {
return fmt.Errorf("destination must be a non-nil pointer to a struct (%v %v)", toType, toType.Kind())
}
values := map[string]reflect.Value{}
fields := map[string]reflect.Value{}
fromJSON := fromVal.FieldByName("JSON")
toJSON := toVal.FieldByName("JSON")
// Iterate through the fields of v and load all the "normal" fields in the struct to the map of
// string to reflect.Value, as well as their raw .JSON.Foo counterpart indicated by j.
var getFields func(t reflect.Type, v reflect.Value)
getFields = func(t reflect.Type, v reflect.Value) {
j := v.FieldByName("JSON")
// Recurse into anonymous fields first, since the fields on the object should win over the fields in the
// embedded object.
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Anonymous {
getFields(field.Type, v.Field(i))
continue
}
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
ptag, ok := parseJSONStructTag(field)
if !ok || ptag.name == "-" {
continue
}
values[ptag.name] = v.Field(i)
if j.IsValid() {
fields[ptag.name] = j.FieldByName(field.Name)
}
}
}
getFields(fromType, fromVal)
// Use the values from the previous step to populate the 'to' struct.
for i := 0; i < toType.NumField(); i++ {
field := toType.Field(i)
ptag, ok := parseJSONStructTag(field)
if !ok {
continue
}
if ptag.name == "-" {
continue
}
if value, ok := values[ptag.name]; ok {
delete(values, ptag.name)
if field.Type.Kind() == reflect.Interface {
toVal.Field(i).Set(value)
} else {
switch value.Kind() {
case reflect.String:
toVal.Field(i).SetString(value.String())
case reflect.Bool:
toVal.Field(i).SetBool(value.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
toVal.Field(i).SetInt(value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
toVal.Field(i).SetUint(value.Uint())
case reflect.Float32, reflect.Float64:
toVal.Field(i).SetFloat(value.Float())
default:
toVal.Field(i).Set(value)
}
}
}
if fromJSONField, ok := fields[ptag.name]; ok {
if toJSONField := toJSON.FieldByName(field.Name); toJSONField.IsValid() {
toJSONField.Set(fromJSONField)
}
}
}
// Finally, copy over the .JSON.raw and .JSON.ExtraFields
if toJSON.IsValid() {
if raw := toJSON.FieldByName("raw"); raw.IsValid() {
setUnexportedField(raw, fromJSON.Interface().(interface{ RawJSON() string }).RawJSON())
}
if toExtraFields := toJSON.FieldByName("ExtraFields"); toExtraFields.IsValid() {
if fromExtraFields := fromJSON.FieldByName("ExtraFields"); fromExtraFields.IsValid() {
setUnexportedField(toExtraFields, fromExtraFields.Interface())
}
}
}
return nil
}

View file

@ -1,257 +0,0 @@
package apijson
import (
"reflect"
"testing"
)
type Metadata struct {
CreatedAt string `json:"created_at"`
}
// Card is the "combined" type of CardVisa and CardMastercard
type Card struct {
Processor CardProcessor `json:"processor"`
Data any `json:"data"`
IsFoo bool `json:"is_foo"`
IsBar bool `json:"is_bar"`
Metadata Metadata `json:"metadata"`
Value interface{} `json:"value"`
JSON cardJSON
}
type cardJSON struct {
Processor Field
Data Field
IsFoo Field
IsBar Field
Metadata Field
Value Field
ExtraFields map[string]Field
raw string
}
func (r cardJSON) RawJSON() string { return r.raw }
type CardProcessor string
// CardVisa
type CardVisa struct {
Processor CardVisaProcessor `json:"processor"`
Data CardVisaData `json:"data"`
IsFoo bool `json:"is_foo"`
Metadata Metadata `json:"metadata"`
Value string `json:"value"`
JSON cardVisaJSON
}
type cardVisaJSON struct {
Processor Field
Data Field
IsFoo Field
Metadata Field
Value Field
ExtraFields map[string]Field
raw string
}
func (r cardVisaJSON) RawJSON() string { return r.raw }
type CardVisaProcessor string
type CardVisaData struct {
Foo string `json:"foo"`
}
// CardMastercard
type CardMastercard struct {
Processor CardMastercardProcessor `json:"processor"`
Data CardMastercardData `json:"data"`
IsBar bool `json:"is_bar"`
Metadata Metadata `json:"metadata"`
Value bool `json:"value"`
JSON cardMastercardJSON
}
type cardMastercardJSON struct {
Processor Field
Data Field
IsBar Field
Metadata Field
Value Field
ExtraFields map[string]Field
raw string
}
func (r cardMastercardJSON) RawJSON() string { return r.raw }
type CardMastercardProcessor string
type CardMastercardData struct {
Bar int64 `json:"bar"`
}
type CommonFields struct {
Metadata Metadata `json:"metadata"`
Value string `json:"value"`
JSON commonFieldsJSON
}
type commonFieldsJSON struct {
Metadata Field
Value Field
ExtraFields map[string]Field
raw string
}
type CardEmbedded struct {
CommonFields
Processor CardVisaProcessor `json:"processor"`
Data CardVisaData `json:"data"`
IsFoo bool `json:"is_foo"`
JSON cardEmbeddedJSON
}
type cardEmbeddedJSON struct {
Processor Field
Data Field
IsFoo Field
ExtraFields map[string]Field
raw string
}
func (r cardEmbeddedJSON) RawJSON() string { return r.raw }
var portTests = map[string]struct {
from any
to any
}{
"visa to card": {
CardVisa{
Processor: "visa",
IsFoo: true,
Data: CardVisaData{
Foo: "foo",
},
Metadata: Metadata{
CreatedAt: "Mar 29 2024",
},
Value: "value",
JSON: cardVisaJSON{
raw: `{"processor":"visa","is_foo":true,"data":{"foo":"foo"}}`,
Processor: Field{raw: `"visa"`, status: valid},
IsFoo: Field{raw: `true`, status: valid},
Data: Field{raw: `{"foo":"foo"}`, status: valid},
Value: Field{raw: `"value"`, status: valid},
ExtraFields: map[string]Field{"extra": {raw: `"yo"`, status: valid}},
},
},
Card{
Processor: "visa",
IsFoo: true,
IsBar: false,
Data: CardVisaData{
Foo: "foo",
},
Metadata: Metadata{
CreatedAt: "Mar 29 2024",
},
Value: "value",
JSON: cardJSON{
raw: `{"processor":"visa","is_foo":true,"data":{"foo":"foo"}}`,
Processor: Field{raw: `"visa"`, status: valid},
IsFoo: Field{raw: `true`, status: valid},
Data: Field{raw: `{"foo":"foo"}`, status: valid},
Value: Field{raw: `"value"`, status: valid},
ExtraFields: map[string]Field{"extra": {raw: `"yo"`, status: valid}},
},
},
},
"mastercard to card": {
CardMastercard{
Processor: "mastercard",
IsBar: true,
Data: CardMastercardData{
Bar: 13,
},
Value: false,
},
Card{
Processor: "mastercard",
IsFoo: false,
IsBar: true,
Data: CardMastercardData{
Bar: 13,
},
Value: false,
},
},
"embedded to card": {
CardEmbedded{
CommonFields: CommonFields{
Metadata: Metadata{
CreatedAt: "Mar 29 2024",
},
Value: "embedded_value",
JSON: commonFieldsJSON{
Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: valid},
Value: Field{raw: `"embedded_value"`, status: valid},
raw: `should not matter`,
},
},
Processor: "visa",
IsFoo: true,
Data: CardVisaData{
Foo: "embedded_foo",
},
JSON: cardEmbeddedJSON{
raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`,
Processor: Field{raw: `"visa"`, status: valid},
IsFoo: Field{raw: `true`, status: valid},
Data: Field{raw: `{"foo":"embedded_foo"}`, status: valid},
},
},
Card{
Processor: "visa",
IsFoo: true,
IsBar: false,
Data: CardVisaData{
Foo: "embedded_foo",
},
Metadata: Metadata{
CreatedAt: "Mar 29 2024",
},
Value: "embedded_value",
JSON: cardJSON{
raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`,
Processor: Field{raw: `"visa"`, status: 0x3},
IsFoo: Field{raw: "true", status: 0x3},
Data: Field{raw: `{"foo":"embedded_foo"}`, status: 0x3},
Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: 0x3},
Value: Field{raw: `"embedded_value"`, status: 0x3},
},
},
},
}
func TestPort(t *testing.T) {
for name, test := range portTests {
t.Run(name, func(t *testing.T) {
toVal := reflect.New(reflect.TypeOf(test.to))
err := Port(test.from, toVal.Interface())
if err != nil {
t.Fatalf("port of %v failed with error %v", test.from, err)
}
if !reflect.DeepEqual(toVal.Elem().Interface(), test.to) {
t.Fatalf("expected:\n%+#v\n\nto port to:\n%+#v\n\nbut got:\n%+#v", test.from, test.to, toVal.Elem().Interface())
}
})
}
}

View file

@ -1,41 +0,0 @@
package apijson
import (
"reflect"
"github.com/tidwall/gjson"
)
type UnionVariant struct {
TypeFilter gjson.Type
DiscriminatorValue interface{}
Type reflect.Type
}
var unionRegistry = map[reflect.Type]unionEntry{}
var unionVariants = map[reflect.Type]interface{}{}
type unionEntry struct {
discriminatorKey string
variants []UnionVariant
}
func RegisterUnion(typ reflect.Type, discriminator string, variants ...UnionVariant) {
unionRegistry[typ] = unionEntry{
discriminatorKey: discriminator,
variants: variants,
}
for _, variant := range variants {
unionVariants[variant.Type] = typ
}
}
// Useful to wrap a union type to force it to use [apijson.UnmarshalJSON] since you cannot define an
// UnmarshalJSON function on the interface itself.
type UnionUnmarshaler[T any] struct {
Value T
}
func (c *UnionUnmarshaler[T]) UnmarshalJSON(buf []byte) error {
return UnmarshalRoot(buf, &c.Value)
}

View file

@ -1,47 +0,0 @@
package apijson
import (
"reflect"
"strings"
)
const jsonStructTag = "json"
const formatStructTag = "format"
type parsedStructTag struct {
name string
required bool
extras bool
metadata bool
inline bool
}
func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(jsonStructTag)
if !ok {
return
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
return tag, false
}
tag.name = parts[0]
for _, part := range parts[1:] {
switch part {
case "required":
tag.required = true
case "extras":
tag.extras = true
case "metadata":
tag.metadata = true
case "inline":
tag.inline = true
}
}
return
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
return
}

View file

@ -1,341 +0,0 @@
package apiquery
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/sst/opencode-sdk-go/internal/param"
)
var encoders sync.Map // map[reflect.Type]encoderFunc
type encoder struct {
dateFormat string
root bool
settings QuerySettings
}
type encoderFunc func(key string, value reflect.Value) []Pair
type encoderField struct {
tag parsedStructTag
fn encoderFunc
idx []int
}
type encoderEntry struct {
reflect.Type
dateFormat string
root bool
settings QuerySettings
}
type Pair struct {
key string
value string
}
func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
dateFormat: e.dateFormat,
root: e.root,
settings: e.settings,
}
if fi, ok := encoders.Load(entry); ok {
return fi.(encoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f encoderFunc
)
wg.Add(1)
fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value) []Pair {
wg.Wait()
return f(key, v)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = e.newTypeEncoder(t)
wg.Done()
encoders.Store(entry, f)
return f
}
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 !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) {
if !value.IsValid() || value.IsNil() {
return
}
pairs = encoder(key, value.Elem())
return
}
case reflect.Struct:
return e.newStructTypeEncoder(t)
case reflect.Array:
fallthrough
case reflect.Slice:
return e.newArrayTypeEncoder(t)
case reflect.Map:
return e.newMapEncoder(t)
case reflect.Interface:
return e.newInterfaceEncoder()
default:
return e.newPrimitiveTypeEncoder(t)
}
}
func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) {
return e.newFieldTypeEncoder(t)
}
encoderFields := []encoderField{}
// This helper allows us to recursively collect field encoders into a flat
// array. The parameter `index` keeps track of the access patterns necessary
// to get to some field.
var collectEncoderFields func(r reflect.Type, index []int)
collectEncoderFields = func(r reflect.Type, index []int) {
for i := 0; i < r.NumField(); i++ {
idx := append(index, i)
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the field and get their encoders as well.
if field.Anonymous {
collectEncoderFields(field.Type, idx)
continue
}
// If query tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseQueryStructTag(field)
if !ok {
continue
}
if ptag.name == "-" && !ptag.inline {
continue
}
dateFormat, ok := parseFormatStructTag(field)
oldFormat := e.dateFormat
if ok {
switch dateFormat {
case "date-time":
e.dateFormat = time.RFC3339
case "date":
e.dateFormat = "2006-01-02"
}
}
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) {
for _, ef := range encoderFields {
var subkey string = e.renderKeyPath(key, ef.tag.name)
if ef.tag.inline {
subkey = key
}
field := value.FieldByIndex(ef.idx)
pairs = append(pairs, ef.fn(subkey, field)...)
}
return
}
}
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) {
iter := value.MapRange()
for iter.Next() {
encodedKey := keyEncoder("", iter.Key())
if len(encodedKey) != 1 {
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)
pairs = append(pairs, elementEncoder(keyPath, iter.Value())...)
}
return
}
}
func (e *encoder) renderKeyPath(key string, subkey string) string {
if len(key) == 0 {
return subkey
}
if e.settings.NestedFormat == NestedQueryFormatDots {
return fmt.Sprintf("%s.%s", key, subkey)
}
return fmt.Sprintf("%s[%s]", key, subkey)
}
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 {
elements := []string{}
for i := 0; i < v.Len(); i++ {
for _, pair := range innerEncoder("", v.Index(i)) {
elements = append(elements, pair.value)
}
}
if len(elements) == 0 {
return []Pair{}
}
return []Pair{{key, strings.Join(elements, ",")}}
}
case ArrayQueryFormatRepeat:
innerEncoder := e.typeEncoder(t.Elem())
return func(key string, value reflect.Value) (pairs []Pair) {
for i := 0; i < value.Len(); i++ {
pairs = append(pairs, innerEncoder(key, value.Index(i))...)
}
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) []Pair {
pairs := []Pair{}
for i := 0; i < value.Len(); i++ {
pairs = append(pairs, innerEncoder(key+"[]", value.Index(i))...)
}
return pairs
}
default:
panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat))
}
}
func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerEncoder := e.newPrimitiveTypeEncoder(inner)
return func(key string, v reflect.Value) []Pair {
if !v.IsValid() || v.IsNil() {
return nil
}
return innerEncoder(key, v.Elem())
}
case reflect.String:
return func(key string, v reflect.Value) []Pair {
return []Pair{{key, v.String()}}
}
case reflect.Bool:
return func(key string, v reflect.Value) []Pair {
if v.Bool() {
return []Pair{{key, "true"}}
}
return []Pair{{key, "false"}}
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
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 {
return []Pair{{key, strconv.FormatUint(v.Uint(), 10)}}
}
case reflect.Float32, reflect.Float64:
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 {
return []Pair{{key, strconv.FormatComplex(v.Complex(), 'f', -1, bitSize)}}
}
default:
return func(key string, v reflect.Value) []Pair {
return nil
}
}
}
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 {
present := value.FieldByName("Present")
if !present.Bool() {
return nil
}
null := value.FieldByName("Null")
if null.Bool() {
// TODO: Error?
return nil
}
raw := value.FieldByName("Raw")
if !raw.IsNil() {
return e.typeEncoder(raw.Type())(key, raw)
}
return enc(key, value.FieldByName("Value"))
}
}
func (e *encoder) newTimeTypeEncoder(t reflect.Type) encoderFunc {
format := e.dateFormat
return func(key string, value reflect.Value) []Pair {
return []Pair{{
key,
value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format),
}}
}
}
func (e encoder) newInterfaceEncoder() encoderFunc {
return func(key string, value reflect.Value) []Pair {
value = value.Elem()
if !value.IsValid() {
return nil
}
return e.typeEncoder(value.Type())(key, value)
}
}

View file

@ -1,50 +0,0 @@
package apiquery
import (
"net/url"
"reflect"
"time"
)
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
}
typ := val.Type()
for _, pair := range e.typeEncoder(typ)("", val) {
kv.Add(pair.key, pair.value)
}
return kv
}
func Marshal(value interface{}) url.Values {
return MarshalWithSettings(value, QuerySettings{})
}
type Queryer interface {
URLQuery() url.Values
}
type QuerySettings struct {
NestedFormat NestedQueryFormat
ArrayFormat ArrayQueryFormat
}
type NestedQueryFormat int
const (
NestedQueryFormatBrackets NestedQueryFormat = iota
NestedQueryFormatDots
)
type ArrayQueryFormat int
const (
ArrayQueryFormatComma ArrayQueryFormat = iota
ArrayQueryFormatRepeat
ArrayQueryFormatIndices
ArrayQueryFormatBrackets
)

View file

@ -1,335 +0,0 @@
package apiquery
import (
"net/url"
"testing"
"time"
)
func P[T any](v T) *T { return &v }
type Primitives struct {
A bool `query:"a"`
B int `query:"b"`
C uint `query:"c"`
D float64 `query:"d"`
E float32 `query:"e"`
F []int `query:"f"`
}
type PrimitivePointers struct {
A *bool `query:"a"`
B *int `query:"b"`
C *uint `query:"c"`
D *float64 `query:"d"`
E *float32 `query:"e"`
F *[]int `query:"f"`
}
type Slices struct {
Slice []Primitives `query:"slices"`
Mixed []interface{} `query:"mixed"`
}
type DateTime struct {
Date time.Time `query:"date" format:"date"`
DateTime time.Time `query:"date-time" format:"date-time"`
}
type AdditionalProperties struct {
A bool `query:"a"`
Extras map[string]interface{} `query:"-,inline"`
}
type Recursive struct {
Name string `query:"name"`
Child *Recursive `query:"child"`
}
type UnknownStruct struct {
Unknown interface{} `query:"unknown"`
}
type UnionStruct struct {
Union Union `query:"union" format:"date"`
}
type Union interface {
union()
}
type UnionInteger int64
func (UnionInteger) union() {}
type UnionString string
func (UnionString) union() {}
type UnionStructA struct {
Type string `query:"type"`
A string `query:"a"`
B string `query:"b"`
}
func (UnionStructA) union() {}
type UnionStructB struct {
Type string `query:"type"`
A string `query:"a"`
}
func (UnionStructB) union() {}
type UnionTime time.Time
func (UnionTime) union() {}
type DeeplyNested struct {
A DeeplyNested1 `query:"a"`
}
type DeeplyNested1 struct {
B DeeplyNested2 `query:"b"`
}
type DeeplyNested2 struct {
C DeeplyNested3 `query:"c"`
}
type DeeplyNested3 struct {
D *string `query:"d"`
}
var tests = map[string]struct {
enc string
val interface{}
settings QuerySettings
}{
"primitives": {
"a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4",
Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
QuerySettings{},
},
"slices_brackets": {
`mixed[]=1&mixed[]=2.3&mixed[]=hello&slices[][a]=false&slices[][a]=false&slices[][b]=237628372683&slices[][b]=237628372683&slices[][c]=654&slices[][c]=654&slices[][d]=9999.43&slices[][d]=9999.43&slices[][e]=43.7599983215332&slices[][e]=43.7599983215332&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4`,
Slices{
Slice: []Primitives{
{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: []interface{}{1, 2.3, "hello"},
},
QuerySettings{ArrayFormat: ArrayQueryFormatBrackets},
},
"slices_comma": {
`mixed=1,2.3,hello`,
Slices{
Mixed: []interface{}{1, 2.3, "hello"},
},
QuerySettings{ArrayFormat: ArrayQueryFormatComma},
},
"slices_repeat": {
`mixed=1&mixed=2.3&mixed=hello&slices[a]=false&slices[a]=false&slices[b]=237628372683&slices[b]=237628372683&slices[c]=654&slices[c]=654&slices[d]=9999.43&slices[d]=9999.43&slices[e]=43.7599983215332&slices[e]=43.7599983215332&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4`,
Slices{
Slice: []Primitives{
{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: []interface{}{1, 2.3, "hello"},
},
QuerySettings{ArrayFormat: ArrayQueryFormatRepeat},
},
"primitive_pointer_struct": {
"a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4,5",
PrimitivePointers{
A: P(false),
B: P(237628372683),
C: P(uint(654)),
D: P(9999.43),
E: P(float32(43.76)),
F: &[]int{1, 2, 3, 4, 5},
},
QuerySettings{},
},
"datetime_struct": {
`date=2006-01-02&date-time=2006-01-02T15:04:05Z`,
DateTime{
Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC),
DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC),
},
QuerySettings{},
},
"additional_properties": {
`a=true&bar=value&foo=true`,
AdditionalProperties{
A: true,
Extras: map[string]interface{}{
"bar": "value",
"foo": true,
},
},
QuerySettings{},
},
"recursive_struct_brackets": {
`child[name]=Alex&name=Robert`,
Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}},
QuerySettings{NestedFormat: NestedQueryFormatBrackets},
},
"recursive_struct_dots": {
`child.name=Alex&name=Robert`,
Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}},
QuerySettings{NestedFormat: NestedQueryFormatDots},
},
"unknown_struct_number": {
`unknown=12`,
UnknownStruct{
Unknown: 12.,
},
QuerySettings{},
},
"unknown_struct_map_brackets": {
`unknown[foo]=bar`,
UnknownStruct{
Unknown: map[string]interface{}{
"foo": "bar",
},
},
QuerySettings{NestedFormat: NestedQueryFormatBrackets},
},
"unknown_struct_map_dots": {
`unknown.foo=bar`,
UnknownStruct{
Unknown: map[string]interface{}{
"foo": "bar",
},
},
QuerySettings{NestedFormat: NestedQueryFormatDots},
},
"union_string": {
`union=hello`,
UnionStruct{
Union: UnionString("hello"),
},
QuerySettings{},
},
"union_integer": {
`union=12`,
UnionStruct{
Union: UnionInteger(12),
},
QuerySettings{},
},
"union_struct_discriminated_a": {
`union[a]=foo&union[b]=bar&union[type]=typeA`,
UnionStruct{
Union: UnionStructA{
Type: "typeA",
A: "foo",
B: "bar",
},
},
QuerySettings{},
},
"union_struct_discriminated_b": {
`union[a]=foo&union[type]=typeB`,
UnionStruct{
Union: UnionStructB{
Type: "typeB",
A: "foo",
},
},
QuerySettings{},
},
"union_struct_time": {
`union=2010-05-23`,
UnionStruct{
Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)),
},
QuerySettings{},
},
"deeply_nested_brackets": {
`a[b][c][d]=hello`,
DeeplyNested{
A: DeeplyNested1{
B: DeeplyNested2{
C: DeeplyNested3{
D: P("hello"),
},
},
},
},
QuerySettings{NestedFormat: NestedQueryFormatBrackets},
},
"deeply_nested_dots": {
`a.b.c.d=hello`,
DeeplyNested{
A: DeeplyNested1{
B: DeeplyNested2{
C: DeeplyNested3{
D: P("hello"),
},
},
},
},
QuerySettings{NestedFormat: NestedQueryFormatDots},
},
"deeply_nested_brackets_empty": {
``,
DeeplyNested{
A: DeeplyNested1{
B: DeeplyNested2{
C: DeeplyNested3{
D: nil,
},
},
},
},
QuerySettings{NestedFormat: NestedQueryFormatBrackets},
},
"deeply_nested_dots_empty": {
``,
DeeplyNested{
A: DeeplyNested1{
B: DeeplyNested2{
C: DeeplyNested3{
D: nil,
},
},
},
},
QuerySettings{NestedFormat: NestedQueryFormatDots},
},
}
func TestEncode(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
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,41 +0,0 @@
package apiquery
import (
"reflect"
"strings"
)
const queryStructTag = "query"
const formatStructTag = "format"
type parsedStructTag struct {
name string
omitempty bool
inline bool
}
func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(queryStructTag)
if !ok {
return
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
return tag, false
}
tag.name = parts[0]
for _, part := range parts[1:] {
switch part {
case "omitempty":
tag.omitempty = true
case "inline":
tag.inline = true
}
}
return
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
return
}

View file

@ -1,29 +0,0 @@
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,636 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package requestconfig
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math"
"math/rand"
"mime"
"net/http"
"net/url"
"runtime"
"strconv"
"strings"
"time"
"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 {
return map[string]string{
"User-Agent": fmt.Sprintf("Opencode/Go %s", internal.PackageVersion),
}
}
func getNormalizedOS() string {
switch runtime.GOOS {
case "ios":
return "iOS"
case "android":
return "Android"
case "darwin":
return "MacOS"
case "window":
return "Windows"
case "freebsd":
return "FreeBSD"
case "openbsd":
return "OpenBSD"
case "linux":
return "Linux"
default:
return fmt.Sprintf("Other:%s", runtime.GOOS)
}
}
func getNormalizedArchitecture() string {
switch runtime.GOARCH {
case "386":
return "x32"
case "amd64":
return "x64"
case "arm":
return "arm"
case "arm64":
return "arm64"
default:
return fmt.Sprintf("other:%s", runtime.GOARCH)
}
}
func getPlatformProperties() map[string]string {
return map[string]string{
"X-Stainless-Lang": "go",
"X-Stainless-Package-Version": internal.PackageVersion,
"X-Stainless-OS": getNormalizedOS(),
"X-Stainless-Arch": getNormalizedArchitecture(),
"X-Stainless-Runtime": "go",
"X-Stainless-Runtime-Version": runtime.Version(),
}
}
type RequestOption interface {
Apply(*RequestConfig) error
}
type RequestOptionFunc func(*RequestConfig) error
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 interface{}, dst interface{}, opts ...RequestOption) (*RequestConfig, error) {
var reader io.Reader
contentType := "application/json"
hasSerializationFunc := false
if body, ok := body.(json.Marshaler); ok {
content, err := body.MarshalJSON()
if err != nil {
return nil, err
}
reader = bytes.NewBuffer(content)
hasSerializationFunc = true
}
if body, ok := body.(apiform.Marshaler); ok {
var (
content []byte
err error
)
content, contentType, err = body.MarshalMultipart()
if err != nil {
return nil, err
}
reader = bytes.NewBuffer(content)
hasSerializationFunc = true
}
if body, ok := body.(apiquery.Queryer); ok {
hasSerializationFunc = true
params := body.URLQuery().Encode()
if params != "" {
u = u + "?" + params
}
}
if body, ok := body.([]byte); ok {
reader = bytes.NewBuffer(body)
hasSerializationFunc = true
}
if body, ok := body.(io.Reader); ok {
reader = body
hasSerializationFunc = true
}
// Fallback to json serialization if none of the serialization functions that we expect
// to see is present.
if body != nil && !hasSerializationFunc {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(true)
if err := enc.Encode(body); err != nil {
return nil, err
}
reader = buf
}
req, err := http.NewRequestWithContext(ctx, method, u, nil)
if err != nil {
return nil, err
}
if reader != nil {
req.Header.Set("Content-Type", contentType)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Stainless-Retry-Count", "0")
req.Header.Set("X-Stainless-Timeout", "0")
for k, v := range getDefaultHeaders() {
req.Header.Add(k, v)
}
for k, v := range getPlatformProperties() {
req.Header.Add(k, v)
}
cfg := RequestConfig{
MaxRetries: 2,
Context: ctx,
Request: req,
HTTPClient: http.DefaultClient,
Body: reader,
}
cfg.ResponseBodyInto = dst
err = cfg.Apply(opts...)
if err != nil {
return nil, err
}
// This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only
// apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified
// by the user and we should respect that.
if req.Header.Get("X-Stainless-Timeout") == "0" {
if cfg.RequestTimeout == time.Duration(0) {
req.Header.Del("X-Stainless-Timeout")
} else {
req.Header.Set("X-Stainless-Timeout", strconv.Itoa(int(cfg.RequestTimeout.Seconds())))
}
}
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 {
Do(req *http.Request) (*http.Response, error)
}
// RequestConfig represents all the state related to one request.
//
// Editing the variables inside RequestConfig directly is unstable api. Prefer
// composing the RequestOption instead if possible.
type RequestConfig struct {
MaxRetries int
RequestTimeout time.Duration
Context context.Context
Request *http.Request
BaseURL *url.URL
// DefaultBaseURL will be used if BaseURL is not explicitly overridden using
// WithBaseURL.
DefaultBaseURL *url.URL
CustomHTTPDoer HTTPDoer
HTTPClient *http.Client
Middlewares []middleware
// 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 interface{}
// ResponseInto copies the \*http.Response of the corresponding request into the
// given address
ResponseInto **http.Response
Body io.Reader
}
// middleware is exactly the same type as the Middleware type found in the [option] package,
// but it is redeclared here for circular dependency issues.
type middleware = func(*http.Request, middlewareNext) (*http.Response, error)
// middlewareNext is exactly the same type as the MiddlewareNext type found in the [option] package,
// but it is redeclared here for circular dependency issues.
type middlewareNext = func(*http.Request) (*http.Response, error)
func applyMiddleware(middleware middleware, next middlewareNext) middlewareNext {
return func(req *http.Request) (res *http.Response, err error) {
return middleware(req, next)
}
}
func shouldRetry(req *http.Request, res *http.Response) bool {
// If there is no way to recover the Body, then we shouldn't retry.
if req.Body != nil && req.GetBody == nil {
return false
}
// If there is no response, that indicates that there is a connection error
// so we retry the request.
if res == nil {
return true
}
// If the header explicitly wants a retry behavior, respect that over the
// http status code.
if res.Header.Get("x-should-retry") == "true" {
return true
}
if res.Header.Get("x-should-retry") == "false" {
return false
}
return res.StatusCode == http.StatusRequestTimeout ||
res.StatusCode == http.StatusConflict ||
res.StatusCode == http.StatusTooManyRequests ||
res.StatusCode >= http.StatusInternalServerError
}
func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) {
if resp == nil {
return 0, false
}
type retryData struct {
header string
units time.Duration
// custom is used when the regular algorithm failed and is optional.
// the returned duration is used verbatim (units is not applied).
custom func(string) (time.Duration, bool)
}
nop := func(string) (time.Duration, bool) { return 0, false }
// the headers are listed in order of preference
retries := []retryData{
{
header: "Retry-After-Ms",
units: time.Millisecond,
custom: nop,
},
{
header: "Retry-After",
units: time.Second,
// retry-after values are expressed in either number of
// seconds or an HTTP-date indicating when to try again
custom: func(ra string) (time.Duration, bool) {
t, err := time.Parse(time.RFC1123, ra)
if err != nil {
return 0, false
}
return time.Until(t), true
},
},
}
for _, retry := range retries {
v := resp.Header.Get(retry.header)
if v == "" {
continue
}
if retryAfter, err := strconv.ParseFloat(v, 64); err == nil {
return time.Duration(retryAfter * float64(retry.units)), true
}
if d, ok := retry.custom(v); ok {
return d, true
}
}
return 0, false
}
// isBeforeContextDeadline reports whether the non-zero Time t is
// before ctx's deadline. If ctx does not have a deadline, it
// always reports true (the deadline is considered infinite).
func isBeforeContextDeadline(t time.Time, ctx context.Context) bool {
d, ok := ctx.Deadline()
if !ok {
return true
}
return t.Before(d)
}
// bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func
// to handle timeouts etc. It wraps an existing io.ReadCloser.
type bodyWithTimeout struct {
stop func() // stops the time.Timer waiting to cancel the request
rc io.ReadCloser
}
func (b *bodyWithTimeout) Read(p []byte) (n int, err error) {
n, err = b.rc.Read(p)
if err == nil {
return n, nil
}
if err == io.EOF {
return n, err
}
return n, err
}
func (b *bodyWithTimeout) Close() error {
err := b.rc.Close()
b.stop()
return err
}
func retryDelay(res *http.Response, retryCount int) time.Duration {
// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
// just do what it says.
if retryAfterDelay, ok := parseRetryAfterHeader(res); ok && 0 <= retryAfterDelay && retryAfterDelay < time.Minute {
return retryAfterDelay
}
maxDelay := 8 * time.Second
delay := time.Duration(0.5 * float64(time.Second) * math.Pow(2, float64(retryCount)))
if delay > maxDelay {
delay = maxDelay
}
jitter := rand.Int63n(int64(delay / 4))
delay -= time.Duration(jitter)
return delay
}
func (cfg *RequestConfig) Execute() (err error) {
if cfg.BaseURL == nil {
if cfg.DefaultBaseURL != nil {
cfg.BaseURL = cfg.DefaultBaseURL
} else {
return fmt.Errorf("requestconfig: base url is not set")
}
}
cfg.Request.URL, err = cfg.BaseURL.Parse(strings.TrimLeft(cfg.Request.URL.String(), "/"))
if err != nil {
return err
}
if cfg.Body != nil && cfg.Request.Body == nil {
switch body := cfg.Body.(type) {
case *bytes.Buffer:
b := body.Bytes()
cfg.Request.ContentLength = int64(body.Len())
cfg.Request.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }
cfg.Request.Body, _ = cfg.Request.GetBody()
case *bytes.Reader:
cfg.Request.ContentLength = int64(body.Len())
cfg.Request.GetBody = func() (io.ReadCloser, error) {
_, err := body.Seek(0, 0)
return io.NopCloser(body), err
}
cfg.Request.Body, _ = cfg.Request.GetBody()
default:
if rc, ok := body.(io.ReadCloser); ok {
cfg.Request.Body = rc
} else {
cfg.Request.Body = io.NopCloser(body)
}
}
}
handler := cfg.HTTPClient.Do
if cfg.CustomHTTPDoer != nil {
handler = cfg.CustomHTTPDoer.Do
}
for i := len(cfg.Middlewares) - 1; i >= 0; i -= 1 {
handler = applyMiddleware(cfg.Middlewares[i], handler)
}
// Don't send the current retry count in the headers if the caller modified the header defaults.
shouldSendRetryCount := cfg.Request.Header.Get("X-Stainless-Retry-Count") == "0"
var res *http.Response
var cancel context.CancelFunc
for retryCount := 0; retryCount <= cfg.MaxRetries; retryCount += 1 {
ctx := cfg.Request.Context()
if cfg.RequestTimeout != time.Duration(0) && isBeforeContextDeadline(time.Now().Add(cfg.RequestTimeout), ctx) {
ctx, cancel = context.WithTimeout(ctx, cfg.RequestTimeout)
defer func() {
// The cancel function is nil if it was handed off to be handled in a different scope.
if cancel != nil {
cancel()
}
}()
}
req := cfg.Request.Clone(ctx)
if shouldSendRetryCount {
req.Header.Set("X-Stainless-Retry-Count", strconv.Itoa(retryCount))
}
res, err = handler(req)
if ctx != nil && ctx.Err() != nil {
return ctx.Err()
}
if !shouldRetry(cfg.Request, res) || retryCount >= cfg.MaxRetries {
break
}
// Prepare next request and wait for the retry delay
if cfg.Request.GetBody != nil {
cfg.Request.Body, err = cfg.Request.GetBody()
if err != nil {
return err
}
}
// Can't actually refresh the body, so we don't attempt to retry here
if cfg.Request.GetBody == nil && cfg.Request.Body != nil {
break
}
// Close the response body before retrying to prevent connection leaks
if res != nil && res.Body != nil {
res.Body.Close()
}
time.Sleep(retryDelay(res, retryCount))
}
// Save *http.Response if it is requested to, even if there was an error making the request. This is
// useful in cases where you might want to debug by inspecting the response. Note that if err != nil,
// the response should be generally be empty, but there are edge cases.
if cfg.ResponseInto != nil {
*cfg.ResponseInto = res
}
if responseBodyInto, ok := cfg.ResponseBodyInto.(**http.Response); ok {
*responseBodyInto = res
}
// If there was a connection error in the final request or any other transport error,
// return that early without trying to coerce into an APIError.
if err != nil {
return err
}
if res.StatusCode >= 400 {
contents, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
return err
}
// If there is an APIError, re-populate the response body so that debugging
// utilities can conveniently dump the response without issue.
res.Body = io.NopCloser(bytes.NewBuffer(contents))
// Load the contents into the error format if it is provided.
aerr := apierror.Error{Request: cfg.Request, Response: res, StatusCode: res.StatusCode}
err = aerr.UnmarshalJSON(contents)
if err != nil {
return err
}
return &aerr
}
_, intoCustomResponseBody := cfg.ResponseBodyInto.(**http.Response)
if cfg.ResponseBodyInto == nil || intoCustomResponseBody {
// We aren't reading the response body in this scope, but whoever is will need the
// cancel func from the context to observe request timeouts.
// Put the cancel function in the response body so it can be handled elsewhere.
if cancel != nil {
res.Body = &bodyWithTimeout{rc: res.Body, stop: cancel}
cancel = nil
}
return nil
}
contents, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
return fmt.Errorf("error reading response body: %w", err)
}
// If we are not json, return plaintext
contentType := res.Header.Get("content-type")
mediaType, _, _ := mime.ParseMediaType(contentType)
isJSON := strings.Contains(mediaType, "application/json") || strings.HasSuffix(mediaType, "+json")
if !isJSON {
switch dst := cfg.ResponseBodyInto.(type) {
case *string:
*dst = string(contents)
case **string:
tmp := string(contents)
*dst = &tmp
case *[]byte:
*dst = contents
default:
return fmt.Errorf("expected destination type of 'string' or '[]byte' for responses with content-type '%s' that is not 'application/json'", contentType)
}
return nil
}
switch dst := cfg.ResponseBodyInto.(type) {
// If the response happens to be a byte array, deserialize the body as-is.
case *[]byte:
*dst = contents
default:
err = json.NewDecoder(bytes.NewReader(contents)).Decode(cfg.ResponseBodyInto)
if err != nil {
return fmt.Errorf("error parsing response json: %w", err)
}
}
return nil
}
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
}
return cfg.Execute()
}
func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig {
if cfg == nil {
return nil
}
req := cfg.Request.Clone(ctx)
var err error
if req.Body != nil {
req.Body, err = req.GetBody()
}
if err != nil {
return nil
}
new := &RequestConfig{
MaxRetries: cfg.MaxRetries,
RequestTimeout: cfg.RequestTimeout,
Context: ctx,
Request: req,
BaseURL: cfg.BaseURL,
HTTPClient: cfg.HTTPClient,
Middlewares: cfg.Middlewares,
}
return new
}
func (cfg *RequestConfig) Apply(opts ...RequestOption) error {
for _, opt := range opts {
err := opt.Apply(cfg)
if err != nil {
return err
}
}
return nil
}
// PreRequestOptions is used to collect all the options which need to be known before
// a call to [RequestConfig.ExecuteNewRequest], such as path parameters
// or global defaults.
// PreRequestOptions will return a [RequestConfig] with the options applied.
//
// Only request option functions of type [PreRequestOptionFunc] are applied.
func PreRequestOptions(opts ...RequestOption) (RequestConfig, error) {
cfg := RequestConfig{}
for _, opt := range opts {
if opt, ok := opt.(PreRequestOptionFunc); ok {
err := opt.Apply(&cfg)
if err != nil {
return cfg, err
}
}
}
return cfg, nil
}
// WithDefaultBaseURL returns a RequestOption that sets the client's default Base URL.
// This is always overridden by setting a base URL with WithBaseURL.
// WithBaseURL should be used instead of WithDefaultBaseURL except in internal code.
func WithDefaultBaseURL(baseURL string) RequestOption {
u, err := url.Parse(baseURL)
return RequestOptionFunc(func(r *RequestConfig) error {
if err != nil {
return err
}
r.DefaultBaseURL = u
return nil
})
}

View file

@ -1,27 +0,0 @@
package testutil
import (
"net/http"
"os"
"strconv"
"testing"
)
func CheckTestServer(t *testing.T, url string) bool {
if _, err := http.Get(url); err != nil {
const SKIP_MOCK_TESTS = "SKIP_MOCK_TESTS"
if str, ok := os.LookupEnv(SKIP_MOCK_TESTS); ok {
skip, err := strconv.ParseBool(str)
if err != nil {
t.Fatalf("strconv.ParseBool(os.LookupEnv(%s)) failed: %s", SKIP_MOCK_TESTS, err)
}
if skip {
t.Skip("The test will not run without a mock Prism server running against your OpenAPI spec")
return false
}
t.Errorf("The test will not run without a mock Prism server running against your OpenAPI spec. You can set the environment variable %s to true to skip running any tests that require the mock server", SKIP_MOCK_TESTS)
return false
}
}
return true
}

View file

@ -1,5 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package internal
const PackageVersion = "0.18.0" // x-release-please-version

View file

@ -1,4 +0,0 @@
File generated from our OpenAPI spec by Stainless.
This directory can be used to store custom files to expand the SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

View file

@ -1,38 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package option
import (
"log"
"net/http"
"net/http/httputil"
)
// WithDebugLog logs the HTTP request and response content.
// If the logger parameter is nil, it uses the default logger.
//
// WithDebugLog is for debugging and development purposes only.
// It should not be used in production code. The behavior and interface
// of WithDebugLog is not guaranteed to be stable.
func WithDebugLog(logger *log.Logger) RequestOption {
return WithMiddleware(func(req *http.Request, nxt MiddlewareNext) (*http.Response, error) {
if logger == nil {
logger = log.Default()
}
if reqBytes, err := httputil.DumpRequest(req, true); err == nil {
logger.Printf("Request Content:\n%s\n", reqBytes)
}
resp, err := nxt(req)
if err != nil {
return resp, err
}
if respBytes, err := httputil.DumpResponse(resp, true); err == nil {
logger.Printf("Response Content:\n%s\n", respBytes)
}
return resp, err
})
}

View file

@ -1,267 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package option
import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/tidwall/sjson"
)
// RequestOption is an option for the requests made by the opencode API Client
// 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/sst/opencode-sdk-go#readme-requestoptions
type RequestOption = requestconfig.RequestOption
// WithBaseURL returns a RequestOption that sets the BaseURL for the client.
//
// For security reasons, ensure that the base URL is trusted.
func WithBaseURL(base string) RequestOption {
u, err := url.Parse(base)
if err == nil && u.Path != "" && !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
if err != nil {
return fmt.Errorf("requestoption: WithBaseURL failed to parse url %s", err)
}
r.BaseURL = u
return nil
})
}
// HTTPClient is primarily used to describe an [*http.Client], but also
// supports custom implementations.
//
// For bespoke implementations, prefer using an [*http.Client] with a
// custom transport. See [http.RoundTripper] for further information.
type HTTPClient interface {
Do(*http.Request) (*http.Response, error)
}
// WithHTTPClient returns a RequestOption that changes the underlying http client used to make this
// request, which by default is [http.DefaultClient].
//
// For custom uses cases, it is recommended to provide an [*http.Client] with a custom
// [http.RoundTripper] as its transport, rather than directly implementing [HTTPClient].
func WithHTTPClient(client HTTPClient) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
if client == nil {
return fmt.Errorf("requestoption: custom http client cannot be nil")
}
if c, ok := client.(*http.Client); ok {
// Prefer the native client if possible.
r.HTTPClient = c
r.CustomHTTPDoer = nil
} else {
r.CustomHTTPDoer = client
}
return nil
})
}
// MiddlewareNext is a function which is called by a middleware to pass an HTTP request
// to the next stage in the middleware chain.
type MiddlewareNext = func(*http.Request) (*http.Response, error)
// Middleware is a function which intercepts HTTP requests, processing or modifying
// them, and then passing the request to the next middleware or handler
// in the chain by calling the provided MiddlewareNext function.
type Middleware = func(*http.Request, MiddlewareNext) (*http.Response, error)
// WithMiddleware returns a RequestOption that applies the given middleware
// to the requests made. Each middleware will execute in the order they were given.
func WithMiddleware(middlewares ...Middleware) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.Middlewares = append(r.Middlewares, middlewares...)
return nil
})
}
// WithMaxRetries returns a RequestOption that sets the maximum number of retries that the client
// attempts to make. When given 0, the client only makes one request. By
// default, the client retries two times.
//
// WithMaxRetries panics when retries is negative.
func WithMaxRetries(retries int) RequestOption {
if retries < 0 {
panic("option: cannot have fewer than 0 retries")
}
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.MaxRetries = retries
return nil
})
}
// WithHeader returns a RequestOption that sets the header value to the associated key. It overwrites
// any value if there was one already present.
func WithHeader(key, value string) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.Request.Header.Set(key, value)
return nil
})
}
// WithHeaderAdd returns a RequestOption that adds the header value to the associated key. It appends
// onto any existing values.
func WithHeaderAdd(key, value string) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.Request.Header.Add(key, value)
return nil
})
}
// WithHeaderDel returns a RequestOption that deletes the header value(s) associated with the given key.
func WithHeaderDel(key string) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.Request.Header.Del(key)
return nil
})
}
// WithQuery returns a RequestOption that sets the query value to the associated key. It overwrites
// any value if there was one already present.
func WithQuery(key, value string) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
query := r.Request.URL.Query()
query.Set(key, value)
r.Request.URL.RawQuery = query.Encode()
return nil
})
}
// WithQueryAdd returns a RequestOption that adds the query value to the associated key. It appends
// onto any existing values.
func WithQueryAdd(key, value string) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
query := r.Request.URL.Query()
query.Add(key, value)
r.Request.URL.RawQuery = query.Encode()
return nil
})
}
// WithQueryDel returns a RequestOption that deletes the query value(s) associated with the key.
func WithQueryDel(key string) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
query := r.Request.URL.Query()
query.Del(key)
r.Request.URL.RawQuery = query.Encode()
return nil
})
}
// WithJSONSet returns a RequestOption that sets the body's JSON value associated with the key.
// The key accepts a string as defined by the [sjson format].
//
// [sjson format]: https://github.com/tidwall/sjson
func WithJSONSet(key string, value interface{}) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) {
var b []byte
if r.Body == nil {
b, err = sjson.SetBytes(nil, key, value)
if err != nil {
return err
}
} else if buffer, ok := r.Body.(*bytes.Buffer); ok {
b = buffer.Bytes()
b, err = sjson.SetBytes(b, key, value)
if err != nil {
return err
}
} else {
return fmt.Errorf("cannot use WithJSONSet on a body that is not serialized as *bytes.Buffer")
}
r.Body = bytes.NewBuffer(b)
return nil
})
}
// WithJSONDel returns a RequestOption that deletes the body's JSON value associated with the key.
// The key accepts a string as defined by the [sjson format].
//
// [sjson format]: https://github.com/tidwall/sjson
func WithJSONDel(key string) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) {
if buffer, ok := r.Body.(*bytes.Buffer); ok {
b := buffer.Bytes()
b, err = sjson.DeleteBytes(b, key)
if err != nil {
return err
}
r.Body = bytes.NewBuffer(b)
return nil
}
return fmt.Errorf("cannot use WithJSONDel on a body that is not serialized as *bytes.Buffer")
})
}
// WithResponseBodyInto returns a RequestOption that overwrites the deserialization target with
// the given destination. If provided, we don't deserialize into the default struct.
func WithResponseBodyInto(dst any) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.ResponseBodyInto = dst
return nil
})
}
// WithResponseInto returns a RequestOption that copies the [*http.Response] into the given address.
func WithResponseInto(dst **http.Response) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.ResponseInto = dst
return nil
})
}
// WithRequestBody returns a RequestOption that provides a custom serialized body with the given
// content type.
//
// body accepts an io.Reader or raw []bytes.
func WithRequestBody(contentType string, body any) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
if reader, ok := body.(io.Reader); ok {
r.Body = reader
return r.Apply(WithHeader("Content-Type", contentType))
}
if b, ok := body.([]byte); ok {
r.Body = bytes.NewBuffer(b)
return r.Apply(WithHeader("Content-Type", contentType))
}
return fmt.Errorf("body must be a byte slice or implement io.Reader")
})
}
// WithRequestTimeout returns a RequestOption that sets the timeout for
// each request attempt. This should be smaller than the timeout defined in
// the context, which spans all retries.
func WithRequestTimeout(dur time.Duration) RequestOption {
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
r.RequestTimeout = dur
return nil
})
}
// WithEnvironmentProduction returns a RequestOption that sets the current
// environment to be the "production" environment. An environment specifies which base URL
// to use by default.
func WithEnvironmentProduction() RequestOption {
return requestconfig.WithDefaultBaseURL("http://localhost:54321/")
}

View file

@ -1,181 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package ssestream
import (
"bufio"
"bytes"
"encoding/json"
"io"
"net/http"
"strings"
)
type Decoder interface {
Event() Event
Next() bool
Close() error
Err() error
}
func NewDecoder(res *http.Response) Decoder {
if res == nil || res.Body == nil {
return nil
}
var decoder Decoder
contentType := res.Header.Get("content-type")
if t, ok := decoderTypes[contentType]; ok {
decoder = t(res.Body)
} else {
scn := bufio.NewScanner(res.Body)
scn.Buffer(nil, bufio.MaxScanTokenSize<<9)
decoder = &eventStreamDecoder{rc: res.Body, scn: scn}
}
return decoder
}
var decoderTypes = map[string](func(io.ReadCloser) Decoder){}
func RegisterDecoder(contentType string, decoder func(io.ReadCloser) Decoder) {
decoderTypes[strings.ToLower(contentType)] = decoder
}
type Event struct {
Type string
Data []byte
}
// A base implementation of a Decoder for text/event-stream.
type eventStreamDecoder struct {
evt Event
rc io.ReadCloser
scn *bufio.Scanner
err error
}
func (s *eventStreamDecoder) Next() bool {
if s.err != nil {
return false
}
event := ""
data := bytes.NewBuffer(nil)
for s.scn.Scan() {
txt := s.scn.Bytes()
// Dispatch event on an empty line
if len(txt) == 0 {
s.evt = Event{
Type: event,
Data: data.Bytes(),
}
return true
}
// Split a string like "event: bar" into name="event" and value=" bar".
name, value, _ := bytes.Cut(txt, []byte(":"))
// Consume an optional space after the colon if it exists.
if len(value) > 0 && value[0] == ' ' {
value = value[1:]
}
switch string(name) {
case "":
// An empty line in the for ": something" is a comment and should be ignored.
continue
case "event":
event = string(value)
case "data":
_, s.err = data.Write(value)
if s.err != nil {
break
}
_, s.err = data.WriteRune('\n')
if s.err != nil {
break
}
}
}
if s.scn.Err() != nil {
s.err = s.scn.Err()
}
return false
}
func (s *eventStreamDecoder) Event() Event {
return s.evt
}
func (s *eventStreamDecoder) Close() error {
return s.rc.Close()
}
func (s *eventStreamDecoder) Err() error {
return s.err
}
type Stream[T any] struct {
decoder Decoder
cur T
err error
}
func NewStream[T any](decoder Decoder, err error) *Stream[T] {
return &Stream[T]{
decoder: decoder,
err: err,
}
}
// Next returns false if the stream has ended or an error occurred.
// Call Stream.Current() to get the current value.
// Call Stream.Err() to get the error.
//
// for stream.Next() {
// data := stream.Current()
// }
//
// if stream.Err() != nil {
// ...
// }
func (s *Stream[T]) Next() bool {
if s.err != nil {
return false
}
for s.decoder.Next() {
var nxt T
s.err = json.Unmarshal(s.decoder.Event().Data, &nxt)
if s.err != nil {
return false
}
s.cur = nxt
return true
}
// decoder.Next() may be false because of an error
s.err = s.decoder.Err()
return false
}
func (s *Stream[T]) Current() T {
return s.cur
}
func (s *Stream[T]) Err() error {
return s.err
}
func (s *Stream[T]) Close() error {
if s.decoder == nil {
// already closed
return nil
}
return s.decoder.Close()
}

View file

@ -1,81 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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"
)
// PathService 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 [NewPathService] method instead.
type PathService struct {
Options []option.RequestOption
}
// NewPathService 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 NewPathService(opts ...option.RequestOption) (r *PathService) {
r = &PathService{}
r.Options = opts
return
}
// Get the current path
func (r *PathService) Get(ctx context.Context, query PathGetParams, opts ...option.RequestOption) (res *Path, err error) {
opts = slices.Concat(r.Options, opts)
path := "path"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
type Path struct {
Config string `json:"config,required"`
Directory string `json:"directory,required"`
State string `json:"state,required"`
Worktree string `json:"worktree,required"`
JSON pathJSON `json:"-"`
}
// pathJSON contains the JSON metadata for the struct [Path]
type pathJSON struct {
Config apijson.Field
Directory apijson.Field
State apijson.Field
Worktree apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *Path) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r pathJSON) RawJSON() string {
return r.raw
}
type PathGetParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [PathGetParams]'s query parameters as `url.Values`.
func (r PathGetParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}

View file

@ -1,38 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestPathGetWithOptionalParams(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.Path.Get(context.TODO(), opencode.PathGetParams{
Directory: opencode.F("directory"),
})
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,137 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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"
)
// 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, query ProjectListParams, opts ...option.RequestOption) (res *[]Project, err error) {
opts = slices.Concat(r.Options, opts)
path := "project"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Get the current project
func (r *ProjectService) Current(ctx context.Context, query ProjectCurrentParams, opts ...option.RequestOption) (res *Project, err error) {
opts = slices.Concat(r.Options, opts)
path := "project/current"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
type Project struct {
ID string `json:"id,required"`
Time ProjectTime `json:"time,required"`
Worktree string `json:"worktree,required"`
Vcs ProjectVcs `json:"vcs"`
JSON projectJSON `json:"-"`
}
// projectJSON contains the JSON metadata for the struct [Project]
type projectJSON struct {
ID apijson.Field
Time apijson.Field
Worktree apijson.Field
Vcs apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *Project) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r projectJSON) RawJSON() string {
return r.raw
}
type ProjectTime struct {
Created float64 `json:"created,required"`
Initialized float64 `json:"initialized"`
JSON projectTimeJSON `json:"-"`
}
// projectTimeJSON contains the JSON metadata for the struct [ProjectTime]
type projectTimeJSON struct {
Created apijson.Field
Initialized apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ProjectTime) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r projectTimeJSON) RawJSON() string {
return r.raw
}
type ProjectVcs string
const (
ProjectVcsGit ProjectVcs = "git"
)
func (r ProjectVcs) IsKnown() bool {
switch r {
case ProjectVcsGit:
return true
}
return false
}
type ProjectListParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [ProjectListParams]'s query parameters as `url.Values`.
func (r ProjectListParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type ProjectCurrentParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [ProjectCurrentParams]'s query parameters as `url.Values`.
func (r ProjectCurrentParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}

View file

@ -1,62 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestProjectListWithOptionalParams(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.Project.List(context.TODO(), opencode.ProjectListParams{
Directory: opencode.F("directory"),
})
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 TestProjectCurrentWithOptionalParams(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.Project.Current(context.TODO(), opencode.ProjectCurrentParams{
Directory: opencode.F("directory"),
})
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,64 +0,0 @@
{
"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"]
}

View file

@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
brew bundle check >/dev/null 2>&1 || {
echo -n "==> Install Homebrew dependencies? (y/N): "
read -r response
case "$response" in
[yY][eE][sS]|[yY])
brew bundle
;;
*)
;;
esac
echo
}
fi
echo "==> Installing Go dependencies…"
go mod tidy -e

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
echo "==> Running gofmt -s -w"
gofmt -s -w .

View file

@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
echo "==> Running Go build"
go build ./...
echo "==> Checking tests compile"
go test -run=^$ ./...

View file

@ -1,41 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
if [[ -n "$1" && "$1" != '--'* ]]; then
URL="$1"
shift
else
URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)"
fi
# Check if the URL is empty
if [ -z "$URL" ]; then
echo "Error: No OpenAPI spec path/url provided or found in .stats.yml"
exit 1
fi
echo "==> Starting mock server with URL ${URL}"
# Run prism mock on the given spec
if [ "$1" == "--daemon" ]; then
npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log &
# Wait for server to come online
echo -n "Waiting for server"
while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do
echo -n "."
sleep 0.1
done
if grep -q "✖ fatal" ".prism.log"; then
cat .prism.log
exit 1
fi
echo
else
npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL"
fi

View file

@ -1,56 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
function prism_is_running() {
curl --silent "http://localhost:4010" >/dev/null 2>&1
}
kill_server_on_port() {
pids=$(lsof -t -i tcp:"$1" || echo "")
if [ "$pids" != "" ]; then
kill "$pids"
echo "Stopped $pids."
fi
}
function is_overriding_api_base_url() {
[ -n "$TEST_API_BASE_URL" ]
}
if ! is_overriding_api_base_url && ! prism_is_running ; then
# When we exit this script, make sure to kill the background mock server process
trap 'kill_server_on_port 4010' EXIT
# Start the dev server
./scripts/mock --daemon
fi
if is_overriding_api_base_url ; then
echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}"
echo
elif ! prism_is_running ; then
echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server"
echo -e "running against your OpenAPI spec."
echo
echo -e "To run the server, pass in the path or url of your OpenAPI"
echo -e "spec to the prism command:"
echo
echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}"
echo
exit 1
else
echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}"
echo
fi
echo "==> Running tests"
go test ./... "$@"

File diff suppressed because it is too large Load diff

View file

@ -1,552 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestSessionNewWithOptionalParams(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.New(context.TODO(), opencode.SessionNewParams{
Directory: opencode.F("directory"),
ParentID: opencode.F("sesJ!"),
Title: opencode.F("title"),
})
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"
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.Update(
context.TODO(),
"id",
opencode.SessionUpdateParams{
Directory: opencode.F("directory"),
Title: opencode.F("title"),
},
)
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 TestSessionListWithOptionalParams(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.List(context.TODO(), opencode.SessionListParams{
Directory: opencode.F("directory"),
})
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 TestSessionDeleteWithOptionalParams(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.Delete(
context.TODO(),
"sesJ!",
opencode.SessionDeleteParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionAbortWithOptionalParams(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.Abort(
context.TODO(),
"id",
opencode.SessionAbortParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionChildrenWithOptionalParams(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.Children(
context.TODO(),
"sesJ!",
opencode.SessionChildrenParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionCommandWithOptionalParams(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.Command(
context.TODO(),
"id",
opencode.SessionCommandParams{
Arguments: opencode.F("arguments"),
Command: opencode.F("command"),
Directory: opencode.F("directory"),
Agent: opencode.F("agent"),
MessageID: opencode.F("msgJ!"),
Model: opencode.F("model"),
},
)
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 TestSessionGetWithOptionalParams(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.Get(
context.TODO(),
"sesJ!",
opencode.SessionGetParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionInitWithOptionalParams(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("msgJ!"),
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
Directory: opencode.F("directory"),
},
)
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 TestSessionMessageWithOptionalParams(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",
opencode.SessionMessageParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionMessagesWithOptionalParams(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",
opencode.SessionMessagesParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionPromptWithOptionalParams(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.Prompt(
context.TODO(),
"id",
opencode.SessionPromptParams{
Parts: opencode.F([]opencode.SessionPromptParamsPartUnion{opencode.TextPartInputParam{
Text: opencode.F("text"),
Type: opencode.F(opencode.TextPartInputTypeText),
ID: opencode.F("id"),
Metadata: opencode.F(map[string]interface{}{
"foo": "bar",
}),
Synthetic: opencode.F(true),
Time: opencode.F(opencode.TextPartInputTimeParam{
Start: opencode.F(0.000000),
End: opencode.F(0.000000),
}),
}}),
Directory: opencode.F("directory"),
Agent: opencode.F("agent"),
MessageID: opencode.F("msgJ!"),
Model: opencode.F(opencode.SessionPromptParamsModel{
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
}),
NoReply: opencode.F(true),
System: opencode.F("system"),
Tools: opencode.F(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 TestSessionRevertWithOptionalParams(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.Revert(
context.TODO(),
"id",
opencode.SessionRevertParams{
MessageID: opencode.F("msgJ!"),
Directory: opencode.F("directory"),
PartID: opencode.F("prtJ!"),
},
)
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 TestSessionShareWithOptionalParams(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.Share(
context.TODO(),
"id",
opencode.SessionShareParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionShellWithOptionalParams(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.Shell(
context.TODO(),
"id",
opencode.SessionShellParams{
Agent: opencode.F("agent"),
Command: opencode.F("command"),
Directory: opencode.F("directory"),
},
)
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 TestSessionSummarizeWithOptionalParams(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.Summarize(
context.TODO(),
"id",
opencode.SessionSummarizeParams{
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
Directory: opencode.F("directory"),
},
)
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 TestSessionUnrevertWithOptionalParams(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",
opencode.SessionUnrevertParams{
Directory: opencode.F("directory"),
},
)
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 TestSessionUnshareWithOptionalParams(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(),
"sesJ!",
opencode.SessionUnshareParams{
Directory: opencode.F("directory"),
},
)
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,170 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"reflect"
"slices"
"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"
"github.com/sst/opencode-sdk-go/shared"
"github.com/tidwall/gjson"
)
// 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, params SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(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, params, &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 PermissionPatternUnion `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
}
// Union satisfied by [shared.UnionString] or [PermissionPatternArray].
type PermissionPatternUnion interface {
ImplementsPermissionPatternUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*PermissionPatternUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(shared.UnionString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(PermissionPatternArray{}),
},
)
}
type PermissionPatternArray []string
func (r PermissionPatternArray) ImplementsPermissionPatternUnion() {}
type SessionPermissionRespondParams struct {
Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
Directory param.Field[string] `query:"directory"`
}
func (r SessionPermissionRespondParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
// URLQuery serializes [SessionPermissionRespondParams]'s query parameters as
// `url.Values`.
func (r SessionPermissionRespondParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
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

@ -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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestSessionPermissionRespondWithOptionalParams(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.Permissions.Respond(
context.TODO(),
"id",
"permissionID",
opencode.SessionPermissionRespondParams{
Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
Directory: opencode.F("directory"),
},
)
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,194 +0,0 @@
// 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 MessageAbortedErrorData `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 MessageAbortedErrorData struct {
Message string `json:"message,required"`
JSON messageAbortedErrorDataJSON `json:"-"`
}
// messageAbortedErrorDataJSON contains the JSON metadata for the struct
// [MessageAbortedErrorData]
type messageAbortedErrorDataJSON struct {
Message apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *MessageAbortedErrorData) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r messageAbortedErrorDataJSON) RawJSON() string {
return r.raw
}
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
}

View file

@ -1,15 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package shared
type UnionString string
func (UnionString) ImplementsPermissionPatternUnion() {}
type UnionBool bool
func (UnionBool) ImplementsConfigProviderOptionsTimeoutUnion() {}
type UnionInt int64
func (UnionInt) ImplementsConfigProviderOptionsTimeoutUnion() {}

View file

@ -1,250 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode
import (
"context"
"net/http"
"net/url"
"slices"
"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"
)
// TuiService 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 [NewTuiService] method instead.
type TuiService struct {
Options []option.RequestOption
}
// 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{}
r.Options = opts
return
}
// Append prompt to the TUI
func (r *TuiService) AppendPrompt(ctx context.Context, params TuiAppendPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/append-prompt"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Clear the prompt
func (r *TuiService) ClearPrompt(ctx context.Context, body TuiClearPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/clear-prompt"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Execute a TUI command (e.g. agent_cycle)
func (r *TuiService) ExecuteCommand(ctx context.Context, params TuiExecuteCommandParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/execute-command"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Open the help dialog
func (r *TuiService) OpenHelp(ctx context.Context, body TuiOpenHelpParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/open-help"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Open the model dialog
func (r *TuiService) OpenModels(ctx context.Context, body TuiOpenModelsParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/open-models"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Open the session dialog
func (r *TuiService) OpenSessions(ctx context.Context, body TuiOpenSessionsParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/open-sessions"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Open the theme dialog
func (r *TuiService) OpenThemes(ctx context.Context, body TuiOpenThemesParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/open-themes"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Show a toast notification in the TUI
func (r *TuiService) ShowToast(ctx context.Context, params TuiShowToastParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/show-toast"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Submit the prompt
func (r *TuiService) SubmitPrompt(ctx context.Context, body TuiSubmitPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = slices.Concat(r.Options, opts)
path := "tui/submit-prompt"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
type TuiAppendPromptParams struct {
Text param.Field[string] `json:"text,required"`
Directory param.Field[string] `query:"directory"`
}
func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
// URLQuery serializes [TuiAppendPromptParams]'s query parameters as `url.Values`.
func (r TuiAppendPromptParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiClearPromptParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [TuiClearPromptParams]'s query parameters as `url.Values`.
func (r TuiClearPromptParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiExecuteCommandParams struct {
Command param.Field[string] `json:"command,required"`
Directory param.Field[string] `query:"directory"`
}
func (r TuiExecuteCommandParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
// URLQuery serializes [TuiExecuteCommandParams]'s query parameters as
// `url.Values`.
func (r TuiExecuteCommandParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiOpenHelpParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [TuiOpenHelpParams]'s query parameters as `url.Values`.
func (r TuiOpenHelpParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiOpenModelsParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [TuiOpenModelsParams]'s query parameters as `url.Values`.
func (r TuiOpenModelsParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiOpenSessionsParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [TuiOpenSessionsParams]'s query parameters as `url.Values`.
func (r TuiOpenSessionsParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiOpenThemesParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [TuiOpenThemesParams]'s query parameters as `url.Values`.
func (r TuiOpenThemesParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiShowToastParams struct {
Message param.Field[string] `json:"message,required"`
Variant param.Field[TuiShowToastParamsVariant] `json:"variant,required"`
Directory param.Field[string] `query:"directory"`
Title param.Field[string] `json:"title"`
}
func (r TuiShowToastParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
// URLQuery serializes [TuiShowToastParams]'s query parameters as `url.Values`.
func (r TuiShowToastParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
type TuiShowToastParamsVariant string
const (
TuiShowToastParamsVariantInfo TuiShowToastParamsVariant = "info"
TuiShowToastParamsVariantSuccess TuiShowToastParamsVariant = "success"
TuiShowToastParamsVariantWarning TuiShowToastParamsVariant = "warning"
TuiShowToastParamsVariantError TuiShowToastParamsVariant = "error"
)
func (r TuiShowToastParamsVariant) IsKnown() bool {
switch r {
case TuiShowToastParamsVariantInfo, TuiShowToastParamsVariantSuccess, TuiShowToastParamsVariantWarning, TuiShowToastParamsVariantError:
return true
}
return false
}
type TuiSubmitPromptParams struct {
Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [TuiSubmitPromptParams]'s query parameters as `url.Values`.
func (r TuiSubmitPromptParams) URLQuery() (v url.Values) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}

View file

@ -1,235 +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/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/internal/testutil"
"github.com/sst/opencode-sdk-go/option"
)
func TestTuiAppendPromptWithOptionalParams(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.Tui.AppendPrompt(context.TODO(), opencode.TuiAppendPromptParams{
Text: opencode.F("text"),
Directory: opencode.F("directory"),
})
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 TestTuiClearPromptWithOptionalParams(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.Tui.ClearPrompt(context.TODO(), opencode.TuiClearPromptParams{
Directory: opencode.F("directory"),
})
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 TestTuiExecuteCommandWithOptionalParams(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.Tui.ExecuteCommand(context.TODO(), opencode.TuiExecuteCommandParams{
Command: opencode.F("command"),
Directory: opencode.F("directory"),
})
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 TestTuiOpenHelpWithOptionalParams(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.Tui.OpenHelp(context.TODO(), opencode.TuiOpenHelpParams{
Directory: opencode.F("directory"),
})
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 TestTuiOpenModelsWithOptionalParams(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.Tui.OpenModels(context.TODO(), opencode.TuiOpenModelsParams{
Directory: opencode.F("directory"),
})
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 TestTuiOpenSessionsWithOptionalParams(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.Tui.OpenSessions(context.TODO(), opencode.TuiOpenSessionsParams{
Directory: opencode.F("directory"),
})
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 TestTuiOpenThemesWithOptionalParams(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.Tui.OpenThemes(context.TODO(), opencode.TuiOpenThemesParams{
Directory: opencode.F("directory"),
})
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 TestTuiShowToastWithOptionalParams(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.Tui.ShowToast(context.TODO(), opencode.TuiShowToastParams{
Message: opencode.F("message"),
Variant: opencode.F(opencode.TuiShowToastParamsVariantInfo),
Directory: opencode.F("directory"),
Title: opencode.F("title"),
})
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 TestTuiSubmitPromptWithOptionalParams(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.Tui.SubmitPrompt(context.TODO(), opencode.TuiSubmitPromptParams{
Directory: opencode.F("directory"),
})
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,32 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package opencode_test
import (
"context"
"os"
"testing"
"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) {
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),
)
sessions, err := client.Session.List(context.TODO(), opencode.SessionListParams{})
if err != nil {
t.Error(err)
return
}
t.Logf("%+v\n", sessions)
}

View file

@ -1,22 +0,0 @@
__pycache__/
*.py[cod]
*.egg-info/
.build/
build/
dist/
.coverage
htmlcov/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.venv/
.conda/
.env
.DS_Store
openapi.json
site/
# IDE
.vscode/
.idea/

View file

@ -1,92 +0,0 @@
# Opencode Python SDK
This package provides a Python SDK for the Opencode API. It is generated using openapi-python-client (not Stainless).
Documentation
- Full docs: see `mkdocs` site under `packages/sdk/python/docs/`
- Preview locally:
```bash
uv run --project packages/sdk/python mkdocs serve -f packages/sdk/python/mkdocs.yml
```
Badges
- PyPI: https://img.shields.io/pypi/v/opencode-ai?style=flat-square
Requirements
- Python 3.8+
- uv (recommended) -> https://docs.astral.sh/uv/
- openapi-python-client (invoked via `uvx`)
Install uv
```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Set up the environment (from this directory)
```bash
uv sync --dev
```
Generate client code (from CLI-generated spec)
```bash
# From repository root OR from this directory
uv run python packages/sdk/python/scripts/generate.py --source cli
```
Alternatively, fetch spec from a running server
```bash
uv run python packages/sdk/python/scripts/generate.py --source server --server-url http://localhost:4096/doc
```
This will:
1. Produce an OpenAPI spec from the local CLI or a running server
2. Run openapi-python-client (via `uvx`) to generate client code
3. Copy the generated Python package into src/opencode_ai
Usage (after generation)
```python
from opencode_ai import OpenCodeClient
client = OpenCodeClient(base_url="http://localhost:4096")
print(client.get_config())
# See examples/basic_usage.py for more details
# Streaming events (sync)
for event in client.subscribe_events():
print(event)
break
# Error handling and retries
# Set retries>0 to enable exponential backoff for transient errors like 429/5xx
client = OpenCodeClient(retries=2, backoff_factor=0.1)
# Async usage example
# uv run --project packages/sdk/python python - <<'PY'
# import asyncio
# from opencode_ai import OpenCodeClient
# async def main():
# client = OpenCodeClient()
# async for event in client.subscribe_events_async():
# print(event)
# break
# asyncio.run(main())
# PY
```
Notes
- We intentionally do not use Stainless for the Python SDK.
- The generator targets OpenAPI 3.1 emitted by the opencode server at /doc.
- See scripts/generate.py for details and customization points.

View file

@ -1,22 +0,0 @@
# Generation workflow
The SDK is generated from the Opencode server's OpenAPI 3.1 spec.
Two source modes are supported:
- CLI (default): runs `bun dev generate` to emit the OpenAPI JSON
- Server: fetches `http://localhost:4096/doc` from a running server
Generator command
```bash
# From repo root
uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source cli
# Or
uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source server --server-url http://localhost:4096/doc
```
Post-generation
- The generator injects `extras.py` (OpenCodeClient) and patches `__init__.py` to export it
- Code is formatted with `ruff` (imports) and `black`

View file

@ -1,12 +0,0 @@
# Opencode Python SDK
The official Python client for the Opencode API, generated from the OpenAPI spec and extended with ergonomic helpers.
Highlights
- Provider-agnostic client generated from OpenAPI 3.1
- Thin convenience wrapper (OpenCodeClient) for common tasks
- Sync and async SSE streaming for live event feeds
- First-class uv support for development
If you're new, start with Quickstart or Installation in the navigation.

View file

@ -1,32 +0,0 @@
# Installation
Requirements
- Python 3.8+
- uv (recommended) -> https://docs.astral.sh/uv/
Install uv
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Project setup
```bash
# From repo root or this directory
uv sync --dev --project packages/sdk/python
```
Using pip (alternative)
```bash
pip install opencode-ai
```
Preview docs locally
```bash
# From repo root
uv run --project packages/sdk/python mkdocs serve -f packages/sdk/python/mkdocs.yml
```

View file

@ -1,27 +0,0 @@
# Publishing (maintainers)
Automated publishing runs on GitHub Releases.
Workflow
- Create a new Release (the tag value becomes the package version)
- The `publish-python-sdk` workflow will:
- Generate the SDK from OpenAPI (CLI path)
- Set the version in `pyproject.toml` and generator config
- Build wheel/sdist and upload to PyPI
Prerequisites
- Repository secret: `PYPI_API_TOKEN`
Manual publish
```bash
# TestPyPI
REPOSITORY=testpypi PYPI_TOKEN=$TEST_PYPI_API_TOKEN \
uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
# PyPI
REPOSITORY=pypi PYPI_TOKEN=$PYPI_API_TOKEN \
uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
```

View file

@ -1,22 +0,0 @@
# Quickstart
Create a client and make your first calls.
```python
from opencode_ai import OpenCodeClient
client = OpenCodeClient(base_url="http://localhost:4096")
# List projects
for p in client.list_projects() or []:
print(p.id, p.directory)
# Get path info
path = client.get_path()
print(path.directory)
# Stream events (sync)
for event in client.subscribe_events():
print(event)
break
```

View file

@ -1,16 +0,0 @@
# Testing
Run unit, mock, and integration tests.
```bash
# Sync dev dependencies
uv sync --dev --project packages/sdk/python
# Run tests
uv run --project packages/sdk/python pytest -q
```
Notes
- Integration test starts a headless opencode server via Bun in a subprocess
- SSE behavior is validated using real streaming from the server

View file

@ -1,21 +0,0 @@
# Configuration
OpenCodeClient accepts common options for auth, timeouts, and retries.
```python
from opencode_ai import OpenCodeClient
client = OpenCodeClient(
base_url="http://localhost:4096",
token="pypi-or-other-token",
auth_header_name="Authorization",
auth_prefix="Bearer",
timeout=30.0, # seconds
retries=2,
backoff_factor=0.2, # exponential backoff
)
```
- Auth: sets the header `{auth_header_name}: {auth_prefix} {token}` when `token` is provided
- Retries: retry on transient httpx.RequestError and 429/5xx
- Timeouts: passed to httpx.Timeout

View file

@ -1,22 +0,0 @@
# Files & Projects
Access file status and project information.
```python
from opencode_ai import OpenCodeClient
client = OpenCodeClient()
# Projects
for p in client.list_projects() or []:
print(p.id, p.directory)
# Current path
pinfo = client.get_path()
print(pinfo.directory)
# File status
files = client.file_status() or []
for f in files:
print(f.path, f.type)
```

View file

@ -1,18 +0,0 @@
# Sessions
List sessions and inspect them. The wrapper exposes a convenience method while the generated API remains available under `opencode_ai.api.default`.
```python
from opencode_ai import OpenCodeClient
from opencode_ai.api.default import session_list as generated
client = OpenCodeClient()
# Wrapper
sessions = client.list_sessions() or []
# Generated function
sessions2 = generated.sync(client=client.client)
print(len(sessions), len(sessions2))
```

View file

@ -1,29 +0,0 @@
# Streaming (SSE)
Subscribe to the event stream. The wrapper provides both sync and async interfaces.
```python
from opencode_ai import OpenCodeClient
client = OpenCodeClient()
# Sync streaming
for event in client.subscribe_events():
print(event)
break
```
Async variant:
```python
import asyncio
from opencode_ai import OpenCodeClient
async def main():
client = OpenCodeClient()
async for event in client.subscribe_events_async():
print(event)
break
asyncio.run(main())
```

View file

@ -1,19 +0,0 @@
# Basic usage example (placeholder)
# After generating the client, this should reflect actual client entrypoints.
try:
from opencode_ai import client # type: ignore
except Exception: # pragma: no cover
client = None
def main() -> None:
if client is None:
print("Client not generated yet. Run the generator first:")
print(" uv run python packages/sdk/python/scripts/generate.py")
return
print("Replace this with real example code once the client is generated.")
if __name__ == "__main__":
main()

View file

@ -1,6 +0,0 @@
from opencode_ai import OpenCodeClient
client = OpenCodeClient()
files = client.file_status() or []
for f in files:
print(f.path, f.type)

View file

@ -1,4 +0,0 @@
from opencode_ai import OpenCodeClient
client = OpenCodeClient()
print([s.id for s in client.list_sessions() or []])

View file

@ -1,29 +0,0 @@
site_name: Opencode Python SDK
site_description: Official Python SDK for the Opencode API
site_url: https://opencode.ai
repo_url: https://github.com/sst/opencode
repo_name: sst/opencode
edit_uri: ""
theme:
name: material
features:
- navigation.tabs
- navigation.sections
- content.code.copy
markdown_extensions:
- admonition
- codehilite
- toc:
permalink: true
nav:
- Overview: index.md
- Installation: installation.md
- Quickstart: quickstart.md
- Usage:
- Configuration: usage/configuration.md
- Sessions: usage/sessions.md
- Files & Projects: usage/files_projects.md
- Streaming (SSE): usage/streaming.md
- Generation: generation.md
- Testing: testing.md
- Publishing (maintainers): publishing.md

View file

@ -1,5 +0,0 @@
# Configuration for openapi-python-client
# Ensures consistent project and package names and version when generating.
project_name_override: opencode-ai
package_name_override: opencode_ai
package_version_override: 0.1.0

View file

@ -1,56 +0,0 @@
[build-system]
requires = ["hatchling>=1.17.0"]
build-backend = "hatchling.build"
[project]
name = "opencode-ai"
version = "0.1.0"
description = "Python client for the Opencode API (generated via openapi-python-client)"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{ name = "Opencode Authors", email = "support@sst.dev" }
]
dependencies = [
"httpx>=0.27.0",
"pydantic>=2.0.0",
"python-dateutil>=2.8.2"
]
[project.urls]
Homepage = "https://opencode.ai"
Repository = "https://github.com/sst/opencode"
[tool.uv]
# Development-time dependencies installed with `uv sync --dev`
dev-dependencies = [
"openapi-python-client",
"black",
"isort",
"ruff",
"pytest",
"pytest-asyncio",
"sseclient-py",
"build",
"twine",
"mkdocs",
"mkdocs-material",
]
[tool.black]
line-length = 120
target-version = ["py38", "py39", "py310", "py311", "py312"]
[tool.isort]
profile = "black"
line_length = 120
[tool.ruff]
line-length = 120
select = ["E", "F", "I", "UP"]
ignore = []
[tool.pytest.ini_options]
addopts = "-q"
pythonpath = ["src"]

View file

@ -1,210 +0,0 @@
#!/usr/bin/env python3
"""
Generate the Opencode Python SDK using openapi-python-client and place it under src/opencode_ai.
Steps:
- Generate OpenAPI JSON from the local CLI (bun dev generate)
- Run openapi-python-client (via `uvx` if available, else fallback to PATH)
- Copy the generated module into src/opencode_ai
Requires:
- Bun installed (for `bun dev generate`)
- uv installed (recommended) to run `uvx openapi-python-client`
"""
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
from urllib.request import urlopen
def run(cmd: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess:
print("$", " ".join(cmd))
return subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=True, capture_output=True, text=True)
def find_repo_root(start: Path) -> Path:
p = start
for _ in range(10):
if (p / ".git").exists() or (p / "sst.config.ts").exists():
return p
if p.parent == p:
break
p = p.parent
# Fallback: assume 4 levels up from scripts/
return start.parents[4]
def write_json(path: Path, content: str) -> None:
# Validate JSON before writing
json.loads(content)
path.write_text(content)
def main() -> int:
parser = argparse.ArgumentParser(description="Generate the Opencode Python SDK from OpenAPI spec.")
parser.add_argument(
"--source", choices=["cli", "server"], default="cli", help="Where to fetch the OpenAPI spec from"
)
parser.add_argument(
"--server-url",
default="http://localhost:4096/doc",
help="OpenAPI document URL when --source=server",
)
parser.add_argument(
"--out-spec",
default=None,
help="Output path for the OpenAPI spec (defaults to packages/sdk/python/openapi.json)",
)
parser.add_argument(
"--only-spec",
action="store_true",
help="Only fetch and write the OpenAPI spec without generating the client",
)
args = parser.parse_args()
script_dir = Path(__file__).resolve().parent
sdk_dir = script_dir.parent
repo_root = find_repo_root(script_dir)
opencode_dir = repo_root / "packages" / "opencode"
openapi_json = Path(args.out_spec) if args.out_spec else (sdk_dir / "openapi.json")
build_dir = sdk_dir / ".build"
out_pkg_dir = sdk_dir / "src" / "opencode_ai"
build_dir.mkdir(parents=True, exist_ok=True)
(sdk_dir / "src").mkdir(parents=True, exist_ok=True)
# 1) Obtain OpenAPI spec
if args.source == "server":
print(f"Fetching OpenAPI spec from {args.server_url} ...")
try:
with urlopen(args.server_url) as resp:
if resp.status != 200:
print(f"ERROR: GET {args.server_url} -> HTTP {resp.status}", file=sys.stderr)
return 1
text = resp.read().decode("utf-8")
except Exception as e:
print(f"ERROR: Failed to fetch from server: {e}", file=sys.stderr)
return 1
try:
write_json(openapi_json, text)
except json.JSONDecodeError as je:
print("ERROR: Response from server was not valid JSON:", file=sys.stderr)
print(str(je), file=sys.stderr)
return 1
print(f"Wrote OpenAPI spec to {openapi_json}")
else:
print("Generating OpenAPI spec via 'bun dev generate' ...")
try:
proc = run(["bun", "dev", "generate"], cwd=opencode_dir)
except subprocess.CalledProcessError as e:
print(e.stdout)
print(e.stderr, file=sys.stderr)
print(
"ERROR: Failed to run 'bun dev generate'. Ensure Bun is installed and available in PATH.",
file=sys.stderr,
)
return 1
try:
write_json(openapi_json, proc.stdout)
except json.JSONDecodeError as je:
print("ERROR: Output from 'bun dev generate' was not valid JSON:", file=sys.stderr)
print(str(je), file=sys.stderr)
return 1
print(f"Wrote OpenAPI spec to {openapi_json}")
if args.only_spec:
print("Spec written; skipping client generation (--only-spec).")
return 0
# 2) Run openapi-python-client
print("Running openapi-python-client generate ...")
# Prefer uvx if available
use_uvx = shutil.which("uvx") is not None
cmd = (["uvx", "openapi-python-client", "generate"] if use_uvx else ["openapi-python-client", "generate"]) + [
"--path",
str(openapi_json),
"--output-path",
str(build_dir),
"--overwrite",
"--config",
str(sdk_dir / "openapi-python-client.yaml"),
]
try:
run(cmd, cwd=sdk_dir)
except subprocess.CalledProcessError as e:
print(e.stdout)
print(e.stderr, file=sys.stderr)
print(
"ERROR: Failed to run openapi-python-client. Install uv and try again: curl -LsSf https://astral.sh/uv/install.sh | sh",
file=sys.stderr,
)
return 1
# 3) Locate generated module directory and copy to src/opencode_ai
generated_module: Path | None = None
for candidate in build_dir.rglob("__init__.py"):
if candidate.parent.name.startswith("."):
continue
siblings = {p.name for p in candidate.parent.glob("*.py")}
if "client.py" in siblings or "api_client.py" in siblings:
generated_module = candidate.parent
break
if not generated_module:
print("ERROR: Could not locate generated module directory in .build", file=sys.stderr)
return 1
print(f"Found generated module at {generated_module}")
# Clean target then copy
if out_pkg_dir.exists():
shutil.rmtree(out_pkg_dir)
shutil.copytree(generated_module, out_pkg_dir)
# Inject local extras from template if present
extras_template = sdk_dir / "templates" / "extras.py"
if extras_template.exists():
(out_pkg_dir / "extras.py").write_text(extras_template.read_text())
# Patch __init__ to export OpenCodeClient if present
init_path = out_pkg_dir / "__init__.py"
if init_path.exists() and (out_pkg_dir / "extras.py").exists():
init_text = (
'"""A client library for accessing opencode\n\n'
"This package is generated by openapi-python-client.\n"
"A thin convenience wrapper `OpenCodeClient` is also provided.\n"
'"""\n\n'
"from .client import AuthenticatedClient, Client\n"
"from .extras import OpenCodeClient\n\n"
"__all__ = (\n"
' "AuthenticatedClient",\n'
' "Client",\n'
' "OpenCodeClient",\n'
")\n"
)
init_path.write_text(init_text)
print(f"Copied generated client to {out_pkg_dir}")
# 4) Format generated code
try:
run(["uv", "run", "--project", str(sdk_dir), "ruff", "check", "--select", "I", "--fix", str(out_pkg_dir)])
run(["uv", "run", "--project", str(sdk_dir), "black", str(out_pkg_dir)])
except subprocess.CalledProcessError as e:
print("WARNING: formatting failed; continuing", file=sys.stderr)
print(e.stdout)
print(e.stderr, file=sys.stderr)
print("Done.")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,68 +0,0 @@
#!/usr/bin/env python3
"""
Python SDK publishing helper.
- Builds sdist and wheel using `python -m build` into dist/
- Uploads using twine. Configure either TestPyPI or PyPI via environment:
Environment variables:
REPOSITORY : "pypi" (default) or "testpypi"
PYPI_TOKEN : API token (e.g., pypi-XXXX). For TestPyPI, use the TestPyPI token.
Examples:
REPOSITORY=testpypi PYPI_TOKEN=${{TEST_PYPI_API_TOKEN}} uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
"""
from __future__ import annotations
import os
import subprocess
from pathlib import Path
def run(cmd: list[str], cwd: Path | None = None) -> None:
print("$", " ".join(cmd))
subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=True)
def main() -> int:
sdk_dir = Path(__file__).resolve().parent.parent
repo = os.environ.get("REPOSITORY", "pypi").strip()
token = os.environ.get("PYPI_TOKEN")
if not token:
print("ERROR: PYPI_TOKEN not set", flush=True)
return 1
dist = sdk_dir / "dist"
if dist.exists():
for f in dist.iterdir():
f.unlink()
# Build
run(["python", "-m", "build"], cwd=sdk_dir)
# Upload
repo_url = {
"pypi": "https://upload.pypi.org/legacy/",
"testpypi": "https://test.pypi.org/legacy/",
}.get(repo, repo)
env = os.environ.copy()
env["TWINE_USERNAME"] = "__token__"
env["TWINE_PASSWORD"] = token
print(f"Uploading to {repo_url}")
subprocess.run(
["python", "-m", "twine", "check", "dist/*"], cwd=sdk_dir, check=True
)
subprocess.run(
["python", "-m", "twine", "upload", "--repository-url", repo_url, "dist/*"],
cwd=sdk_dir,
check=True,
env=env,
)
print("Publish complete")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,14 +0,0 @@
"""A client library for accessing opencode
This package is generated by openapi-python-client.
A thin convenience wrapper `OpenCodeClient` is also provided.
"""
from .client import AuthenticatedClient, Client
from .extras import OpenCodeClient
__all__ = (
"AuthenticatedClient",
"Client",
"OpenCodeClient",
)

View file

@ -1 +0,0 @@
"""Contains methods for accessing the API"""

View file

@ -1 +0,0 @@
"""Contains endpoint functions for accessing the API"""

Some files were not shown because too many files have changed in this diff Show more