2
0
Dax 6 сар өмнө
parent
commit
33cef075d2
100 өөрчлөгдсөн 14253 нэмэгдсэн , 1522 устгасан
  1. 6 12
      .github/workflows/publish.yml
  2. 43 484
      bun.lock
  3. 3 1
      package.json
  4. 1 1
      packages/opencode/package.json
  5. 5 13
      packages/opencode/script/publish.ts
  6. 46 4
      packages/opencode/src/format/formatter.ts
  7. 26 0
      packages/opencode/src/server/server.ts
  8. 0 15
      packages/sdk/.devcontainer/devcontainer.json
  9. 0 7
      packages/sdk/.prettierignore
  10. 0 7
      packages/sdk/.prettierrc.json
  11. 0 3
      packages/sdk/.release-please-manifest.json
  12. 0 4
      packages/sdk/.stats.yml
  13. 0 1
      packages/sdk/Brewfile
  14. 0 196
      packages/sdk/CHANGELOG.md
  15. 0 107
      packages/sdk/CONTRIBUTING.md
  16. 0 370
      packages/sdk/README.md
  17. 0 139
      packages/sdk/api.md
  18. 0 22
      packages/sdk/bin/check-release-environment
  19. 0 61
      packages/sdk/bin/publish-npm
  20. 0 42
      packages/sdk/eslint.config.mjs
  21. 7 0
      packages/sdk/go/.devcontainer/devcontainer.json
  22. 49 0
      packages/sdk/go/.github/workflows/ci.yml
  23. 4 0
      packages/sdk/go/.gitignore
  24. 3 0
      packages/sdk/go/.release-please-manifest.json
  25. 4 0
      packages/sdk/go/.stats.yml
  26. 1 0
      packages/sdk/go/Brewfile
  27. 73 0
      packages/sdk/go/CHANGELOG.md
  28. 66 0
      packages/sdk/go/CONTRIBUTING.md
  29. 0 0
      packages/sdk/go/LICENSE
  30. 354 0
      packages/sdk/go/README.md
  31. 0 0
      packages/sdk/go/SECURITY.md
  32. 43 0
      packages/sdk/go/aliases.go
  33. 128 0
      packages/sdk/go/api.md
  34. 368 0
      packages/sdk/go/app.go
  35. 131 0
      packages/sdk/go/app_test.go
  36. 125 0
      packages/sdk/go/client.go
  37. 332 0
      packages/sdk/go/client_test.go
  38. 887 0
      packages/sdk/go/config.go
  39. 36 0
      packages/sdk/go/config_test.go
  40. 1373 0
      packages/sdk/go/event.go
  41. 1 1
      packages/sdk/go/examples/.keep
  42. 50 0
      packages/sdk/go/field.go
  43. 142 0
      packages/sdk/go/file.go
  44. 60 0
      packages/sdk/go/file_test.go
  45. 326 0
      packages/sdk/go/find.go
  46. 86 0
      packages/sdk/go/find_test.go
  47. 13 0
      packages/sdk/go/go.mod
  48. 10 0
      packages/sdk/go/go.sum
  49. 53 0
      packages/sdk/go/internal/apierror/apierror.go
  50. 383 0
      packages/sdk/go/internal/apiform/encoder.go
  51. 5 0
      packages/sdk/go/internal/apiform/form.go
  52. 440 0
      packages/sdk/go/internal/apiform/form_test.go
  53. 48 0
      packages/sdk/go/internal/apiform/tag.go
  54. 670 0
      packages/sdk/go/internal/apijson/decoder.go
  55. 398 0
      packages/sdk/go/internal/apijson/encoder.go
  56. 41 0
      packages/sdk/go/internal/apijson/field.go
  57. 66 0
      packages/sdk/go/internal/apijson/field_test.go
  58. 617 0
      packages/sdk/go/internal/apijson/json_test.go
  59. 120 0
      packages/sdk/go/internal/apijson/port.go
  60. 257 0
      packages/sdk/go/internal/apijson/port_test.go
  61. 41 0
      packages/sdk/go/internal/apijson/registry.go
  62. 47 0
      packages/sdk/go/internal/apijson/tag.go
  63. 341 0
      packages/sdk/go/internal/apiquery/encoder.go
  64. 50 0
      packages/sdk/go/internal/apiquery/query.go
  65. 335 0
      packages/sdk/go/internal/apiquery/query_test.go
  66. 41 0
      packages/sdk/go/internal/apiquery/tag.go
  67. 29 0
      packages/sdk/go/internal/param/field.go
  68. 629 0
      packages/sdk/go/internal/requestconfig/requestconfig.go
  69. 27 0
      packages/sdk/go/internal/testutil/testutil.go
  70. 5 0
      packages/sdk/go/internal/version.go
  71. 1 1
      packages/sdk/go/lib/.keep
  72. 38 0
      packages/sdk/go/option/middleware.go
  73. 267 0
      packages/sdk/go/option/requestoption.go
  74. 181 0
      packages/sdk/go/packages/ssestream/ssestream.go
  75. 6 3
      packages/sdk/go/release-please-config.json
  76. 2 4
      packages/sdk/go/scripts/bootstrap
  77. 8 0
      packages/sdk/go/scripts/format
  78. 11 0
      packages/sdk/go/scripts/lint
  79. 0 0
      packages/sdk/go/scripts/mock
  80. 1 1
      packages/sdk/go/scripts/test
  81. 2117 0
      packages/sdk/go/session.go
  82. 323 0
      packages/sdk/go/session_test.go
  83. 173 0
      packages/sdk/go/shared/shared.go
  84. 56 0
      packages/sdk/go/tui.go
  85. 60 0
      packages/sdk/go/tui_test.go
  86. 32 0
      packages/sdk/go/usage_test.go
  87. 0 23
      packages/sdk/jest.config.ts
  88. 17 0
      packages/sdk/js/package.json
  89. 41 0
      packages/sdk/js/script/generate.ts
  90. 24 0
      packages/sdk/js/script/publish.ts
  91. 18 0
      packages/sdk/js/src/gen/client.gen.ts
  92. 195 0
      packages/sdk/js/src/gen/client/client.ts
  93. 22 0
      packages/sdk/js/src/gen/client/index.ts
  94. 222 0
      packages/sdk/js/src/gen/client/types.ts
  95. 417 0
      packages/sdk/js/src/gen/client/utils.ts
  96. 40 0
      packages/sdk/js/src/gen/core/auth.ts
  97. 88 0
      packages/sdk/js/src/gen/core/bodySerializer.ts
  98. 151 0
      packages/sdk/js/src/gen/core/params.ts
  99. 179 0
      packages/sdk/js/src/gen/core/pathSerializer.ts
  100. 118 0
      packages/sdk/js/src/gen/core/types.ts

+ 6 - 12
.github/workflows/publish.yml

@@ -2,13 +2,11 @@ name: publish
 
 on:
   workflow_dispatch:
-  push:
-    branches:
-      - dev
-    tags:
-      - "*"
-      - "!vscode-v*"
-      - "!github-v*"
+    inputs:
+      version:
+        description: "Version to publish"
+        required: true
+        type: string
 
 concurrency: ${{ github.workflow }}-${{ github.ref }}
 
@@ -53,11 +51,7 @@ jobs:
       - name: Publish
         run: |
           bun install
-          if [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
-            ./script/publish.ts
-          else
-            ./script/publish.ts --snapshot
-          fi
+          OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
         working-directory: ./packages/opencode
         env:
           GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 43 - 484
bun.lock


+ 3 - 1
package.json

@@ -12,10 +12,12 @@
   },
   "workspaces": {
     "packages": [
-      "packages/*"
+      "packages/*",
+      "packages/sdk/js"
     ],
     "catalog": {
       "@types/node": "22.13.9",
+      "@tsconfig/node22": "22.0.2",
       "ai": "5.0.0-beta.33",
       "hono": "4.7.10",
       "typescript": "5.8.2",

+ 1 - 1
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "0.0.5",
+  "version": "0.0.0",
   "name": "opencode",
   "type": "module",
   "private": true,

+ 5 - 13
packages/opencode/script/publish.ts

@@ -1,21 +1,13 @@
 #!/usr/bin/env bun
-
+const dir = new URL("..", import.meta.url).pathname
+process.chdir(dir)
 import { $ } from "bun"
 
 import pkg from "../package.json"
 
-const dry = process.argv.includes("--dry")
-const snapshot = process.argv.includes("--snapshot")
-
-const version = snapshot
-  ? `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
-  : await $`git describe --tags --abbrev=0`
-      .text()
-      .then((x) => x.substring(1).trim())
-      .catch(() => {
-        console.error("tag not found")
-        process.exit(1)
-      })
+const dry = process.env["OPENCODE_DRY"] === "true"
+const version = process.env["OPENCODE_VERSION"]!
+const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
 
 console.log(`publishing ${version}`)
 

+ 46 - 4
packages/opencode/src/format/formatter.ts

@@ -1,7 +1,6 @@
 import { App } from "../app/app"
 import { BunProc } from "../bun"
 import { Filesystem } from "../util/filesystem"
-import path from "path"
 
 export interface Info {
   name: string
@@ -65,14 +64,57 @@ export const prettier: Info = {
   ],
   async enabled() {
     const app = App.info()
-    const nms = await Filesystem.findUp("node_modules", app.path.cwd, app.path.root)
-    for (const item of nms) {
-      if (await Bun.file(path.join(item, ".bin", "prettier")).exists()) return true
+    const items = await Filesystem.findUp("package.json", app.path.cwd, app.path.root)
+    for (const item of items) {
+      const json = await Bun.file(item).json()
+      if (json.dependencies?.prettier) return true
+      if (json.devDependencies?.prettier) return true
     }
     return false
   },
 }
 
+export const biome: Info = {
+  name: "biome",
+  command: [BunProc.which(), "x", "biome", "format", "--write", "$FILE"],
+  environment: {
+    BUN_BE_BUN: "1",
+  },
+  extensions: [
+    ".js",
+    ".jsx",
+    ".mjs",
+    ".cjs",
+    ".ts",
+    ".tsx",
+    ".mts",
+    ".cts",
+    ".html",
+    ".htm",
+    ".css",
+    ".scss",
+    ".sass",
+    ".less",
+    ".vue",
+    ".svelte",
+    ".json",
+    ".jsonc",
+    ".yaml",
+    ".yml",
+    ".toml",
+    ".xml",
+    ".md",
+    ".mdx",
+    ".graphql",
+    ".gql",
+  ],
+  async enabled() {
+    const app = App.info()
+    const items = await Filesystem.findUp("biome.json", app.path.cwd, app.path.root)
+    return items.length > 0
+  },
+}
+
 export const zig: Info = {
   name: "zig",
   command: ["zig", "fmt", "$FILE"],

+ 26 - 0
packages/opencode/src/server/server.ts

@@ -94,6 +94,7 @@ export namespace Server {
         "/event",
         describeRoute({
           description: "Get events",
+          operationId: "event.subscribe",
           responses: {
             200: {
               description: "Event stream",
@@ -137,6 +138,7 @@ export namespace Server {
         "/app",
         describeRoute({
           description: "Get app info",
+          operationId: "app.get",
           responses: {
             200: {
               description: "200",
@@ -156,6 +158,7 @@ export namespace Server {
         "/app/init",
         describeRoute({
           description: "Initialize the app",
+          operationId: "app.init",
           responses: {
             200: {
               description: "Initialize the app",
@@ -176,6 +179,7 @@ export namespace Server {
         "/config",
         describeRoute({
           description: "Get config info",
+          operationId: "config.get",
           responses: {
             200: {
               description: "Get config info",
@@ -195,6 +199,7 @@ export namespace Server {
         "/session",
         describeRoute({
           description: "List all sessions",
+          operationId: "session.list",
           responses: {
             200: {
               description: "List of sessions",
@@ -216,6 +221,7 @@ export namespace Server {
         "/session",
         describeRoute({
           description: "Create a new session",
+          operationId: "session.create",
           responses: {
             ...ERRORS,
             200: {
@@ -237,6 +243,7 @@ export namespace Server {
         "/session/:id",
         describeRoute({
           description: "Delete a session and all its data",
+          operationId: "session.delete",
           responses: {
             200: {
               description: "Successfully deleted session",
@@ -263,6 +270,7 @@ export namespace Server {
         "/session/:id/init",
         describeRoute({
           description: "Analyze the app and create an AGENTS.md file",
+          operationId: "session.init",
           responses: {
             200: {
               description: "200",
@@ -299,6 +307,7 @@ export namespace Server {
         "/session/:id/abort",
         describeRoute({
           description: "Abort a session",
+          operationId: "session.abort",
           responses: {
             200: {
               description: "Aborted session",
@@ -324,6 +333,7 @@ export namespace Server {
         "/session/:id/share",
         describeRoute({
           description: "Share a session",
+          operationId: "session.share",
           responses: {
             200: {
               description: "Successfully shared session",
@@ -352,6 +362,7 @@ export namespace Server {
         "/session/:id/share",
         describeRoute({
           description: "Unshare the session",
+          operationId: "session.unshare",
           responses: {
             200: {
               description: "Successfully unshared session",
@@ -380,6 +391,7 @@ export namespace Server {
         "/session/:id/summarize",
         describeRoute({
           description: "Summarize the session",
+          operationId: "session.summarize",
           responses: {
             200: {
               description: "Summarized session",
@@ -415,6 +427,7 @@ export namespace Server {
         "/session/:id/message",
         describeRoute({
           description: "List messages for a session",
+          operationId: "session.messages",
           responses: {
             200: {
               description: "List of messages",
@@ -448,6 +461,7 @@ export namespace Server {
         "/session/:id/message",
         describeRoute({
           description: "Create and send a new message to a session",
+          operationId: "session.chat",
           responses: {
             200: {
               description: "Created message",
@@ -477,6 +491,7 @@ export namespace Server {
         "/session/:id/revert",
         describeRoute({
           description: "Revert a message",
+          operationId: "session.revert",
           responses: {
             200: {
               description: "Updated session",
@@ -506,6 +521,7 @@ export namespace Server {
         "/session/:id/unrevert",
         describeRoute({
           description: "Restore all reverted messages",
+          operationId: "session.unrevert",
           responses: {
             200: {
               description: "Updated session",
@@ -533,6 +549,7 @@ export namespace Server {
         "/config/providers",
         describeRoute({
           description: "List all providers",
+          operationId: "config.providers",
           responses: {
             200: {
               description: "List of providers",
@@ -561,6 +578,7 @@ export namespace Server {
         "/find",
         describeRoute({
           description: "Find text in files",
+          operationId: "find.text",
           responses: {
             200: {
               description: "Matches",
@@ -593,6 +611,7 @@ export namespace Server {
         "/find/file",
         describeRoute({
           description: "Find files",
+          operationId: "find.files",
           responses: {
             200: {
               description: "File paths",
@@ -625,6 +644,7 @@ export namespace Server {
         "/find/symbol",
         describeRoute({
           description: "Find workspace symbols",
+          operationId: "find.symbols",
           responses: {
             200: {
               description: "Symbols",
@@ -652,6 +672,7 @@ export namespace Server {
         "/file",
         describeRoute({
           description: "Read a file",
+          operationId: "file.read",
           responses: {
             200: {
               description: "File content",
@@ -688,6 +709,7 @@ export namespace Server {
         "/file/status",
         describeRoute({
           description: "Get file status",
+          operationId: "file.status",
           responses: {
             200: {
               description: "File status",
@@ -708,6 +730,7 @@ export namespace Server {
         "/log",
         describeRoute({
           description: "Write a log entry to the server logs",
+          operationId: "app.log",
           responses: {
             200: {
               description: "Log entry written successfully",
@@ -757,6 +780,7 @@ export namespace Server {
         "/mode",
         describeRoute({
           description: "List all modes",
+          operationId: "app.modes",
           responses: {
             200: {
               description: "List of modes",
@@ -777,6 +801,7 @@ export namespace Server {
         "/tui/append-prompt",
         describeRoute({
           description: "Append prompt to the TUI",
+          operationId: "tui.appendPrompt",
           responses: {
             200: {
               description: "Prompt processed successfully",
@@ -800,6 +825,7 @@ export namespace Server {
         "/tui/open-help",
         describeRoute({
           description: "Open the help dialog",
+          operationId: "tui.openHelp",
           responses: {
             200: {
               description: "Help dialog opened successfully",

+ 0 - 15
packages/sdk/.devcontainer/devcontainer.json

@@ -1,15 +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/typescript-node:latest",
-  "features": {
-    "ghcr.io/devcontainers/features/node:1": {}
-  },
-  "postCreateCommand": "yarn install",
-  "customizations": {
-    "vscode": {
-      "extensions": ["esbenp.prettier-vscode"]
-    }
-  }
-}

+ 0 - 7
packages/sdk/.prettierignore

@@ -1,7 +0,0 @@
-CHANGELOG.md
-/ecosystem-tests/*/**
-/node_modules
-/deno
-
-# don't format tsc output, will break source maps
-/dist

+ 0 - 7
packages/sdk/.prettierrc.json

@@ -1,7 +0,0 @@
-{
-  "arrowParens": "always",
-  "experimentalTernaries": true,
-  "printWidth": 110,
-  "singleQuote": true,
-  "trailingComma": "all"
-}

+ 0 - 3
packages/sdk/.release-please-manifest.json

@@ -1,3 +0,0 @@
-{
-  ".": "0.1.0-alpha.20"
-}

+ 0 - 4
packages/sdk/.stats.yml

@@ -1,4 +0,0 @@
-configured_endpoints: 26
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-62d8fccba4eb8dc3a80434e0849eab3352e49fb96a718bb7b6d17ed8e582b716.yml
-openapi_spec_hash: 4ff9376cf9634e91731e63fe482ea532
-config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3

+ 0 - 1
packages/sdk/Brewfile

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

+ 0 - 196
packages/sdk/CHANGELOG.md

@@ -1,196 +0,0 @@
-# Changelog
-
-## 0.1.0-alpha.20 (2025-07-16)
-
-Full Changelog: [v0.1.0-alpha.19...v0.1.0-alpha.20](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.19...v0.1.0-alpha.20)
-
-### Features
-
-* **api:** api update ([d296473](https://github.com/sst/opencode-sdk-js/commit/d296473db58378932b85d1afaa60942ac5599c49))
-* **api:** api update ([af2b587](https://github.com/sst/opencode-sdk-js/commit/af2b5875534a4782fac186542ecb9b04393c9b0a))
-
-## 0.1.0-alpha.19 (2025-07-16)
-
-Full Changelog: [v0.1.0-alpha.18...v0.1.0-alpha.19](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.18...v0.1.0-alpha.19)
-
-### Features
-
-* **api:** api update ([2e505ef](https://github.com/sst/opencode-sdk-js/commit/2e505ef451fdcf49358189c5f76bdc42fb821352))
-
-## 0.1.0-alpha.18 (2025-07-15)
-
-Full Changelog: [v0.1.0-alpha.17...v0.1.0-alpha.18](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.17...v0.1.0-alpha.18)
-
-### Features
-
-* **api:** api update ([25a23e5](https://github.com/sst/opencode-sdk-js/commit/25a23e599f1180754910961df65f0cc044aa2935))
-
-## 0.1.0-alpha.17 (2025-07-15)
-
-Full Changelog: [v0.1.0-alpha.16...v0.1.0-alpha.17](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.16...v0.1.0-alpha.17)
-
-### Features
-
-* **api:** api update ([8b5d592](https://github.com/sst/opencode-sdk-js/commit/8b5d59243a0212f98269412f4483e729e2367a77))
-* **api:** api update ([ebd8986](https://github.com/sst/opencode-sdk-js/commit/ebd89862c48be2742eda727c83c01430413e00c0))
-
-## 0.1.0-alpha.16 (2025-07-15)
-
-Full Changelog: [v0.1.0-alpha.15...v0.1.0-alpha.16](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.15...v0.1.0-alpha.16)
-
-### Features
-
-* **api:** api update ([f26379d](https://github.com/sst/opencode-sdk-js/commit/f26379d83ae7094d6ba91c6705a97a3fbd88a55a))
-
-
-### Chores
-
-* make some internal functions async ([36b1db9](https://github.com/sst/opencode-sdk-js/commit/36b1db9ca9d47d9199e2eab5f0b454b7cd31f58f))
-
-## 0.1.0-alpha.15 (2025-07-05)
-
-Full Changelog: [v0.1.0-alpha.14...v0.1.0-alpha.15](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.14...v0.1.0-alpha.15)
-
-### Features
-
-* **api:** manual updates ([f6ee467](https://github.com/sst/opencode-sdk-js/commit/f6ee46752d0c174c8b934894cf2b140864864208))
-
-
-### Chores
-
-* **internal:** codegen related update ([47a1a97](https://github.com/sst/opencode-sdk-js/commit/47a1a972e755735d6b5472c61f726ab2face9e62))
-
-## 0.1.0-alpha.14 (2025-07-03)
-
-Full Changelog: [v0.1.0-alpha.13...v0.1.0-alpha.14](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.13...v0.1.0-alpha.14)
-
-### Features
-
-* **api:** api update ([a1d7cf9](https://github.com/sst/opencode-sdk-js/commit/a1d7cf948a2ff47ce4e98b4a52d0e4d213b87bf6))
-
-
-### Chores
-
-* **internal:** version bump ([f8ad145](https://github.com/sst/opencode-sdk-js/commit/f8ad145b9af0c4a465642630043e59236d5f4e8d))
-
-## 0.1.0-alpha.13 (2025-07-03)
-
-Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.12...v0.1.0-alpha.13)
-
-### Bug Fixes
-
-* avoid console usage ([f96ac97](https://github.com/sst/opencode-sdk-js/commit/f96ac97fbaf7417efda306d8727654d1a4138386))
-
-
-### Chores
-
-* add docs to RequestOptions type ([1ca6677](https://github.com/sst/opencode-sdk-js/commit/1ca667765c22b706732c61ea3d9d2823aeda0a8e))
-
-## 0.1.0-alpha.12 (2025-07-02)
-
-Full Changelog: [v0.1.0-alpha.11...v0.1.0-alpha.12](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.11...v0.1.0-alpha.12)
-
-### Features
-
-* **api:** update via SDK Studio ([7739340](https://github.com/sst/opencode-sdk-js/commit/77393403648067fe937637c39e80067c347a8c5b))
-
-## 0.1.0-alpha.11 (2025-06-30)
-
-Full Changelog: [v0.1.0-alpha.10...v0.1.0-alpha.11](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.10...v0.1.0-alpha.11)
-
-### Features
-
-* **api:** update via SDK Studio ([2ce98e5](https://github.com/sst/opencode-sdk-js/commit/2ce98e55bf330cca0c38f60f011ffd9063b34ea0))
-
-## 0.1.0-alpha.10 (2025-06-30)
-
-Full Changelog: [v0.1.0-alpha.9...v0.1.0-alpha.10](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.9...v0.1.0-alpha.10)
-
-### Features
-
-* **api:** update via SDK Studio ([fa7c91c](https://github.com/sst/opencode-sdk-js/commit/fa7c91cc2fe52d42be7365ca2c4ce3e48c2e76ac))
-
-
-### Chores
-
-* **ci:** only run for pushes and fork pull requests ([0e850e5](https://github.com/sst/opencode-sdk-js/commit/0e850e51daac413dcf2d5e30c0ea7a1cd5346c4b))
-* **client:** improve path param validation ([bc3ff0e](https://github.com/sst/opencode-sdk-js/commit/bc3ff0ee2de9af8be42deae87d12f003fb5f7aa5))
-
-## 0.1.0-alpha.9 (2025-06-27)
-
-Full Changelog: [v0.1.0-alpha.8...v0.1.0-alpha.9](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.8...v0.1.0-alpha.9)
-
-### Features
-
-* **api:** update via SDK Studio ([7009d10](https://github.com/sst/opencode-sdk-js/commit/7009d10aab99be7102371cee49013ab3edae4450))
-* **api:** update via SDK Studio ([e60aa00](https://github.com/sst/opencode-sdk-js/commit/e60aa0024079671e3725ee6f6bfbf8c2dad78da2))
-
-## 0.1.0-alpha.8 (2025-06-27)
-
-Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.7...v0.1.0-alpha.8)
-
-### Features
-
-* **api:** update via SDK Studio ([171e3d5](https://github.com/sst/opencode-sdk-js/commit/171e3d5f3ba69ff9ba8547dac90d85b1a0a137c1))
-
-## 0.1.0-alpha.7 (2025-06-27)
-
-Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.6...v0.1.0-alpha.7)
-
-### Features
-
-* **api:** update via SDK Studio ([14d2d04](https://github.com/sst/opencode-sdk-js/commit/14d2d04d80c1d5880940c9c70a5c1ea200df2ebc))
-
-## 0.1.0-alpha.6 (2025-06-27)
-
-Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.5...v0.1.0-alpha.6)
-
-### Features
-
-* **api:** update via SDK Studio ([45e78b2](https://github.com/sst/opencode-sdk-js/commit/45e78b2f0fca18f537de9986e358aa876fb0b686))
-
-## 0.1.0-alpha.5 (2025-06-27)
-
-Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)
-
-### Features
-
-* **api:** update via SDK Studio ([10a5be9](https://github.com/sst/opencode-sdk-js/commit/10a5be9261c4ba8aeece7bb6921752f5fa6d9f28))
-
-## 0.1.0-alpha.4 (2025-06-27)
-
-Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.3...v0.1.0-alpha.4)
-
-### Features
-
-* **api:** update via SDK Studio ([20dcd17](https://github.com/sst/opencode-sdk-js/commit/20dcd171405b05801e5a56f1b40fd635259b6a94))
-
-## 0.1.0-alpha.3 (2025-06-27)
-
-Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.2...v0.1.0-alpha.3)
-
-### Bug Fixes
-
-* **ci:** release-doctor — report correct token name ([128884f](https://github.com/sst/opencode-sdk-js/commit/128884f4bc64e618177a0b090cd6d52b122a059a))
-
-## 0.1.0-alpha.2 (2025-06-24)
-
-Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.1...v0.1.0-alpha.2)
-
-### Features
-
-* **api:** update via SDK Studio ([2320f32](https://github.com/sst/opencode-sdk-js/commit/2320f32190ab58d15d00d7c3328f9fba2421536c))
-
-## 0.1.0-alpha.1 (2025-06-24)
-
-Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencode-sdk-js/compare/v0.0.1-alpha.0...v0.1.0-alpha.1)
-
-### Features
-
-* **api:** update via SDK Studio ([e448306](https://github.com/sst/opencode-sdk-js/commit/e4483068738cbb10233fca5a9d9d44a9c9815c8b))
-* **api:** update via SDK Studio ([b222c96](https://github.com/sst/opencode-sdk-js/commit/b222c96a679a8aeecb60bcf92c247fef90c75b3d))
-
-
-### Chores
-
-* update SDK settings ([c1481ea](https://github.com/sst/opencode-sdk-js/commit/c1481ea7949c1422bedaeac278600b4ec3f58038))

+ 0 - 107
packages/sdk/CONTRIBUTING.md

@@ -1,107 +0,0 @@
-## Setting up the environment
-
-This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install).
-Other package managers may work but are not officially supported for development.
-
-To set up the repository, run:
-
-```sh
-$ yarn
-$ yarn build
-```
-
-This will install all the required dependencies and build output files to `dist/`.
-
-## 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 `src/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.
-
-```ts
-// add an example to examples/<your-example>.ts
-
-#!/usr/bin/env -S npm run tsn -T
-…
-```
-
-```sh
-$ chmod +x examples/<your-example>.ts
-# run the example against your api
-$ yarn tsn -T examples/<your-example>.ts
-```
-
-## Using the repository from source
-
-If you’d like to use the repository from source, you can either install from git or link to a cloned repository:
-
-To install via git:
-
-```sh
-$ npm install git+ssh://[email protected]:sst/opencode-sdk-js.git
-```
-
-Alternatively, to link a local copy of the repo:
-
-```sh
-# Clone
-$ git clone https://www.github.com/sst/opencode-sdk-js
-$ cd opencode-sdk-js
-
-# With yarn
-$ yarn link
-$ cd ../my-package
-$ yarn link @opencode-ai/sdk
-
-# With pnpm
-$ pnpm link --global
-$ cd ../my-package
-$ pnpm link -—global @opencode-ai/sdk
-```
-
-## 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
-$ npx prism mock path/to/your/openapi.yml
-```
-
-```sh
-$ yarn run test
-```
-
-## Linting and formatting
-
-This repository uses [prettier](https://www.npmjs.com/package/prettier) and
-[eslint](https://www.npmjs.com/package/eslint) to format the code in the repository.
-
-To lint:
-
-```sh
-$ yarn lint
-```
-
-To format and fix all lint issues automatically:
-
-```sh
-$ yarn fix
-```
-
-## Publishing and releases
-
-Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If
-the changes aren't made through the automated pipeline, you may want to make releases manually.
-
-### Publish with a GitHub workflow
-
-You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/sst/opencode-sdk-js/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up.
-
-### Publish manually
-
-If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on
-the environment.

+ 0 - 370
packages/sdk/README.md

@@ -1,370 +0,0 @@
-# Opencode TypeScript API Library
-
-[![NPM version](<https://img.shields.io/npm/v/@opencode-ai/sdk.svg?label=npm%20(stable)>)](https://npmjs.org/package/@opencode-ai/sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@opencode-ai/sdk)
-
-This library provides convenient access to the Opencode REST API from server-side TypeScript or JavaScript.
-
-The REST API documentation can be found on [opencode.ai](https://opencode.ai/docs). The full API of this library can be found in [api.md](api.md).
-
-It is generated with [Stainless](https://www.stainless.com/).
-
-## Installation
-
-```sh
-npm install @opencode-ai/sdk
-```
-
-## Usage
-
-The full API of this library can be found in [api.md](api.md).
-
-<!-- prettier-ignore -->
-```js
-import Opencode from '@opencode-ai/sdk';
-
-const client = new Opencode();
-
-const sessions = await client.session.list();
-```
-
-## Streaming responses
-
-We provide support for streaming responses using Server Sent Events (SSE).
-
-```ts
-import Opencode from '@opencode-ai/sdk';
-
-const client = new Opencode();
-
-const stream = await client.event.list();
-for await (const eventListResponse of stream) {
-  console.log(eventListResponse);
-}
-```
-
-If you need to cancel a stream, you can `break` from the loop
-or call `stream.controller.abort()`.
-
-### Request & Response types
-
-This library includes TypeScript definitions for all request params and response fields. You may import and use them like so:
-
-<!-- prettier-ignore -->
-```ts
-import Opencode from '@opencode-ai/sdk';
-
-const client = new Opencode();
-
-const sessions: Opencode.SessionListResponse = await client.session.list();
-```
-
-Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors.
-
-## Handling errors
-
-When the library is unable to connect to the API,
-or if the API returns a non-success status code (i.e., 4xx or 5xx response),
-a subclass of `APIError` will be thrown:
-
-<!-- prettier-ignore -->
-```ts
-const sessions = await client.session.list().catch(async (err) => {
-  if (err instanceof Opencode.APIError) {
-    console.log(err.status); // 400
-    console.log(err.name); // BadRequestError
-    console.log(err.headers); // {server: 'nginx', ...}
-  } else {
-    throw err;
-  }
-});
-```
-
-Error codes are as follows:
-
-| Status Code | Error Type                 |
-| ----------- | -------------------------- |
-| 400         | `BadRequestError`          |
-| 401         | `AuthenticationError`      |
-| 403         | `PermissionDeniedError`    |
-| 404         | `NotFoundError`            |
-| 422         | `UnprocessableEntityError` |
-| 429         | `RateLimitError`           |
-| >=500       | `InternalServerError`      |
-| N/A         | `APIConnectionError`       |
-
-### Retries
-
-Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
-Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
-429 Rate Limit, and >=500 Internal errors will all be retried by default.
-
-You can use the `maxRetries` option to configure or disable this:
-
-<!-- prettier-ignore -->
-```js
-// Configure the default for all requests:
-const client = new Opencode({
-  maxRetries: 0, // default is 2
-});
-
-// Or, configure per-request:
-await client.session.list({
-  maxRetries: 5,
-});
-```
-
-### Timeouts
-
-Requests time out after 1 minute by default. You can configure this with a `timeout` option:
-
-<!-- prettier-ignore -->
-```ts
-// Configure the default for all requests:
-const client = new Opencode({
-  timeout: 20 * 1000, // 20 seconds (default is 1 minute)
-});
-
-// Override per-request:
-await client.session.list({
-  timeout: 5 * 1000,
-});
-```
-
-On timeout, an `APIConnectionTimeoutError` is thrown.
-
-Note that requests which time out will be [retried twice by default](#retries).
-
-## Advanced Usage
-
-### Accessing raw Response data (e.g., headers)
-
-The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return.
-This method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic.
-
-You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data.
-Unlike `.asResponse()` this method consumes the body, returning once it is parsed.
-
-<!-- prettier-ignore -->
-```ts
-const client = new Opencode();
-
-const response = await client.session.list().asResponse();
-console.log(response.headers.get('X-My-Header'));
-console.log(response.statusText); // access the underlying Response object
-
-const { data: sessions, response: raw } = await client.session.list().withResponse();
-console.log(raw.headers.get('X-My-Header'));
-console.log(sessions);
-```
-
-### Logging
-
-> [!IMPORTANT]
-> All log messages are intended for debugging only. The format and content of log messages
-> may change between releases.
-
-#### Log levels
-
-The log level can be configured in two ways:
-
-1. Via the `OPENCODE_LOG` environment variable
-2. Using the `logLevel` client option (overrides the environment variable if set)
-
-```ts
-import Opencode from '@opencode-ai/sdk';
-
-const client = new Opencode({
-  logLevel: 'debug', // Show all log messages
-});
-```
-
-Available log levels, from most to least verbose:
-
-- `'debug'` - Show debug messages, info, warnings, and errors
-- `'info'` - Show info messages, warnings, and errors
-- `'warn'` - Show warnings and errors (default)
-- `'error'` - Show only errors
-- `'off'` - Disable all logging
-
-At the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies.
-Some authentication-related headers are redacted, but sensitive data in request and response bodies
-may still be visible.
-
-#### Custom logger
-
-By default, this library logs to `globalThis.console`. You can also provide a custom logger.
-Most logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue.
-
-When providing a custom logger, the `logLevel` option still controls which messages are emitted, messages
-below the configured level will not be sent to your logger.
-
-```ts
-import Opencode from '@opencode-ai/sdk';
-import pino from 'pino';
-
-const logger = pino();
-
-const client = new Opencode({
-  logger: logger.child({ name: 'Opencode' }),
-  logLevel: 'debug', // Send all messages to pino, allowing it to filter
-});
-```
-
-### 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.
-Options on the client, such as retries, will be respected when making these requests.
-
-```ts
-await client.post('/some/path', {
-  body: { some_prop: 'foo' },
-  query: { some_query_arg: 'bar' },
-});
-```
-
-#### Undocumented request params
-
-To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented
-parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you
-send will be sent as-is.
-
-```ts
-client.session.list({
-  // ...
-  // @ts-expect-error baz is not yet public
-  baz: 'undocumented option',
-});
-```
-
-For requests with the `GET` verb, any extra params will be in the query, all other requests will send the
-extra param in the body.
-
-If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request
-options.
-
-#### Undocumented response properties
-
-To access undocumented response properties, you may access the response object with `// @ts-expect-error` on
-the response object, or cast the response object to the requisite type. Like the request params, we do not
-validate or strip extra properties from the response from the API.
-
-### Customizing the fetch client
-
-By default, this library expects a global `fetch` function is defined.
-
-If you want to use a different `fetch` function, you can either polyfill the global:
-
-```ts
-import fetch from 'my-fetch';
-
-globalThis.fetch = fetch;
-```
-
-Or pass it to the client:
-
-```ts
-import Opencode from '@opencode-ai/sdk';
-import fetch from 'my-fetch';
-
-const client = new Opencode({ fetch });
-```
-
-### Fetch options
-
-If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.)
-
-```ts
-import Opencode from '@opencode-ai/sdk';
-
-const client = new Opencode({
-  fetchOptions: {
-    // `RequestInit` options
-  },
-});
-```
-
-#### Configuring proxies
-
-To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy
-options to requests:
-
-<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/node.svg" align="top" width="18" height="21"> **Node** <sup>[[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)]</sup>
-
-```ts
-import Opencode from '@opencode-ai/sdk';
-import * as undici from 'undici';
-
-const proxyAgent = new undici.ProxyAgent('http://localhost:8888');
-const client = new Opencode({
-  fetchOptions: {
-    dispatcher: proxyAgent,
-  },
-});
-```
-
-<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/bun.svg" align="top" width="18" height="21"> **Bun** <sup>[[docs](https://bun.sh/guides/http/proxy)]</sup>
-
-```ts
-import Opencode from '@opencode-ai/sdk';
-
-const client = new Opencode({
-  fetchOptions: {
-    proxy: 'http://localhost:8888',
-  },
-});
-```
-
-<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/deno.svg" align="top" width="18" height="21"> **Deno** <sup>[[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)]</sup>
-
-```ts
-import Opencode from 'npm:@opencode-ai/sdk';
-
-const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } });
-const client = new Opencode({
-  fetchOptions: {
-    client: httpClient,
-  },
-});
-```
-
-## Frequently Asked Questions
-
-## 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 that only affect static types, without breaking runtime behavior.
-2. 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.)_
-3. 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-js/issues) with questions, bugs, or suggestions.
-
-## Requirements
-
-TypeScript >= 4.9 is supported.
-
-The following runtimes are supported:
-
-- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more)
-- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions.
-- Deno v1.28.0 or higher.
-- Bun 1.0 or later.
-- Cloudflare Workers.
-- Vercel Edge Runtime.
-- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time).
-- Nitro v2.6 or greater.
-
-Note that React Native is not supported at this time.
-
-If you are interested in other runtime environments, please open or upvote an issue on GitHub.
-
-## Contributing
-
-See [the contributing documentation](./CONTRIBUTING.md).

+ 0 - 139
packages/sdk/api.md

@@ -1,139 +0,0 @@
-# Shared
-
-Types:
-
-- <code><a href="./src/resources/shared.ts">MessageAbortedError</a></code>
-- <code><a href="./src/resources/shared.ts">ProviderAuthError</a></code>
-- <code><a href="./src/resources/shared.ts">UnknownError</a></code>
-
-# Event
-
-Types:
-
-- <code><a href="./src/resources/event.ts">EventListResponse</a></code>
-
-Methods:
-
-- <code title="get /event">client.event.<a href="./src/resources/event.ts">list</a>() -> EventListResponse</code>
-
-# App
-
-Types:
-
-- <code><a href="./src/resources/app.ts">App</a></code>
-- <code><a href="./src/resources/app.ts">Mode</a></code>
-- <code><a href="./src/resources/app.ts">Model</a></code>
-- <code><a href="./src/resources/app.ts">Provider</a></code>
-- <code><a href="./src/resources/app.ts">AppInitResponse</a></code>
-- <code><a href="./src/resources/app.ts">AppLogResponse</a></code>
-- <code><a href="./src/resources/app.ts">AppModesResponse</a></code>
-- <code><a href="./src/resources/app.ts">AppProvidersResponse</a></code>
-
-Methods:
-
-- <code title="get /app">client.app.<a href="./src/resources/app.ts">get</a>() -> App</code>
-- <code title="post /app/init">client.app.<a href="./src/resources/app.ts">init</a>() -> AppInitResponse</code>
-- <code title="post /log">client.app.<a href="./src/resources/app.ts">log</a>({ ...params }) -> AppLogResponse</code>
-- <code title="get /mode">client.app.<a href="./src/resources/app.ts">modes</a>() -> AppModesResponse</code>
-- <code title="get /config/providers">client.app.<a href="./src/resources/app.ts">providers</a>() -> AppProvidersResponse</code>
-
-# Find
-
-Types:
-
-- <code><a href="./src/resources/find.ts">Symbol</a></code>
-- <code><a href="./src/resources/find.ts">FindFilesResponse</a></code>
-- <code><a href="./src/resources/find.ts">FindSymbolsResponse</a></code>
-- <code><a href="./src/resources/find.ts">FindTextResponse</a></code>
-
-Methods:
-
-- <code title="get /find/file">client.find.<a href="./src/resources/find.ts">files</a>({ ...params }) -> FindFilesResponse</code>
-- <code title="get /find/symbol">client.find.<a href="./src/resources/find.ts">symbols</a>({ ...params }) -> FindSymbolsResponse</code>
-- <code title="get /find">client.find.<a href="./src/resources/find.ts">text</a>({ ...params }) -> FindTextResponse</code>
-
-# File
-
-Types:
-
-- <code><a href="./src/resources/file.ts">File</a></code>
-- <code><a href="./src/resources/file.ts">FileReadResponse</a></code>
-- <code><a href="./src/resources/file.ts">FileStatusResponse</a></code>
-
-Methods:
-
-- <code title="get /file">client.file.<a href="./src/resources/file.ts">read</a>({ ...params }) -> FileReadResponse</code>
-- <code title="get /file/status">client.file.<a href="./src/resources/file.ts">status</a>() -> FileStatusResponse</code>
-
-# Config
-
-Types:
-
-- <code><a href="./src/resources/config.ts">Config</a></code>
-- <code><a href="./src/resources/config.ts">KeybindsConfig</a></code>
-- <code><a href="./src/resources/config.ts">McpLocalConfig</a></code>
-- <code><a href="./src/resources/config.ts">McpRemoteConfig</a></code>
-- <code><a href="./src/resources/config.ts">ModeConfig</a></code>
-
-Methods:
-
-- <code title="get /config">client.config.<a href="./src/resources/config.ts">get</a>() -> Config</code>
-
-# Session
-
-Types:
-
-- <code><a href="./src/resources/session.ts">AssistantMessage</a></code>
-- <code><a href="./src/resources/session.ts">FilePart</a></code>
-- <code><a href="./src/resources/session.ts">FilePartInput</a></code>
-- <code><a href="./src/resources/session.ts">FilePartSource</a></code>
-- <code><a href="./src/resources/session.ts">FilePartSourceText</a></code>
-- <code><a href="./src/resources/session.ts">FileSource</a></code>
-- <code><a href="./src/resources/session.ts">Message</a></code>
-- <code><a href="./src/resources/session.ts">Part</a></code>
-- <code><a href="./src/resources/session.ts">Session</a></code>
-- <code><a href="./src/resources/session.ts">SnapshotPart</a></code>
-- <code><a href="./src/resources/session.ts">StepFinishPart</a></code>
-- <code><a href="./src/resources/session.ts">StepStartPart</a></code>
-- <code><a href="./src/resources/session.ts">SymbolSource</a></code>
-- <code><a href="./src/resources/session.ts">TextPart</a></code>
-- <code><a href="./src/resources/session.ts">TextPartInput</a></code>
-- <code><a href="./src/resources/session.ts">ToolPart</a></code>
-- <code><a href="./src/resources/session.ts">ToolStateCompleted</a></code>
-- <code><a href="./src/resources/session.ts">ToolStateError</a></code>
-- <code><a href="./src/resources/session.ts">ToolStatePending</a></code>
-- <code><a href="./src/resources/session.ts">ToolStateRunning</a></code>
-- <code><a href="./src/resources/session.ts">UserMessage</a></code>
-- <code><a href="./src/resources/session.ts">SessionListResponse</a></code>
-- <code><a href="./src/resources/session.ts">SessionDeleteResponse</a></code>
-- <code><a href="./src/resources/session.ts">SessionAbortResponse</a></code>
-- <code><a href="./src/resources/session.ts">SessionInitResponse</a></code>
-- <code><a href="./src/resources/session.ts">SessionMessagesResponse</a></code>
-- <code><a href="./src/resources/session.ts">SessionSummarizeResponse</a></code>
-
-Methods:
-
-- <code title="post /session">client.session.<a href="./src/resources/session.ts">create</a>() -> Session</code>
-- <code title="get /session">client.session.<a href="./src/resources/session.ts">list</a>() -> SessionListResponse</code>
-- <code title="delete /session/{id}">client.session.<a href="./src/resources/session.ts">delete</a>(id) -> SessionDeleteResponse</code>
-- <code title="post /session/{id}/abort">client.session.<a href="./src/resources/session.ts">abort</a>(id) -> SessionAbortResponse</code>
-- <code title="post /session/{id}/message">client.session.<a href="./src/resources/session.ts">chat</a>(id, { ...params }) -> AssistantMessage</code>
-- <code title="post /session/{id}/init">client.session.<a href="./src/resources/session.ts">init</a>(id, { ...params }) -> SessionInitResponse</code>
-- <code title="get /session/{id}/message">client.session.<a href="./src/resources/session.ts">messages</a>(id) -> SessionMessagesResponse</code>
-- <code title="post /session/{id}/revert">client.session.<a href="./src/resources/session.ts">revert</a>(id, { ...params }) -> Session</code>
-- <code title="post /session/{id}/share">client.session.<a href="./src/resources/session.ts">share</a>(id) -> Session</code>
-- <code title="post /session/{id}/summarize">client.session.<a href="./src/resources/session.ts">summarize</a>(id, { ...params }) -> SessionSummarizeResponse</code>
-- <code title="post /session/{id}/unrevert">client.session.<a href="./src/resources/session.ts">unrevert</a>(id) -> Session</code>
-- <code title="delete /session/{id}/share">client.session.<a href="./src/resources/session.ts">unshare</a>(id) -> Session</code>
-
-# Tui
-
-Types:
-
-- <code><a href="./src/resources/tui.ts">TuiAppendPromptResponse</a></code>
-- <code><a href="./src/resources/tui.ts">TuiOpenHelpResponse</a></code>
-
-Methods:
-
-- <code title="post /tui/append-prompt">client.tui.<a href="./src/resources/tui.ts">appendPrompt</a>({ ...params }) -> TuiAppendPromptResponse</code>
-- <code title="post /tui/open-help">client.tui.<a href="./src/resources/tui.ts">openHelp</a>() -> TuiOpenHelpResponse</code>

+ 0 - 22
packages/sdk/bin/check-release-environment

@@ -1,22 +0,0 @@
-#!/usr/bin/env bash
-
-errors=()
-
-if [ -z "${NPM_TOKEN}" ]; then
-  errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets")
-fi
-
-lenErrors=${#errors[@]}
-
-if [[ lenErrors -gt 0 ]]; then
-  echo -e "Found the following errors in the release environment:\n"
-
-  for error in "${errors[@]}"; do
-    echo -e "- $error\n"
-  done
-
-  exit 1
-fi
-
-echo "The environment is ready to push releases!"
-

+ 0 - 61
packages/sdk/bin/publish-npm

@@ -1,61 +0,0 @@
-#!/usr/bin/env bash
-
-set -eux
-
-npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN"
-
-yarn build
-cd dist
-
-# Get package name and version from package.json
-PACKAGE_NAME="$(jq -r -e '.name' ./package.json)"
-VERSION="$(jq -r -e '.version' ./package.json)"
-
-# Get latest version from npm
-#
-# If the package doesn't exist, npm will return:
-# {
-#   "error": {
-#     "code": "E404",
-#     "summary": "Unpublished on 2025-06-05T09:54:53.528Z",
-#     "detail": "'the_package' is not in this registry..."
-#   }
-# }
-NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)"
-
-# Check if we got an E404 error
-if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then
-  # Package doesn't exist yet, no last version
-  LAST_VERSION=""
-elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then
-  # Report other errors
-  echo "ERROR: npm returned unexpected data:"
-  echo "$NPM_INFO"
-  exit 1
-else
-  # Success - get the version
-  LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes
-fi
-
-# Check if current version is pre-release (e.g. alpha / beta / rc)
-CURRENT_IS_PRERELEASE=false
-if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then
-  CURRENT_IS_PRERELEASE=true
-  CURRENT_TAG="${BASH_REMATCH[1]}"
-fi
-
-# Check if last version is a stable release
-LAST_IS_STABLE_RELEASE=true
-if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then
-  LAST_IS_STABLE_RELEASE=false
-fi
-
-# Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease.
-if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then
-  TAG="$CURRENT_TAG"
-else
-  TAG="latest"
-fi
-
-# Publish with the appropriate tag
-yarn publish --access public --tag "$TAG"

+ 0 - 42
packages/sdk/eslint.config.mjs

@@ -1,42 +0,0 @@
-// @ts-check
-import tseslint from 'typescript-eslint';
-import unusedImports from 'eslint-plugin-unused-imports';
-import prettier from 'eslint-plugin-prettier';
-
-export default tseslint.config(
-  {
-    languageOptions: {
-      parser: tseslint.parser,
-      parserOptions: { sourceType: 'module' },
-    },
-    files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'],
-    ignores: ['dist/'],
-    plugins: {
-      '@typescript-eslint': tseslint.plugin,
-      'unused-imports': unusedImports,
-      prettier,
-    },
-    rules: {
-      'no-unused-vars': 'off',
-      'prettier/prettier': 'error',
-      'unused-imports/no-unused-imports': 'error',
-      'no-restricted-imports': [
-        'error',
-        {
-          patterns: [
-            {
-              regex: '^@opencode-ai/sdk(/.*)?',
-              message: 'Use a relative import, not a package import.',
-            },
-          ],
-        },
-      ],
-    },
-  },
-  {
-    files: ['tests/**', 'examples/**'],
-    rules: {
-      'no-restricted-imports': 'off',
-    },
-  },
-);

+ 7 - 0
packages/sdk/go/.devcontainer/devcontainer.json

@@ -0,0 +1,7 @@
+// 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"
+}

+ 49 - 0
packages/sdk/go/.github/workflows/ci.yml

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

+ 4 - 0
packages/sdk/go/.gitignore

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

+ 3 - 0
packages/sdk/go/.release-please-manifest.json

@@ -0,0 +1,3 @@
+{
+  ".": "0.1.0-alpha.8"
+}

+ 4 - 0
packages/sdk/go/.stats.yml

@@ -0,0 +1,4 @@
+configured_endpoints: 26
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-5bf6a39123d248d306490c1dee61b46ba113ea2c415a4de1a631c76462769c49.yml
+openapi_spec_hash: 3c5b25f121429281275ffd70c9d5cfe4
+config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3

+ 1 - 0
packages/sdk/go/Brewfile

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

+ 73 - 0
packages/sdk/go/CHANGELOG.md

@@ -0,0 +1,73 @@
+# Changelog
+
+## 0.1.0-alpha.8 (2025-07-02)
+
+Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.7...v0.1.0-alpha.8)
+
+### Features
+
+* **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519))
+
+## 0.1.0-alpha.7 (2025-06-30)
+
+Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.6...v0.1.0-alpha.7)
+
+### Features
+
+* **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))
+
+
+### Chores
+
+* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
+
+## 0.1.0-alpha.6 (2025-06-28)
+
+Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.5...v0.1.0-alpha.6)
+
+### Bug Fixes
+
+* don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d))
+
+## 0.1.0-alpha.5 (2025-06-27)
+
+Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)
+
+### Features
+
+* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
+
+## 0.1.0-alpha.4 (2025-06-27)
+
+Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.3...v0.1.0-alpha.4)
+
+### Features
+
+* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
+
+## 0.1.0-alpha.3 (2025-06-27)
+
+Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.2...v0.1.0-alpha.3)
+
+### Features
+
+* **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61))
+
+## 0.1.0-alpha.2 (2025-06-27)
+
+Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.1...v0.1.0-alpha.2)
+
+### Features
+
+* **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6))
+
+## 0.1.0-alpha.1 (2025-06-27)
+
+Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencode-sdk-go/compare/v0.0.1-alpha.0...v0.1.0-alpha.1)
+
+### Features
+
+* **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a))
+* **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))

+ 66 - 0
packages/sdk/go/CONTRIBUTING.md

@@ -0,0 +1,66 @@
+## 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.18+ 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
+```

+ 0 - 0
packages/sdk/LICENSE → packages/sdk/go/LICENSE


+ 354 - 0
packages/sdk/go/README.md

@@ -0,0 +1,354 @@
+# 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/[email protected]'
+```
+
+<!-- x-release-please-end -->
+
+## Requirements
+
+This library requires Go 1.18+.
+
+## 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())
+	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())
+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,
+	// 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(), 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(), 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).

+ 0 - 0
packages/sdk/SECURITY.md → packages/sdk/go/SECURITY.md


+ 43 - 0
packages/sdk/go/aliases.go

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

+ 128 - 0
packages/sdk/go/api.md

@@ -0,0 +1,128 @@
+# 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>) (<a href="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>
+
+# 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#App">App</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#Mode">Mode</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#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="get /app">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#App">App</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /app/init">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /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>, 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#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 /mode">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Modes">Modes</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Mode">Mode</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>) (<a href="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>
+
+# 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#FileReadResponse">FileReadResponse</a>
+
+Methods:
+
+- <code title="get /file">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Read">Read</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadParams">FileReadParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /file/status">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Status">Status</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+
+# 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>
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ModeConfig">ModeConfig</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>) (<a href="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>
+
+# 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#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#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#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#SessionMessagesResponse">SessionMessagesResponse</a>
+
+Methods:
+
+- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+
+# 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>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>

+ 368 - 0
packages/sdk/go/app.go

@@ -0,0 +1,368 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/sst/opencode-sdk-go/internal/apijson"
+	"github.com/sst/opencode-sdk-go/internal/param"
+	"github.com/sst/opencode-sdk-go/internal/requestconfig"
+	"github.com/sst/opencode-sdk-go/option"
+)
+
+// 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
+}
+
+// Get app info
+func (r *AppService) Get(ctx context.Context, opts ...option.RequestOption) (res *App, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "app"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+	return
+}
+
+// Initialize the app
+func (r *AppService) Init(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "app/init"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Write a log entry to the server logs
+func (r *AppService) Log(ctx context.Context, body AppLogParams, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "log"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+	return
+}
+
+// List all modes
+func (r *AppService) Modes(ctx context.Context, opts ...option.RequestOption) (res *[]Mode, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "mode"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+	return
+}
+
+// List all providers
+func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption) (res *AppProvidersResponse, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "config/providers"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+	return
+}
+
+type App struct {
+	Git      bool    `json:"git,required"`
+	Hostname string  `json:"hostname,required"`
+	Path     AppPath `json:"path,required"`
+	Time     AppTime `json:"time,required"`
+	JSON     appJSON `json:"-"`
+}
+
+// appJSON contains the JSON metadata for the struct [App]
+type appJSON struct {
+	Git         apijson.Field
+	Hostname    apijson.Field
+	Path        apijson.Field
+	Time        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *App) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r appJSON) RawJSON() string {
+	return r.raw
+}
+
+type AppPath struct {
+	Config string      `json:"config,required"`
+	Cwd    string      `json:"cwd,required"`
+	Data   string      `json:"data,required"`
+	Root   string      `json:"root,required"`
+	State  string      `json:"state,required"`
+	JSON   appPathJSON `json:"-"`
+}
+
+// appPathJSON contains the JSON metadata for the struct [AppPath]
+type appPathJSON struct {
+	Config      apijson.Field
+	Cwd         apijson.Field
+	Data        apijson.Field
+	Root        apijson.Field
+	State       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AppPath) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r appPathJSON) RawJSON() string {
+	return r.raw
+}
+
+type AppTime struct {
+	Initialized float64     `json:"initialized"`
+	JSON        appTimeJSON `json:"-"`
+}
+
+// appTimeJSON contains the JSON metadata for the struct [AppTime]
+type appTimeJSON struct {
+	Initialized apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AppTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r appTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type Mode struct {
+	Name        string          `json:"name,required"`
+	Tools       map[string]bool `json:"tools,required"`
+	Model       ModeModel       `json:"model"`
+	Prompt      string          `json:"prompt"`
+	Temperature float64         `json:"temperature"`
+	JSON        modeJSON        `json:"-"`
+}
+
+// modeJSON contains the JSON metadata for the struct [Mode]
+type modeJSON struct {
+	Name        apijson.Field
+	Tools       apijson.Field
+	Model       apijson.Field
+	Prompt      apijson.Field
+	Temperature apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *Mode) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r modeJSON) RawJSON() string {
+	return r.raw
+}
+
+type ModeModel struct {
+	ModelID    string        `json:"modelID,required"`
+	ProviderID string        `json:"providerID,required"`
+	JSON       modeModelJSON `json:"-"`
+}
+
+// modeModelJSON contains the JSON metadata for the struct [ModeModel]
+type modeModelJSON struct {
+	ModelID     apijson.Field
+	ProviderID  apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ModeModel) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r modeModelJSON) RawJSON() string {
+	return r.raw
+}
+
+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"`
+	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
+	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 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"`
+	// 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)
+}
+
+// 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
+}

+ 131 - 0
packages/sdk/go/app_test.go

@@ -0,0 +1,131 @@
+// 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 TestAppGet(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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.Get(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestAppInit(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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.Init(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestAppLogWithOptionalParams(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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"),
+		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 TestAppModes(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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.Modes(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestAppProviders(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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())
+	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())
+	}
+}

+ 125 - 0
packages/sdk/go/client.go

@@ -0,0 +1,125 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"net/http"
+	"os"
+
+	"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
+	App     *AppService
+	Find    *FindService
+	File    *FileService
+	Config  *ConfigService
+	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.App = NewAppService(opts...)
+	r.Find = NewFindService(opts...)
+	r.File = NewFileService(opts...)
+	r.Config = NewConfigService(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 = append(r.Options, opts...)
+	return requestconfig.ExecuteNewRequest(ctx, method, path, params, res, opts...)
+}
+
+// Get makes a GET request with the given URL, params, and optionally deserializes
+// to a response. See [Execute] documentation on the params and response.
+func (r *Client) Get(ctx context.Context, path string, params 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...)
+}

+ 332 - 0
packages/sdk/go/client_test.go

@@ -0,0 +1,332 @@
+// 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())
+	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())
+	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())
+	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())
+	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())
+	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)
+	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)
+	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)
+		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)
+		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(), 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 }

+ 887 - 0
packages/sdk/go/config.go

@@ -0,0 +1,887 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"net/http"
+	"reflect"
+
+	"github.com/sst/opencode-sdk-go/internal/apijson"
+	"github.com/sst/opencode-sdk-go/internal/requestconfig"
+	"github.com/sst/opencode-sdk-go/option"
+	"github.com/tidwall/gjson"
+)
+
+// ConfigService contains methods and other services that help with interacting
+// 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 [NewConfigService] method instead.
+type ConfigService struct {
+	Options []option.RequestOption
+}
+
+// NewConfigService generates a new service that applies the given options to each
+// request. These options are applied after the parent client's options (if there
+// is one), and before any request-specific options.
+func NewConfigService(opts ...option.RequestOption) (r *ConfigService) {
+	r = &ConfigService{}
+	r.Options = opts
+	return
+}
+
+// Get config info
+func (r *ConfigService) Get(ctx context.Context, opts ...option.RequestOption) (res *Config, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "config"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+	return
+}
+
+type Config struct {
+	// JSON schema reference for configuration validation
+	Schema string `json:"$schema"`
+	// Modes configuration, see https://opencode.ai/docs/modes
+	Agent ConfigAgent `json:"agent"`
+	// @deprecated Use 'share' field instead. Share newly created sessions
+	// automatically
+	Autoshare bool `json:"autoshare"`
+	// Automatically update to the latest version
+	Autoupdate bool `json:"autoupdate"`
+	// Disable providers that are loaded automatically
+	DisabledProviders []string           `json:"disabled_providers"`
+	Experimental      ConfigExperimental `json:"experimental"`
+	// Additional instruction files or patterns to include
+	Instructions []string `json:"instructions"`
+	// Custom keybind configurations
+	Keybinds KeybindsConfig `json:"keybinds"`
+	// @deprecated Always uses stretch layout.
+	Layout ConfigLayout `json:"layout"`
+	// MCP (Model Context Protocol) server configurations
+	Mcp map[string]ConfigMcp `json:"mcp"`
+	// Modes configuration, see https://opencode.ai/docs/modes
+	Mode ConfigMode `json:"mode"`
+	// Model to use in the format of provider/model, eg anthropic/claude-2
+	Model      string           `json:"model"`
+	Permission ConfigPermission `json:"permission"`
+	// Custom provider configurations and model overrides
+	Provider map[string]ConfigProvider `json:"provider"`
+	// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
+	// enables automatic sharing, 'disabled' disables all sharing
+	Share ConfigShare `json:"share"`
+	// Small model to use for tasks like summarization and title generation in the
+	// format of provider/model
+	SmallModel string `json:"small_model"`
+	// Theme name to use for the interface
+	Theme string `json:"theme"`
+	// Custom username to display in conversations instead of system username
+	Username string     `json:"username"`
+	JSON     configJSON `json:"-"`
+}
+
+// configJSON contains the JSON metadata for the struct [Config]
+type configJSON struct {
+	Schema            apijson.Field
+	Agent             apijson.Field
+	Autoshare         apijson.Field
+	Autoupdate        apijson.Field
+	DisabledProviders apijson.Field
+	Experimental      apijson.Field
+	Instructions      apijson.Field
+	Keybinds          apijson.Field
+	Layout            apijson.Field
+	Mcp               apijson.Field
+	Mode              apijson.Field
+	Model             apijson.Field
+	Permission        apijson.Field
+	Provider          apijson.Field
+	Share             apijson.Field
+	SmallModel        apijson.Field
+	Theme             apijson.Field
+	Username          apijson.Field
+	raw               string
+	ExtraFields       map[string]apijson.Field
+}
+
+func (r *Config) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configJSON) RawJSON() string {
+	return r.raw
+}
+
+// Modes configuration, see https://opencode.ai/docs/modes
+type ConfigAgent struct {
+	General     ConfigAgentGeneral     `json:"general"`
+	ExtraFields map[string]ConfigAgent `json:"-,extras"`
+	JSON        configAgentJSON        `json:"-"`
+}
+
+// configAgentJSON contains the JSON metadata for the struct [ConfigAgent]
+type configAgentJSON struct {
+	General     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigAgent) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configAgentJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigAgentGeneral struct {
+	Description string                 `json:"description,required"`
+	JSON        configAgentGeneralJSON `json:"-"`
+	ModeConfig
+}
+
+// configAgentGeneralJSON contains the JSON metadata for the struct
+// [ConfigAgentGeneral]
+type configAgentGeneralJSON struct {
+	Description apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigAgentGeneral) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configAgentGeneralJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigExperimental struct {
+	Hook ConfigExperimentalHook `json:"hook"`
+	JSON configExperimentalJSON `json:"-"`
+}
+
+// configExperimentalJSON contains the JSON metadata for the struct
+// [ConfigExperimental]
+type configExperimentalJSON struct {
+	Hook        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigExperimental) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configExperimentalJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigExperimentalHook struct {
+	FileEdited       map[string][]ConfigExperimentalHookFileEdited `json:"file_edited"`
+	SessionCompleted []ConfigExperimentalHookSessionCompleted      `json:"session_completed"`
+	JSON             configExperimentalHookJSON                    `json:"-"`
+}
+
+// configExperimentalHookJSON contains the JSON metadata for the struct
+// [ConfigExperimentalHook]
+type configExperimentalHookJSON struct {
+	FileEdited       apijson.Field
+	SessionCompleted apijson.Field
+	raw              string
+	ExtraFields      map[string]apijson.Field
+}
+
+func (r *ConfigExperimentalHook) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configExperimentalHookJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigExperimentalHookFileEdited struct {
+	Command     []string                             `json:"command,required"`
+	Environment map[string]string                    `json:"environment"`
+	JSON        configExperimentalHookFileEditedJSON `json:"-"`
+}
+
+// configExperimentalHookFileEditedJSON contains the JSON metadata for the struct
+// [ConfigExperimentalHookFileEdited]
+type configExperimentalHookFileEditedJSON struct {
+	Command     apijson.Field
+	Environment apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigExperimentalHookFileEdited) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configExperimentalHookFileEditedJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigExperimentalHookSessionCompleted struct {
+	Command     []string                                   `json:"command,required"`
+	Environment map[string]string                          `json:"environment"`
+	JSON        configExperimentalHookSessionCompletedJSON `json:"-"`
+}
+
+// configExperimentalHookSessionCompletedJSON contains the JSON metadata for the
+// struct [ConfigExperimentalHookSessionCompleted]
+type configExperimentalHookSessionCompletedJSON struct {
+	Command     apijson.Field
+	Environment apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigExperimentalHookSessionCompleted) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configExperimentalHookSessionCompletedJSON) RawJSON() string {
+	return r.raw
+}
+
+// @deprecated Always uses stretch layout.
+type ConfigLayout string
+
+const (
+	ConfigLayoutAuto    ConfigLayout = "auto"
+	ConfigLayoutStretch ConfigLayout = "stretch"
+)
+
+func (r ConfigLayout) IsKnown() bool {
+	switch r {
+	case ConfigLayoutAuto, ConfigLayoutStretch:
+		return true
+	}
+	return false
+}
+
+type ConfigMcp struct {
+	// Type of MCP server connection
+	Type ConfigMcpType `json:"type,required"`
+	// This field can have the runtime type of [[]string].
+	Command interface{} `json:"command"`
+	// Enable or disable the MCP server on startup
+	Enabled bool `json:"enabled"`
+	// This field can have the runtime type of [map[string]string].
+	Environment interface{} `json:"environment"`
+	// This field can have the runtime type of [map[string]string].
+	Headers interface{} `json:"headers"`
+	// URL of the remote MCP server
+	URL   string        `json:"url"`
+	JSON  configMcpJSON `json:"-"`
+	union ConfigMcpUnion
+}
+
+// configMcpJSON contains the JSON metadata for the struct [ConfigMcp]
+type configMcpJSON struct {
+	Type        apijson.Field
+	Command     apijson.Field
+	Enabled     apijson.Field
+	Environment apijson.Field
+	Headers     apijson.Field
+	URL         apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r configMcpJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *ConfigMcp) UnmarshalJSON(data []byte) (err error) {
+	*r = ConfigMcp{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [ConfigMcpUnion] interface which you can cast to the specific
+// types for more type safety.
+//
+// Possible runtime types of the union are [McpLocalConfig], [McpRemoteConfig].
+func (r ConfigMcp) AsUnion() ConfigMcpUnion {
+	return r.union
+}
+
+// Union satisfied by [McpLocalConfig] or [McpRemoteConfig].
+type ConfigMcpUnion interface {
+	implementsConfigMcp()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*ConfigMcpUnion)(nil)).Elem(),
+		"type",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(McpLocalConfig{}),
+			DiscriminatorValue: "local",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(McpRemoteConfig{}),
+			DiscriminatorValue: "remote",
+		},
+	)
+}
+
+// Type of MCP server connection
+type ConfigMcpType string
+
+const (
+	ConfigMcpTypeLocal  ConfigMcpType = "local"
+	ConfigMcpTypeRemote ConfigMcpType = "remote"
+)
+
+func (r ConfigMcpType) IsKnown() bool {
+	switch r {
+	case ConfigMcpTypeLocal, ConfigMcpTypeRemote:
+		return true
+	}
+	return false
+}
+
+// Modes configuration, see https://opencode.ai/docs/modes
+type ConfigMode struct {
+	Build       ModeConfig            `json:"build"`
+	Plan        ModeConfig            `json:"plan"`
+	ExtraFields map[string]ModeConfig `json:"-,extras"`
+	JSON        configModeJSON        `json:"-"`
+}
+
+// configModeJSON contains the JSON metadata for the struct [ConfigMode]
+type configModeJSON struct {
+	Build       apijson.Field
+	Plan        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigMode) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configModeJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigPermission struct {
+	Bash ConfigPermissionBashUnion `json:"bash"`
+	Edit ConfigPermissionEdit      `json:"edit"`
+	JSON configPermissionJSON      `json:"-"`
+}
+
+// configPermissionJSON contains the JSON metadata for the struct
+// [ConfigPermission]
+type configPermissionJSON struct {
+	Bash        apijson.Field
+	Edit        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigPermission) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configPermissionJSON) RawJSON() string {
+	return r.raw
+}
+
+// Union satisfied by [ConfigPermissionBashString] or [ConfigPermissionBashMap].
+type ConfigPermissionBashUnion interface {
+	implementsConfigPermissionBashUnion()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*ConfigPermissionBashUnion)(nil)).Elem(),
+		"",
+		apijson.UnionVariant{
+			TypeFilter: gjson.String,
+			Type:       reflect.TypeOf(ConfigPermissionBashString("")),
+		},
+		apijson.UnionVariant{
+			TypeFilter: gjson.JSON,
+			Type:       reflect.TypeOf(ConfigPermissionBashMap{}),
+		},
+	)
+}
+
+type ConfigPermissionBashString string
+
+const (
+	ConfigPermissionBashStringAsk   ConfigPermissionBashString = "ask"
+	ConfigPermissionBashStringAllow ConfigPermissionBashString = "allow"
+)
+
+func (r ConfigPermissionBashString) IsKnown() bool {
+	switch r {
+	case ConfigPermissionBashStringAsk, ConfigPermissionBashStringAllow:
+		return true
+	}
+	return false
+}
+
+func (r ConfigPermissionBashString) implementsConfigPermissionBashUnion() {}
+
+type ConfigPermissionBashMap map[string]ConfigPermissionBashMapItem
+
+func (r ConfigPermissionBashMap) implementsConfigPermissionBashUnion() {}
+
+type ConfigPermissionBashMapItem string
+
+const (
+	ConfigPermissionBashMapAsk   ConfigPermissionBashMapItem = "ask"
+	ConfigPermissionBashMapAllow ConfigPermissionBashMapItem = "allow"
+)
+
+func (r ConfigPermissionBashMapItem) IsKnown() bool {
+	switch r {
+	case ConfigPermissionBashMapAsk, ConfigPermissionBashMapAllow:
+		return true
+	}
+	return false
+}
+
+type ConfigPermissionEdit string
+
+const (
+	ConfigPermissionEditAsk   ConfigPermissionEdit = "ask"
+	ConfigPermissionEditAllow ConfigPermissionEdit = "allow"
+)
+
+func (r ConfigPermissionEdit) IsKnown() bool {
+	switch r {
+	case ConfigPermissionEditAsk, ConfigPermissionEditAllow:
+		return true
+	}
+	return false
+}
+
+type ConfigProvider struct {
+	Models  map[string]ConfigProviderModel `json:"models,required"`
+	ID      string                         `json:"id"`
+	API     string                         `json:"api"`
+	Env     []string                       `json:"env"`
+	Name    string                         `json:"name"`
+	Npm     string                         `json:"npm"`
+	Options ConfigProviderOptions          `json:"options"`
+	JSON    configProviderJSON             `json:"-"`
+}
+
+// configProviderJSON contains the JSON metadata for the struct [ConfigProvider]
+type configProviderJSON struct {
+	Models      apijson.Field
+	ID          apijson.Field
+	API         apijson.Field
+	Env         apijson.Field
+	Name        apijson.Field
+	Npm         apijson.Field
+	Options     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigProvider) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configProviderJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigProviderModel struct {
+	ID          string                    `json:"id"`
+	Attachment  bool                      `json:"attachment"`
+	Cost        ConfigProviderModelsCost  `json:"cost"`
+	Limit       ConfigProviderModelsLimit `json:"limit"`
+	Name        string                    `json:"name"`
+	Options     map[string]interface{}    `json:"options"`
+	Reasoning   bool                      `json:"reasoning"`
+	ReleaseDate string                    `json:"release_date"`
+	Temperature bool                      `json:"temperature"`
+	ToolCall    bool                      `json:"tool_call"`
+	JSON        configProviderModelJSON   `json:"-"`
+}
+
+// configProviderModelJSON contains the JSON metadata for the struct
+// [ConfigProviderModel]
+type configProviderModelJSON struct {
+	ID          apijson.Field
+	Attachment  apijson.Field
+	Cost        apijson.Field
+	Limit       apijson.Field
+	Name        apijson.Field
+	Options     apijson.Field
+	Reasoning   apijson.Field
+	ReleaseDate apijson.Field
+	Temperature apijson.Field
+	ToolCall    apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigProviderModel) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configProviderModelJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigProviderModelsCost struct {
+	Input      float64                      `json:"input,required"`
+	Output     float64                      `json:"output,required"`
+	CacheRead  float64                      `json:"cache_read"`
+	CacheWrite float64                      `json:"cache_write"`
+	JSON       configProviderModelsCostJSON `json:"-"`
+}
+
+// configProviderModelsCostJSON contains the JSON metadata for the struct
+// [ConfigProviderModelsCost]
+type configProviderModelsCostJSON struct {
+	Input       apijson.Field
+	Output      apijson.Field
+	CacheRead   apijson.Field
+	CacheWrite  apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigProviderModelsCost) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configProviderModelsCostJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigProviderModelsLimit struct {
+	Context float64                       `json:"context,required"`
+	Output  float64                       `json:"output,required"`
+	JSON    configProviderModelsLimitJSON `json:"-"`
+}
+
+// configProviderModelsLimitJSON contains the JSON metadata for the struct
+// [ConfigProviderModelsLimit]
+type configProviderModelsLimitJSON struct {
+	Context     apijson.Field
+	Output      apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigProviderModelsLimit) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configProviderModelsLimitJSON) RawJSON() string {
+	return r.raw
+}
+
+type ConfigProviderOptions struct {
+	APIKey      string                    `json:"apiKey"`
+	BaseURL     string                    `json:"baseURL"`
+	ExtraFields map[string]interface{}    `json:"-,extras"`
+	JSON        configProviderOptionsJSON `json:"-"`
+}
+
+// configProviderOptionsJSON contains the JSON metadata for the struct
+// [ConfigProviderOptions]
+type configProviderOptionsJSON struct {
+	APIKey      apijson.Field
+	BaseURL     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigProviderOptions) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configProviderOptionsJSON) RawJSON() string {
+	return r.raw
+}
+
+// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
+// enables automatic sharing, 'disabled' disables all sharing
+type ConfigShare string
+
+const (
+	ConfigShareManual   ConfigShare = "manual"
+	ConfigShareAuto     ConfigShare = "auto"
+	ConfigShareDisabled ConfigShare = "disabled"
+)
+
+func (r ConfigShare) IsKnown() bool {
+	switch r {
+	case ConfigShareManual, ConfigShareAuto, ConfigShareDisabled:
+		return true
+	}
+	return false
+}
+
+type KeybindsConfig struct {
+	// Exit the application
+	AppExit string `json:"app_exit,required"`
+	// Show help dialog
+	AppHelp string `json:"app_help,required"`
+	// Open external editor
+	EditorOpen string `json:"editor_open,required"`
+	// Close file
+	FileClose string `json:"file_close,required"`
+	// Split/unified diff
+	FileDiffToggle string `json:"file_diff_toggle,required"`
+	// List files
+	FileList string `json:"file_list,required"`
+	// Search file
+	FileSearch string `json:"file_search,required"`
+	// Clear input field
+	InputClear string `json:"input_clear,required"`
+	// Insert newline in input
+	InputNewline string `json:"input_newline,required"`
+	// Paste from clipboard
+	InputPaste string `json:"input_paste,required"`
+	// Submit input
+	InputSubmit string `json:"input_submit,required"`
+	// Leader key for keybind combinations
+	Leader string `json:"leader,required"`
+	// Copy message
+	MessagesCopy string `json:"messages_copy,required"`
+	// Navigate to first message
+	MessagesFirst string `json:"messages_first,required"`
+	// Scroll messages down by half page
+	MessagesHalfPageDown string `json:"messages_half_page_down,required"`
+	// Scroll messages up by half page
+	MessagesHalfPageUp string `json:"messages_half_page_up,required"`
+	// Navigate to last message
+	MessagesLast string `json:"messages_last,required"`
+	// Toggle layout
+	MessagesLayoutToggle string `json:"messages_layout_toggle,required"`
+	// Navigate to next message
+	MessagesNext string `json:"messages_next,required"`
+	// Scroll messages down by one page
+	MessagesPageDown string `json:"messages_page_down,required"`
+	// Scroll messages up by one page
+	MessagesPageUp string `json:"messages_page_up,required"`
+	// Navigate to previous message
+	MessagesPrevious string `json:"messages_previous,required"`
+	// Redo message
+	MessagesRedo string `json:"messages_redo,required"`
+	// @deprecated use messages_undo. Revert message
+	MessagesRevert string `json:"messages_revert,required"`
+	// Undo message
+	MessagesUndo string `json:"messages_undo,required"`
+	// List available models
+	ModelList string `json:"model_list,required"`
+	// Create/update AGENTS.md
+	ProjectInit string `json:"project_init,required"`
+	// Compact the session
+	SessionCompact string `json:"session_compact,required"`
+	// Export session to editor
+	SessionExport string `json:"session_export,required"`
+	// Interrupt current session
+	SessionInterrupt string `json:"session_interrupt,required"`
+	// List all sessions
+	SessionList string `json:"session_list,required"`
+	// Create a new session
+	SessionNew string `json:"session_new,required"`
+	// Share current session
+	SessionShare string `json:"session_share,required"`
+	// Unshare current session
+	SessionUnshare string `json:"session_unshare,required"`
+	// Next mode
+	SwitchMode string `json:"switch_mode,required"`
+	// Previous Mode
+	SwitchModeReverse string `json:"switch_mode_reverse,required"`
+	// List available themes
+	ThemeList string `json:"theme_list,required"`
+	// Toggle tool details
+	ToolDetails string             `json:"tool_details,required"`
+	JSON        keybindsConfigJSON `json:"-"`
+}
+
+// keybindsConfigJSON contains the JSON metadata for the struct [KeybindsConfig]
+type keybindsConfigJSON struct {
+	AppExit              apijson.Field
+	AppHelp              apijson.Field
+	EditorOpen           apijson.Field
+	FileClose            apijson.Field
+	FileDiffToggle       apijson.Field
+	FileList             apijson.Field
+	FileSearch           apijson.Field
+	InputClear           apijson.Field
+	InputNewline         apijson.Field
+	InputPaste           apijson.Field
+	InputSubmit          apijson.Field
+	Leader               apijson.Field
+	MessagesCopy         apijson.Field
+	MessagesFirst        apijson.Field
+	MessagesHalfPageDown apijson.Field
+	MessagesHalfPageUp   apijson.Field
+	MessagesLast         apijson.Field
+	MessagesLayoutToggle apijson.Field
+	MessagesNext         apijson.Field
+	MessagesPageDown     apijson.Field
+	MessagesPageUp       apijson.Field
+	MessagesPrevious     apijson.Field
+	MessagesRedo         apijson.Field
+	MessagesRevert       apijson.Field
+	MessagesUndo         apijson.Field
+	ModelList            apijson.Field
+	ProjectInit          apijson.Field
+	SessionCompact       apijson.Field
+	SessionExport        apijson.Field
+	SessionInterrupt     apijson.Field
+	SessionList          apijson.Field
+	SessionNew           apijson.Field
+	SessionShare         apijson.Field
+	SessionUnshare       apijson.Field
+	SwitchMode           apijson.Field
+	SwitchModeReverse    apijson.Field
+	ThemeList            apijson.Field
+	ToolDetails          apijson.Field
+	raw                  string
+	ExtraFields          map[string]apijson.Field
+}
+
+func (r *KeybindsConfig) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r keybindsConfigJSON) RawJSON() string {
+	return r.raw
+}
+
+type McpLocalConfig struct {
+	// Command and arguments to run the MCP server
+	Command []string `json:"command,required"`
+	// Type of MCP server connection
+	Type McpLocalConfigType `json:"type,required"`
+	// Enable or disable the MCP server on startup
+	Enabled bool `json:"enabled"`
+	// Environment variables to set when running the MCP server
+	Environment map[string]string  `json:"environment"`
+	JSON        mcpLocalConfigJSON `json:"-"`
+}
+
+// mcpLocalConfigJSON contains the JSON metadata for the struct [McpLocalConfig]
+type mcpLocalConfigJSON struct {
+	Command     apijson.Field
+	Type        apijson.Field
+	Enabled     apijson.Field
+	Environment apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *McpLocalConfig) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r mcpLocalConfigJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r McpLocalConfig) implementsConfigMcp() {}
+
+// Type of MCP server connection
+type McpLocalConfigType string
+
+const (
+	McpLocalConfigTypeLocal McpLocalConfigType = "local"
+)
+
+func (r McpLocalConfigType) IsKnown() bool {
+	switch r {
+	case McpLocalConfigTypeLocal:
+		return true
+	}
+	return false
+}
+
+type McpRemoteConfig struct {
+	// Type of MCP server connection
+	Type McpRemoteConfigType `json:"type,required"`
+	// URL of the remote MCP server
+	URL string `json:"url,required"`
+	// Enable or disable the MCP server on startup
+	Enabled bool `json:"enabled"`
+	// Headers to send with the request
+	Headers map[string]string   `json:"headers"`
+	JSON    mcpRemoteConfigJSON `json:"-"`
+}
+
+// mcpRemoteConfigJSON contains the JSON metadata for the struct [McpRemoteConfig]
+type mcpRemoteConfigJSON struct {
+	Type        apijson.Field
+	URL         apijson.Field
+	Enabled     apijson.Field
+	Headers     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *McpRemoteConfig) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r mcpRemoteConfigJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r McpRemoteConfig) implementsConfigMcp() {}
+
+// Type of MCP server connection
+type McpRemoteConfigType string
+
+const (
+	McpRemoteConfigTypeRemote McpRemoteConfigType = "remote"
+)
+
+func (r McpRemoteConfigType) IsKnown() bool {
+	switch r {
+	case McpRemoteConfigTypeRemote:
+		return true
+	}
+	return false
+}
+
+type ModeConfig struct {
+	Disable     bool            `json:"disable"`
+	Model       string          `json:"model"`
+	Prompt      string          `json:"prompt"`
+	Temperature float64         `json:"temperature"`
+	Tools       map[string]bool `json:"tools"`
+	JSON        modeConfigJSON  `json:"-"`
+}
+
+// modeConfigJSON contains the JSON metadata for the struct [ModeConfig]
+type modeConfigJSON struct {
+	Disable     apijson.Field
+	Model       apijson.Field
+	Prompt      apijson.Field
+	Temperature apijson.Field
+	Tools       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ModeConfig) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r modeConfigJSON) RawJSON() string {
+	return r.raw
+}

+ 36 - 0
packages/sdk/go/config_test.go

@@ -0,0 +1,36 @@
+// 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 TestConfigGet(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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())
+	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())
+	}
+}

+ 1373 - 0
packages/sdk/go/event.go

@@ -0,0 +1,1373 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"net/http"
+	"reflect"
+
+	"github.com/sst/opencode-sdk-go/internal/apijson"
+	"github.com/sst/opencode-sdk-go/internal/requestconfig"
+	"github.com/sst/opencode-sdk-go/option"
+	"github.com/sst/opencode-sdk-go/packages/ssestream"
+	"github.com/sst/opencode-sdk-go/shared"
+	"github.com/tidwall/gjson"
+)
+
+// EventService contains methods and other services that help with interacting with
+// 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 [NewEventService] method instead.
+type EventService struct {
+	Options []option.RequestOption
+}
+
+// NewEventService generates a new service that applies the given options to each
+// request. These options are applied after the parent client's options (if there
+// is one), and before any request-specific options.
+func NewEventService(opts ...option.RequestOption) (r *EventService) {
+	r = &EventService{}
+	r.Options = opts
+	return
+}
+
+// Get events
+func (r *EventService) ListStreaming(ctx context.Context, opts ...option.RequestOption) (stream *ssestream.Stream[EventListResponse]) {
+	var (
+		raw *http.Response
+		err error
+	)
+	opts = append(r.Options[:], opts...)
+	path := "event"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
+	return ssestream.NewStream[EventListResponse](ssestream.NewDecoder(raw), err)
+}
+
+type EventListResponse struct {
+	// This field can have the runtime type of
+	// [EventListResponseEventInstallationUpdatedProperties],
+	// [EventListResponseEventLspClientDiagnosticsProperties],
+	// [EventListResponseEventMessageUpdatedProperties],
+	// [EventListResponseEventMessageRemovedProperties],
+	// [EventListResponseEventMessagePartUpdatedProperties],
+	// [EventListResponseEventMessagePartRemovedProperties],
+	// [EventListResponseEventStorageWriteProperties],
+	// [EventListResponseEventPermissionUpdatedProperties],
+	// [EventListResponseEventFileEditedProperties],
+	// [EventListResponseEventSessionUpdatedProperties],
+	// [EventListResponseEventSessionDeletedProperties],
+	// [EventListResponseEventSessionIdleProperties],
+	// [EventListResponseEventSessionErrorProperties], [interface{}],
+	// [EventListResponseEventFileWatcherUpdatedProperties],
+	// [EventListResponseEventIdeInstalledProperties].
+	Properties interface{}           `json:"properties,required"`
+	Type       EventListResponseType `json:"type,required"`
+	JSON       eventListResponseJSON `json:"-"`
+	union      EventListResponseUnion
+}
+
+// eventListResponseJSON contains the JSON metadata for the struct
+// [EventListResponse]
+type eventListResponseJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r eventListResponseJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
+	*r = EventListResponse{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [EventListResponseUnion] interface which you can cast to the
+// specific types for more type safety.
+//
+// Possible runtime types of the union are
+// [EventListResponseEventInstallationUpdated],
+// [EventListResponseEventLspClientDiagnostics],
+// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
+// [EventListResponseEventMessagePartUpdated],
+// [EventListResponseEventMessagePartRemoved],
+// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
+// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated],
+// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
+// [EventListResponseEventSessionError], [EventListResponseEventServerConnected],
+// [EventListResponseEventFileWatcherUpdated],
+// [EventListResponseEventIdeInstalled].
+func (r EventListResponse) AsUnion() EventListResponseUnion {
+	return r.union
+}
+
+// Union satisfied by [EventListResponseEventInstallationUpdated],
+// [EventListResponseEventLspClientDiagnostics],
+// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
+// [EventListResponseEventMessagePartUpdated],
+// [EventListResponseEventMessagePartRemoved],
+// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
+// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated],
+// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
+// [EventListResponseEventSessionError], [EventListResponseEventServerConnected],
+// [EventListResponseEventFileWatcherUpdated] or
+// [EventListResponseEventIdeInstalled].
+type EventListResponseUnion interface {
+	implementsEventListResponse()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*EventListResponseUnion)(nil)).Elem(),
+		"type",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventInstallationUpdated{}),
+			DiscriminatorValue: "installation.updated",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventLspClientDiagnostics{}),
+			DiscriminatorValue: "lsp.client.diagnostics",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventMessageUpdated{}),
+			DiscriminatorValue: "message.updated",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventMessageRemoved{}),
+			DiscriminatorValue: "message.removed",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventMessagePartUpdated{}),
+			DiscriminatorValue: "message.part.updated",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventMessagePartRemoved{}),
+			DiscriminatorValue: "message.part.removed",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventStorageWrite{}),
+			DiscriminatorValue: "storage.write",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
+			DiscriminatorValue: "permission.updated",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventFileEdited{}),
+			DiscriminatorValue: "file.edited",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventSessionUpdated{}),
+			DiscriminatorValue: "session.updated",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventSessionDeleted{}),
+			DiscriminatorValue: "session.deleted",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventSessionIdle{}),
+			DiscriminatorValue: "session.idle",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventSessionError{}),
+			DiscriminatorValue: "session.error",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventServerConnected{}),
+			DiscriminatorValue: "server.connected",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
+			DiscriminatorValue: "file.watcher.updated",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventIdeInstalled{}),
+			DiscriminatorValue: "ide.installed",
+		},
+	)
+}
+
+type EventListResponseEventInstallationUpdated struct {
+	Properties EventListResponseEventInstallationUpdatedProperties `json:"properties,required"`
+	Type       EventListResponseEventInstallationUpdatedType       `json:"type,required"`
+	JSON       eventListResponseEventInstallationUpdatedJSON       `json:"-"`
+}
+
+// eventListResponseEventInstallationUpdatedJSON contains the JSON metadata for the
+// struct [EventListResponseEventInstallationUpdated]
+type eventListResponseEventInstallationUpdatedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventInstallationUpdated) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventInstallationUpdatedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventInstallationUpdated) implementsEventListResponse() {}
+
+type EventListResponseEventInstallationUpdatedProperties struct {
+	Version string                                                  `json:"version,required"`
+	JSON    eventListResponseEventInstallationUpdatedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventInstallationUpdatedPropertiesJSON contains the JSON
+// metadata for the struct [EventListResponseEventInstallationUpdatedProperties]
+type eventListResponseEventInstallationUpdatedPropertiesJSON struct {
+	Version     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventInstallationUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventInstallationUpdatedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventInstallationUpdatedType string
+
+const (
+	EventListResponseEventInstallationUpdatedTypeInstallationUpdated EventListResponseEventInstallationUpdatedType = "installation.updated"
+)
+
+func (r EventListResponseEventInstallationUpdatedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventInstallationUpdatedTypeInstallationUpdated:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventLspClientDiagnostics struct {
+	Properties EventListResponseEventLspClientDiagnosticsProperties `json:"properties,required"`
+	Type       EventListResponseEventLspClientDiagnosticsType       `json:"type,required"`
+	JSON       eventListResponseEventLspClientDiagnosticsJSON       `json:"-"`
+}
+
+// eventListResponseEventLspClientDiagnosticsJSON contains the JSON metadata for
+// the struct [EventListResponseEventLspClientDiagnostics]
+type eventListResponseEventLspClientDiagnosticsJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventLspClientDiagnostics) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventLspClientDiagnosticsJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventLspClientDiagnostics) implementsEventListResponse() {}
+
+type EventListResponseEventLspClientDiagnosticsProperties struct {
+	Path     string                                                   `json:"path,required"`
+	ServerID string                                                   `json:"serverID,required"`
+	JSON     eventListResponseEventLspClientDiagnosticsPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventLspClientDiagnosticsPropertiesJSON contains the JSON
+// metadata for the struct [EventListResponseEventLspClientDiagnosticsProperties]
+type eventListResponseEventLspClientDiagnosticsPropertiesJSON struct {
+	Path        apijson.Field
+	ServerID    apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventLspClientDiagnosticsProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventLspClientDiagnosticsPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventLspClientDiagnosticsType string
+
+const (
+	EventListResponseEventLspClientDiagnosticsTypeLspClientDiagnostics EventListResponseEventLspClientDiagnosticsType = "lsp.client.diagnostics"
+)
+
+func (r EventListResponseEventLspClientDiagnosticsType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventLspClientDiagnosticsTypeLspClientDiagnostics:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventMessageUpdated struct {
+	Properties EventListResponseEventMessageUpdatedProperties `json:"properties,required"`
+	Type       EventListResponseEventMessageUpdatedType       `json:"type,required"`
+	JSON       eventListResponseEventMessageUpdatedJSON       `json:"-"`
+}
+
+// eventListResponseEventMessageUpdatedJSON contains the JSON metadata for the
+// struct [EventListResponseEventMessageUpdated]
+type eventListResponseEventMessageUpdatedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessageUpdated) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessageUpdatedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventMessageUpdated) implementsEventListResponse() {}
+
+type EventListResponseEventMessageUpdatedProperties struct {
+	Info Message                                            `json:"info,required"`
+	JSON eventListResponseEventMessageUpdatedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventMessageUpdatedPropertiesJSON contains the JSON metadata
+// for the struct [EventListResponseEventMessageUpdatedProperties]
+type eventListResponseEventMessageUpdatedPropertiesJSON struct {
+	Info        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessageUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessageUpdatedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventMessageUpdatedType string
+
+const (
+	EventListResponseEventMessageUpdatedTypeMessageUpdated EventListResponseEventMessageUpdatedType = "message.updated"
+)
+
+func (r EventListResponseEventMessageUpdatedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventMessageUpdatedTypeMessageUpdated:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventMessageRemoved struct {
+	Properties EventListResponseEventMessageRemovedProperties `json:"properties,required"`
+	Type       EventListResponseEventMessageRemovedType       `json:"type,required"`
+	JSON       eventListResponseEventMessageRemovedJSON       `json:"-"`
+}
+
+// eventListResponseEventMessageRemovedJSON contains the JSON metadata for the
+// struct [EventListResponseEventMessageRemoved]
+type eventListResponseEventMessageRemovedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessageRemoved) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessageRemovedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventMessageRemoved) implementsEventListResponse() {}
+
+type EventListResponseEventMessageRemovedProperties struct {
+	MessageID string                                             `json:"messageID,required"`
+	SessionID string                                             `json:"sessionID,required"`
+	JSON      eventListResponseEventMessageRemovedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventMessageRemovedPropertiesJSON contains the JSON metadata
+// for the struct [EventListResponseEventMessageRemovedProperties]
+type eventListResponseEventMessageRemovedPropertiesJSON struct {
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessageRemovedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessageRemovedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventMessageRemovedType string
+
+const (
+	EventListResponseEventMessageRemovedTypeMessageRemoved EventListResponseEventMessageRemovedType = "message.removed"
+)
+
+func (r EventListResponseEventMessageRemovedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventMessageRemovedTypeMessageRemoved:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventMessagePartUpdated struct {
+	Properties EventListResponseEventMessagePartUpdatedProperties `json:"properties,required"`
+	Type       EventListResponseEventMessagePartUpdatedType       `json:"type,required"`
+	JSON       eventListResponseEventMessagePartUpdatedJSON       `json:"-"`
+}
+
+// eventListResponseEventMessagePartUpdatedJSON contains the JSON metadata for the
+// struct [EventListResponseEventMessagePartUpdated]
+type eventListResponseEventMessagePartUpdatedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessagePartUpdated) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessagePartUpdatedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventMessagePartUpdated) implementsEventListResponse() {}
+
+type EventListResponseEventMessagePartUpdatedProperties struct {
+	Part Part                                                   `json:"part,required"`
+	JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventMessagePartUpdatedPropertiesJSON contains the JSON
+// metadata for the struct [EventListResponseEventMessagePartUpdatedProperties]
+type eventListResponseEventMessagePartUpdatedPropertiesJSON struct {
+	Part        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessagePartUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessagePartUpdatedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventMessagePartUpdatedType string
+
+const (
+	EventListResponseEventMessagePartUpdatedTypeMessagePartUpdated EventListResponseEventMessagePartUpdatedType = "message.part.updated"
+)
+
+func (r EventListResponseEventMessagePartUpdatedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventMessagePartUpdatedTypeMessagePartUpdated:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventMessagePartRemoved struct {
+	Properties EventListResponseEventMessagePartRemovedProperties `json:"properties,required"`
+	Type       EventListResponseEventMessagePartRemovedType       `json:"type,required"`
+	JSON       eventListResponseEventMessagePartRemovedJSON       `json:"-"`
+}
+
+// eventListResponseEventMessagePartRemovedJSON contains the JSON metadata for the
+// struct [EventListResponseEventMessagePartRemoved]
+type eventListResponseEventMessagePartRemovedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessagePartRemoved) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessagePartRemovedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventMessagePartRemoved) implementsEventListResponse() {}
+
+type EventListResponseEventMessagePartRemovedProperties struct {
+	MessageID string                                                 `json:"messageID,required"`
+	PartID    string                                                 `json:"partID,required"`
+	SessionID string                                                 `json:"sessionID,required"`
+	JSON      eventListResponseEventMessagePartRemovedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventMessagePartRemovedPropertiesJSON contains the JSON
+// metadata for the struct [EventListResponseEventMessagePartRemovedProperties]
+type eventListResponseEventMessagePartRemovedPropertiesJSON struct {
+	MessageID   apijson.Field
+	PartID      apijson.Field
+	SessionID   apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventMessagePartRemovedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventMessagePartRemovedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventMessagePartRemovedType string
+
+const (
+	EventListResponseEventMessagePartRemovedTypeMessagePartRemoved EventListResponseEventMessagePartRemovedType = "message.part.removed"
+)
+
+func (r EventListResponseEventMessagePartRemovedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventMessagePartRemovedTypeMessagePartRemoved:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventStorageWrite struct {
+	Properties EventListResponseEventStorageWriteProperties `json:"properties,required"`
+	Type       EventListResponseEventStorageWriteType       `json:"type,required"`
+	JSON       eventListResponseEventStorageWriteJSON       `json:"-"`
+}
+
+// eventListResponseEventStorageWriteJSON contains the JSON metadata for the struct
+// [EventListResponseEventStorageWrite]
+type eventListResponseEventStorageWriteJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventStorageWrite) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventStorageWriteJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventStorageWrite) implementsEventListResponse() {}
+
+type EventListResponseEventStorageWriteProperties struct {
+	Key     string                                           `json:"key,required"`
+	Content interface{}                                      `json:"content"`
+	JSON    eventListResponseEventStorageWritePropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventStorageWritePropertiesJSON contains the JSON metadata for
+// the struct [EventListResponseEventStorageWriteProperties]
+type eventListResponseEventStorageWritePropertiesJSON struct {
+	Key         apijson.Field
+	Content     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventStorageWriteProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventStorageWritePropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventStorageWriteType string
+
+const (
+	EventListResponseEventStorageWriteTypeStorageWrite EventListResponseEventStorageWriteType = "storage.write"
+)
+
+func (r EventListResponseEventStorageWriteType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventStorageWriteTypeStorageWrite:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventPermissionUpdated struct {
+	Properties EventListResponseEventPermissionUpdatedProperties `json:"properties,required"`
+	Type       EventListResponseEventPermissionUpdatedType       `json:"type,required"`
+	JSON       eventListResponseEventPermissionUpdatedJSON       `json:"-"`
+}
+
+// eventListResponseEventPermissionUpdatedJSON contains the JSON metadata for the
+// struct [EventListResponseEventPermissionUpdated]
+type eventListResponseEventPermissionUpdatedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventPermissionUpdated) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {}
+
+type EventListResponseEventPermissionUpdatedProperties struct {
+	ID        string                                                `json:"id,required"`
+	Metadata  map[string]interface{}                                `json:"metadata,required"`
+	SessionID string                                                `json:"sessionID,required"`
+	Time      EventListResponseEventPermissionUpdatedPropertiesTime `json:"time,required"`
+	Title     string                                                `json:"title,required"`
+	JSON      eventListResponseEventPermissionUpdatedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventPermissionUpdatedPropertiesJSON contains the JSON metadata
+// for the struct [EventListResponseEventPermissionUpdatedProperties]
+type eventListResponseEventPermissionUpdatedPropertiesJSON struct {
+	ID          apijson.Field
+	Metadata    apijson.Field
+	SessionID   apijson.Field
+	Time        apijson.Field
+	Title       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventPermissionUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventPermissionUpdatedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventPermissionUpdatedPropertiesTime struct {
+	Created float64                                                   `json:"created,required"`
+	JSON    eventListResponseEventPermissionUpdatedPropertiesTimeJSON `json:"-"`
+}
+
+// eventListResponseEventPermissionUpdatedPropertiesTimeJSON contains the JSON
+// metadata for the struct [EventListResponseEventPermissionUpdatedPropertiesTime]
+type eventListResponseEventPermissionUpdatedPropertiesTimeJSON struct {
+	Created     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventPermissionUpdatedPropertiesTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventPermissionUpdatedPropertiesTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventPermissionUpdatedType string
+
+const (
+	EventListResponseEventPermissionUpdatedTypePermissionUpdated EventListResponseEventPermissionUpdatedType = "permission.updated"
+)
+
+func (r EventListResponseEventPermissionUpdatedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventPermissionUpdatedTypePermissionUpdated:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventFileEdited struct {
+	Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
+	Type       EventListResponseEventFileEditedType       `json:"type,required"`
+	JSON       eventListResponseEventFileEditedJSON       `json:"-"`
+}
+
+// eventListResponseEventFileEditedJSON contains the JSON metadata for the struct
+// [EventListResponseEventFileEdited]
+type eventListResponseEventFileEditedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventFileEdited) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventFileEditedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventFileEdited) implementsEventListResponse() {}
+
+type EventListResponseEventFileEditedProperties struct {
+	File string                                         `json:"file,required"`
+	JSON eventListResponseEventFileEditedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventFileEditedPropertiesJSON contains the JSON metadata for
+// the struct [EventListResponseEventFileEditedProperties]
+type eventListResponseEventFileEditedPropertiesJSON struct {
+	File        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventFileEditedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventFileEditedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventFileEditedType string
+
+const (
+	EventListResponseEventFileEditedTypeFileEdited EventListResponseEventFileEditedType = "file.edited"
+)
+
+func (r EventListResponseEventFileEditedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventFileEditedTypeFileEdited:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventSessionUpdated struct {
+	Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
+	Type       EventListResponseEventSessionUpdatedType       `json:"type,required"`
+	JSON       eventListResponseEventSessionUpdatedJSON       `json:"-"`
+}
+
+// eventListResponseEventSessionUpdatedJSON contains the JSON metadata for the
+// struct [EventListResponseEventSessionUpdated]
+type eventListResponseEventSessionUpdatedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionUpdated) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionUpdatedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventSessionUpdated) implementsEventListResponse() {}
+
+type EventListResponseEventSessionUpdatedProperties struct {
+	Info Session                                            `json:"info,required"`
+	JSON eventListResponseEventSessionUpdatedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventSessionUpdatedPropertiesJSON contains the JSON metadata
+// for the struct [EventListResponseEventSessionUpdatedProperties]
+type eventListResponseEventSessionUpdatedPropertiesJSON struct {
+	Info        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionUpdatedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventSessionUpdatedType string
+
+const (
+	EventListResponseEventSessionUpdatedTypeSessionUpdated EventListResponseEventSessionUpdatedType = "session.updated"
+)
+
+func (r EventListResponseEventSessionUpdatedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventSessionUpdatedTypeSessionUpdated:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventSessionDeleted struct {
+	Properties EventListResponseEventSessionDeletedProperties `json:"properties,required"`
+	Type       EventListResponseEventSessionDeletedType       `json:"type,required"`
+	JSON       eventListResponseEventSessionDeletedJSON       `json:"-"`
+}
+
+// eventListResponseEventSessionDeletedJSON contains the JSON metadata for the
+// struct [EventListResponseEventSessionDeleted]
+type eventListResponseEventSessionDeletedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionDeleted) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionDeletedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventSessionDeleted) implementsEventListResponse() {}
+
+type EventListResponseEventSessionDeletedProperties struct {
+	Info Session                                            `json:"info,required"`
+	JSON eventListResponseEventSessionDeletedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventSessionDeletedPropertiesJSON contains the JSON metadata
+// for the struct [EventListResponseEventSessionDeletedProperties]
+type eventListResponseEventSessionDeletedPropertiesJSON struct {
+	Info        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionDeletedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionDeletedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventSessionDeletedType string
+
+const (
+	EventListResponseEventSessionDeletedTypeSessionDeleted EventListResponseEventSessionDeletedType = "session.deleted"
+)
+
+func (r EventListResponseEventSessionDeletedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventSessionDeletedTypeSessionDeleted:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventSessionIdle struct {
+	Properties EventListResponseEventSessionIdleProperties `json:"properties,required"`
+	Type       EventListResponseEventSessionIdleType       `json:"type,required"`
+	JSON       eventListResponseEventSessionIdleJSON       `json:"-"`
+}
+
+// eventListResponseEventSessionIdleJSON contains the JSON metadata for the struct
+// [EventListResponseEventSessionIdle]
+type eventListResponseEventSessionIdleJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionIdle) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionIdleJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventSessionIdle) implementsEventListResponse() {}
+
+type EventListResponseEventSessionIdleProperties struct {
+	SessionID string                                          `json:"sessionID,required"`
+	JSON      eventListResponseEventSessionIdlePropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventSessionIdlePropertiesJSON contains the JSON metadata for
+// the struct [EventListResponseEventSessionIdleProperties]
+type eventListResponseEventSessionIdlePropertiesJSON struct {
+	SessionID   apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionIdleProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionIdlePropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventSessionIdleType string
+
+const (
+	EventListResponseEventSessionIdleTypeSessionIdle EventListResponseEventSessionIdleType = "session.idle"
+)
+
+func (r EventListResponseEventSessionIdleType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventSessionIdleTypeSessionIdle:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventSessionError struct {
+	Properties EventListResponseEventSessionErrorProperties `json:"properties,required"`
+	Type       EventListResponseEventSessionErrorType       `json:"type,required"`
+	JSON       eventListResponseEventSessionErrorJSON       `json:"-"`
+}
+
+// eventListResponseEventSessionErrorJSON contains the JSON metadata for the struct
+// [EventListResponseEventSessionError]
+type eventListResponseEventSessionErrorJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventSessionError) implementsEventListResponse() {}
+
+type EventListResponseEventSessionErrorProperties struct {
+	Error     EventListResponseEventSessionErrorPropertiesError `json:"error"`
+	SessionID string                                            `json:"sessionID"`
+	JSON      eventListResponseEventSessionErrorPropertiesJSON  `json:"-"`
+}
+
+// eventListResponseEventSessionErrorPropertiesJSON contains the JSON metadata for
+// the struct [EventListResponseEventSessionErrorProperties]
+type eventListResponseEventSessionErrorPropertiesJSON struct {
+	Error       apijson.Field
+	SessionID   apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionErrorProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionErrorPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventSessionErrorPropertiesError struct {
+	// This field can have the runtime type of [shared.ProviderAuthErrorData],
+	// [shared.UnknownErrorData], [interface{}].
+	Data  interface{}                                           `json:"data,required"`
+	Name  EventListResponseEventSessionErrorPropertiesErrorName `json:"name,required"`
+	JSON  eventListResponseEventSessionErrorPropertiesErrorJSON `json:"-"`
+	union EventListResponseEventSessionErrorPropertiesErrorUnion
+}
+
+// eventListResponseEventSessionErrorPropertiesErrorJSON contains the JSON metadata
+// for the struct [EventListResponseEventSessionErrorPropertiesError]
+type eventListResponseEventSessionErrorPropertiesErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r eventListResponseEventSessionErrorPropertiesErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *EventListResponseEventSessionErrorPropertiesError) UnmarshalJSON(data []byte) (err error) {
+	*r = EventListResponseEventSessionErrorPropertiesError{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [EventListResponseEventSessionErrorPropertiesErrorUnion]
+// interface which you can cast to the specific types for more type safety.
+//
+// Possible runtime types of the union are [shared.ProviderAuthError],
+// [shared.UnknownError],
+// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError],
+// [shared.MessageAbortedError].
+func (r EventListResponseEventSessionErrorPropertiesError) AsUnion() EventListResponseEventSessionErrorPropertiesErrorUnion {
+	return r.union
+}
+
+// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError],
+// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError] or
+// [shared.MessageAbortedError].
+type EventListResponseEventSessionErrorPropertiesErrorUnion interface {
+	ImplementsEventListResponseEventSessionErrorPropertiesError()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*EventListResponseEventSessionErrorPropertiesErrorUnion)(nil)).Elem(),
+		"name",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.ProviderAuthError{}),
+			DiscriminatorValue: "ProviderAuthError",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.UnknownError{}),
+			DiscriminatorValue: "UnknownError",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError{}),
+			DiscriminatorValue: "MessageOutputLengthError",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.MessageAbortedError{}),
+			DiscriminatorValue: "MessageAbortedError",
+		},
+	)
+}
+
+type EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError struct {
+	Data interface{}                                                                   `json:"data,required"`
+	Name EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName `json:"name,required"`
+	JSON eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON `json:"-"`
+}
+
+// eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON
+// contains the JSON metadata for the struct
+// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError]
+type eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError) ImplementsEventListResponseEventSessionErrorPropertiesError() {
+}
+
+type EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName string
+
+const (
+	EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorNameMessageOutputLengthError EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName = "MessageOutputLengthError"
+)
+
+func (r EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName) IsKnown() bool {
+	switch r {
+	case EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorNameMessageOutputLengthError:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventSessionErrorPropertiesErrorName string
+
+const (
+	EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError        EventListResponseEventSessionErrorPropertiesErrorName = "ProviderAuthError"
+	EventListResponseEventSessionErrorPropertiesErrorNameUnknownError             EventListResponseEventSessionErrorPropertiesErrorName = "UnknownError"
+	EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError EventListResponseEventSessionErrorPropertiesErrorName = "MessageOutputLengthError"
+	EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError      EventListResponseEventSessionErrorPropertiesErrorName = "MessageAbortedError"
+)
+
+func (r EventListResponseEventSessionErrorPropertiesErrorName) IsKnown() bool {
+	switch r {
+	case EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError, EventListResponseEventSessionErrorPropertiesErrorNameUnknownError, EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError, EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventSessionErrorType string
+
+const (
+	EventListResponseEventSessionErrorTypeSessionError EventListResponseEventSessionErrorType = "session.error"
+)
+
+func (r EventListResponseEventSessionErrorType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventSessionErrorTypeSessionError:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventServerConnected struct {
+	Properties interface{}                               `json:"properties,required"`
+	Type       EventListResponseEventServerConnectedType `json:"type,required"`
+	JSON       eventListResponseEventServerConnectedJSON `json:"-"`
+}
+
+// eventListResponseEventServerConnectedJSON contains the JSON metadata for the
+// struct [EventListResponseEventServerConnected]
+type eventListResponseEventServerConnectedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventServerConnectedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventServerConnected) implementsEventListResponse() {}
+
+type EventListResponseEventServerConnectedType string
+
+const (
+	EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected"
+)
+
+func (r EventListResponseEventServerConnectedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventServerConnectedTypeServerConnected:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventFileWatcherUpdated struct {
+	Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
+	Type       EventListResponseEventFileWatcherUpdatedType       `json:"type,required"`
+	JSON       eventListResponseEventFileWatcherUpdatedJSON       `json:"-"`
+}
+
+// eventListResponseEventFileWatcherUpdatedJSON contains the JSON metadata for the
+// struct [EventListResponseEventFileWatcherUpdated]
+type eventListResponseEventFileWatcherUpdatedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventFileWatcherUpdated) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventFileWatcherUpdatedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventFileWatcherUpdated) implementsEventListResponse() {}
+
+type EventListResponseEventFileWatcherUpdatedProperties struct {
+	Event EventListResponseEventFileWatcherUpdatedPropertiesEvent `json:"event,required"`
+	File  string                                                  `json:"file,required"`
+	JSON  eventListResponseEventFileWatcherUpdatedPropertiesJSON  `json:"-"`
+}
+
+// eventListResponseEventFileWatcherUpdatedPropertiesJSON contains the JSON
+// metadata for the struct [EventListResponseEventFileWatcherUpdatedProperties]
+type eventListResponseEventFileWatcherUpdatedPropertiesJSON struct {
+	Event       apijson.Field
+	File        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventFileWatcherUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventFileWatcherUpdatedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventFileWatcherUpdatedPropertiesEvent string
+
+const (
+	EventListResponseEventFileWatcherUpdatedPropertiesEventRename EventListResponseEventFileWatcherUpdatedPropertiesEvent = "rename"
+	EventListResponseEventFileWatcherUpdatedPropertiesEventChange EventListResponseEventFileWatcherUpdatedPropertiesEvent = "change"
+)
+
+func (r EventListResponseEventFileWatcherUpdatedPropertiesEvent) IsKnown() bool {
+	switch r {
+	case EventListResponseEventFileWatcherUpdatedPropertiesEventRename, EventListResponseEventFileWatcherUpdatedPropertiesEventChange:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventFileWatcherUpdatedType string
+
+const (
+	EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated EventListResponseEventFileWatcherUpdatedType = "file.watcher.updated"
+)
+
+func (r EventListResponseEventFileWatcherUpdatedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated:
+		return true
+	}
+	return false
+}
+
+type EventListResponseEventIdeInstalled struct {
+	Properties EventListResponseEventIdeInstalledProperties `json:"properties,required"`
+	Type       EventListResponseEventIdeInstalledType       `json:"type,required"`
+	JSON       eventListResponseEventIdeInstalledJSON       `json:"-"`
+}
+
+// eventListResponseEventIdeInstalledJSON contains the JSON metadata for the struct
+// [EventListResponseEventIdeInstalled]
+type eventListResponseEventIdeInstalledJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventIdeInstalled) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventIdeInstalledJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventIdeInstalled) implementsEventListResponse() {}
+
+type EventListResponseEventIdeInstalledProperties struct {
+	Ide  string                                           `json:"ide,required"`
+	JSON eventListResponseEventIdeInstalledPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventIdeInstalledPropertiesJSON contains the JSON metadata for
+// the struct [EventListResponseEventIdeInstalledProperties]
+type eventListResponseEventIdeInstalledPropertiesJSON struct {
+	Ide         apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventIdeInstalledProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventIdeInstalledPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventIdeInstalledType string
+
+const (
+	EventListResponseEventIdeInstalledTypeIdeInstalled EventListResponseEventIdeInstalledType = "ide.installed"
+)
+
+func (r EventListResponseEventIdeInstalledType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventIdeInstalledTypeIdeInstalled:
+		return true
+	}
+	return false
+}
+
+type EventListResponseType string
+
+const (
+	EventListResponseTypeInstallationUpdated  EventListResponseType = "installation.updated"
+	EventListResponseTypeLspClientDiagnostics EventListResponseType = "lsp.client.diagnostics"
+	EventListResponseTypeMessageUpdated       EventListResponseType = "message.updated"
+	EventListResponseTypeMessageRemoved       EventListResponseType = "message.removed"
+	EventListResponseTypeMessagePartUpdated   EventListResponseType = "message.part.updated"
+	EventListResponseTypeMessagePartRemoved   EventListResponseType = "message.part.removed"
+	EventListResponseTypeStorageWrite         EventListResponseType = "storage.write"
+	EventListResponseTypePermissionUpdated    EventListResponseType = "permission.updated"
+	EventListResponseTypeFileEdited           EventListResponseType = "file.edited"
+	EventListResponseTypeSessionUpdated       EventListResponseType = "session.updated"
+	EventListResponseTypeSessionDeleted       EventListResponseType = "session.deleted"
+	EventListResponseTypeSessionIdle          EventListResponseType = "session.idle"
+	EventListResponseTypeSessionError         EventListResponseType = "session.error"
+	EventListResponseTypeServerConnected      EventListResponseType = "server.connected"
+	EventListResponseTypeFileWatcherUpdated   EventListResponseType = "file.watcher.updated"
+	EventListResponseTypeIdeInstalled         EventListResponseType = "ide.installed"
+)
+
+func (r EventListResponseType) IsKnown() bool {
+	switch r {
+	case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
+		return true
+	}
+	return false
+}

+ 1 - 1
packages/sdk/examples/.keep → packages/sdk/go/examples/.keep

@@ -1,4 +1,4 @@
 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.
+It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

+ 50 - 0
packages/sdk/go/field.go

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

+ 142 - 0
packages/sdk/go/file.go

@@ -0,0 +1,142 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"net/http"
+	"net/url"
+
+	"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
+}
+
+// Read a file
+func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...option.RequestOption) (res *FileReadResponse, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "file"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+	return
+}
+
+// Get file status
+func (r *FileService) Status(ctx context.Context, opts ...option.RequestOption) (res *[]File, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "file/status"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+	return
+}
+
+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 FileReadResponse struct {
+	Content string               `json:"content,required"`
+	Type    FileReadResponseType `json:"type,required"`
+	JSON    fileReadResponseJSON `json:"-"`
+}
+
+// fileReadResponseJSON contains the JSON metadata for the struct
+// [FileReadResponse]
+type fileReadResponseJSON struct {
+	Content     apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *FileReadResponse) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r fileReadResponseJSON) RawJSON() string {
+	return r.raw
+}
+
+type FileReadResponseType string
+
+const (
+	FileReadResponseTypeRaw   FileReadResponseType = "raw"
+	FileReadResponseTypePatch FileReadResponseType = "patch"
+)
+
+func (r FileReadResponseType) IsKnown() bool {
+	switch r {
+	case FileReadResponseTypeRaw, FileReadResponseTypePatch:
+		return true
+	}
+	return false
+}
+
+type FileReadParams struct {
+	Path param.Field[string] `query:"path,required"`
+}
+
+// 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,
+	})
+}

+ 60 - 0
packages/sdk/go/file_test.go

@@ -0,0 +1,60 @@
+// 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 TestFileRead(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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"),
+	})
+	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 TestFileStatus(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.File.Status(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}

+ 326 - 0
packages/sdk/go/find.go

@@ -0,0 +1,326 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"net/http"
+	"net/url"
+
+	"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 = append(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 = append(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 = append(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"`
+}
+
+// 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"`
+}
+
+// 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"`
+}
+
+// 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,
+	})
+}

+ 86 - 0
packages/sdk/go/find_test.go

@@ -0,0 +1,86 @@
+// 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 TestFindFiles(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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"),
+	})
+	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 TestFindSymbols(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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"),
+	})
+	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 TestFindText(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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"),
+	})
+	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())
+	}
+}

+ 13 - 0
packages/sdk/go/go.mod

@@ -0,0 +1,13 @@
+module github.com/sst/opencode-sdk-go
+
+go 1.21
+
+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
+)

+ 10 - 0
packages/sdk/go/go.sum

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

+ 53 - 0
packages/sdk/go/internal/apierror/apierror.go

@@ -0,0 +1,53 @@
+// 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
+}

+ 383 - 0
packages/sdk/go/internal/apiform/encoder.go

@@ -0,0 +1,383 @@
+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)
+	}
+}

+ 5 - 0
packages/sdk/go/internal/apiform/form.go

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

+ 440 - 0
packages/sdk/go/internal/apiform/form_test.go

@@ -0,0 +1,440 @@
+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))
+			}
+		})
+	}
+}

+ 48 - 0
packages/sdk/go/internal/apiform/tag.go

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

+ 670 - 0
packages/sdk/go/internal/apijson/decoder.go

@@ -0,0 +1,670 @@
+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(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
+}

+ 398 - 0
packages/sdk/go/internal/apijson/encoder.go

@@ -0,0 +1,398 @@
+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
+
+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, 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(sjsonReplacer.Replace(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, 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
+	}
+}
+
+// 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 sjsonReplacer *strings.Replacer = strings.NewReplacer(".", "\\.", ":", "\\:", "*", "\\*")

+ 41 - 0
packages/sdk/go/internal/apijson/field.go

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

+ 66 - 0
packages/sdk/go/internal/apijson/field_test.go

@@ -0,0 +1,66 @@
+package apijson
+
+import (
+	"testing"
+	"time"
+
+	"github.com/sst/opencode-sdk-go/internal/param"
+)
+
+type Struct struct {
+	A string `json:"a"`
+	B int64  `json:"b"`
+}
+
+type FieldStruct struct {
+	A param.Field[string]    `json:"a"`
+	B param.Field[int64]     `json:"b"`
+	C param.Field[Struct]    `json:"c"`
+	D param.Field[time.Time] `json:"d" format:"date"`
+	E param.Field[time.Time] `json:"e" format:"date-time"`
+	F param.Field[int64]     `json:"f"`
+}
+
+func TestFieldMarshal(t *testing.T) {
+	tests := map[string]struct {
+		value    interface{}
+		expected string
+	}{
+		"null_string": {param.Field[string]{Present: true, Null: true}, "null"},
+		"null_int":    {param.Field[int]{Present: true, Null: true}, "null"},
+		"null_int64":  {param.Field[int64]{Present: true, Null: true}, "null"},
+		"null_struct": {param.Field[Struct]{Present: true, Null: true}, "null"},
+
+		"string": {param.Field[string]{Present: true, Value: "string"}, `"string"`},
+		"int":    {param.Field[int]{Present: true, Value: 123}, "123"},
+		"int64":  {param.Field[int64]{Present: true, Value: int64(123456789123456789)}, "123456789123456789"},
+		"struct": {param.Field[Struct]{Present: true, Value: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`},
+
+		"string_raw": {param.Field[int]{Present: true, Raw: "string"}, `"string"`},
+		"int_raw":    {param.Field[int]{Present: true, Raw: 123}, "123"},
+		"int64_raw":  {param.Field[int]{Present: true, Raw: int64(123456789123456789)}, "123456789123456789"},
+		"struct_raw": {param.Field[int]{Present: true, Raw: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`},
+
+		"param_struct": {
+			FieldStruct{
+				A: param.Field[string]{Present: true, Value: "hello"},
+				B: param.Field[int64]{Present: true, Value: int64(12)},
+				D: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
+				E: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
+			},
+			`{"a":"hello","b":12,"d":"2023-03-18","e":"2023-03-18T14:47:38Z"}`,
+		},
+	}
+
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			b, err := Marshal(test.value)
+			if err != nil {
+				t.Fatalf("didn't expect error %v", err)
+			}
+			if string(b) != test.expected {
+				t.Fatalf("expected %s, received %s", test.expected, string(b))
+			}
+		})
+	}
+}

+ 617 - 0
packages/sdk/go/internal/apijson/json_test.go

@@ -0,0 +1,617 @@
+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":"bar"}`, map[string]string{":a.b.c*:d*-1e.f": "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))
+			}
+		})
+	}
+}

+ 120 - 0
packages/sdk/go/internal/apijson/port.go

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

+ 257 - 0
packages/sdk/go/internal/apijson/port_test.go

@@ -0,0 +1,257 @@
+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())
+			}
+		})
+	}
+}

+ 41 - 0
packages/sdk/go/internal/apijson/registry.go

@@ -0,0 +1,41 @@
+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)
+}

+ 47 - 0
packages/sdk/go/internal/apijson/tag.go

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

+ 341 - 0
packages/sdk/go/internal/apiquery/encoder.go

@@ -0,0 +1,341 @@
+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)
+	}
+
+}

+ 50 - 0
packages/sdk/go/internal/apiquery/query.go

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

+ 335 - 0
packages/sdk/go/internal/apiquery/query_test.go

@@ -0,0 +1,335 @@
+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)
+			}
+		})
+	}
+}

+ 41 - 0
packages/sdk/go/internal/apiquery/tag.go

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

+ 29 - 0
packages/sdk/go/internal/param/field.go

@@ -0,0 +1,29 @@
+package param
+
+import (
+	"fmt"
+)
+
+type FieldLike interface{ field() }
+
+// Field is a wrapper used for all values sent to the API,
+// to distinguish zero values from null or omitted fields.
+//
+// It also allows sending arbitrary deserializable values.
+//
+// To instantiate a Field, use the helpers exported from
+// the package root: `F()`, `Null()`, `Raw()`, etc.
+type Field[T any] struct {
+	FieldLike
+	Value   T
+	Null    bool
+	Present bool
+	Raw     any
+}
+
+func (f Field[T]) String() string {
+	if s, ok := any(f.Value).(fmt.Stringer); ok {
+		return s.String()
+	}
+	return fmt.Sprintf("%v", f.Value)
+}

+ 629 - 0
packages/sdk/go/internal/requestconfig/requestconfig.go

@@ -0,0 +1,629 @@
+// 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 {
+		content, err := json.Marshal(body)
+		if err != nil {
+			return nil, err
+		}
+		reader = bytes.NewBuffer(content)
+	}
+
+	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
+		}
+
+		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
+	})
+}

+ 27 - 0
packages/sdk/go/internal/testutil/testutil.go

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

+ 5 - 0
packages/sdk/go/internal/version.go

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

+ 1 - 1
packages/sdk/src/lib/.keep → packages/sdk/go/lib/.keep

@@ -1,4 +1,4 @@
 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.
+It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

+ 38 - 0
packages/sdk/go/option/middleware.go

@@ -0,0 +1,38 @@
+// 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
+	})
+}

+ 267 - 0
packages/sdk/go/option/requestoption.go

@@ -0,0 +1,267 @@
+// 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/")
+}

+ 181 - 0
packages/sdk/go/packages/ssestream/ssestream.go

@@ -0,0 +1,181 @@
+// 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()
+}

+ 6 - 3
packages/sdk/release-please-config.json → packages/sdk/go/release-please-config.json

@@ -59,6 +59,9 @@
       "hidden": true
     }
   ],
-  "release-type": "node",
-  "extra-files": ["src/version.ts", "README.md"]
-}
+  "release-type": "go",
+  "extra-files": [
+    "internal/version.go",
+    "README.md"
+  ]
+}

+ 2 - 4
packages/sdk/scripts/bootstrap → packages/sdk/go/scripts/bootstrap

@@ -11,8 +11,6 @@ if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ];
   }
 fi
 
-echo "==> Installing Node dependencies…"
+echo "==> Installing Go dependencies…"
 
-PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm")
-
-$PACKAGE_MANAGER install
+go mod tidy -e

+ 8 - 0
packages/sdk/go/scripts/format

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

+ 11 - 0
packages/sdk/go/scripts/lint

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

+ 0 - 0
packages/sdk/scripts/mock → packages/sdk/go/scripts/mock


+ 1 - 1
packages/sdk/scripts/test → packages/sdk/go/scripts/test

@@ -53,4 +53,4 @@ else
 fi
 
 echo "==> Running tests"
-./node_modules/.bin/jest "$@"
+go test ./... "$@"

+ 2117 - 0
packages/sdk/go/session.go

@@ -0,0 +1,2117 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"reflect"
+
+	"github.com/sst/opencode-sdk-go/internal/apijson"
+	"github.com/sst/opencode-sdk-go/internal/param"
+	"github.com/sst/opencode-sdk-go/internal/requestconfig"
+	"github.com/sst/opencode-sdk-go/option"
+	"github.com/sst/opencode-sdk-go/shared"
+	"github.com/tidwall/gjson"
+)
+
+// SessionService 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 [NewSessionService] method instead.
+type SessionService struct {
+	Options []option.RequestOption
+}
+
+// NewSessionService 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 NewSessionService(opts ...option.RequestOption) (r *SessionService) {
+	r = &SessionService{}
+	r.Options = opts
+	return
+}
+
+// Create a new session
+func (r *SessionService) New(ctx context.Context, opts ...option.RequestOption) (res *Session, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "session"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// List all sessions
+func (r *SessionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Session, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "session"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+	return
+}
+
+// Delete a session and all its data
+func (r *SessionService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...)
+	return
+}
+
+// Abort a session
+func (r *SessionService) Abort(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/abort", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Create and send a new message to a session
+func (r *SessionService) Chat(ctx context.Context, id string, body SessionChatParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/message", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+	return
+}
+
+// Analyze the app and create an AGENTS.md file
+func (r *SessionService) Init(ctx context.Context, id string, body SessionInitParams, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/init", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+	return
+}
+
+// List messages for a session
+func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/message", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+	return
+}
+
+// Revert a message
+func (r *SessionService) Revert(ctx context.Context, id string, body SessionRevertParams, opts ...option.RequestOption) (res *Session, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/revert", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+	return
+}
+
+// Share a session
+func (r *SessionService) Share(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/share", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Summarize the session
+func (r *SessionService) Summarize(ctx context.Context, id string, body SessionSummarizeParams, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/summarize", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+	return
+}
+
+// Restore all reverted messages
+func (r *SessionService) Unrevert(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/unrevert", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Unshare the session
+func (r *SessionService) Unshare(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
+	opts = append(r.Options[:], opts...)
+	if id == "" {
+		err = errors.New("missing required id parameter")
+		return
+	}
+	path := fmt.Sprintf("session/%s/share", id)
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...)
+	return
+}
+
+type AssistantMessage struct {
+	ID         string                 `json:"id,required"`
+	Cost       float64                `json:"cost,required"`
+	Mode       string                 `json:"mode,required"`
+	ModelID    string                 `json:"modelID,required"`
+	Path       AssistantMessagePath   `json:"path,required"`
+	ProviderID string                 `json:"providerID,required"`
+	Role       AssistantMessageRole   `json:"role,required"`
+	SessionID  string                 `json:"sessionID,required"`
+	System     []string               `json:"system,required"`
+	Time       AssistantMessageTime   `json:"time,required"`
+	Tokens     AssistantMessageTokens `json:"tokens,required"`
+	Error      AssistantMessageError  `json:"error"`
+	Summary    bool                   `json:"summary"`
+	JSON       assistantMessageJSON   `json:"-"`
+}
+
+// assistantMessageJSON contains the JSON metadata for the struct
+// [AssistantMessage]
+type assistantMessageJSON struct {
+	ID          apijson.Field
+	Cost        apijson.Field
+	Mode        apijson.Field
+	ModelID     apijson.Field
+	Path        apijson.Field
+	ProviderID  apijson.Field
+	Role        apijson.Field
+	SessionID   apijson.Field
+	System      apijson.Field
+	Time        apijson.Field
+	Tokens      apijson.Field
+	Error       apijson.Field
+	Summary     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AssistantMessage) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r assistantMessageJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r AssistantMessage) implementsMessage() {}
+
+type AssistantMessagePath struct {
+	Cwd  string                   `json:"cwd,required"`
+	Root string                   `json:"root,required"`
+	JSON assistantMessagePathJSON `json:"-"`
+}
+
+// assistantMessagePathJSON contains the JSON metadata for the struct
+// [AssistantMessagePath]
+type assistantMessagePathJSON struct {
+	Cwd         apijson.Field
+	Root        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AssistantMessagePath) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r assistantMessagePathJSON) RawJSON() string {
+	return r.raw
+}
+
+type AssistantMessageRole string
+
+const (
+	AssistantMessageRoleAssistant AssistantMessageRole = "assistant"
+)
+
+func (r AssistantMessageRole) IsKnown() bool {
+	switch r {
+	case AssistantMessageRoleAssistant:
+		return true
+	}
+	return false
+}
+
+type AssistantMessageTime struct {
+	Created   float64                  `json:"created,required"`
+	Completed float64                  `json:"completed"`
+	JSON      assistantMessageTimeJSON `json:"-"`
+}
+
+// assistantMessageTimeJSON contains the JSON metadata for the struct
+// [AssistantMessageTime]
+type assistantMessageTimeJSON struct {
+	Created     apijson.Field
+	Completed   apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AssistantMessageTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r assistantMessageTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type AssistantMessageTokens struct {
+	Cache     AssistantMessageTokensCache `json:"cache,required"`
+	Input     float64                     `json:"input,required"`
+	Output    float64                     `json:"output,required"`
+	Reasoning float64                     `json:"reasoning,required"`
+	JSON      assistantMessageTokensJSON  `json:"-"`
+}
+
+// assistantMessageTokensJSON contains the JSON metadata for the struct
+// [AssistantMessageTokens]
+type assistantMessageTokensJSON struct {
+	Cache       apijson.Field
+	Input       apijson.Field
+	Output      apijson.Field
+	Reasoning   apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AssistantMessageTokens) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r assistantMessageTokensJSON) RawJSON() string {
+	return r.raw
+}
+
+type AssistantMessageTokensCache struct {
+	Read  float64                         `json:"read,required"`
+	Write float64                         `json:"write,required"`
+	JSON  assistantMessageTokensCacheJSON `json:"-"`
+}
+
+// assistantMessageTokensCacheJSON contains the JSON metadata for the struct
+// [AssistantMessageTokensCache]
+type assistantMessageTokensCacheJSON struct {
+	Read        apijson.Field
+	Write       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AssistantMessageTokensCache) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r assistantMessageTokensCacheJSON) RawJSON() string {
+	return r.raw
+}
+
+type AssistantMessageError struct {
+	// This field can have the runtime type of [shared.ProviderAuthErrorData],
+	// [shared.UnknownErrorData], [interface{}].
+	Data  interface{}               `json:"data,required"`
+	Name  AssistantMessageErrorName `json:"name,required"`
+	JSON  assistantMessageErrorJSON `json:"-"`
+	union AssistantMessageErrorUnion
+}
+
+// assistantMessageErrorJSON contains the JSON metadata for the struct
+// [AssistantMessageError]
+type assistantMessageErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r assistantMessageErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *AssistantMessageError) UnmarshalJSON(data []byte) (err error) {
+	*r = AssistantMessageError{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [AssistantMessageErrorUnion] interface which you can cast to
+// the specific types for more type safety.
+//
+// Possible runtime types of the union are [shared.ProviderAuthError],
+// [shared.UnknownError], [AssistantMessageErrorMessageOutputLengthError],
+// [shared.MessageAbortedError].
+func (r AssistantMessageError) AsUnion() AssistantMessageErrorUnion {
+	return r.union
+}
+
+// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError],
+// [AssistantMessageErrorMessageOutputLengthError] or [shared.MessageAbortedError].
+type AssistantMessageErrorUnion interface {
+	ImplementsAssistantMessageError()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*AssistantMessageErrorUnion)(nil)).Elem(),
+		"name",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.ProviderAuthError{}),
+			DiscriminatorValue: "ProviderAuthError",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.UnknownError{}),
+			DiscriminatorValue: "UnknownError",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(AssistantMessageErrorMessageOutputLengthError{}),
+			DiscriminatorValue: "MessageOutputLengthError",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.MessageAbortedError{}),
+			DiscriminatorValue: "MessageAbortedError",
+		},
+	)
+}
+
+type AssistantMessageErrorMessageOutputLengthError struct {
+	Data interface{}                                       `json:"data,required"`
+	Name AssistantMessageErrorMessageOutputLengthErrorName `json:"name,required"`
+	JSON assistantMessageErrorMessageOutputLengthErrorJSON `json:"-"`
+}
+
+// assistantMessageErrorMessageOutputLengthErrorJSON contains the JSON metadata for
+// the struct [AssistantMessageErrorMessageOutputLengthError]
+type assistantMessageErrorMessageOutputLengthErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *AssistantMessageErrorMessageOutputLengthError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r assistantMessageErrorMessageOutputLengthErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r AssistantMessageErrorMessageOutputLengthError) ImplementsAssistantMessageError() {}
+
+type AssistantMessageErrorMessageOutputLengthErrorName string
+
+const (
+	AssistantMessageErrorMessageOutputLengthErrorNameMessageOutputLengthError AssistantMessageErrorMessageOutputLengthErrorName = "MessageOutputLengthError"
+)
+
+func (r AssistantMessageErrorMessageOutputLengthErrorName) IsKnown() bool {
+	switch r {
+	case AssistantMessageErrorMessageOutputLengthErrorNameMessageOutputLengthError:
+		return true
+	}
+	return false
+}
+
+type AssistantMessageErrorName string
+
+const (
+	AssistantMessageErrorNameProviderAuthError        AssistantMessageErrorName = "ProviderAuthError"
+	AssistantMessageErrorNameUnknownError             AssistantMessageErrorName = "UnknownError"
+	AssistantMessageErrorNameMessageOutputLengthError AssistantMessageErrorName = "MessageOutputLengthError"
+	AssistantMessageErrorNameMessageAbortedError      AssistantMessageErrorName = "MessageAbortedError"
+)
+
+func (r AssistantMessageErrorName) IsKnown() bool {
+	switch r {
+	case AssistantMessageErrorNameProviderAuthError, AssistantMessageErrorNameUnknownError, AssistantMessageErrorNameMessageOutputLengthError, AssistantMessageErrorNameMessageAbortedError:
+		return true
+	}
+	return false
+}
+
+type FilePart struct {
+	ID        string         `json:"id,required"`
+	MessageID string         `json:"messageID,required"`
+	Mime      string         `json:"mime,required"`
+	SessionID string         `json:"sessionID,required"`
+	Type      FilePartType   `json:"type,required"`
+	URL       string         `json:"url,required"`
+	Filename  string         `json:"filename"`
+	Source    FilePartSource `json:"source"`
+	JSON      filePartJSON   `json:"-"`
+}
+
+// filePartJSON contains the JSON metadata for the struct [FilePart]
+type filePartJSON struct {
+	ID          apijson.Field
+	MessageID   apijson.Field
+	Mime        apijson.Field
+	SessionID   apijson.Field
+	Type        apijson.Field
+	URL         apijson.Field
+	Filename    apijson.Field
+	Source      apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *FilePart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r filePartJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r FilePart) implementsPart() {}
+
+type FilePartType string
+
+const (
+	FilePartTypeFile FilePartType = "file"
+)
+
+func (r FilePartType) IsKnown() bool {
+	switch r {
+	case FilePartTypeFile:
+		return true
+	}
+	return false
+}
+
+type FilePartInputParam struct {
+	Mime     param.Field[string]                   `json:"mime,required"`
+	Type     param.Field[FilePartInputType]        `json:"type,required"`
+	URL      param.Field[string]                   `json:"url,required"`
+	ID       param.Field[string]                   `json:"id"`
+	Filename param.Field[string]                   `json:"filename"`
+	Source   param.Field[FilePartSourceUnionParam] `json:"source"`
+}
+
+func (r FilePartInputParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+func (r FilePartInputParam) implementsSessionChatParamsPartUnion() {}
+
+type FilePartInputType string
+
+const (
+	FilePartInputTypeFile FilePartInputType = "file"
+)
+
+func (r FilePartInputType) IsKnown() bool {
+	switch r {
+	case FilePartInputTypeFile:
+		return true
+	}
+	return false
+}
+
+type FilePartSource struct {
+	Path string             `json:"path,required"`
+	Text FilePartSourceText `json:"text,required"`
+	Type FilePartSourceType `json:"type,required"`
+	Kind int64              `json:"kind"`
+	Name string             `json:"name"`
+	// This field can have the runtime type of [SymbolSourceRange].
+	Range interface{}        `json:"range"`
+	JSON  filePartSourceJSON `json:"-"`
+	union FilePartSourceUnion
+}
+
+// filePartSourceJSON contains the JSON metadata for the struct [FilePartSource]
+type filePartSourceJSON struct {
+	Path        apijson.Field
+	Text        apijson.Field
+	Type        apijson.Field
+	Kind        apijson.Field
+	Name        apijson.Field
+	Range       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r filePartSourceJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *FilePartSource) UnmarshalJSON(data []byte) (err error) {
+	*r = FilePartSource{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [FilePartSourceUnion] interface which you can cast to the
+// specific types for more type safety.
+//
+// Possible runtime types of the union are [FileSource], [SymbolSource].
+func (r FilePartSource) AsUnion() FilePartSourceUnion {
+	return r.union
+}
+
+// Union satisfied by [FileSource] or [SymbolSource].
+type FilePartSourceUnion interface {
+	implementsFilePartSource()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*FilePartSourceUnion)(nil)).Elem(),
+		"type",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(FileSource{}),
+			DiscriminatorValue: "file",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(SymbolSource{}),
+			DiscriminatorValue: "symbol",
+		},
+	)
+}
+
+type FilePartSourceType string
+
+const (
+	FilePartSourceTypeFile   FilePartSourceType = "file"
+	FilePartSourceTypeSymbol FilePartSourceType = "symbol"
+)
+
+func (r FilePartSourceType) IsKnown() bool {
+	switch r {
+	case FilePartSourceTypeFile, FilePartSourceTypeSymbol:
+		return true
+	}
+	return false
+}
+
+type FilePartSourceParam struct {
+	Path  param.Field[string]                  `json:"path,required"`
+	Text  param.Field[FilePartSourceTextParam] `json:"text,required"`
+	Type  param.Field[FilePartSourceType]      `json:"type,required"`
+	Kind  param.Field[int64]                   `json:"kind"`
+	Name  param.Field[string]                  `json:"name"`
+	Range param.Field[interface{}]             `json:"range"`
+}
+
+func (r FilePartSourceParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+func (r FilePartSourceParam) implementsFilePartSourceUnionParam() {}
+
+// Satisfied by [FileSourceParam], [SymbolSourceParam], [FilePartSourceParam].
+type FilePartSourceUnionParam interface {
+	implementsFilePartSourceUnionParam()
+}
+
+type FilePartSourceText struct {
+	End   int64                  `json:"end,required"`
+	Start int64                  `json:"start,required"`
+	Value string                 `json:"value,required"`
+	JSON  filePartSourceTextJSON `json:"-"`
+}
+
+// filePartSourceTextJSON contains the JSON metadata for the struct
+// [FilePartSourceText]
+type filePartSourceTextJSON struct {
+	End         apijson.Field
+	Start       apijson.Field
+	Value       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *FilePartSourceText) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r filePartSourceTextJSON) RawJSON() string {
+	return r.raw
+}
+
+type FilePartSourceTextParam struct {
+	End   param.Field[int64]  `json:"end,required"`
+	Start param.Field[int64]  `json:"start,required"`
+	Value param.Field[string] `json:"value,required"`
+}
+
+func (r FilePartSourceTextParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type FileSource struct {
+	Path string             `json:"path,required"`
+	Text FilePartSourceText `json:"text,required"`
+	Type FileSourceType     `json:"type,required"`
+	JSON fileSourceJSON     `json:"-"`
+}
+
+// fileSourceJSON contains the JSON metadata for the struct [FileSource]
+type fileSourceJSON struct {
+	Path        apijson.Field
+	Text        apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *FileSource) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r fileSourceJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r FileSource) implementsFilePartSource() {}
+
+type FileSourceType string
+
+const (
+	FileSourceTypeFile FileSourceType = "file"
+)
+
+func (r FileSourceType) IsKnown() bool {
+	switch r {
+	case FileSourceTypeFile:
+		return true
+	}
+	return false
+}
+
+type FileSourceParam struct {
+	Path param.Field[string]                  `json:"path,required"`
+	Text param.Field[FilePartSourceTextParam] `json:"text,required"`
+	Type param.Field[FileSourceType]          `json:"type,required"`
+}
+
+func (r FileSourceParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+func (r FileSourceParam) implementsFilePartSourceUnionParam() {}
+
+type Message struct {
+	ID        string      `json:"id,required"`
+	Role      MessageRole `json:"role,required"`
+	SessionID string      `json:"sessionID,required"`
+	// This field can have the runtime type of [UserMessageTime],
+	// [AssistantMessageTime].
+	Time interface{} `json:"time,required"`
+	Cost float64     `json:"cost"`
+	// This field can have the runtime type of [AssistantMessageError].
+	Error   interface{} `json:"error"`
+	Mode    string      `json:"mode"`
+	ModelID string      `json:"modelID"`
+	// This field can have the runtime type of [AssistantMessagePath].
+	Path       interface{} `json:"path"`
+	ProviderID string      `json:"providerID"`
+	Summary    bool        `json:"summary"`
+	// This field can have the runtime type of [[]string].
+	System interface{} `json:"system"`
+	// This field can have the runtime type of [AssistantMessageTokens].
+	Tokens interface{} `json:"tokens"`
+	JSON   messageJSON `json:"-"`
+	union  MessageUnion
+}
+
+// messageJSON contains the JSON metadata for the struct [Message]
+type messageJSON struct {
+	ID          apijson.Field
+	Role        apijson.Field
+	SessionID   apijson.Field
+	Time        apijson.Field
+	Cost        apijson.Field
+	Error       apijson.Field
+	Mode        apijson.Field
+	ModelID     apijson.Field
+	Path        apijson.Field
+	ProviderID  apijson.Field
+	Summary     apijson.Field
+	System      apijson.Field
+	Tokens      apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r messageJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *Message) UnmarshalJSON(data []byte) (err error) {
+	*r = Message{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [MessageUnion] interface which you can cast to the specific
+// types for more type safety.
+//
+// Possible runtime types of the union are [UserMessage], [AssistantMessage].
+func (r Message) AsUnion() MessageUnion {
+	return r.union
+}
+
+// Union satisfied by [UserMessage] or [AssistantMessage].
+type MessageUnion interface {
+	implementsMessage()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*MessageUnion)(nil)).Elem(),
+		"role",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(UserMessage{}),
+			DiscriminatorValue: "user",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(AssistantMessage{}),
+			DiscriminatorValue: "assistant",
+		},
+	)
+}
+
+type MessageRole string
+
+const (
+	MessageRoleUser      MessageRole = "user"
+	MessageRoleAssistant MessageRole = "assistant"
+)
+
+func (r MessageRole) IsKnown() bool {
+	switch r {
+	case MessageRoleUser, MessageRoleAssistant:
+		return true
+	}
+	return false
+}
+
+type Part struct {
+	ID        string   `json:"id,required"`
+	MessageID string   `json:"messageID,required"`
+	SessionID string   `json:"sessionID,required"`
+	Type      PartType `json:"type,required"`
+	CallID    string   `json:"callID"`
+	Cost      float64  `json:"cost"`
+	Filename  string   `json:"filename"`
+	// This field can have the runtime type of [[]string].
+	Files    interface{}    `json:"files"`
+	Hash     string         `json:"hash"`
+	Mime     string         `json:"mime"`
+	Snapshot string         `json:"snapshot"`
+	Source   FilePartSource `json:"source"`
+	// This field can have the runtime type of [ToolPartState].
+	State     interface{} `json:"state"`
+	Synthetic bool        `json:"synthetic"`
+	Text      string      `json:"text"`
+	// This field can have the runtime type of [TextPartTime].
+	Time interface{} `json:"time"`
+	// This field can have the runtime type of [StepFinishPartTokens].
+	Tokens interface{} `json:"tokens"`
+	Tool   string      `json:"tool"`
+	URL    string      `json:"url"`
+	JSON   partJSON    `json:"-"`
+	union  PartUnion
+}
+
+// partJSON contains the JSON metadata for the struct [Part]
+type partJSON struct {
+	ID          apijson.Field
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	Type        apijson.Field
+	CallID      apijson.Field
+	Cost        apijson.Field
+	Filename    apijson.Field
+	Files       apijson.Field
+	Hash        apijson.Field
+	Mime        apijson.Field
+	Snapshot    apijson.Field
+	Source      apijson.Field
+	State       apijson.Field
+	Synthetic   apijson.Field
+	Text        apijson.Field
+	Time        apijson.Field
+	Tokens      apijson.Field
+	Tool        apijson.Field
+	URL         apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r partJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *Part) UnmarshalJSON(data []byte) (err error) {
+	*r = Part{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [PartUnion] interface which you can cast to the specific types
+// for more type safety.
+//
+// Possible runtime types of the union are [TextPart], [FilePart], [ToolPart],
+// [StepStartPart], [StepFinishPart], [SnapshotPart], [PartPatchPart].
+func (r Part) AsUnion() PartUnion {
+	return r.union
+}
+
+// Union satisfied by [TextPart], [FilePart], [ToolPart], [StepStartPart],
+// [StepFinishPart], [SnapshotPart] or [PartPatchPart].
+type PartUnion interface {
+	implementsPart()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*PartUnion)(nil)).Elem(),
+		"type",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(TextPart{}),
+			DiscriminatorValue: "text",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(FilePart{}),
+			DiscriminatorValue: "file",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(ToolPart{}),
+			DiscriminatorValue: "tool",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(StepStartPart{}),
+			DiscriminatorValue: "step-start",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(StepFinishPart{}),
+			DiscriminatorValue: "step-finish",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(SnapshotPart{}),
+			DiscriminatorValue: "snapshot",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(PartPatchPart{}),
+			DiscriminatorValue: "patch",
+		},
+	)
+}
+
+type PartPatchPart struct {
+	ID        string            `json:"id,required"`
+	Files     []string          `json:"files,required"`
+	Hash      string            `json:"hash,required"`
+	MessageID string            `json:"messageID,required"`
+	SessionID string            `json:"sessionID,required"`
+	Type      PartPatchPartType `json:"type,required"`
+	JSON      partPatchPartJSON `json:"-"`
+}
+
+// partPatchPartJSON contains the JSON metadata for the struct [PartPatchPart]
+type partPatchPartJSON struct {
+	ID          apijson.Field
+	Files       apijson.Field
+	Hash        apijson.Field
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *PartPatchPart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r partPatchPartJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r PartPatchPart) implementsPart() {}
+
+type PartPatchPartType string
+
+const (
+	PartPatchPartTypePatch PartPatchPartType = "patch"
+)
+
+func (r PartPatchPartType) IsKnown() bool {
+	switch r {
+	case PartPatchPartTypePatch:
+		return true
+	}
+	return false
+}
+
+type PartType string
+
+const (
+	PartTypeText       PartType = "text"
+	PartTypeFile       PartType = "file"
+	PartTypeTool       PartType = "tool"
+	PartTypeStepStart  PartType = "step-start"
+	PartTypeStepFinish PartType = "step-finish"
+	PartTypeSnapshot   PartType = "snapshot"
+	PartTypePatch      PartType = "patch"
+)
+
+func (r PartType) IsKnown() bool {
+	switch r {
+	case PartTypeText, PartTypeFile, PartTypeTool, PartTypeStepStart, PartTypeStepFinish, PartTypeSnapshot, PartTypePatch:
+		return true
+	}
+	return false
+}
+
+type Session struct {
+	ID       string        `json:"id,required"`
+	Time     SessionTime   `json:"time,required"`
+	Title    string        `json:"title,required"`
+	Version  string        `json:"version,required"`
+	ParentID string        `json:"parentID"`
+	Revert   SessionRevert `json:"revert"`
+	Share    SessionShare  `json:"share"`
+	JSON     sessionJSON   `json:"-"`
+}
+
+// sessionJSON contains the JSON metadata for the struct [Session]
+type sessionJSON struct {
+	ID          apijson.Field
+	Time        apijson.Field
+	Title       apijson.Field
+	Version     apijson.Field
+	ParentID    apijson.Field
+	Revert      apijson.Field
+	Share       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *Session) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionJSON) RawJSON() string {
+	return r.raw
+}
+
+type SessionTime struct {
+	Created float64         `json:"created,required"`
+	Updated float64         `json:"updated,required"`
+	JSON    sessionTimeJSON `json:"-"`
+}
+
+// sessionTimeJSON contains the JSON metadata for the struct [SessionTime]
+type sessionTimeJSON struct {
+	Created     apijson.Field
+	Updated     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SessionTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type SessionRevert struct {
+	MessageID string            `json:"messageID,required"`
+	Diff      string            `json:"diff"`
+	PartID    string            `json:"partID"`
+	Snapshot  string            `json:"snapshot"`
+	JSON      sessionRevertJSON `json:"-"`
+}
+
+// sessionRevertJSON contains the JSON metadata for the struct [SessionRevert]
+type sessionRevertJSON struct {
+	MessageID   apijson.Field
+	Diff        apijson.Field
+	PartID      apijson.Field
+	Snapshot    apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SessionRevert) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionRevertJSON) RawJSON() string {
+	return r.raw
+}
+
+type SessionShare struct {
+	URL  string           `json:"url,required"`
+	JSON sessionShareJSON `json:"-"`
+}
+
+// sessionShareJSON contains the JSON metadata for the struct [SessionShare]
+type sessionShareJSON struct {
+	URL         apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SessionShare) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionShareJSON) RawJSON() string {
+	return r.raw
+}
+
+type SnapshotPart struct {
+	ID        string           `json:"id,required"`
+	MessageID string           `json:"messageID,required"`
+	SessionID string           `json:"sessionID,required"`
+	Snapshot  string           `json:"snapshot,required"`
+	Type      SnapshotPartType `json:"type,required"`
+	JSON      snapshotPartJSON `json:"-"`
+}
+
+// snapshotPartJSON contains the JSON metadata for the struct [SnapshotPart]
+type snapshotPartJSON struct {
+	ID          apijson.Field
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	Snapshot    apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SnapshotPart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r snapshotPartJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r SnapshotPart) implementsPart() {}
+
+type SnapshotPartType string
+
+const (
+	SnapshotPartTypeSnapshot SnapshotPartType = "snapshot"
+)
+
+func (r SnapshotPartType) IsKnown() bool {
+	switch r {
+	case SnapshotPartTypeSnapshot:
+		return true
+	}
+	return false
+}
+
+type StepFinishPart struct {
+	ID        string               `json:"id,required"`
+	Cost      float64              `json:"cost,required"`
+	MessageID string               `json:"messageID,required"`
+	SessionID string               `json:"sessionID,required"`
+	Tokens    StepFinishPartTokens `json:"tokens,required"`
+	Type      StepFinishPartType   `json:"type,required"`
+	JSON      stepFinishPartJSON   `json:"-"`
+}
+
+// stepFinishPartJSON contains the JSON metadata for the struct [StepFinishPart]
+type stepFinishPartJSON struct {
+	ID          apijson.Field
+	Cost        apijson.Field
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	Tokens      apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r StepFinishPart) implementsPart() {}
+
+type StepFinishPartTokens struct {
+	Cache     StepFinishPartTokensCache `json:"cache,required"`
+	Input     float64                   `json:"input,required"`
+	Output    float64                   `json:"output,required"`
+	Reasoning float64                   `json:"reasoning,required"`
+	JSON      stepFinishPartTokensJSON  `json:"-"`
+}
+
+// stepFinishPartTokensJSON contains the JSON metadata for the struct
+// [StepFinishPartTokens]
+type stepFinishPartTokensJSON struct {
+	Cache       apijson.Field
+	Input       apijson.Field
+	Output      apijson.Field
+	Reasoning   apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPartTokens) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartTokensJSON) RawJSON() string {
+	return r.raw
+}
+
+type StepFinishPartTokensCache struct {
+	Read  float64                       `json:"read,required"`
+	Write float64                       `json:"write,required"`
+	JSON  stepFinishPartTokensCacheJSON `json:"-"`
+}
+
+// stepFinishPartTokensCacheJSON contains the JSON metadata for the struct
+// [StepFinishPartTokensCache]
+type stepFinishPartTokensCacheJSON struct {
+	Read        apijson.Field
+	Write       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPartTokensCache) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartTokensCacheJSON) RawJSON() string {
+	return r.raw
+}
+
+type StepFinishPartType string
+
+const (
+	StepFinishPartTypeStepFinish StepFinishPartType = "step-finish"
+)
+
+func (r StepFinishPartType) IsKnown() bool {
+	switch r {
+	case StepFinishPartTypeStepFinish:
+		return true
+	}
+	return false
+}
+
+type StepStartPart struct {
+	ID        string            `json:"id,required"`
+	MessageID string            `json:"messageID,required"`
+	SessionID string            `json:"sessionID,required"`
+	Type      StepStartPartType `json:"type,required"`
+	JSON      stepStartPartJSON `json:"-"`
+}
+
+// stepStartPartJSON contains the JSON metadata for the struct [StepStartPart]
+type stepStartPartJSON struct {
+	ID          apijson.Field
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *StepStartPart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepStartPartJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r StepStartPart) implementsPart() {}
+
+type StepStartPartType string
+
+const (
+	StepStartPartTypeStepStart StepStartPartType = "step-start"
+)
+
+func (r StepStartPartType) IsKnown() bool {
+	switch r {
+	case StepStartPartTypeStepStart:
+		return true
+	}
+	return false
+}
+
+type SymbolSource struct {
+	Kind  int64              `json:"kind,required"`
+	Name  string             `json:"name,required"`
+	Path  string             `json:"path,required"`
+	Range SymbolSourceRange  `json:"range,required"`
+	Text  FilePartSourceText `json:"text,required"`
+	Type  SymbolSourceType   `json:"type,required"`
+	JSON  symbolSourceJSON   `json:"-"`
+}
+
+// symbolSourceJSON contains the JSON metadata for the struct [SymbolSource]
+type symbolSourceJSON struct {
+	Kind        apijson.Field
+	Name        apijson.Field
+	Path        apijson.Field
+	Range       apijson.Field
+	Text        apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SymbolSource) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r symbolSourceJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r SymbolSource) implementsFilePartSource() {}
+
+type SymbolSourceRange struct {
+	End   SymbolSourceRangeEnd   `json:"end,required"`
+	Start SymbolSourceRangeStart `json:"start,required"`
+	JSON  symbolSourceRangeJSON  `json:"-"`
+}
+
+// symbolSourceRangeJSON contains the JSON metadata for the struct
+// [SymbolSourceRange]
+type symbolSourceRangeJSON struct {
+	End         apijson.Field
+	Start       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SymbolSourceRange) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r symbolSourceRangeJSON) RawJSON() string {
+	return r.raw
+}
+
+type SymbolSourceRangeEnd struct {
+	Character float64                  `json:"character,required"`
+	Line      float64                  `json:"line,required"`
+	JSON      symbolSourceRangeEndJSON `json:"-"`
+}
+
+// symbolSourceRangeEndJSON contains the JSON metadata for the struct
+// [SymbolSourceRangeEnd]
+type symbolSourceRangeEndJSON struct {
+	Character   apijson.Field
+	Line        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SymbolSourceRangeEnd) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r symbolSourceRangeEndJSON) RawJSON() string {
+	return r.raw
+}
+
+type SymbolSourceRangeStart struct {
+	Character float64                    `json:"character,required"`
+	Line      float64                    `json:"line,required"`
+	JSON      symbolSourceRangeStartJSON `json:"-"`
+}
+
+// symbolSourceRangeStartJSON contains the JSON metadata for the struct
+// [SymbolSourceRangeStart]
+type symbolSourceRangeStartJSON struct {
+	Character   apijson.Field
+	Line        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SymbolSourceRangeStart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r symbolSourceRangeStartJSON) RawJSON() string {
+	return r.raw
+}
+
+type SymbolSourceType string
+
+const (
+	SymbolSourceTypeSymbol SymbolSourceType = "symbol"
+)
+
+func (r SymbolSourceType) IsKnown() bool {
+	switch r {
+	case SymbolSourceTypeSymbol:
+		return true
+	}
+	return false
+}
+
+type SymbolSourceParam struct {
+	Kind  param.Field[int64]                   `json:"kind,required"`
+	Name  param.Field[string]                  `json:"name,required"`
+	Path  param.Field[string]                  `json:"path,required"`
+	Range param.Field[SymbolSourceRangeParam]  `json:"range,required"`
+	Text  param.Field[FilePartSourceTextParam] `json:"text,required"`
+	Type  param.Field[SymbolSourceType]        `json:"type,required"`
+}
+
+func (r SymbolSourceParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+func (r SymbolSourceParam) implementsFilePartSourceUnionParam() {}
+
+type SymbolSourceRangeParam struct {
+	End   param.Field[SymbolSourceRangeEndParam]   `json:"end,required"`
+	Start param.Field[SymbolSourceRangeStartParam] `json:"start,required"`
+}
+
+func (r SymbolSourceRangeParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type SymbolSourceRangeEndParam struct {
+	Character param.Field[float64] `json:"character,required"`
+	Line      param.Field[float64] `json:"line,required"`
+}
+
+func (r SymbolSourceRangeEndParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type SymbolSourceRangeStartParam struct {
+	Character param.Field[float64] `json:"character,required"`
+	Line      param.Field[float64] `json:"line,required"`
+}
+
+func (r SymbolSourceRangeStartParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type TextPart struct {
+	ID        string       `json:"id,required"`
+	MessageID string       `json:"messageID,required"`
+	SessionID string       `json:"sessionID,required"`
+	Text      string       `json:"text,required"`
+	Type      TextPartType `json:"type,required"`
+	Synthetic bool         `json:"synthetic"`
+	Time      TextPartTime `json:"time"`
+	JSON      textPartJSON `json:"-"`
+}
+
+// textPartJSON contains the JSON metadata for the struct [TextPart]
+type textPartJSON struct {
+	ID          apijson.Field
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	Text        apijson.Field
+	Type        apijson.Field
+	Synthetic   apijson.Field
+	Time        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *TextPart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r textPartJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r TextPart) implementsPart() {}
+
+type TextPartType string
+
+const (
+	TextPartTypeText TextPartType = "text"
+)
+
+func (r TextPartType) IsKnown() bool {
+	switch r {
+	case TextPartTypeText:
+		return true
+	}
+	return false
+}
+
+type TextPartTime struct {
+	Start float64          `json:"start,required"`
+	End   float64          `json:"end"`
+	JSON  textPartTimeJSON `json:"-"`
+}
+
+// textPartTimeJSON contains the JSON metadata for the struct [TextPartTime]
+type textPartTimeJSON struct {
+	Start       apijson.Field
+	End         apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *TextPartTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r textPartTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type TextPartInputParam struct {
+	Text      param.Field[string]                 `json:"text,required"`
+	Type      param.Field[TextPartInputType]      `json:"type,required"`
+	ID        param.Field[string]                 `json:"id"`
+	Synthetic param.Field[bool]                   `json:"synthetic"`
+	Time      param.Field[TextPartInputTimeParam] `json:"time"`
+}
+
+func (r TextPartInputParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+func (r TextPartInputParam) implementsSessionChatParamsPartUnion() {}
+
+type TextPartInputType string
+
+const (
+	TextPartInputTypeText TextPartInputType = "text"
+)
+
+func (r TextPartInputType) IsKnown() bool {
+	switch r {
+	case TextPartInputTypeText:
+		return true
+	}
+	return false
+}
+
+type TextPartInputTimeParam struct {
+	Start param.Field[float64] `json:"start,required"`
+	End   param.Field[float64] `json:"end"`
+}
+
+func (r TextPartInputTimeParam) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type ToolPart struct {
+	ID        string        `json:"id,required"`
+	CallID    string        `json:"callID,required"`
+	MessageID string        `json:"messageID,required"`
+	SessionID string        `json:"sessionID,required"`
+	State     ToolPartState `json:"state,required"`
+	Tool      string        `json:"tool,required"`
+	Type      ToolPartType  `json:"type,required"`
+	JSON      toolPartJSON  `json:"-"`
+}
+
+// toolPartJSON contains the JSON metadata for the struct [ToolPart]
+type toolPartJSON struct {
+	ID          apijson.Field
+	CallID      apijson.Field
+	MessageID   apijson.Field
+	SessionID   apijson.Field
+	State       apijson.Field
+	Tool        apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolPart) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolPartJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r ToolPart) implementsPart() {}
+
+type ToolPartState struct {
+	Status ToolPartStateStatus `json:"status,required"`
+	Error  string              `json:"error"`
+	// This field can have the runtime type of [interface{}], [map[string]interface{}].
+	Input interface{} `json:"input"`
+	// This field can have the runtime type of [map[string]interface{}].
+	Metadata interface{} `json:"metadata"`
+	Output   string      `json:"output"`
+	// This field can have the runtime type of [ToolStateRunningTime],
+	// [ToolStateCompletedTime], [ToolStateErrorTime].
+	Time  interface{}       `json:"time"`
+	Title string            `json:"title"`
+	JSON  toolPartStateJSON `json:"-"`
+	union ToolPartStateUnion
+}
+
+// toolPartStateJSON contains the JSON metadata for the struct [ToolPartState]
+type toolPartStateJSON struct {
+	Status      apijson.Field
+	Error       apijson.Field
+	Input       apijson.Field
+	Metadata    apijson.Field
+	Output      apijson.Field
+	Time        apijson.Field
+	Title       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r toolPartStateJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r *ToolPartState) UnmarshalJSON(data []byte) (err error) {
+	*r = ToolPartState{}
+	err = apijson.UnmarshalRoot(data, &r.union)
+	if err != nil {
+		return err
+	}
+	return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [ToolPartStateUnion] interface which you can cast to the
+// specific types for more type safety.
+//
+// Possible runtime types of the union are [ToolStatePending], [ToolStateRunning],
+// [ToolStateCompleted], [ToolStateError].
+func (r ToolPartState) AsUnion() ToolPartStateUnion {
+	return r.union
+}
+
+// Union satisfied by [ToolStatePending], [ToolStateRunning], [ToolStateCompleted]
+// or [ToolStateError].
+type ToolPartStateUnion interface {
+	implementsToolPartState()
+}
+
+func init() {
+	apijson.RegisterUnion(
+		reflect.TypeOf((*ToolPartStateUnion)(nil)).Elem(),
+		"status",
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(ToolStatePending{}),
+			DiscriminatorValue: "pending",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(ToolStateRunning{}),
+			DiscriminatorValue: "running",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(ToolStateCompleted{}),
+			DiscriminatorValue: "completed",
+		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(ToolStateError{}),
+			DiscriminatorValue: "error",
+		},
+	)
+}
+
+type ToolPartStateStatus string
+
+const (
+	ToolPartStateStatusPending   ToolPartStateStatus = "pending"
+	ToolPartStateStatusRunning   ToolPartStateStatus = "running"
+	ToolPartStateStatusCompleted ToolPartStateStatus = "completed"
+	ToolPartStateStatusError     ToolPartStateStatus = "error"
+)
+
+func (r ToolPartStateStatus) IsKnown() bool {
+	switch r {
+	case ToolPartStateStatusPending, ToolPartStateStatusRunning, ToolPartStateStatusCompleted, ToolPartStateStatusError:
+		return true
+	}
+	return false
+}
+
+type ToolPartType string
+
+const (
+	ToolPartTypeTool ToolPartType = "tool"
+)
+
+func (r ToolPartType) IsKnown() bool {
+	switch r {
+	case ToolPartTypeTool:
+		return true
+	}
+	return false
+}
+
+type ToolStateCompleted struct {
+	Input    map[string]interface{}   `json:"input,required"`
+	Metadata map[string]interface{}   `json:"metadata,required"`
+	Output   string                   `json:"output,required"`
+	Status   ToolStateCompletedStatus `json:"status,required"`
+	Time     ToolStateCompletedTime   `json:"time,required"`
+	Title    string                   `json:"title,required"`
+	JSON     toolStateCompletedJSON   `json:"-"`
+}
+
+// toolStateCompletedJSON contains the JSON metadata for the struct
+// [ToolStateCompleted]
+type toolStateCompletedJSON struct {
+	Input       apijson.Field
+	Metadata    apijson.Field
+	Output      apijson.Field
+	Status      apijson.Field
+	Time        apijson.Field
+	Title       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolStateCompleted) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolStateCompletedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r ToolStateCompleted) implementsToolPartState() {}
+
+type ToolStateCompletedStatus string
+
+const (
+	ToolStateCompletedStatusCompleted ToolStateCompletedStatus = "completed"
+)
+
+func (r ToolStateCompletedStatus) IsKnown() bool {
+	switch r {
+	case ToolStateCompletedStatusCompleted:
+		return true
+	}
+	return false
+}
+
+type ToolStateCompletedTime struct {
+	End   float64                    `json:"end,required"`
+	Start float64                    `json:"start,required"`
+	JSON  toolStateCompletedTimeJSON `json:"-"`
+}
+
+// toolStateCompletedTimeJSON contains the JSON metadata for the struct
+// [ToolStateCompletedTime]
+type toolStateCompletedTimeJSON struct {
+	End         apijson.Field
+	Start       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolStateCompletedTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolStateCompletedTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type ToolStateError struct {
+	Error  string                 `json:"error,required"`
+	Input  map[string]interface{} `json:"input,required"`
+	Status ToolStateErrorStatus   `json:"status,required"`
+	Time   ToolStateErrorTime     `json:"time,required"`
+	JSON   toolStateErrorJSON     `json:"-"`
+}
+
+// toolStateErrorJSON contains the JSON metadata for the struct [ToolStateError]
+type toolStateErrorJSON struct {
+	Error       apijson.Field
+	Input       apijson.Field
+	Status      apijson.Field
+	Time        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolStateError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolStateErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r ToolStateError) implementsToolPartState() {}
+
+type ToolStateErrorStatus string
+
+const (
+	ToolStateErrorStatusError ToolStateErrorStatus = "error"
+)
+
+func (r ToolStateErrorStatus) IsKnown() bool {
+	switch r {
+	case ToolStateErrorStatusError:
+		return true
+	}
+	return false
+}
+
+type ToolStateErrorTime struct {
+	End   float64                `json:"end,required"`
+	Start float64                `json:"start,required"`
+	JSON  toolStateErrorTimeJSON `json:"-"`
+}
+
+// toolStateErrorTimeJSON contains the JSON metadata for the struct
+// [ToolStateErrorTime]
+type toolStateErrorTimeJSON struct {
+	End         apijson.Field
+	Start       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolStateErrorTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolStateErrorTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type ToolStatePending struct {
+	Status ToolStatePendingStatus `json:"status,required"`
+	JSON   toolStatePendingJSON   `json:"-"`
+}
+
+// toolStatePendingJSON contains the JSON metadata for the struct
+// [ToolStatePending]
+type toolStatePendingJSON struct {
+	Status      apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolStatePending) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolStatePendingJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r ToolStatePending) implementsToolPartState() {}
+
+type ToolStatePendingStatus string
+
+const (
+	ToolStatePendingStatusPending ToolStatePendingStatus = "pending"
+)
+
+func (r ToolStatePendingStatus) IsKnown() bool {
+	switch r {
+	case ToolStatePendingStatusPending:
+		return true
+	}
+	return false
+}
+
+type ToolStateRunning struct {
+	Status   ToolStateRunningStatus `json:"status,required"`
+	Time     ToolStateRunningTime   `json:"time,required"`
+	Input    interface{}            `json:"input"`
+	Metadata map[string]interface{} `json:"metadata"`
+	Title    string                 `json:"title"`
+	JSON     toolStateRunningJSON   `json:"-"`
+}
+
+// toolStateRunningJSON contains the JSON metadata for the struct
+// [ToolStateRunning]
+type toolStateRunningJSON struct {
+	Status      apijson.Field
+	Time        apijson.Field
+	Input       apijson.Field
+	Metadata    apijson.Field
+	Title       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolStateRunning) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolStateRunningJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r ToolStateRunning) implementsToolPartState() {}
+
+type ToolStateRunningStatus string
+
+const (
+	ToolStateRunningStatusRunning ToolStateRunningStatus = "running"
+)
+
+func (r ToolStateRunningStatus) IsKnown() bool {
+	switch r {
+	case ToolStateRunningStatusRunning:
+		return true
+	}
+	return false
+}
+
+type ToolStateRunningTime struct {
+	Start float64                  `json:"start,required"`
+	JSON  toolStateRunningTimeJSON `json:"-"`
+}
+
+// toolStateRunningTimeJSON contains the JSON metadata for the struct
+// [ToolStateRunningTime]
+type toolStateRunningTimeJSON struct {
+	Start       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ToolStateRunningTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r toolStateRunningTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type UserMessage struct {
+	ID        string          `json:"id,required"`
+	Role      UserMessageRole `json:"role,required"`
+	SessionID string          `json:"sessionID,required"`
+	Time      UserMessageTime `json:"time,required"`
+	JSON      userMessageJSON `json:"-"`
+}
+
+// userMessageJSON contains the JSON metadata for the struct [UserMessage]
+type userMessageJSON struct {
+	ID          apijson.Field
+	Role        apijson.Field
+	SessionID   apijson.Field
+	Time        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *UserMessage) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r userMessageJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r UserMessage) implementsMessage() {}
+
+type UserMessageRole string
+
+const (
+	UserMessageRoleUser UserMessageRole = "user"
+)
+
+func (r UserMessageRole) IsKnown() bool {
+	switch r {
+	case UserMessageRoleUser:
+		return true
+	}
+	return false
+}
+
+type UserMessageTime struct {
+	Created float64             `json:"created,required"`
+	JSON    userMessageTimeJSON `json:"-"`
+}
+
+// userMessageTimeJSON contains the JSON metadata for the struct [UserMessageTime]
+type userMessageTimeJSON struct {
+	Created     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *UserMessageTime) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r userMessageTimeJSON) RawJSON() string {
+	return r.raw
+}
+
+type SessionMessagesResponse struct {
+	Info  Message                     `json:"info,required"`
+	Parts []Part                      `json:"parts,required"`
+	JSON  sessionMessagesResponseJSON `json:"-"`
+}
+
+// sessionMessagesResponseJSON contains the JSON metadata for the struct
+// [SessionMessagesResponse]
+type sessionMessagesResponseJSON struct {
+	Info        apijson.Field
+	Parts       apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *SessionMessagesResponse) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionMessagesResponseJSON) RawJSON() string {
+	return r.raw
+}
+
+type SessionChatParams struct {
+	ModelID    param.Field[string]                       `json:"modelID,required"`
+	Parts      param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
+	ProviderID param.Field[string]                       `json:"providerID,required"`
+	MessageID  param.Field[string]                       `json:"messageID"`
+	Mode       param.Field[string]                       `json:"mode"`
+	System     param.Field[string]                       `json:"system"`
+	Tools      param.Field[map[string]bool]              `json:"tools"`
+}
+
+func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type SessionChatParamsPart struct {
+	Type      param.Field[SessionChatParamsPartsType] `json:"type,required"`
+	ID        param.Field[string]                     `json:"id"`
+	Filename  param.Field[string]                     `json:"filename"`
+	Mime      param.Field[string]                     `json:"mime"`
+	Source    param.Field[FilePartSourceUnionParam]   `json:"source"`
+	Synthetic param.Field[bool]                       `json:"synthetic"`
+	Text      param.Field[string]                     `json:"text"`
+	Time      param.Field[interface{}]                `json:"time"`
+	URL       param.Field[string]                     `json:"url"`
+}
+
+func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {}
+
+// Satisfied by [TextPartInputParam], [FilePartInputParam],
+// [SessionChatParamsPart].
+type SessionChatParamsPartUnion interface {
+	implementsSessionChatParamsPartUnion()
+}
+
+type SessionChatParamsPartsType string
+
+const (
+	SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
+	SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
+)
+
+func (r SessionChatParamsPartsType) IsKnown() bool {
+	switch r {
+	case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile:
+		return true
+	}
+	return false
+}
+
+type SessionInitParams struct {
+	MessageID  param.Field[string] `json:"messageID,required"`
+	ModelID    param.Field[string] `json:"modelID,required"`
+	ProviderID param.Field[string] `json:"providerID,required"`
+}
+
+func (r SessionInitParams) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type SessionRevertParams struct {
+	MessageID param.Field[string] `json:"messageID,required"`
+	PartID    param.Field[string] `json:"partID"`
+}
+
+func (r SessionRevertParams) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}
+
+type SessionSummarizeParams struct {
+	ModelID    param.Field[string] `json:"modelID,required"`
+	ProviderID param.Field[string] `json:"providerID,required"`
+}
+
+func (r SessionSummarizeParams) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}

+ 323 - 0
packages/sdk/go/session_test.go

@@ -0,0 +1,323 @@
+// 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 TestSessionNew(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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())
+	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 TestSessionList(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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())
+	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 TestSessionDelete(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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(), "id")
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestSessionAbort(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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")
+	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 TestSessionChatWithOptionalParams(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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.Chat(
+		context.TODO(),
+		"id",
+		opencode.SessionChatParams{
+			ModelID: opencode.F("modelID"),
+			Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartInputParam{
+				Text:      opencode.F("text"),
+				Type:      opencode.F(opencode.TextPartInputTypeText),
+				ID:        opencode.F("id"),
+				Synthetic: opencode.F(true),
+				Time: opencode.F(opencode.TextPartInputTimeParam{
+					Start: opencode.F(0.000000),
+					End:   opencode.F(0.000000),
+				}),
+			}}),
+			ProviderID: opencode.F("providerID"),
+			MessageID:  opencode.F("msg"),
+			Mode:       opencode.F("mode"),
+			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 TestSessionInit(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Session.Init(
+		context.TODO(),
+		"id",
+		opencode.SessionInitParams{
+			MessageID:  opencode.F("messageID"),
+			ModelID:    opencode.F("modelID"),
+			ProviderID: opencode.F("providerID"),
+		},
+	)
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestSessionMessages(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Session.Messages(context.TODO(), "id")
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestSessionRevertWithOptionalParams(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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("msg"),
+			PartID:    opencode.F("prt"),
+		},
+	)
+	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 TestSessionShare(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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")
+	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 TestSessionSummarize(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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"),
+		},
+	)
+	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 TestSessionUnrevert(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Session.Unrevert(context.TODO(), "id")
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestSessionUnshare(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Session.Unshare(context.TODO(), "id")
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}

+ 173 - 0
packages/sdk/go/shared/shared.go

@@ -0,0 +1,173 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package shared
+
+import (
+	"github.com/sst/opencode-sdk-go/internal/apijson"
+)
+
+type MessageAbortedError struct {
+	Data interface{}             `json:"data,required"`
+	Name MessageAbortedErrorName `json:"name,required"`
+	JSON messageAbortedErrorJSON `json:"-"`
+}
+
+// messageAbortedErrorJSON contains the JSON metadata for the struct
+// [MessageAbortedError]
+type messageAbortedErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *MessageAbortedError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r messageAbortedErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r MessageAbortedError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
+
+func (r MessageAbortedError) ImplementsAssistantMessageError() {}
+
+type MessageAbortedErrorName string
+
+const (
+	MessageAbortedErrorNameMessageAbortedError MessageAbortedErrorName = "MessageAbortedError"
+)
+
+func (r MessageAbortedErrorName) IsKnown() bool {
+	switch r {
+	case MessageAbortedErrorNameMessageAbortedError:
+		return true
+	}
+	return false
+}
+
+type ProviderAuthError struct {
+	Data ProviderAuthErrorData `json:"data,required"`
+	Name ProviderAuthErrorName `json:"name,required"`
+	JSON providerAuthErrorJSON `json:"-"`
+}
+
+// providerAuthErrorJSON contains the JSON metadata for the struct
+// [ProviderAuthError]
+type providerAuthErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ProviderAuthError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r providerAuthErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r ProviderAuthError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
+
+func (r ProviderAuthError) ImplementsAssistantMessageError() {}
+
+type ProviderAuthErrorData struct {
+	Message    string                    `json:"message,required"`
+	ProviderID string                    `json:"providerID,required"`
+	JSON       providerAuthErrorDataJSON `json:"-"`
+}
+
+// providerAuthErrorDataJSON contains the JSON metadata for the struct
+// [ProviderAuthErrorData]
+type providerAuthErrorDataJSON struct {
+	Message     apijson.Field
+	ProviderID  apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *ProviderAuthErrorData) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r providerAuthErrorDataJSON) RawJSON() string {
+	return r.raw
+}
+
+type ProviderAuthErrorName string
+
+const (
+	ProviderAuthErrorNameProviderAuthError ProviderAuthErrorName = "ProviderAuthError"
+)
+
+func (r ProviderAuthErrorName) IsKnown() bool {
+	switch r {
+	case ProviderAuthErrorNameProviderAuthError:
+		return true
+	}
+	return false
+}
+
+type UnknownError struct {
+	Data UnknownErrorData `json:"data,required"`
+	Name UnknownErrorName `json:"name,required"`
+	JSON unknownErrorJSON `json:"-"`
+}
+
+// unknownErrorJSON contains the JSON metadata for the struct [UnknownError]
+type unknownErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *UnknownError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r unknownErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r UnknownError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
+
+func (r UnknownError) ImplementsAssistantMessageError() {}
+
+type UnknownErrorData struct {
+	Message string               `json:"message,required"`
+	JSON    unknownErrorDataJSON `json:"-"`
+}
+
+// unknownErrorDataJSON contains the JSON metadata for the struct
+// [UnknownErrorData]
+type unknownErrorDataJSON struct {
+	Message     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *UnknownErrorData) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r unknownErrorDataJSON) RawJSON() string {
+	return r.raw
+}
+
+type UnknownErrorName string
+
+const (
+	UnknownErrorNameUnknownError UnknownErrorName = "UnknownError"
+)
+
+func (r UnknownErrorName) IsKnown() bool {
+	switch r {
+	case UnknownErrorNameUnknownError:
+		return true
+	}
+	return false
+}

+ 56 - 0
packages/sdk/go/tui.go

@@ -0,0 +1,56 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/sst/opencode-sdk-go/internal/apijson"
+	"github.com/sst/opencode-sdk-go/internal/param"
+	"github.com/sst/opencode-sdk-go/internal/requestconfig"
+	"github.com/sst/opencode-sdk-go/option"
+)
+
+// 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, body TuiAppendPromptParams, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/append-prompt"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+	return
+}
+
+// Open the help dialog
+func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/open-help"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+type TuiAppendPromptParams struct {
+	Text param.Field[string] `json:"text,required"`
+}
+
+func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}

+ 60 - 0
packages/sdk/go/tui_test.go

@@ -0,0 +1,60 @@
+// 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 TestTuiAppendPrompt(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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"),
+	})
+	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 TestTuiOpenHelp(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	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())
+	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())
+	}
+}

+ 32 - 0
packages/sdk/go/usage_test.go

@@ -0,0 +1,32 @@
+// 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())
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	t.Logf("%+v\n", sessions)
+}

+ 0 - 23
packages/sdk/jest.config.ts

@@ -1,23 +0,0 @@
-import type { JestConfigWithTsJest } from 'ts-jest';
-
-const config: JestConfigWithTsJest = {
-  preset: 'ts-jest/presets/default-esm',
-  testEnvironment: 'node',
-  transform: {
-    '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }],
-  },
-  moduleNameMapper: {
-    '^@opencode-ai/sdk$': '<rootDir>/src/index.ts',
-    '^@opencode-ai/sdk/(.*)$': '<rootDir>/src/$1',
-  },
-  modulePathIgnorePatterns: [
-    '<rootDir>/ecosystem-tests/',
-    '<rootDir>/dist/',
-    '<rootDir>/deno/',
-    '<rootDir>/deno_tests/',
-    '<rootDir>/packages/',
-  ],
-  testPathIgnorePatterns: ['scripts'],
-};
-
-export default config;

+ 17 - 0
packages/sdk/js/package.json

@@ -0,0 +1,17 @@
+{
+  "$schema": "https://json.schemastore.org/package.json",
+  "name": "@opencode-ai/sdk",
+  "version": "0.0.0",
+  "type": "module",
+  "exports": {
+    ".": "./dist/index.js"
+  },
+  "files": [
+    "dist"
+  ],
+  "devDependencies": {
+    "typescript": "catalog:",
+    "@hey-api/openapi-ts": "0.80.1",
+    "@tsconfig/node22": "catalog:"
+  }
+}

+ 41 - 0
packages/sdk/js/script/generate.ts

@@ -0,0 +1,41 @@
+#!/usr/bin/env bun
+
+const dir = new URL("..", import.meta.url).pathname
+process.chdir(dir)
+
+import { $ } from "bun"
+import fs from "fs/promises"
+import path from "path"
+
+console.log("=== Generating JS SDK ===")
+console.log()
+
+import { createClient } from "@hey-api/openapi-ts"
+
+await fs.rm(path.join(dir, "src/gen"), { recursive: true, force: true })
+await $`bun run ../../opencode/src/index.ts generate > openapi.json`
+
+await createClient({
+  input: "./openapi.json",
+  output: "./src/gen",
+  plugins: [
+    {
+      name: "@hey-api/typescript",
+      exportFromIndex: false,
+    },
+    {
+      name: "@hey-api/sdk",
+      instance: "OpencodeClient",
+      exportFromIndex: false,
+      auth: false,
+    },
+    {
+      name: "@hey-api/client-fetch",
+      exportFromIndex: false,
+      baseUrl: "http://localhost:4096",
+    },
+  ],
+})
+
+await $`rm -rf dist`
+await $`bun tsc`

+ 24 - 0
packages/sdk/js/script/publish.ts

@@ -0,0 +1,24 @@
+#!/usr/bin/env bun
+
+const dir = new URL("..", import.meta.url).pathname
+process.chdir(dir)
+
+import { $ } from "bun"
+
+const version = process.env["OPENCODE_VERSION"]
+if (!version) {
+  throw new Error("OPENCODE_VERSION is required")
+}
+
+await import("./generate")
+
+const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
+
+await $`bun pm version --allow-same-version --no-git-tag-version ${version}`
+if (snapshot) {
+  await $`bun publish --tag snapshot`
+}
+if (!snapshot) {
+  await $`bun publish`
+}
+await $`bun pm version 0.0.0 --no-git-tag-version`

+ 18 - 0
packages/sdk/js/src/gen/client.gen.ts

@@ -0,0 +1,18 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import type { ClientOptions } from './types.gen';
+import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client';
+
+/**
+ * The `createClientConfig()` function will be called on client initialization
+ * and the returned object will become the client's initial configuration.
+ *
+ * You may want to initialize your client this way instead of calling
+ * `setConfig()`. This is useful for example if you're using Next.js
+ * to ensure your client always has the correct values.
+ */
+export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>;
+
+export const client = createClient(createConfig<ClientOptions>({
+    baseUrl: 'http://localhost:4096'
+}));

+ 195 - 0
packages/sdk/js/src/gen/client/client.ts

@@ -0,0 +1,195 @@
+import type { Client, Config, RequestOptions } from './types';
+import {
+  buildUrl,
+  createConfig,
+  createInterceptors,
+  getParseAs,
+  mergeConfigs,
+  mergeHeaders,
+  setAuthParams,
+} from './utils';
+
+type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
+  body?: any;
+  headers: ReturnType<typeof mergeHeaders>;
+};
+
+export const createClient = (config: Config = {}): Client => {
+  let _config = mergeConfigs(createConfig(), config);
+
+  const getConfig = (): Config => ({ ..._config });
+
+  const setConfig = (config: Config): Config => {
+    _config = mergeConfigs(_config, config);
+    return getConfig();
+  };
+
+  const interceptors = createInterceptors<
+    Request,
+    Response,
+    unknown,
+    RequestOptions
+  >();
+
+  const request: Client['request'] = async (options) => {
+    const opts = {
+      ..._config,
+      ...options,
+      fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
+      headers: mergeHeaders(_config.headers, options.headers),
+    };
+
+    if (opts.security) {
+      await setAuthParams({
+        ...opts,
+        security: opts.security,
+      });
+    }
+
+    if (opts.requestValidator) {
+      await opts.requestValidator(opts);
+    }
+
+    if (opts.body && opts.bodySerializer) {
+      opts.body = opts.bodySerializer(opts.body);
+    }
+
+    // remove Content-Type header if body is empty to avoid sending invalid requests
+    if (opts.body === undefined || opts.body === '') {
+      opts.headers.delete('Content-Type');
+    }
+
+    const url = buildUrl(opts);
+    const requestInit: ReqInit = {
+      redirect: 'follow',
+      ...opts,
+    };
+
+    let request = new Request(url, requestInit);
+
+    for (const fn of interceptors.request._fns) {
+      if (fn) {
+        request = await fn(request, opts);
+      }
+    }
+
+    // fetch must be assigned here, otherwise it would throw the error:
+    // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
+    const _fetch = opts.fetch!;
+    let response = await _fetch(request);
+
+    for (const fn of interceptors.response._fns) {
+      if (fn) {
+        response = await fn(response, request, opts);
+      }
+    }
+
+    const result = {
+      request,
+      response,
+    };
+
+    if (response.ok) {
+      if (
+        response.status === 204 ||
+        response.headers.get('Content-Length') === '0'
+      ) {
+        return opts.responseStyle === 'data'
+          ? {}
+          : {
+              data: {},
+              ...result,
+            };
+      }
+
+      const parseAs =
+        (opts.parseAs === 'auto'
+          ? getParseAs(response.headers.get('Content-Type'))
+          : opts.parseAs) ?? 'json';
+
+      let data: any;
+      switch (parseAs) {
+        case 'arrayBuffer':
+        case 'blob':
+        case 'formData':
+        case 'json':
+        case 'text':
+          data = await response[parseAs]();
+          break;
+        case 'stream':
+          return opts.responseStyle === 'data'
+            ? response.body
+            : {
+                data: response.body,
+                ...result,
+              };
+      }
+
+      if (parseAs === 'json') {
+        if (opts.responseValidator) {
+          await opts.responseValidator(data);
+        }
+
+        if (opts.responseTransformer) {
+          data = await opts.responseTransformer(data);
+        }
+      }
+
+      return opts.responseStyle === 'data'
+        ? data
+        : {
+            data,
+            ...result,
+          };
+    }
+
+    const textError = await response.text();
+    let jsonError: unknown;
+
+    try {
+      jsonError = JSON.parse(textError);
+    } catch {
+      // noop
+    }
+
+    const error = jsonError ?? textError;
+    let finalError = error;
+
+    for (const fn of interceptors.error._fns) {
+      if (fn) {
+        finalError = (await fn(error, response, request, opts)) as string;
+      }
+    }
+
+    finalError = finalError || ({} as string);
+
+    if (opts.throwOnError) {
+      throw finalError;
+    }
+
+    // TODO: we probably want to return error and improve types
+    return opts.responseStyle === 'data'
+      ? undefined
+      : {
+          error: finalError,
+          ...result,
+        };
+  };
+
+  return {
+    buildUrl,
+    connect: (options) => request({ ...options, method: 'CONNECT' }),
+    delete: (options) => request({ ...options, method: 'DELETE' }),
+    get: (options) => request({ ...options, method: 'GET' }),
+    getConfig,
+    head: (options) => request({ ...options, method: 'HEAD' }),
+    interceptors,
+    options: (options) => request({ ...options, method: 'OPTIONS' }),
+    patch: (options) => request({ ...options, method: 'PATCH' }),
+    post: (options) => request({ ...options, method: 'POST' }),
+    put: (options) => request({ ...options, method: 'PUT' }),
+    request,
+    setConfig,
+    trace: (options) => request({ ...options, method: 'TRACE' }),
+  };
+};

+ 22 - 0
packages/sdk/js/src/gen/client/index.ts

@@ -0,0 +1,22 @@
+export type { Auth } from '../core/auth';
+export type { QuerySerializerOptions } from '../core/bodySerializer';
+export {
+  formDataBodySerializer,
+  jsonBodySerializer,
+  urlSearchParamsBodySerializer,
+} from '../core/bodySerializer';
+export { buildClientParams } from '../core/params';
+export { createClient } from './client';
+export type {
+  Client,
+  ClientOptions,
+  Config,
+  CreateClientConfig,
+  Options,
+  OptionsLegacyParser,
+  RequestOptions,
+  RequestResult,
+  ResponseStyle,
+  TDataShape,
+} from './types';
+export { createConfig, mergeHeaders } from './utils';

+ 222 - 0
packages/sdk/js/src/gen/client/types.ts

@@ -0,0 +1,222 @@
+import type { Auth } from '../core/auth';
+import type {
+  Client as CoreClient,
+  Config as CoreConfig,
+} from '../core/types';
+import type { Middleware } from './utils';
+
+export type ResponseStyle = 'data' | 'fields';
+
+export interface Config<T extends ClientOptions = ClientOptions>
+  extends Omit<RequestInit, 'body' | 'headers' | 'method'>,
+    CoreConfig {
+  /**
+   * Base URL for all requests made by this client.
+   */
+  baseUrl?: T['baseUrl'];
+  /**
+   * Fetch API implementation. You can use this option to provide a custom
+   * fetch instance.
+   *
+   * @default globalThis.fetch
+   */
+  fetch?: (request: Request) => ReturnType<typeof fetch>;
+  /**
+   * Please don't use the Fetch client for Next.js applications. The `next`
+   * options won't have any effect.
+   *
+   * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
+   */
+  next?: never;
+  /**
+   * Return the response data parsed in a specified format. By default, `auto`
+   * will infer the appropriate method from the `Content-Type` response header.
+   * You can override this behavior with any of the {@link Body} methods.
+   * Select `stream` if you don't want to parse response data at all.
+   *
+   * @default 'auto'
+   */
+  parseAs?:
+    | 'arrayBuffer'
+    | 'auto'
+    | 'blob'
+    | 'formData'
+    | 'json'
+    | 'stream'
+    | 'text';
+  /**
+   * Should we return only data or multiple fields (data, error, response, etc.)?
+   *
+   * @default 'fields'
+   */
+  responseStyle?: ResponseStyle;
+  /**
+   * Throw an error instead of returning it in the response?
+   *
+   * @default false
+   */
+  throwOnError?: T['throwOnError'];
+}
+
+export interface RequestOptions<
+  TResponseStyle extends ResponseStyle = 'fields',
+  ThrowOnError extends boolean = boolean,
+  Url extends string = string,
+> extends Config<{
+    responseStyle: TResponseStyle;
+    throwOnError: ThrowOnError;
+  }> {
+  /**
+   * Any body that you want to add to your request.
+   *
+   * {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
+   */
+  body?: unknown;
+  path?: Record<string, unknown>;
+  query?: Record<string, unknown>;
+  /**
+   * Security mechanism(s) to use for the request.
+   */
+  security?: ReadonlyArray<Auth>;
+  url: Url;
+}
+
+export type RequestResult<
+  TData = unknown,
+  TError = unknown,
+  ThrowOnError extends boolean = boolean,
+  TResponseStyle extends ResponseStyle = 'fields',
+> = ThrowOnError extends true
+  ? Promise<
+      TResponseStyle extends 'data'
+        ? TData extends Record<string, unknown>
+          ? TData[keyof TData]
+          : TData
+        : {
+            data: TData extends Record<string, unknown>
+              ? TData[keyof TData]
+              : TData;
+            request: Request;
+            response: Response;
+          }
+    >
+  : Promise<
+      TResponseStyle extends 'data'
+        ?
+            | (TData extends Record<string, unknown>
+                ? TData[keyof TData]
+                : TData)
+            | undefined
+        : (
+            | {
+                data: TData extends Record<string, unknown>
+                  ? TData[keyof TData]
+                  : TData;
+                error: undefined;
+              }
+            | {
+                data: undefined;
+                error: TError extends Record<string, unknown>
+                  ? TError[keyof TError]
+                  : TError;
+              }
+          ) & {
+            request: Request;
+            response: Response;
+          }
+    >;
+
+export interface ClientOptions {
+  baseUrl?: string;
+  responseStyle?: ResponseStyle;
+  throwOnError?: boolean;
+}
+
+type MethodFn = <
+  TData = unknown,
+  TError = unknown,
+  ThrowOnError extends boolean = false,
+  TResponseStyle extends ResponseStyle = 'fields',
+>(
+  options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'>,
+) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
+
+type RequestFn = <
+  TData = unknown,
+  TError = unknown,
+  ThrowOnError extends boolean = false,
+  TResponseStyle extends ResponseStyle = 'fields',
+>(
+  options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, 'method'> &
+    Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, 'method'>,
+) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
+
+type BuildUrlFn = <
+  TData extends {
+    body?: unknown;
+    path?: Record<string, unknown>;
+    query?: Record<string, unknown>;
+    url: string;
+  },
+>(
+  options: Pick<TData, 'url'> & Options<TData>,
+) => string;
+
+export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
+  interceptors: Middleware<Request, Response, unknown, RequestOptions>;
+};
+
+/**
+ * The `createClientConfig()` function will be called on client initialization
+ * and the returned object will become the client's initial configuration.
+ *
+ * You may want to initialize your client this way instead of calling
+ * `setConfig()`. This is useful for example if you're using Next.js
+ * to ensure your client always has the correct values.
+ */
+export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
+  override?: Config<ClientOptions & T>,
+) => Config<Required<ClientOptions> & T>;
+
+export interface TDataShape {
+  body?: unknown;
+  headers?: unknown;
+  path?: unknown;
+  query?: unknown;
+  url: string;
+}
+
+type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
+
+export type Options<
+  TData extends TDataShape = TDataShape,
+  ThrowOnError extends boolean = boolean,
+  TResponseStyle extends ResponseStyle = 'fields',
+> = OmitKeys<
+  RequestOptions<TResponseStyle, ThrowOnError>,
+  'body' | 'path' | 'query' | 'url'
+> &
+  Omit<TData, 'url'>;
+
+export type OptionsLegacyParser<
+  TData = unknown,
+  ThrowOnError extends boolean = boolean,
+  TResponseStyle extends ResponseStyle = 'fields',
+> = TData extends { body?: any }
+  ? TData extends { headers?: any }
+    ? OmitKeys<
+        RequestOptions<TResponseStyle, ThrowOnError>,
+        'body' | 'headers' | 'url'
+      > &
+        TData
+    : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'body' | 'url'> &
+        TData &
+        Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'headers'>
+  : TData extends { headers?: any }
+    ? OmitKeys<
+        RequestOptions<TResponseStyle, ThrowOnError>,
+        'headers' | 'url'
+      > &
+        TData &
+        Pick<RequestOptions<TResponseStyle, ThrowOnError>, 'body'>
+    : OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, 'url'> & TData;

+ 417 - 0
packages/sdk/js/src/gen/client/utils.ts

@@ -0,0 +1,417 @@
+import { getAuthToken } from '../core/auth';
+import type {
+  QuerySerializer,
+  QuerySerializerOptions,
+} from '../core/bodySerializer';
+import { jsonBodySerializer } from '../core/bodySerializer';
+import {
+  serializeArrayParam,
+  serializeObjectParam,
+  serializePrimitiveParam,
+} from '../core/pathSerializer';
+import type { Client, ClientOptions, Config, RequestOptions } from './types';
+
+interface PathSerializer {
+  path: Record<string, unknown>;
+  url: string;
+}
+
+const PATH_PARAM_RE = /\{[^{}]+\}/g;
+
+type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
+type MatrixStyle = 'label' | 'matrix' | 'simple';
+type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
+
+const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
+  let url = _url;
+  const matches = _url.match(PATH_PARAM_RE);
+  if (matches) {
+    for (const match of matches) {
+      let explode = false;
+      let name = match.substring(1, match.length - 1);
+      let style: ArraySeparatorStyle = 'simple';
+
+      if (name.endsWith('*')) {
+        explode = true;
+        name = name.substring(0, name.length - 1);
+      }
+
+      if (name.startsWith('.')) {
+        name = name.substring(1);
+        style = 'label';
+      } else if (name.startsWith(';')) {
+        name = name.substring(1);
+        style = 'matrix';
+      }
+
+      const value = path[name];
+
+      if (value === undefined || value === null) {
+        continue;
+      }
+
+      if (Array.isArray(value)) {
+        url = url.replace(
+          match,
+          serializeArrayParam({ explode, name, style, value }),
+        );
+        continue;
+      }
+
+      if (typeof value === 'object') {
+        url = url.replace(
+          match,
+          serializeObjectParam({
+            explode,
+            name,
+            style,
+            value: value as Record<string, unknown>,
+            valueOnly: true,
+          }),
+        );
+        continue;
+      }
+
+      if (style === 'matrix') {
+        url = url.replace(
+          match,
+          `;${serializePrimitiveParam({
+            name,
+            value: value as string,
+          })}`,
+        );
+        continue;
+      }
+
+      const replaceValue = encodeURIComponent(
+        style === 'label' ? `.${value as string}` : (value as string),
+      );
+      url = url.replace(match, replaceValue);
+    }
+  }
+  return url;
+};
+
+export const createQuerySerializer = <T = unknown>({
+  allowReserved,
+  array,
+  object,
+}: QuerySerializerOptions = {}) => {
+  const querySerializer = (queryParams: T) => {
+    const search: string[] = [];
+    if (queryParams && typeof queryParams === 'object') {
+      for (const name in queryParams) {
+        const value = queryParams[name];
+
+        if (value === undefined || value === null) {
+          continue;
+        }
+
+        if (Array.isArray(value)) {
+          const serializedArray = serializeArrayParam({
+            allowReserved,
+            explode: true,
+            name,
+            style: 'form',
+            value,
+            ...array,
+          });
+          if (serializedArray) search.push(serializedArray);
+        } else if (typeof value === 'object') {
+          const serializedObject = serializeObjectParam({
+            allowReserved,
+            explode: true,
+            name,
+            style: 'deepObject',
+            value: value as Record<string, unknown>,
+            ...object,
+          });
+          if (serializedObject) search.push(serializedObject);
+        } else {
+          const serializedPrimitive = serializePrimitiveParam({
+            allowReserved,
+            name,
+            value: value as string,
+          });
+          if (serializedPrimitive) search.push(serializedPrimitive);
+        }
+      }
+    }
+    return search.join('&');
+  };
+  return querySerializer;
+};
+
+/**
+ * Infers parseAs value from provided Content-Type header.
+ */
+export const getParseAs = (
+  contentType: string | null,
+): Exclude<Config['parseAs'], 'auto'> => {
+  if (!contentType) {
+    // If no Content-Type header is provided, the best we can do is return the raw response body,
+    // which is effectively the same as the 'stream' option.
+    return 'stream';
+  }
+
+  const cleanContent = contentType.split(';')[0]?.trim();
+
+  if (!cleanContent) {
+    return;
+  }
+
+  if (
+    cleanContent.startsWith('application/json') ||
+    cleanContent.endsWith('+json')
+  ) {
+    return 'json';
+  }
+
+  if (cleanContent === 'multipart/form-data') {
+    return 'formData';
+  }
+
+  if (
+    ['application/', 'audio/', 'image/', 'video/'].some((type) =>
+      cleanContent.startsWith(type),
+    )
+  ) {
+    return 'blob';
+  }
+
+  if (cleanContent.startsWith('text/')) {
+    return 'text';
+  }
+
+  return;
+};
+
+export const setAuthParams = async ({
+  security,
+  ...options
+}: Pick<Required<RequestOptions>, 'security'> &
+  Pick<RequestOptions, 'auth' | 'query'> & {
+    headers: Headers;
+  }) => {
+  for (const auth of security) {
+    const token = await getAuthToken(auth, options.auth);
+
+    if (!token) {
+      continue;
+    }
+
+    const name = auth.name ?? 'Authorization';
+
+    switch (auth.in) {
+      case 'query':
+        if (!options.query) {
+          options.query = {};
+        }
+        options.query[name] = token;
+        break;
+      case 'cookie':
+        options.headers.append('Cookie', `${name}=${token}`);
+        break;
+      case 'header':
+      default:
+        options.headers.set(name, token);
+        break;
+    }
+
+    return;
+  }
+};
+
+export const buildUrl: Client['buildUrl'] = (options) => {
+  const url = getUrl({
+    baseUrl: options.baseUrl as string,
+    path: options.path,
+    query: options.query,
+    querySerializer:
+      typeof options.querySerializer === 'function'
+        ? options.querySerializer
+        : createQuerySerializer(options.querySerializer),
+    url: options.url,
+  });
+  return url;
+};
+
+export const getUrl = ({
+  baseUrl,
+  path,
+  query,
+  querySerializer,
+  url: _url,
+}: {
+  baseUrl?: string;
+  path?: Record<string, unknown>;
+  query?: Record<string, unknown>;
+  querySerializer: QuerySerializer;
+  url: string;
+}) => {
+  const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
+  let url = (baseUrl ?? '') + pathUrl;
+  if (path) {
+    url = defaultPathSerializer({ path, url });
+  }
+  let search = query ? querySerializer(query) : '';
+  if (search.startsWith('?')) {
+    search = search.substring(1);
+  }
+  if (search) {
+    url += `?${search}`;
+  }
+  return url;
+};
+
+export const mergeConfigs = (a: Config, b: Config): Config => {
+  const config = { ...a, ...b };
+  if (config.baseUrl?.endsWith('/')) {
+    config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
+  }
+  config.headers = mergeHeaders(a.headers, b.headers);
+  return config;
+};
+
+export const mergeHeaders = (
+  ...headers: Array<Required<Config>['headers'] | undefined>
+): Headers => {
+  const mergedHeaders = new Headers();
+  for (const header of headers) {
+    if (!header || typeof header !== 'object') {
+      continue;
+    }
+
+    const iterator =
+      header instanceof Headers ? header.entries() : Object.entries(header);
+
+    for (const [key, value] of iterator) {
+      if (value === null) {
+        mergedHeaders.delete(key);
+      } else if (Array.isArray(value)) {
+        for (const v of value) {
+          mergedHeaders.append(key, v as string);
+        }
+      } else if (value !== undefined) {
+        // assume object headers are meant to be JSON stringified, i.e. their
+        // content value in OpenAPI specification is 'application/json'
+        mergedHeaders.set(
+          key,
+          typeof value === 'object' ? JSON.stringify(value) : (value as string),
+        );
+      }
+    }
+  }
+  return mergedHeaders;
+};
+
+type ErrInterceptor<Err, Res, Req, Options> = (
+  error: Err,
+  response: Res,
+  request: Req,
+  options: Options,
+) => Err | Promise<Err>;
+
+type ReqInterceptor<Req, Options> = (
+  request: Req,
+  options: Options,
+) => Req | Promise<Req>;
+
+type ResInterceptor<Res, Req, Options> = (
+  response: Res,
+  request: Req,
+  options: Options,
+) => Res | Promise<Res>;
+
+class Interceptors<Interceptor> {
+  _fns: (Interceptor | null)[];
+
+  constructor() {
+    this._fns = [];
+  }
+
+  clear() {
+    this._fns = [];
+  }
+
+  getInterceptorIndex(id: number | Interceptor): number {
+    if (typeof id === 'number') {
+      return this._fns[id] ? id : -1;
+    } else {
+      return this._fns.indexOf(id);
+    }
+  }
+  exists(id: number | Interceptor) {
+    const index = this.getInterceptorIndex(id);
+    return !!this._fns[index];
+  }
+
+  eject(id: number | Interceptor) {
+    const index = this.getInterceptorIndex(id);
+    if (this._fns[index]) {
+      this._fns[index] = null;
+    }
+  }
+
+  update(id: number | Interceptor, fn: Interceptor) {
+    const index = this.getInterceptorIndex(id);
+    if (this._fns[index]) {
+      this._fns[index] = fn;
+      return id;
+    } else {
+      return false;
+    }
+  }
+
+  use(fn: Interceptor) {
+    this._fns = [...this._fns, fn];
+    return this._fns.length - 1;
+  }
+}
+
+// `createInterceptors()` response, meant for external use as it does not
+// expose internals
+export interface Middleware<Req, Res, Err, Options> {
+  error: Pick<
+    Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
+    'eject' | 'use'
+  >;
+  request: Pick<Interceptors<ReqInterceptor<Req, Options>>, 'eject' | 'use'>;
+  response: Pick<
+    Interceptors<ResInterceptor<Res, Req, Options>>,
+    'eject' | 'use'
+  >;
+}
+
+// do not add `Middleware` as return type so we can use _fns internally
+export const createInterceptors = <Req, Res, Err, Options>() => ({
+  error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
+  request: new Interceptors<ReqInterceptor<Req, Options>>(),
+  response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
+});
+
+const defaultQuerySerializer = createQuerySerializer({
+  allowReserved: false,
+  array: {
+    explode: true,
+    style: 'form',
+  },
+  object: {
+    explode: true,
+    style: 'deepObject',
+  },
+});
+
+const defaultHeaders = {
+  'Content-Type': 'application/json',
+};
+
+export const createConfig = <T extends ClientOptions = ClientOptions>(
+  override: Config<Omit<ClientOptions, keyof T> & T> = {},
+): Config<Omit<ClientOptions, keyof T> & T> => ({
+  ...jsonBodySerializer,
+  headers: defaultHeaders,
+  parseAs: 'auto',
+  querySerializer: defaultQuerySerializer,
+  ...override,
+});

+ 40 - 0
packages/sdk/js/src/gen/core/auth.ts

@@ -0,0 +1,40 @@
+export type AuthToken = string | undefined;
+
+export interface Auth {
+  /**
+   * Which part of the request do we use to send the auth?
+   *
+   * @default 'header'
+   */
+  in?: 'header' | 'query' | 'cookie';
+  /**
+   * Header or query parameter name.
+   *
+   * @default 'Authorization'
+   */
+  name?: string;
+  scheme?: 'basic' | 'bearer';
+  type: 'apiKey' | 'http';
+}
+
+export const getAuthToken = async (
+  auth: Auth,
+  callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
+): Promise<string | undefined> => {
+  const token =
+    typeof callback === 'function' ? await callback(auth) : callback;
+
+  if (!token) {
+    return;
+  }
+
+  if (auth.scheme === 'bearer') {
+    return `Bearer ${token}`;
+  }
+
+  if (auth.scheme === 'basic') {
+    return `Basic ${btoa(token)}`;
+  }
+
+  return token;
+};

+ 88 - 0
packages/sdk/js/src/gen/core/bodySerializer.ts

@@ -0,0 +1,88 @@
+import type {
+  ArrayStyle,
+  ObjectStyle,
+  SerializerOptions,
+} from './pathSerializer';
+
+export type QuerySerializer = (query: Record<string, unknown>) => string;
+
+export type BodySerializer = (body: any) => any;
+
+export interface QuerySerializerOptions {
+  allowReserved?: boolean;
+  array?: SerializerOptions<ArrayStyle>;
+  object?: SerializerOptions<ObjectStyle>;
+}
+
+const serializeFormDataPair = (
+  data: FormData,
+  key: string,
+  value: unknown,
+): void => {
+  if (typeof value === 'string' || value instanceof Blob) {
+    data.append(key, value);
+  } else {
+    data.append(key, JSON.stringify(value));
+  }
+};
+
+const serializeUrlSearchParamsPair = (
+  data: URLSearchParams,
+  key: string,
+  value: unknown,
+): void => {
+  if (typeof value === 'string') {
+    data.append(key, value);
+  } else {
+    data.append(key, JSON.stringify(value));
+  }
+};
+
+export const formDataBodySerializer = {
+  bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
+    body: T,
+  ): FormData => {
+    const data = new FormData();
+
+    Object.entries(body).forEach(([key, value]) => {
+      if (value === undefined || value === null) {
+        return;
+      }
+      if (Array.isArray(value)) {
+        value.forEach((v) => serializeFormDataPair(data, key, v));
+      } else {
+        serializeFormDataPair(data, key, value);
+      }
+    });
+
+    return data;
+  },
+};
+
+export const jsonBodySerializer = {
+  bodySerializer: <T>(body: T): string =>
+    JSON.stringify(body, (_key, value) =>
+      typeof value === 'bigint' ? value.toString() : value,
+    ),
+};
+
+export const urlSearchParamsBodySerializer = {
+  bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
+    body: T,
+  ): string => {
+    const data = new URLSearchParams();
+
+    Object.entries(body).forEach(([key, value]) => {
+      if (value === undefined || value === null) {
+        return;
+      }
+      if (Array.isArray(value)) {
+        value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
+      } else {
+        serializeUrlSearchParamsPair(data, key, value);
+      }
+    });
+
+    return data.toString();
+  },
+};

+ 151 - 0
packages/sdk/js/src/gen/core/params.ts

@@ -0,0 +1,151 @@
+type Slot = 'body' | 'headers' | 'path' | 'query';
+
+export type Field =
+  | {
+      in: Exclude<Slot, 'body'>;
+      /**
+       * Field name. This is the name we want the user to see and use.
+       */
+      key: string;
+      /**
+       * Field mapped name. This is the name we want to use in the request.
+       * If omitted, we use the same value as `key`.
+       */
+      map?: string;
+    }
+  | {
+      in: Extract<Slot, 'body'>;
+      /**
+       * Key isn't required for bodies.
+       */
+      key?: string;
+      map?: string;
+    };
+
+export interface Fields {
+  allowExtra?: Partial<Record<Slot, boolean>>;
+  args?: ReadonlyArray<Field>;
+}
+
+export type FieldsConfig = ReadonlyArray<Field | Fields>;
+
+const extraPrefixesMap: Record<string, Slot> = {
+  $body_: 'body',
+  $headers_: 'headers',
+  $path_: 'path',
+  $query_: 'query',
+};
+const extraPrefixes = Object.entries(extraPrefixesMap);
+
+type KeyMap = Map<
+  string,
+  {
+    in: Slot;
+    map?: string;
+  }
+>;
+
+const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
+  if (!map) {
+    map = new Map();
+  }
+
+  for (const config of fields) {
+    if ('in' in config) {
+      if (config.key) {
+        map.set(config.key, {
+          in: config.in,
+          map: config.map,
+        });
+      }
+    } else if (config.args) {
+      buildKeyMap(config.args, map);
+    }
+  }
+
+  return map;
+};
+
+interface Params {
+  body: unknown;
+  headers: Record<string, unknown>;
+  path: Record<string, unknown>;
+  query: Record<string, unknown>;
+}
+
+const stripEmptySlots = (params: Params) => {
+  for (const [slot, value] of Object.entries(params)) {
+    if (value && typeof value === 'object' && !Object.keys(value).length) {
+      delete params[slot as Slot];
+    }
+  }
+};
+
+export const buildClientParams = (
+  args: ReadonlyArray<unknown>,
+  fields: FieldsConfig,
+) => {
+  const params: Params = {
+    body: {},
+    headers: {},
+    path: {},
+    query: {},
+  };
+
+  const map = buildKeyMap(fields);
+
+  let config: FieldsConfig[number] | undefined;
+
+  for (const [index, arg] of args.entries()) {
+    if (fields[index]) {
+      config = fields[index];
+    }
+
+    if (!config) {
+      continue;
+    }
+
+    if ('in' in config) {
+      if (config.key) {
+        const field = map.get(config.key)!;
+        const name = field.map || config.key;
+        (params[field.in] as Record<string, unknown>)[name] = arg;
+      } else {
+        params.body = arg;
+      }
+    } else {
+      for (const [key, value] of Object.entries(arg ?? {})) {
+        const field = map.get(key);
+
+        if (field) {
+          const name = field.map || key;
+          (params[field.in] as Record<string, unknown>)[name] = value;
+        } else {
+          const extra = extraPrefixes.find(([prefix]) =>
+            key.startsWith(prefix),
+          );
+
+          if (extra) {
+            const [prefix, slot] = extra;
+            (params[slot] as Record<string, unknown>)[
+              key.slice(prefix.length)
+            ] = value;
+          } else {
+            for (const [slot, allowed] of Object.entries(
+              config.allowExtra ?? {},
+            )) {
+              if (allowed) {
+                (params[slot as Slot] as Record<string, unknown>)[key] = value;
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  stripEmptySlots(params);
+
+  return params;
+};

+ 179 - 0
packages/sdk/js/src/gen/core/pathSerializer.ts

@@ -0,0 +1,179 @@
+interface SerializeOptions<T>
+  extends SerializePrimitiveOptions,
+    SerializerOptions<T> {}
+
+interface SerializePrimitiveOptions {
+  allowReserved?: boolean;
+  name: string;
+}
+
+export interface SerializerOptions<T> {
+  /**
+   * @default true
+   */
+  explode: boolean;
+  style: T;
+}
+
+export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
+export type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
+type MatrixStyle = 'label' | 'matrix' | 'simple';
+export type ObjectStyle = 'form' | 'deepObject';
+type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
+
+interface SerializePrimitiveParam extends SerializePrimitiveOptions {
+  value: string;
+}
+
+export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
+  switch (style) {
+    case 'label':
+      return '.';
+    case 'matrix':
+      return ';';
+    case 'simple':
+      return ',';
+    default:
+      return '&';
+  }
+};
+
+export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
+  switch (style) {
+    case 'form':
+      return ',';
+    case 'pipeDelimited':
+      return '|';
+    case 'spaceDelimited':
+      return '%20';
+    default:
+      return ',';
+  }
+};
+
+export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
+  switch (style) {
+    case 'label':
+      return '.';
+    case 'matrix':
+      return ';';
+    case 'simple':
+      return ',';
+    default:
+      return '&';
+  }
+};
+
+export const serializeArrayParam = ({
+  allowReserved,
+  explode,
+  name,
+  style,
+  value,
+}: SerializeOptions<ArraySeparatorStyle> & {
+  value: unknown[];
+}) => {
+  if (!explode) {
+    const joinedValues = (
+      allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
+    ).join(separatorArrayNoExplode(style));
+    switch (style) {
+      case 'label':
+        return `.${joinedValues}`;
+      case 'matrix':
+        return `;${name}=${joinedValues}`;
+      case 'simple':
+        return joinedValues;
+      default:
+        return `${name}=${joinedValues}`;
+    }
+  }
+
+  const separator = separatorArrayExplode(style);
+  const joinedValues = value
+    .map((v) => {
+      if (style === 'label' || style === 'simple') {
+        return allowReserved ? v : encodeURIComponent(v as string);
+      }
+
+      return serializePrimitiveParam({
+        allowReserved,
+        name,
+        value: v as string,
+      });
+    })
+    .join(separator);
+  return style === 'label' || style === 'matrix'
+    ? separator + joinedValues
+    : joinedValues;
+};
+
+export const serializePrimitiveParam = ({
+  allowReserved,
+  name,
+  value,
+}: SerializePrimitiveParam) => {
+  if (value === undefined || value === null) {
+    return '';
+  }
+
+  if (typeof value === 'object') {
+    throw new Error(
+      'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.',
+    );
+  }
+
+  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
+};
+
+export const serializeObjectParam = ({
+  allowReserved,
+  explode,
+  name,
+  style,
+  value,
+  valueOnly,
+}: SerializeOptions<ObjectSeparatorStyle> & {
+  value: Record<string, unknown> | Date;
+  valueOnly?: boolean;
+}) => {
+  if (value instanceof Date) {
+    return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
+  }
+
+  if (style !== 'deepObject' && !explode) {
+    let values: string[] = [];
+    Object.entries(value).forEach(([key, v]) => {
+      values = [
+        ...values,
+        key,
+        allowReserved ? (v as string) : encodeURIComponent(v as string),
+      ];
+    });
+    const joinedValues = values.join(',');
+    switch (style) {
+      case 'form':
+        return `${name}=${joinedValues}`;
+      case 'label':
+        return `.${joinedValues}`;
+      case 'matrix':
+        return `;${name}=${joinedValues}`;
+      default:
+        return joinedValues;
+    }
+  }
+
+  const separator = separatorObjectExplode(style);
+  const joinedValues = Object.entries(value)
+    .map(([key, v]) =>
+      serializePrimitiveParam({
+        allowReserved,
+        name: style === 'deepObject' ? `${name}[${key}]` : key,
+        value: v as string,
+      }),
+    )
+    .join(separator);
+  return style === 'label' || style === 'matrix'
+    ? separator + joinedValues
+    : joinedValues;
+};

+ 118 - 0
packages/sdk/js/src/gen/core/types.ts

@@ -0,0 +1,118 @@
+import type { Auth, AuthToken } from './auth';
+import type {
+  BodySerializer,
+  QuerySerializer,
+  QuerySerializerOptions,
+} from './bodySerializer';
+
+export interface Client<
+  RequestFn = never,
+  Config = unknown,
+  MethodFn = never,
+  BuildUrlFn = never,
+> {
+  /**
+   * Returns the final request URL.
+   */
+  buildUrl: BuildUrlFn;
+  connect: MethodFn;
+  delete: MethodFn;
+  get: MethodFn;
+  getConfig: () => Config;
+  head: MethodFn;
+  options: MethodFn;
+  patch: MethodFn;
+  post: MethodFn;
+  put: MethodFn;
+  request: RequestFn;
+  setConfig: (config: Config) => Config;
+  trace: MethodFn;
+}
+
+export interface Config {
+  /**
+   * Auth token or a function returning auth token. The resolved value will be
+   * added to the request payload as defined by its `security` array.
+   */
+  auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;
+  /**
+   * A function for serializing request body parameter. By default,
+   * {@link JSON.stringify()} will be used.
+   */
+  bodySerializer?: BodySerializer | null;
+  /**
+   * An object containing any HTTP headers that you want to pre-populate your
+   * `Headers` object with.
+   *
+   * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
+   */
+  headers?:
+    | RequestInit['headers']
+    | Record<
+        string,
+        | string
+        | number
+        | boolean
+        | (string | number | boolean)[]
+        | null
+        | undefined
+        | unknown
+      >;
+  /**
+   * The request method.
+   *
+   * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
+   */
+  method?:
+    | 'CONNECT'
+    | 'DELETE'
+    | 'GET'
+    | 'HEAD'
+    | 'OPTIONS'
+    | 'PATCH'
+    | 'POST'
+    | 'PUT'
+    | 'TRACE';
+  /**
+   * A function for serializing request query parameters. By default, arrays
+   * will be exploded in form style, objects will be exploded in deepObject
+   * style, and reserved characters are percent-encoded.
+   *
+   * This method will have no effect if the native `paramsSerializer()` Axios
+   * API function is used.
+   *
+   * {@link https://swagger.io/docs/specification/serialization/#query View examples}
+   */
+  querySerializer?: QuerySerializer | QuerySerializerOptions;
+  /**
+   * A function validating request data. This is useful if you want to ensure
+   * the request conforms to the desired shape, so it can be safely sent to
+   * the server.
+   */
+  requestValidator?: (data: unknown) => Promise<unknown>;
+  /**
+   * A function transforming response data before it's returned. This is useful
+   * for post-processing data, e.g. converting ISO strings into Date objects.
+   */
+  responseTransformer?: (data: unknown) => Promise<unknown>;
+  /**
+   * A function validating response data. This is useful if you want to ensure
+   * the response conforms to the desired shape, so it can be safely passed to
+   * the transformers and returned to the user.
+   */
+  responseValidator?: (data: unknown) => Promise<unknown>;
+}
+
+type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
+  ? true
+  : [T] extends [never | undefined]
+    ? [undefined] extends [T]
+      ? false
+      : true
+    : false;
+
+export type OmitNever<T extends Record<string, unknown>> = {
+  [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
+    ? never
+    : K]: T[K];
+};

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно