Dax Raad 4 months ago
parent
commit
66a34798a4
61 changed files with 7179 additions and 982 deletions
  1. 38 56
      bun.lock
  2. 1 1
      package.json
  3. 67 80
      packages/opencode/src/acp/agent.ts
  4. 6 8
      packages/opencode/src/acp/session.ts
  5. 1 1
      packages/opencode/src/acp/types.ts
  6. 1 1
      packages/opencode/src/cli/cmd/acp.ts
  7. 18 21
      packages/opencode/src/cli/cmd/run.ts
  8. 1 1
      packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx
  9. 10 24
      packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
  10. 1 3
      packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx
  11. 2 6
      packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx
  12. 1 3
      packages/opencode/src/cli/cmd/tui/component/dialog-tag.tsx
  13. 1 3
      packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
  14. 1 1
      packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx
  15. 30 44
      packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
  16. 1 1
      packages/opencode/src/cli/cmd/tui/context/keybind.tsx
  17. 7 4
      packages/opencode/src/cli/cmd/tui/context/sdk.tsx
  18. 9 9
      packages/opencode/src/cli/cmd/tui/context/sync.tsx
  19. 4 12
      packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx
  20. 1 1
      packages/opencode/src/cli/cmd/tui/routes/session/dialog-timeline.tsx
  21. 1 1
      packages/opencode/src/cli/cmd/tui/routes/session/header.tsx
  22. 18 39
      packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
  23. 1 1
      packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
  24. 8 4
      packages/opencode/src/plugin/index.ts
  25. 102 100
      packages/opencode/src/server/server.ts
  26. 1 1
      packages/opencode/src/share/share-next.ts
  27. 51 51
      packages/sdk/js/openapi.json
  28. 5 2
      packages/sdk/js/package.json
  29. 28 0
      packages/sdk/js/script/build.ts
  30. 7 6
      packages/sdk/js/src/client.ts
  31. 6 10
      packages/sdk/js/src/gen/client.gen.ts
  32. 100 34
      packages/sdk/js/src/gen/client/client.gen.ts
  33. 1 1
      packages/sdk/js/src/gen/client/index.ts
  34. 6 26
      packages/sdk/js/src/gen/client/types.gen.ts
  35. 43 41
      packages/sdk/js/src/gen/client/utils.gen.ts
  36. 11 3
      packages/sdk/js/src/gen/core/bodySerializer.gen.ts
  37. 34 9
      packages/sdk/js/src/gen/core/params.gen.ts
  38. 111 0
      packages/sdk/js/src/gen/core/queryKeySerializer.gen.ts
  39. 28 1
      packages/sdk/js/src/gen/core/serverSentEvents.gen.ts
  40. 7 12
      packages/sdk/js/src/gen/core/types.gen.ts
  41. 29 1
      packages/sdk/js/src/gen/core/utils.gen.ts
  42. 289 271
      packages/sdk/js/src/gen/sdk.gen.ts
  43. 93 88
      packages/sdk/js/src/gen/types.gen.ts
  44. 30 0
      packages/sdk/js/src/v2/client.ts
  45. 16 0
      packages/sdk/js/src/v2/gen/client.gen.ts
  46. 301 0
      packages/sdk/js/src/v2/gen/client/client.gen.ts
  47. 25 0
      packages/sdk/js/src/v2/gen/client/index.ts
  48. 241 0
      packages/sdk/js/src/v2/gen/client/types.gen.ts
  49. 332 0
      packages/sdk/js/src/v2/gen/client/utils.gen.ts
  50. 42 0
      packages/sdk/js/src/v2/gen/core/auth.gen.ts
  51. 100 0
      packages/sdk/js/src/v2/gen/core/bodySerializer.gen.ts
  52. 176 0
      packages/sdk/js/src/v2/gen/core/params.gen.ts
  53. 181 0
      packages/sdk/js/src/v2/gen/core/pathSerializer.gen.ts
  54. 136 0
      packages/sdk/js/src/v2/gen/core/queryKeySerializer.gen.ts
  55. 264 0
      packages/sdk/js/src/v2/gen/core/serverSentEvents.gen.ts
  56. 118 0
      packages/sdk/js/src/v2/gen/core/types.gen.ts
  57. 143 0
      packages/sdk/js/src/v2/gen/core/utils.gen.ts
  58. 4 0
      packages/sdk/js/src/v2/gen/sdk.gen.ts
  59. 3748 0
      packages/sdk/js/src/v2/gen/types.gen.ts
  60. 21 0
      packages/sdk/js/src/v2/index.ts
  61. 120 0
      packages/sdk/js/src/v2/server.ts

+ 38 - 56
bun.lock

@@ -324,7 +324,7 @@
       "name": "@opencode-ai/sdk",
       "version": "1.0.134",
       "devDependencies": {
-        "@hey-api/openapi-ts": "0.81.0",
+        "@hey-api/openapi-ts": "0.88.0",
         "@tsconfig/node22": "catalog:",
         "@types/node": "catalog:",
         "@typescript/native-preview": "catalog:",
@@ -468,7 +468,7 @@
     "@types/bun": "1.3.3",
     "@types/luxon": "3.7.1",
     "@types/node": "22.13.9",
-    "@typescript/native-preview": "7.0.0-dev.20251014.1",
+    "@typescript/native-preview": "7.0.0-dev.20251207.1",
     "ai": "5.0.97",
     "diff": "8.0.2",
     "fuzzysort": "3.1.0",
@@ -851,9 +851,11 @@
 
     "@happy-dom/global-registrator": ["@happy-dom/[email protected]", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
 
-    "@hey-api/json-schema-ref-parser": ["@hey-api/[email protected]", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w=="],
+    "@hey-api/codegen-core": ["@hey-api/[email protected]", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-vArVDtrvdzFewu1hnjUm4jX1NBITlSCeO81EdWq676MxQbyxsGcDPAgohaSA+Wvr4HjPSvsg2/1s2zYxUtXebg=="],
 
-    "@hey-api/openapi-ts": ["@hey-api/[email protected]", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "js-yaml": "4.1.0", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-PoJukNBkUfHOoMDpN33bBETX49TUhy7Hu8Sa0jslOvFndvZ5VjQr4Nl/Dzjb9LG1Lp5HjybyTJMA6a1zYk/q6A=="],
+    "@hey-api/json-schema-ref-parser": ["@hey-api/[email protected]", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-inPeksRLq+j3ArnuGOzQPQE//YrhezQG0+9Y9yizScBN2qatJ78fIByhEgKdNAbtguDCn4RPxmEhcrePwHxs4A=="],
+
+    "@hey-api/openapi-ts": ["@hey-api/[email protected]", "", { "dependencies": { "@hey-api/codegen-core": "^0.3.3", "@hey-api/json-schema-ref-parser": "1.2.1", "ansi-colors": "4.1.3", "c12": "3.3.2", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-ZrvmDfmVf+N4ry786LAhS/DoH+xkIjIJIeDnj2aL1qnMTIDsdRIXXvr80EnAZVBgunzu1wTBbHb3H9OfyDQ2EQ=="],
 
     "@hono/standard-validator": ["@hono/[email protected]", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
 
@@ -1763,21 +1765,21 @@
 
     "@types/yargs-parser": ["@types/[email protected]", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
 
-    "@typescript/native-preview": ["@typescript/[email protected]014.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251014.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251014.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-IqmX5CYCBqXbfL+HKlcQAMaDlfJ0Z8OhUxvADFV2TENnzSYI4CuhvKxwOB2wFSLXufVsgtAlf3Fjwn24KmMyPQ=="],
+    "@typescript/native-preview": ["@typescript/[email protected]207.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251207.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-4QcRnzB0pi9rS0AOvg8kWbmuwHv5X7B2EXHbgcms9+56hsZ8SZrZjNgBJb2rUIodJ4kU5mrkj/xlTTT4r9VcpQ=="],
 
-    "@typescript/native-preview-darwin-arm64": ["@typescript/[email protected]014.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7rQoLlerWnwnvrM56hP4rdEbo4xDE4zr7cch+EzgENq/tbXYereGq1fmnR83UNglb1Eyy53OvJZ3O2csYBa2vg=="],
+    "@typescript/native-preview-darwin-arm64": ["@typescript/[email protected]207.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-waWJnuuvkXh4WdpbTjYf7pyahJzx0ycesV2BylyHrE9OxU9FSKcD/cRLQYvbq3YcBSdF7sZwRLDBer7qTeLsYA=="],
 
-    "@typescript/native-preview-darwin-x64": ["@typescript/[email protected]014.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-SF29o9NFRGDM23Jz0nVO4/yS78GQ81rtOemmCVNXuJotoY4bP3npGDyEmfkZQHZgDOXogs2OWy3t7NUJ235ANQ=="],
+    "@typescript/native-preview-darwin-x64": ["@typescript/[email protected]207.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-3bkD9QuIjxETtp6J1l5X2oKgudJ8z+8fwUq0izCjK1JrIs2vW1aQnbzxhynErSyHWH7URGhHHzcsXHbikckAsg=="],
 
-    "@typescript/native-preview-linux-arm": ["@typescript/[email protected]014.1", "", { "os": "linux", "cpu": "arm" }, "sha512-o5cu7h+BBAp6V4qxYY5RWuaYouN3j+MGFLrrUtvvNj4XKM+kbq5qwsgVRsmJZ1LfUvHmzyQs86vt9djAWedzjQ=="],
+    "@typescript/native-preview-linux-arm": ["@typescript/[email protected]207.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OjrZBq8XJkB7uCQvT1AZ1FPsp+lT0cHxY5SisE+ZTAU6V0IHAZMwJ7J/mnwlGsBcCKRLBT+lX3hgEuOTSwHr9w=="],
 
-    "@typescript/native-preview-linux-arm64": ["@typescript/[email protected]014.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+YWbW/JF4uggEUBr+vflqI5i7bL4Z3XInCOyUO1qQEY7VmfDCsPEzIwGi37O1mixfxw9Qj8LQsptCkU+fqKwGw=="],
+    "@typescript/native-preview-linux-arm64": ["@typescript/[email protected]207.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qhp06OObkwy5B+PlAhAmq+Ls3GVt4LHAovrTRcpLB3Mk3yJ0h9DnIQwPQiayp16TdvTsGHI3jdIX4MGm5L/ghA=="],
 
-    "@typescript/native-preview-linux-x64": ["@typescript/[email protected]014.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3LC4tgcgi6zWJWBUpBNXOGSY3yISJrQezSP/T+v+mQRApkdoIpTSHIyQAhgaagcs3MOQRaqiIPaLOVrdHXdU6A=="],
+    "@typescript/native-preview-linux-x64": ["@typescript/[email protected]207.1", "", { "os": "linux", "cpu": "x64" }, "sha512-fPRw0zfTBeVmrkgi5Le+sSwoeAz6pIdvcsa1OYZcrspueS9hn3qSC5bLEc5yX4NJP1vItadBqyGLUQ7u8FJjow=="],
 
-    "@typescript/native-preview-win32-arm64": ["@typescript/[email protected]014.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-P0D4UEXwzFZh3pHexe2Ky1tW/HjY/HxTBTIajz2ViDCNPw7uDSEsXSB4H9TTiFJw8gVdTUFbsoAQp1MteTeORA=="],
+    "@typescript/native-preview-win32-arm64": ["@typescript/[email protected]207.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-KxY1i+HxeSFfzZ+HVsKwMGBM79laTRZv1ibFqHu22CEsfSPDt4yiV1QFis8Nw7OBXswNqJG/UGqY47VP8FeTvw=="],
 
-    "@typescript/native-preview-win32-x64": ["@typescript/[email protected]014.1", "", { "os": "win32", "cpu": "x64" }, "sha512-fi53g2ihH7tkQLlz8hZGAb2V+3aNZpcxrZ530CQ4xcWwAqssEj0EaZJX0VLEtIQBar1ttGVK9Pz/wJU9sYyVzg=="],
+    "@typescript/native-preview-win32-x64": ["@typescript/[email protected]207.1", "", { "os": "win32", "cpu": "x64" }, "sha512-5l51HlXjX7lXwo65DEl1IaCFLjmkMtL6K3NrSEamPNeNTtTQwZRa3pQ9V65dCglnnCQ0M3+VF1RqzC7FU0iDKg=="],
 
     "@typescript/vfs": ["@typescript/[email protected]", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g=="],
 
@@ -1999,7 +2001,7 @@
 
     "bytes": ["[email protected]", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
 
-    "c12": ["c12@2.0.1", "", { "dependencies": { "chokidar": "^4.0.1", "confbox": "^0.1.7", "defu": "^6.1.4", "dotenv": "^16.4.5", "giget": "^1.2.3", "jiti": "^2.3.0", "mlly": "^1.7.1", "ohash": "^1.1.4", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", "pkg-types": "^1.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A=="],
+    "c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="],
 
     "call-bind": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
 
@@ -2081,7 +2083,7 @@
 
     "comma-separated-tokens": ["[email protected]", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
 
-    "commander": ["commander@13.0.0", "", {}, "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ=="],
+    "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
 
     "common-ancestor-path": ["[email protected]", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="],
 
@@ -2089,7 +2091,7 @@
 
     "condense-newlines": ["[email protected]", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", "kind-of": "^3.0.2" } }, "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg=="],
 
-    "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+    "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
 
     "config-chain": ["[email protected]", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="],
 
@@ -2213,7 +2215,7 @@
 
     "dot-prop": ["[email protected]", "", { "dependencies": { "type-fest": "^3.8.0" } }, "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ=="],
 
-    "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+    "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
 
     "drizzle-kit": ["[email protected]", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l6dMSE100u7sDaTbLczibrQZjA35jLsHNqIV+jmhNVO3O8jzM6kywMOmV9uOz9ZVSCMPQhAZEFjL/qDPVrqpUA=="],
 
@@ -2331,6 +2333,8 @@
 
     "expressive-code": ["[email protected]", "", { "dependencies": { "@expressive-code/core": "^0.41.3", "@expressive-code/plugin-frames": "^0.41.3", "@expressive-code/plugin-shiki": "^0.41.3", "@expressive-code/plugin-text-markers": "^0.41.3" } }, "sha512-YLnD62jfgBZYrXIPQcJ0a51Afv9h8VlWqEGK9uU2T5nL/5rb8SnA86+7+mgCZe5D34Tff5RNEA5hjNVJYHzrFg=="],
 
+    "exsolve": ["[email protected]", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
+
     "extend": ["[email protected]", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
 
     "extend-shallow": ["[email protected]", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
@@ -2393,8 +2397,6 @@
 
     "fs-extra": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
 
-    "fs-minipass": ["[email protected]", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
-
     "fs.realpath": ["[email protected]", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
 
     "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -2441,7 +2443,7 @@
 
     "gifwrap": ["[email protected]", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
 
-    "giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="],
+    "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
 
     "github-from-package": ["[email protected]", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
 
@@ -2471,8 +2473,6 @@
 
     "h3": ["[email protected]", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="],
 
-    "handlebars": ["[email protected]", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
-
     "happy-dom": ["[email protected]", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g=="],
 
     "has-bigints": ["[email protected]", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@@ -2645,6 +2645,8 @@
 
     "is-hexadecimal": ["[email protected]", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
 
+    "is-in-ssh": ["[email protected]", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="],
+
     "is-inside-container": ["[email protected]", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
 
     "is-map": ["[email protected]", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
@@ -2983,8 +2985,6 @@
 
     "mkdirp-classic": ["[email protected]", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
 
-    "mlly": ["[email protected]", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
-
     "mrmime": ["[email protected]", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
 
     "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -3003,8 +3003,6 @@
 
     "negotiator": ["[email protected]", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
 
-    "neo-async": ["[email protected]", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
-
     "neotraverse": ["[email protected]", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
 
     "nf3": ["[email protected]", "", {}, "sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ=="],
@@ -3043,7 +3041,7 @@
 
     "nth-check": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
 
-    "nypm": ["nypm@0.5.4", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "tinyexec": "^0.3.2", "ufo": "^1.5.4" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA=="],
+    "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="],
 
     "object-assign": ["[email protected]", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
 
@@ -3159,7 +3157,7 @@
 
     "peek-readable": ["[email protected]", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
 
-    "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
+    "perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="],
 
     "piccolore": ["[email protected]", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
 
@@ -3177,7 +3175,7 @@
 
     "pkg-dir": ["[email protected]", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
 
-    "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
+    "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
 
     "pkg-up": ["[email protected]", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
 
@@ -3205,6 +3203,8 @@
 
     "postgres": ["[email protected]", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
 
+    "powershell-utils": ["[email protected]", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
+
     "prebuild-install": ["[email protected]", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
 
     "prettier": ["[email protected]", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
@@ -3459,7 +3459,7 @@
 
     "solid-use": ["[email protected]", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
 
-    "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+    "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
 
     "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
 
@@ -3659,8 +3659,6 @@
 
     "ufo": ["[email protected]", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
 
-    "uglify-js": ["[email protected]", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
-
     "ulid": ["[email protected]", "", { "bin": { "ulid": "dist/cli.js" } }, "sha512-dPJyqPzx8preQhqq24bBG1YNkvigm87K8kVEHCD+ruZg24t6IFEFv00xMWfxcC4djmFtiTLdFuADn4+DOz6R7Q=="],
 
     "ultrahtml": ["[email protected]", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
@@ -3789,8 +3787,6 @@
 
     "widest-line": ["[email protected]", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
 
-    "wordwrap": ["[email protected]", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
-
     "workerd": ["[email protected]", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251118.0", "@cloudflare/workerd-darwin-arm64": "1.20251118.0", "@cloudflare/workerd-linux-64": "1.20251118.0", "@cloudflare/workerd-linux-arm64": "1.20251118.0", "@cloudflare/workerd-windows-64": "1.20251118.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ=="],
 
     "wrangler": ["[email protected]", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.11", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251118.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251118.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251118.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-+nuZuHZxDdKmAyXOSrHlciGshCoAPiy5dM+t6mEohWm7HpXvTHmWQGUf/na9jjWlWJHCJYOWzkA1P5HBJqrIEA=="],
@@ -3803,6 +3799,8 @@
 
     "ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
 
+    "wsl-utils": ["[email protected]", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ=="],
+
     "xdg-basedir": ["[email protected]", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
 
     "xml-parse-from-string": ["[email protected]", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
@@ -3897,8 +3895,6 @@
 
     "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/[email protected]", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.13.0", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hX2cLC/KW74Io1zIbn92kI482j9J7LleBLGCVU9EP3BeH5MVrnFawOnqD0t/q6D1Z+ZNeQG2gNKMslCcO36wng=="],
 
-    "@astrojs/mdx/source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
-
     "@astrojs/sitemap/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
 
     "@astrojs/solid-js/vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
@@ -3985,6 +3981,8 @@
 
     "@expressive-code/plugin-shiki/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
 
+    "@hey-api/openapi-ts/open": ["[email protected]", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
+
     "@hono/zod-validator/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
 
     "@isaacs/cliui/string-width": ["[email protected]", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
@@ -4031,8 +4029,6 @@
 
     "@jsx-email/doiuse-email/htmlparser2": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
 
-    "@mdx-js/mdx/source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
-
     "@modelcontextprotocol/sdk/express": ["[email protected]", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
 
     "@modelcontextprotocol/sdk/raw-body": ["[email protected]", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
@@ -4177,9 +4173,7 @@
 
     "boxen/chalk": ["[email protected]", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
 
-    "c12/ohash": ["[email protected]", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="],
-
-    "c12/pathe": ["[email protected]", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
+    "clean-css/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
 
     "compress-commons/is-stream": ["[email protected]", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
 
@@ -4199,8 +4193,6 @@
 
     "esbuild-plugin-copy/chokidar": ["[email protected]", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
 
-    "estree-util-to-js/source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
-
     "execa/is-stream": ["[email protected]", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
 
     "express/cookie": ["[email protected]", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
@@ -4215,14 +4207,10 @@
 
     "form-data/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
 
-    "fs-minipass/minipass": ["[email protected]", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
-
     "gaxios/is-stream": ["[email protected]", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
 
     "gaxios/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
 
-    "giget/tar": ["[email protected]", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
-
     "glob/minimatch": ["[email protected]", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
 
     "globby/ignore": ["[email protected]", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -4259,6 +4247,8 @@
 
     "npm-run-path/path-key": ["[email protected]", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
 
+    "nypm/tinyexec": ["[email protected]", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
+
     "opencode/@ai-sdk/anthropic": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
 
     "opencode/@ai-sdk/openai": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
@@ -4319,6 +4309,8 @@
 
     "sitemap/sax": ["[email protected]", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
 
+    "source-map-support/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
     "sst/aws4fetch": ["[email protected]", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
 
     "sst/jose": ["[email protected]", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
@@ -4823,14 +4815,6 @@
 
     "form-data/mime-types/mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
 
-    "giget/tar/chownr": ["[email protected]", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
-
-    "giget/tar/minipass": ["[email protected]", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
-
-    "giget/tar/minizlib": ["[email protected]", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
-
-    "giget/tar/mkdirp": ["[email protected]", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
-
     "gray-matter/js-yaml/argparse": ["[email protected]", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
 
     "js-beautify/glob/jackspeak": ["[email protected]", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
@@ -5007,8 +4991,6 @@
 
     "esbuild-plugin-copy/chokidar/readdirp/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
 
-    "giget/tar/minizlib/minipass": ["[email protected]", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
-
     "js-beautify/glob/path-scurry/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
 
     "opencontrol/@modelcontextprotocol/sdk/express/accepts": ["[email protected]", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],

+ 1 - 1
package.json

@@ -39,7 +39,7 @@
       "fuzzysort": "3.1.0",
       "luxon": "3.6.1",
       "typescript": "5.8.2",
-      "@typescript/native-preview": "7.0.0-dev.20251014.1",
+      "@typescript/native-preview": "7.0.0-dev.20251207.1",
       "zod": "4.1.8",
       "remeda": "2.26.0",
       "solid-list": "0.3.0",

+ 67 - 80
packages/opencode/src/acp/agent.ts

@@ -29,7 +29,7 @@ import { MCP } from "@/mcp"
 import { Todo } from "@/session/todo"
 import { z } from "zod"
 import { LoadAPIKeyError } from "ai"
-import type { OpencodeClient } from "@opencode-ai/sdk"
+import type { OpencodeClient } from "@opencode-ai/sdk/v2"
 
 export namespace ACP {
   const log = Log.create({ service: "acp-agent" })
@@ -68,7 +68,7 @@ export namespace ACP {
         { optionId: "always", kind: "allow_always", name: "Always allow" },
         { optionId: "reject", kind: "reject_once", name: "Reject" },
       ]
-      this.config.sdk.event.subscribe({ query: { directory } }).then(async (events) => {
+      this.config.sdk.event.subscribe({ directory }).then(async (events) => {
         for await (const event of events.stream) {
           switch (event.type) {
             case "permission.updated":
@@ -93,32 +93,29 @@ export namespace ACP {
                       permissionID: permission.id,
                       sessionID: permission.sessionID,
                     })
-                    await this.config.sdk.postSessionIdPermissionsPermissionId({
-                      path: { id: permission.sessionID, permissionID: permission.id },
-                      body: {
-                        response: "reject",
-                      },
-                      query: { directory },
+                    await this.config.sdk.permission.respond({
+                      sessionID: permission.sessionID,
+                      permissionID: permission.id,
+                      response: "reject",
+                      directory,
                     })
                     return
                   })
                 if (!res) return
                 if (res.outcome.outcome !== "selected") {
-                  await this.config.sdk.postSessionIdPermissionsPermissionId({
-                    path: { id: permission.sessionID, permissionID: permission.id },
-                    body: {
-                      response: "reject",
-                    },
-                    query: { directory },
+                  await this.config.sdk.permission.respond({
+                    sessionID: permission.sessionID,
+                    permissionID: permission.id,
+                    response: "reject",
+                    directory,
                   })
                   return
                 }
-                await this.config.sdk.postSessionIdPermissionsPermissionId({
-                  path: { id: permission.sessionID, permissionID: permission.id },
-                  body: {
-                    response: res.outcome.optionId as "once" | "always" | "reject",
-                  },
-                  query: { directory },
+                await this.config.sdk.permission.respond({
+                  sessionID: permission.sessionID,
+                  permissionID: permission.id,
+                  response: res.outcome.optionId as "once" | "always" | "reject",
+                  directory,
                 })
               } catch (err) {
                 log.error("unexpected error when handling permission", { error: err })
@@ -133,14 +130,14 @@ export namespace ACP {
                 const { part } = props
 
                 const message = await this.config.sdk.session
-                  .message({
-                    throwOnError: true,
-                    path: {
-                      id: part.sessionID,
+                  .message(
+                    {
+                      sessionID: part.sessionID,
                       messageID: part.messageID,
+                      directory,
                     },
-                    query: { directory },
-                  })
+                    { throwOnError: true },
+                  )
                   .then((x) => x.data)
                   .catch((err) => {
                     log.error("unexpected error when fetching message", { error: err })
@@ -420,9 +417,7 @@ export namespace ACP {
       const model = await defaultModel(this.config, directory)
       const sessionId = params.sessionId
 
-      const providers = await this.sdk.config
-        .providers({ throwOnError: true, query: { directory } })
-        .then((x) => x.data.providers)
+      const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
       const entries = providers.sort((a, b) => {
         const nameA = a.name.toLowerCase()
         const nameB = b.name.toLowerCase()
@@ -439,22 +434,22 @@ export namespace ACP {
       })
 
       const agents = await this.config.sdk.app
-        .agents({
-          throwOnError: true,
-          query: {
+        .agents(
+          {
             directory,
           },
-        })
-        .then((resp) => resp.data)
+          { throwOnError: true },
+        )
+        .then((resp) => resp.data!)
 
       const commands = await this.config.sdk.command
-        .list({
-          throwOnError: true,
-          query: {
+        .list(
+          {
             directory,
           },
-        })
-        .then((resp) => resp.data)
+          { throwOnError: true },
+        )
+        .then((resp) => resp.data!)
 
       const availableCommands = commands.map((command) => ({
         name: command.name,
@@ -503,14 +498,14 @@ export namespace ACP {
       await Promise.all(
         Object.entries(mcpServers).map(async ([key, mcp]) => {
           await this.sdk.mcp
-            .add({
-              throwOnError: true,
-              query: { directory },
-              body: {
+            .add(
+              {
+                directory,
                 name: key,
                 config: mcp,
               },
-            })
+              { throwOnError: true },
+            )
             .catch((error) => {
               log.error("failed to add mcp server", { name: key, error })
             })
@@ -559,7 +554,7 @@ export namespace ACP {
     async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
       this.sessionManager.get(params.sessionId)
       await this.config.sdk.app
-        .agents({ throwOnError: true })
+        .agents({}, { throwOnError: true })
         .then((x) => x.data)
         .then((agent) => {
           if (!agent) throw new Error(`Agent not found: ${params.modeId}`)
@@ -651,50 +646,42 @@ export namespace ACP {
 
       if (!cmd) {
         await this.sdk.session.prompt({
-          path: { id: sessionID },
-          body: {
-            model: {
-              providerID: model.providerID,
-              modelID: model.modelID,
-            },
-            parts,
-            agent,
-          },
-          query: {
-            directory,
+          sessionID,
+          model: {
+            providerID: model.providerID,
+            modelID: model.modelID,
           },
+          parts,
+          agent,
+          directory,
         })
         return done
       }
 
       const command = await this.config.sdk.command
-        .list({ throwOnError: true, query: { directory } })
-        .then((x) => x.data.find((c) => c.name === cmd.name))
+        .list({ directory }, { throwOnError: true })
+        .then((x) => x.data!.find((c) => c.name === cmd.name))
       if (command) {
         await this.sdk.session.command({
-          path: { id: sessionID },
-          body: {
-            command: command.name,
-            arguments: cmd.args,
-            model: model.providerID + "/" + model.modelID,
-            agent,
-          },
-          query: {
-            directory,
-          },
+          sessionID,
+          command: command.name,
+          arguments: cmd.args,
+          model: model.providerID + "/" + model.modelID,
+          agent,
+          directory,
         })
         return done
       }
 
       switch (cmd.name) {
         case "compact":
-          await this.config.sdk.session.summarize({
-            path: { id: sessionID },
-            throwOnError: true,
-            query: {
+          await this.config.sdk.session.summarize(
+            {
+              sessionID,
               directory,
             },
-          })
+            { throwOnError: true },
+          )
           break
       }
 
@@ -703,13 +690,13 @@ export namespace ACP {
 
     async cancel(params: CancelNotification) {
       const session = this.sessionManager.get(params.sessionId)
-      await this.config.sdk.session.abort({
-        path: { id: params.sessionId },
-        throwOnError: true,
-        query: {
+      await this.config.sdk.session.abort(
+        {
+          sessionID: params.sessionId,
           directory: session.cwd,
         },
-      })
+        { throwOnError: true },
+      )
     }
   }
 
@@ -766,10 +753,10 @@ export namespace ACP {
     if (configured) return configured
 
     const model = await sdk.config
-      .get({ throwOnError: true, query: { directory: cwd } })
+      .get({ directory: cwd }, { throwOnError: true })
       .then((resp) => {
         const cfg = resp.data
-        if (!cfg.model) return undefined
+        if (!cfg || !cfg.model) return undefined
         const parsed = Provider.parseModel(cfg.model)
         return {
           providerID: parsed.providerID,

+ 6 - 8
packages/opencode/src/acp/session.ts

@@ -1,7 +1,7 @@
 import { RequestError, type McpServer } from "@agentclientprotocol/sdk"
 import type { ACPSessionState } from "./types"
 import { Log } from "@/util/log"
-import type { OpencodeClient } from "@opencode-ai/sdk"
+import type { OpencodeClient } from "@opencode-ai/sdk/v2"
 
 const log = Log.create({ service: "acp-session-manager" })
 
@@ -15,16 +15,14 @@ export class ACPSessionManager {
 
   async create(cwd: string, mcpServers: McpServer[], model?: ACPSessionState["model"]): Promise<ACPSessionState> {
     const session = await this.sdk.session
-      .create({
-        body: {
+      .create(
+        {
           title: `ACP Session ${crypto.randomUUID()}`,
-        },
-        query: {
           directory: cwd,
         },
-        throwOnError: true,
-      })
-      .then((x) => x.data)
+        { throwOnError: true },
+      )
+      .then((x) => x.data!)
 
     const sessionId = session.id
     const resolvedModel = model

+ 1 - 1
packages/opencode/src/acp/types.ts

@@ -1,5 +1,5 @@
 import type { McpServer } from "@agentclientprotocol/sdk"
-import type { OpencodeClient } from "@opencode-ai/sdk"
+import type { OpencodeClient } from "@opencode-ai/sdk/v2"
 
 export interface ACPSessionState {
   id: string

+ 1 - 1
packages/opencode/src/cli/cmd/acp.ts

@@ -4,7 +4,7 @@ import { cmd } from "./cmd"
 import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
 import { ACP } from "@/acp/agent"
 import { Server } from "@/server/server"
-import { createOpencodeClient } from "@opencode-ai/sdk"
+import { createOpencodeClient } from "@opencode-ai/sdk/v2"
 
 const log = Log.create({ service: "acp-command" })
 

+ 18 - 21
packages/opencode/src/cli/cmd/run.ts

@@ -7,7 +7,7 @@ import { bootstrap } from "../bootstrap"
 import { Command } from "../../command"
 import { EOL } from "os"
 import { select } from "@clack/prompts"
-import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk"
+import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2"
 import { Server } from "../../server/server"
 import { Provider } from "../../provider/provider"
 
@@ -212,9 +212,10 @@ export const RunCommand = cmd({
               initialValue: "once",
             }).catch(() => "reject")
             const response = (result.toString().includes("cancel") ? "reject" : result) as "once" | "always" | "reject"
-            await sdk.postSessionIdPermissionsPermissionId({
-              path: { id: sessionID, permissionID: permission.id },
-              body: { response },
+            await sdk.permission.respond({
+              sessionID,
+              permissionID: permission.id,
+              response,
             })
           }
         }
@@ -222,23 +223,19 @@ export const RunCommand = cmd({
 
       if (args.command) {
         await sdk.session.command({
-          path: { id: sessionID },
-          body: {
-            agent: args.agent || "build",
-            model: args.model,
-            command: args.command,
-            arguments: message,
-          },
+          sessionID,
+          agent: args.agent || "build",
+          model: args.model,
+          command: args.command,
+          arguments: message,
         })
       } else {
         const modelParam = args.model ? Provider.parseModel(args.model) : undefined
         await sdk.session.prompt({
-          path: { id: sessionID },
-          body: {
-            agent: args.agent || "build",
-            model: modelParam,
-            parts: [...fileParts, { type: "text", text: message }],
-          },
+          sessionID,
+          agent: args.agent || "build",
+          model: modelParam,
+          parts: [...fileParts, { type: "text", text: message }],
         })
       }
 
@@ -263,7 +260,7 @@ export const RunCommand = cmd({
               : args.title
             : undefined
 
-        const result = await sdk.session.create({ body: title ? { title } : {} })
+        const result = await sdk.session.create(title ? { title } : {})
         return result.data?.id
       })()
 
@@ -274,7 +271,7 @@ export const RunCommand = cmd({
 
       const cfgResult = await sdk.config.get()
       if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) {
-        const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => {
+        const shareResult = await sdk.session.share({ sessionID }).catch((error) => {
           if (error instanceof Error && error.message.includes("disabled")) {
             UI.println(UI.Style.TEXT_DANGER_BOLD + "!  " + error.message)
           }
@@ -315,7 +312,7 @@ export const RunCommand = cmd({
               : args.title
             : undefined
 
-        const result = await sdk.session.create({ body: title ? { title } : {} })
+        const result = await sdk.session.create(title ? { title } : {})
         return result.data?.id
       })()
 
@@ -327,7 +324,7 @@ export const RunCommand = cmd({
 
       const cfgResult = await sdk.config.get()
       if (cfgResult.data && (cfgResult.data.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share)) {
-        const shareResult = await sdk.session.share({ path: { id: sessionID } }).catch((error) => {
+        const shareResult = await sdk.session.share({ sessionID }).catch((error) => {
           if (error instanceof Error && error.message.includes("disabled")) {
             UI.println(UI.Style.TEXT_DANGER_BOLD + "!  " + error.message)
           }

+ 1 - 1
packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx

@@ -11,7 +11,7 @@ import {
 } from "solid-js"
 import { useKeyboard } from "@opentui/solid"
 import { useKeybind } from "@tui/context/keybind"
-import type { KeybindsConfig } from "@opencode-ai/sdk"
+import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
 
 type Context = ReturnType<typeof init>
 const ctx = createContext<Context>()

+ 10 - 24
packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx

@@ -7,7 +7,7 @@ import { useSDK } from "../context/sdk"
 import { DialogPrompt } from "../ui/dialog-prompt"
 import { useTheme } from "../context/theme"
 import { TextAttributes } from "@opentui/core"
-import type { ProviderAuthAuthorization } from "@opencode-ai/sdk"
+import type { ProviderAuthAuthorization } from "@opencode-ai/sdk/v2"
 import { DialogModel } from "./dialog-model"
 
 const PROVIDER_PRIORITY: Record<string, number> = {
@@ -64,12 +64,8 @@ export function createDialogProviderOptions() {
           const method = methods[index]
           if (method.type === "oauth") {
             const result = await sdk.client.provider.oauth.authorize({
-              path: {
-                id: provider.id,
-              },
-              body: {
-                method: index,
-              },
+              providerID: provider.id,
+              method: index,
             })
             if (result.data?.method === "code") {
               dialog.replace(() => (
@@ -111,12 +107,8 @@ function AutoMethod(props: AutoMethodProps) {
 
   onMount(async () => {
     const result = await sdk.client.provider.oauth.callback({
-      path: {
-        id: props.providerID,
-      },
-      body: {
-        method: props.index,
-      },
+      providerID: props.providerID,
+      method: props.index,
     })
     if (result.error) {
       dialog.clear()
@@ -161,13 +153,9 @@ function CodeMethod(props: CodeMethodProps) {
       placeholder="Authorization code"
       onConfirm={async (value) => {
         const { error } = await sdk.client.provider.oauth.callback({
-          path: {
-            id: props.providerID,
-          },
-          body: {
-            method: props.index,
-            code: value,
-          },
+          providerID: props.providerID,
+          method: props.index,
+          code: value,
         })
         if (!error) {
           await sdk.client.instance.dispose()
@@ -219,10 +207,8 @@ function ApiMethod(props: ApiMethodProps) {
       onConfirm={async (value) => {
         if (!value) return
         sdk.client.auth.set({
-          path: {
-            id: props.providerID,
-          },
-          body: {
+          providerID: props.providerID,
+          auth: {
             type: "api",
             key: value,
           },

+ 1 - 3
packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx

@@ -74,9 +74,7 @@ export function DialogSessionList() {
           onTrigger: async (option) => {
             if (toDelete() === option.value) {
               sdk.client.session.delete({
-                path: {
-                  id: option.value,
-                },
+                sessionID: option.value,
               })
               setToDelete(undefined)
               // dialog.clear()

+ 2 - 6
packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx

@@ -20,12 +20,8 @@ export function DialogSessionRename(props: DialogSessionRenameProps) {
       value={session()?.title}
       onConfirm={(value) => {
         sdk.client.session.update({
-          path: {
-            id: props.session,
-          },
-          body: {
-            title: value,
-          },
+          sessionID: props.session,
+          title: value,
         })
         dialog.clear()
       }}

+ 1 - 3
packages/opencode/src/cli/cmd/tui/component/dialog-tag.tsx

@@ -16,9 +16,7 @@ export function DialogTag(props: { onSelect?: (value: string) => void }) {
     () => [store.filter],
     async () => {
       const result = await sdk.client.find.files({
-        query: {
-          query: store.filter,
-        },
+        query: store.filter,
       })
       if (result.error) return []
       const sliced = (result.data ?? []).slice(0, 5)

+ 1 - 3
packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

@@ -140,9 +140,7 @@ export function Autocomplete(props: {
 
       // Get files from SDK
       const result = await sdk.client.find.files({
-        query: {
-          query: query ?? "",
-        },
+        query: query ?? "",
       })
 
       const options: AutocompleteOption[] = []

+ 1 - 1
packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx

@@ -5,7 +5,7 @@ import { createStore, produce } from "solid-js/store"
 import { clone } from "remeda"
 import { createSimpleContext } from "../../context/helper"
 import { appendFile, writeFile } from "fs/promises"
-import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk"
+import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk/v2"
 
 export type PromptInfo = {
   input: string

+ 30 - 44
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -17,7 +17,7 @@ import { useRenderer } from "@opentui/solid"
 import { Editor } from "@tui/util/editor"
 import { useExit } from "../../context/exit"
 import { Clipboard } from "../../util/clipboard"
-import type { FilePart } from "@opencode-ai/sdk"
+import type { FilePart } from "@opencode-ai/sdk/v2"
 import { TuiEvent } from "../../event"
 import { iife } from "@/util/iife"
 import { Locale } from "@/util/locale"
@@ -170,9 +170,7 @@ export function Prompt(props: PromptProps) {
 
           if (store.interrupt >= 2) {
             sdk.client.session.abort({
-              path: {
-                id: props.sessionID,
-              },
+              sessionID: props.sessionID,
             })
             setStore("interrupt", 0)
           }
@@ -447,17 +445,13 @@ export function Prompt(props: PromptProps) {
 
     if (store.mode === "shell") {
       sdk.client.session.shell({
-        path: {
-          id: sessionID,
-        },
-        body: {
-          agent: local.agent.current().name,
-          model: {
-            providerID: selectedModel.providerID,
-            modelID: selectedModel.modelID,
-          },
-          command: inputText,
+        sessionID,
+        agent: local.agent.current().name,
+        model: {
+          providerID: selectedModel.providerID,
+          modelID: selectedModel.modelID,
         },
+        command: inputText,
       })
       setStore("mode", "normal")
     } else if (
@@ -470,39 +464,31 @@ export function Prompt(props: PromptProps) {
     ) {
       let [command, ...args] = inputText.split(" ")
       sdk.client.session.command({
-        path: {
-          id: sessionID,
-        },
-        body: {
-          command: command.slice(1),
-          arguments: args.join(" "),
-          agent: local.agent.current().name,
-          model: `${selectedModel.providerID}/${selectedModel.modelID}`,
-          messageID,
-        },
+        sessionID,
+        command: command.slice(1),
+        arguments: args.join(" "),
+        agent: local.agent.current().name,
+        model: `${selectedModel.providerID}/${selectedModel.modelID}`,
+        messageID,
       })
     } else {
       sdk.client.session.prompt({
-        path: {
-          id: sessionID,
-        },
-        body: {
-          ...selectedModel,
-          messageID,
-          agent: local.agent.current().name,
-          model: selectedModel,
-          parts: [
-            {
-              id: Identifier.ascending("part"),
-              type: "text",
-              text: inputText,
-            },
-            ...nonTextParts.map((x) => ({
-              id: Identifier.ascending("part"),
-              ...x,
-            })),
-          ],
-        },
+        sessionID,
+        ...selectedModel,
+        messageID,
+        agent: local.agent.current().name,
+        model: selectedModel,
+        parts: [
+          {
+            id: Identifier.ascending("part"),
+            type: "text",
+            text: inputText,
+          },
+          ...nonTextParts.map((x) => ({
+            id: Identifier.ascending("part"),
+            ...x,
+          })),
+        ],
       })
     }
     history.append(store.prompt)

+ 1 - 1
packages/opencode/src/cli/cmd/tui/context/keybind.tsx

@@ -2,7 +2,7 @@ import { createMemo } from "solid-js"
 import { useSync } from "@tui/context/sync"
 import { Keybind } from "@/util/keybind"
 import { pipe, mapValues } from "remeda"
-import type { KeybindsConfig } from "@opencode-ai/sdk"
+import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
 import type { ParsedKey, Renderable } from "@opentui/core"
 import { createStore } from "solid-js/store"
 import { useKeyboard, useRenderer } from "@opentui/solid"

+ 7 - 4
packages/opencode/src/cli/cmd/tui/context/sdk.tsx

@@ -1,4 +1,4 @@
-import { createOpencodeClient, type Event } from "@opencode-ai/sdk"
+import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2"
 import { createSimpleContext } from "./helper"
 import { createGlobalEmitter } from "@solid-primitives/event-bus"
 import { batch, onCleanup, onMount } from "solid-js"
@@ -20,9 +20,12 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
     onMount(async () => {
       while (true) {
         if (abort.signal.aborted) break
-        const events = await sdk.event.subscribe({
-          signal: abort.signal,
-        })
+        const events = await sdk.event.subscribe(
+          {},
+          {
+            signal: abort.signal,
+          },
+        )
         let queue: Event[] = []
         let timer: Timer | undefined
         let last = 0

+ 9 - 9
packages/opencode/src/cli/cmd/tui/context/sync.tsx

@@ -15,7 +15,7 @@ import type {
   ProviderListResponse,
   ProviderAuthMethod,
   VcsInfo,
-} from "@opencode-ai/sdk"
+} from "@opencode-ai/sdk/v2"
 import { createStore, produce, reconcile } from "solid-js/store"
 import { useSDK } from "@tui/context/sdk"
 import { Binary } from "@opencode-ai/util/binary"
@@ -255,19 +255,19 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
     async function bootstrap() {
       // blocking
       await Promise.all([
-        sdk.client.config.providers({ throwOnError: true }).then((x) => {
+        sdk.client.config.providers({}, { throwOnError: true }).then((x) => {
           batch(() => {
             setStore("provider", x.data!.providers)
             setStore("provider_default", x.data!.default)
           })
         }),
-        sdk.client.provider.list({ throwOnError: true }).then((x) => {
+        sdk.client.provider.list({}, { throwOnError: true }).then((x) => {
           batch(() => {
             setStore("provider_next", x.data!)
           })
         }),
-        sdk.client.app.agents({ throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
-        sdk.client.config.get({ throwOnError: true }).then((x) => setStore("config", x.data!)),
+        sdk.client.app.agents({}, { throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
+        sdk.client.config.get({}, { throwOnError: true }).then((x) => setStore("config", x.data!)),
       ])
         .then(() => {
           if (store.status !== "complete") setStore("status", "partial")
@@ -333,10 +333,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
         async sync(sessionID: string) {
           if (fullSyncedSessions.has(sessionID)) return
           const [session, messages, todo, diff] = await Promise.all([
-            sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }),
-            sdk.client.session.messages({ path: { id: sessionID }, query: { limit: 100 } }),
-            sdk.client.session.todo({ path: { id: sessionID } }),
-            sdk.client.session.diff({ path: { id: sessionID } }),
+            sdk.client.session.get({ sessionID }, { throwOnError: true }),
+            sdk.client.session.messages({ sessionID, limit: 100 }),
+            sdk.client.session.todo({ sessionID }),
+            sdk.client.session.diff({ sessionID }),
           ])
           setStore(
             produce((draft) => {

+ 4 - 12
packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx

@@ -29,12 +29,8 @@ export function DialogMessage(props: {
             if (!msg) return
 
             sdk.client.session.revert({
-              path: {
-                id: props.sessionID,
-              },
-              body: {
-                messageID: msg.id,
-              },
+              sessionID: props.sessionID,
+              messageID: msg.id,
             })
 
             if (props.setPrompt) {
@@ -81,12 +77,8 @@ export function DialogMessage(props: {
           description: "create a new session",
           onSelect: async (dialog) => {
             const result = await sdk.client.session.fork({
-              path: {
-                id: props.sessionID,
-              },
-              body: {
-                messageID: props.messageID,
-              },
+              sessionID: props.sessionID,
+              messageID: props.messageID,
             })
             route.navigate({
               sessionID: result.data!.id,

+ 1 - 1
packages/opencode/src/cli/cmd/tui/routes/session/dialog-timeline.tsx

@@ -1,7 +1,7 @@
 import { createMemo, onMount } from "solid-js"
 import { useSync } from "@tui/context/sync"
 import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
-import type { TextPart } from "@opencode-ai/sdk"
+import type { TextPart } from "@opencode-ai/sdk/v2"
 import { Locale } from "@/util/locale"
 import { DialogMessage } from "./dialog-message"
 import { useDialog } from "../../ui/dialog"

+ 1 - 1
packages/opencode/src/cli/cmd/tui/routes/session/header.tsx

@@ -4,7 +4,7 @@ import { useSync } from "@tui/context/sync"
 import { pipe, sumBy } from "remeda"
 import { useTheme } from "@tui/context/theme"
 import { SplitBorder, EmptyBorder } from "@tui/component/border"
-import type { AssistantMessage, Session } from "@opencode-ai/sdk"
+import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2"
 import { useDirectory } from "../../context/directory"
 import { useKeybind } from "../../context/keybind"
 

+ 18 - 39
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -25,7 +25,7 @@ import {
   type ScrollAcceleration,
 } from "@opentui/core"
 import { Prompt, type PromptRef } from "@tui/component/prompt"
-import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart, ReasoningPart } from "@opencode-ai/sdk"
+import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart, ReasoningPart } from "@opencode-ai/sdk/v2"
 import { useLocal } from "@tui/context/local"
 import { Locale } from "@/util/locale"
 import type { Tool } from "@/tool/tool"
@@ -150,7 +150,8 @@ export function Session() {
       .then(() => {
         if (scroll) scroll.scrollBy(100_000)
       })
-      .catch(() => {
+      .catch((e) => {
+        console.error(e)
         toast.show({
           message: `Session not found: ${route.sessionID}`,
           variant: "error",
@@ -202,14 +203,10 @@ export function Session() {
         return
       })
       if (response) {
-        sdk.client.postSessionIdPermissionsPermissionId({
-          path: {
-            permissionID: first.id,
-            id: route.sessionID,
-          },
-          body: {
-            response: response,
-          },
+        sdk.client.permission.respond({
+          permissionID: first.id,
+          sessionID: route.sessionID,
+          response: response,
         })
       }
     }
@@ -254,9 +251,7 @@ export function Session() {
             onSelect: async (dialog: any) => {
               await sdk.client.session
                 .share({
-                  path: {
-                    id: route.sessionID,
-                  },
+                  sessionID: route.sessionID,
                 })
                 .then((res) =>
                   Clipboard.copy(res.data!.share!.url).catch(() =>
@@ -314,13 +309,9 @@ export function Session() {
           return
         }
         sdk.client.session.summarize({
-          path: {
-            id: route.sessionID,
-          },
-          body: {
-            modelID: selectedModel.modelID,
-            providerID: selectedModel.providerID,
-          },
+          sessionID: route.sessionID,
+          modelID: selectedModel.modelID,
+          providerID: selectedModel.providerID,
         })
         dialog.clear()
       },
@@ -333,9 +324,7 @@ export function Session() {
       category: "Session",
       onSelect: (dialog) => {
         sdk.client.session.unshare({
-          path: {
-            id: route.sessionID,
-          },
+          sessionID: route.sessionID,
         })
         dialog.clear()
       },
@@ -347,18 +336,14 @@ export function Session() {
       category: "Session",
       onSelect: async (dialog) => {
         const status = sync.data.session_status[route.sessionID]
-        if (status?.type !== "idle") await sdk.client.session.abort({ path: { id: route.sessionID } }).catch(() => {})
+        if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
         const revert = session().revert?.messageID
         const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user")
         if (!message) return
         sdk.client.session
           .revert({
-            path: {
-              id: route.sessionID,
-            },
-            body: {
-              messageID: message.id,
-            },
+            sessionID: route.sessionID,
+            messageID: message.id,
           })
           .then(() => {
             toBottom()
@@ -392,20 +377,14 @@ export function Session() {
         const message = messages().find((x) => x.role === "user" && x.id > messageID)
         if (!message) {
           sdk.client.session.unrevert({
-            path: {
-              id: route.sessionID,
-            },
+            sessionID: route.sessionID,
           })
           prompt.set({ input: "", parts: [] })
           return
         }
         sdk.client.session.revert({
-          path: {
-            id: route.sessionID,
-          },
-          body: {
-            messageID: message.id,
-          },
+          sessionID: route.sessionID,
+          messageID: message.id,
         })
       },
     },

+ 1 - 1
packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx

@@ -4,7 +4,7 @@ import { createStore } from "solid-js/store"
 import { useTheme } from "../../context/theme"
 import { Locale } from "@/util/locale"
 import path from "path"
-import type { AssistantMessage } from "@opencode-ai/sdk"
+import type { AssistantMessage } from "@opencode-ai/sdk/v2"
 import { Global } from "@/global"
 import { Installation } from "@/installation"
 import { useKeybind } from "../../context/keybind"

+ 8 - 4
packages/opencode/src/plugin/index.ts

@@ -61,10 +61,14 @@ export namespace Plugin {
     for (const hook of await state().then((x) => x.hooks)) {
       const fn = hook[name]
       if (!fn) continue
-      // @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
-      // give up.
-      // try-counter: 2
-      await fn(input, output)
+      try {
+        // @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
+        // give up.
+        // try-counter: 2
+        await fn(input, output)
+      } catch (e) {
+        log.error("failed to trigger hook", { name, error: e })
+      }
     }
     return output
   }

+ 102 - 100
packages/opencode/src/server/server.ts

@@ -209,7 +209,7 @@ export namespace Server {
         },
       )
       .get(
-        "/pty/:id",
+        "/pty/:ptyID",
         describeRoute({
           description: "Get PTY session info",
           operationId: "pty.get",
@@ -225,9 +225,9 @@ export namespace Server {
             ...errors(404),
           },
         }),
-        validator("param", z.object({ id: z.string() })),
+        validator("param", z.object({ ptyID: z.string() })),
         async (c) => {
-          const info = Pty.get(c.req.valid("param").id)
+          const info = Pty.get(c.req.valid("param").ptyID)
           if (!info) {
             throw new Storage.NotFoundError({ message: "Session not found" })
           }
@@ -235,7 +235,7 @@ export namespace Server {
         },
       )
       .put(
-        "/pty/:id",
+        "/pty/:ptyID",
         describeRoute({
           description: "Update PTY session",
           operationId: "pty.update",
@@ -251,15 +251,15 @@ export namespace Server {
             ...errors(400),
           },
         }),
-        validator("param", z.object({ id: z.string() })),
+        validator("param", z.object({ ptyID: z.string() })),
         validator("json", Pty.UpdateInput),
         async (c) => {
-          const info = await Pty.update(c.req.valid("param").id, c.req.valid("json"))
+          const info = await Pty.update(c.req.valid("param").ptyID, c.req.valid("json"))
           return c.json(info)
         },
       )
       .delete(
-        "/pty/:id",
+        "/pty/:ptyID",
         describeRoute({
           description: "Remove a PTY session",
           operationId: "pty.remove",
@@ -275,14 +275,14 @@ export namespace Server {
             ...errors(404),
           },
         }),
-        validator("param", z.object({ id: z.string() })),
+        validator("param", z.object({ ptyID: z.string() })),
         async (c) => {
-          await Pty.remove(c.req.valid("param").id)
+          await Pty.remove(c.req.valid("param").ptyID)
           return c.json(true)
         },
       )
       .get(
-        "/pty/:id/connect",
+        "/pty/:ptyID/connect",
         describeRoute({
           description: "Connect to a PTY session",
           operationId: "pty.connect",
@@ -298,9 +298,9 @@ export namespace Server {
             ...errors(404),
           },
         }),
-        validator("param", z.object({ id: z.string() })),
+        validator("param", z.object({ ptyID: z.string() })),
         upgradeWebSocket((c) => {
-          const id = c.req.param("id")
+          const id = c.req.param("ptyID")
           let handler: ReturnType<typeof Pty.connect>
           if (!Pty.get(id)) throw new Error("Session not found")
           return {
@@ -557,7 +557,7 @@ export namespace Server {
         },
       )
       .get(
-        "/session/:id",
+        "/session/:sessionID",
         describeRoute({
           description: "Get session",
           operationId: "session.get",
@@ -576,17 +576,18 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: Session.get.schema,
+            sessionID: Session.get.schema,
           }),
         ),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
+          log.info("SEARCH", { url: c.req.url })
           const session = await Session.get(sessionID)
           return c.json(session)
         },
       )
       .get(
-        "/session/:id/children",
+        "/session/:sessionID/children",
         describeRoute({
           description: "Get a session's children",
           operationId: "session.children",
@@ -605,17 +606,17 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: Session.children.schema,
+            sessionID: Session.children.schema,
           }),
         ),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const session = await Session.children(sessionID)
           return c.json(session)
         },
       )
       .get(
-        "/session/:id/todo",
+        "/session/:sessionID/todo",
         describeRoute({
           description: "Get the todo list for a session",
           operationId: "session.todo",
@@ -634,11 +635,11 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const todos = await Todo.get(sessionID)
           return c.json(todos)
         },
@@ -668,7 +669,7 @@ export namespace Server {
         },
       )
       .delete(
-        "/session/:id",
+        "/session/:sessionID",
         describeRoute({
           description: "Delete a session and all its data",
           operationId: "session.delete",
@@ -687,11 +688,11 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: Session.remove.schema,
+            sessionID: Session.remove.schema,
           }),
         ),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           await Session.remove(sessionID)
           await Bus.publish(TuiEvent.CommandExecute, {
             command: "session.list",
@@ -700,7 +701,7 @@ export namespace Server {
         },
       )
       .patch(
-        "/session/:id",
+        "/session/:sessionID",
         describeRoute({
           description: "Update session properties",
           operationId: "session.update",
@@ -719,7 +720,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string(),
+            sessionID: z.string(),
           }),
         ),
         validator(
@@ -729,7 +730,7 @@ export namespace Server {
           }),
         ),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const updates = c.req.valid("json")
 
           const updatedSession = await Session.update(sessionID, (session) => {
@@ -742,7 +743,7 @@ export namespace Server {
         },
       )
       .post(
-        "/session/:id/init",
+        "/session/:sessionID/init",
         describeRoute({
           description: "Analyze the app and create an AGENTS.md file",
           operationId: "session.init",
@@ -761,19 +762,19 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         validator("json", Session.initialize.schema.omit({ sessionID: true })),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const body = c.req.valid("json")
           await Session.initialize({ ...body, sessionID })
           return c.json(true)
         },
       )
       .post(
-        "/session/:id/fork",
+        "/session/:sessionID/fork",
         describeRoute({
           description: "Fork an existing session at a specific message",
           operationId: "session.fork",
@@ -791,19 +792,19 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: Session.fork.schema.shape.sessionID,
+            sessionID: Session.fork.schema.shape.sessionID,
           }),
         ),
         validator("json", Session.fork.schema.omit({ sessionID: true })),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const body = c.req.valid("json")
           const result = await Session.fork({ ...body, sessionID })
           return c.json(result)
         },
       )
       .post(
-        "/session/:id/abort",
+        "/session/:sessionID/abort",
         describeRoute({
           description: "Abort a session",
           operationId: "session.abort",
@@ -822,16 +823,16 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string(),
+            sessionID: z.string(),
           }),
         ),
         async (c) => {
-          SessionPrompt.cancel(c.req.valid("param").id)
+          SessionPrompt.cancel(c.req.valid("param").sessionID)
           return c.json(true)
         },
       )
       .post(
-        "/session/:id/share",
+        "/session/:sessionID/share",
         describeRoute({
           description: "Share a session",
           operationId: "session.share",
@@ -850,18 +851,18 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string(),
+            sessionID: z.string(),
           }),
         ),
         async (c) => {
-          const id = c.req.valid("param").id
-          await Session.share(id)
-          const session = await Session.get(id)
+          const sessionID = c.req.valid("param").sessionID
+          await Session.share(sessionID)
+          const session = await Session.get(sessionID)
           return c.json(session)
         },
       )
       .get(
-        "/session/:id/diff",
+        "/session/:sessionID/diff",
         describeRoute({
           description: "Get the diff that resulted from this user message",
           operationId: "session.diff",
@@ -879,7 +880,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: SessionSummary.diff.schema.shape.sessionID,
+            sessionID: SessionSummary.diff.schema.shape.sessionID,
           }),
         ),
         validator(
@@ -892,14 +893,14 @@ export namespace Server {
           const query = c.req.valid("query")
           const params = c.req.valid("param")
           const result = await SessionSummary.diff({
-            sessionID: params.id,
+            sessionID: params.sessionID,
             messageID: query.messageID,
           })
           return c.json(result)
         },
       )
       .delete(
-        "/session/:id/share",
+        "/session/:sessionID/share",
         describeRoute({
           description: "Unshare the session",
           operationId: "session.unshare",
@@ -918,18 +919,18 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: Session.unshare.schema,
+            sessionID: Session.unshare.schema,
           }),
         ),
         async (c) => {
-          const id = c.req.valid("param").id
-          await Session.unshare(id)
-          const session = await Session.get(id)
+          const sessionID = c.req.valid("param").sessionID
+          await Session.unshare(sessionID)
+          const session = await Session.get(sessionID)
           return c.json(session)
         },
       )
       .post(
-        "/session/:id/summarize",
+        "/session/:sessionID/summarize",
         describeRoute({
           description: "Summarize the session",
           operationId: "session.summarize",
@@ -948,7 +949,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         validator(
@@ -959,9 +960,9 @@ export namespace Server {
           }),
         ),
         async (c) => {
-          const id = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const body = c.req.valid("json")
-          const msgs = await Session.messages({ sessionID: id })
+          const msgs = await Session.messages({ sessionID })
           let currentAgent = "build"
           for (let i = msgs.length - 1; i >= 0; i--) {
             const info = msgs[i].info
@@ -971,7 +972,7 @@ export namespace Server {
             }
           }
           await SessionCompaction.create({
-            sessionID: id,
+            sessionID,
             agent: currentAgent,
             model: {
               providerID: body.providerID,
@@ -979,12 +980,12 @@ export namespace Server {
             },
             auto: false,
           })
-          await SessionPrompt.loop(id)
+          await SessionPrompt.loop(sessionID)
           return c.json(true)
         },
       )
       .get(
-        "/session/:id/message",
+        "/session/:sessionID/message",
         describeRoute({
           description: "List messages for a session",
           operationId: "session.messages",
@@ -1003,7 +1004,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         validator(
@@ -1015,14 +1016,14 @@ export namespace Server {
         async (c) => {
           const query = c.req.valid("query")
           const messages = await Session.messages({
-            sessionID: c.req.valid("param").id,
+            sessionID: c.req.valid("param").sessionID,
             limit: query.limit,
           })
           return c.json(messages)
         },
       )
       .get(
-        "/session/:id/diff",
+        "/session/:sessionID/diff",
         describeRoute({
           description: "Get the diff for this session",
           operationId: "session.diff",
@@ -1041,16 +1042,16 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         async (c) => {
-          const diff = await Session.diff(c.req.valid("param").id)
+          const diff = await Session.diff(c.req.valid("param").sessionID)
           return c.json(diff)
         },
       )
       .get(
-        "/session/:id/message/:messageID",
+        "/session/:sessionID/message/:messageID",
         describeRoute({
           description: "Get a message from a session",
           operationId: "session.message",
@@ -1074,21 +1075,21 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
             messageID: z.string().meta({ description: "Message ID" }),
           }),
         ),
         async (c) => {
           const params = c.req.valid("param")
           const message = await MessageV2.get({
-            sessionID: params.id,
+            sessionID: params.sessionID,
             messageID: params.messageID,
           })
           return c.json(message)
         },
       )
       .post(
-        "/session/:id/message",
+        "/session/:sessionID/message",
         describeRoute({
           description: "Create and send a new message to a session",
           operationId: "session.prompt",
@@ -1112,7 +1113,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
@@ -1120,7 +1121,7 @@ export namespace Server {
           c.status(200)
           c.header("Content-Type", "application/json")
           return stream(c, async (stream) => {
-            const sessionID = c.req.valid("param").id
+            const sessionID = c.req.valid("param").sessionID
             const body = c.req.valid("json")
             const msg = await SessionPrompt.prompt({ ...body, sessionID })
             stream.write(JSON.stringify(msg))
@@ -1128,7 +1129,7 @@ export namespace Server {
         },
       )
       .post(
-        "/session/:id/prompt_async",
+        "/session/:sessionID/prompt_async",
         describeRoute({
           description: "Create and send a new message to a session, start if needed and return immediately",
           operationId: "session.prompt_async",
@@ -1142,7 +1143,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
@@ -1150,14 +1151,14 @@ export namespace Server {
           c.status(204)
           c.header("Content-Type", "application/json")
           return stream(c, async () => {
-            const sessionID = c.req.valid("param").id
+            const sessionID = c.req.valid("param").sessionID
             const body = c.req.valid("json")
             SessionPrompt.prompt({ ...body, sessionID })
           })
         },
       )
       .post(
-        "/session/:id/command",
+        "/session/:sessionID/command",
         describeRoute({
           description: "Send a new command to a session",
           operationId: "session.command",
@@ -1181,19 +1182,19 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const body = c.req.valid("json")
           const msg = await SessionPrompt.command({ ...body, sessionID })
           return c.json(msg)
         },
       )
       .post(
-        "/session/:id/shell",
+        "/session/:sessionID/shell",
         describeRoute({
           description: "Run a shell command",
           operationId: "session.shell",
@@ -1212,19 +1213,19 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Session ID" }),
+            sessionID: z.string().meta({ description: "Session ID" }),
           }),
         ),
         validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
         async (c) => {
-          const sessionID = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           const body = c.req.valid("json")
           const msg = await SessionPrompt.shell({ ...body, sessionID })
           return c.json(msg)
         },
       )
       .post(
-        "/session/:id/revert",
+        "/session/:sessionID/revert",
         describeRoute({
           description: "Revert a message",
           operationId: "session.revert",
@@ -1243,22 +1244,22 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string(),
+            sessionID: z.string(),
           }),
         ),
         validator("json", SessionRevert.RevertInput.omit({ sessionID: true })),
         async (c) => {
-          const id = c.req.valid("param").id
+          const sessionID = c.req.valid("param").sessionID
           log.info("revert", c.req.valid("json"))
           const session = await SessionRevert.revert({
-            sessionID: id,
+            sessionID,
             ...c.req.valid("json"),
           })
           return c.json(session)
         },
       )
       .post(
-        "/session/:id/unrevert",
+        "/session/:sessionID/unrevert",
         describeRoute({
           description: "Restore all reverted messages",
           operationId: "session.unrevert",
@@ -1277,19 +1278,20 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string(),
+            sessionID: z.string(),
           }),
         ),
         async (c) => {
-          const id = c.req.valid("param").id
-          const session = await SessionRevert.unrevert({ sessionID: id })
+          const sessionID = c.req.valid("param").sessionID
+          const session = await SessionRevert.unrevert({ sessionID })
           return c.json(session)
         },
       )
       .post(
-        "/session/:id/permissions/:permissionID",
+        "/session/:sessionID/permissions/:permissionID",
         describeRoute({
           description: "Respond to a permission request",
+          operationId: "permission.respond",
           responses: {
             200: {
               description: "Permission processed successfully",
@@ -1305,17 +1307,17 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string(),
+            sessionID: z.string(),
             permissionID: z.string(),
           }),
         ),
         validator("json", z.object({ response: Permission.Response })),
         async (c) => {
           const params = c.req.valid("param")
-          const id = params.id
+          const sessionID = params.sessionID
           const permissionID = params.permissionID
           Permission.respond({
-            sessionID: id,
+            sessionID,
             permissionID,
             response: c.req.valid("json").response,
           })
@@ -1429,7 +1431,7 @@ export namespace Server {
         },
       )
       .post(
-        "/provider/:id/oauth/authorize",
+        "/provider/:providerID/oauth/authorize",
         describeRoute({
           description: "Authorize a provider using OAuth",
           operationId: "provider.oauth.authorize",
@@ -1448,7 +1450,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Provider ID" }),
+            providerID: z.string().meta({ description: "Provider ID" }),
           }),
         ),
         validator(
@@ -1458,17 +1460,17 @@ export namespace Server {
           }),
         ),
         async (c) => {
-          const id = c.req.valid("param").id
+          const providerID = c.req.valid("param").providerID
           const { method } = c.req.valid("json")
           const result = await ProviderAuth.authorize({
-            providerID: id,
+            providerID,
             method,
           })
           return c.json(result)
         },
       )
       .post(
-        "/provider/:id/oauth/callback",
+        "/provider/:providerID/oauth/callback",
         describeRoute({
           description: "Handle OAuth callback for a provider",
           operationId: "provider.oauth.callback",
@@ -1487,7 +1489,7 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string().meta({ description: "Provider ID" }),
+            providerID: z.string().meta({ description: "Provider ID" }),
           }),
         ),
         validator(
@@ -1498,10 +1500,10 @@ export namespace Server {
           }),
         ),
         async (c) => {
-          const id = c.req.valid("param").id
+          const providerID = c.req.valid("param").providerID
           const { method, code } = c.req.valid("json")
           await ProviderAuth.callback({
-            providerID: id,
+            providerID,
             method,
             code,
           })
@@ -2215,7 +2217,7 @@ export namespace Server {
       )
       .route("/tui/control", TuiRoute)
       .put(
-        "/auth/:id",
+        "/auth/:providerID",
         describeRoute({
           description: "Set authentication credentials",
           operationId: "auth.set",
@@ -2234,14 +2236,14 @@ export namespace Server {
         validator(
           "param",
           z.object({
-            id: z.string(),
+            providerID: z.string(),
           }),
         ),
         validator("json", Auth.Info),
         async (c) => {
-          const id = c.req.valid("param").id
+          const providerID = c.req.valid("param").providerID
           const info = c.req.valid("json")
-          await Auth.set(id, info)
+          await Auth.set(providerID, info)
           return c.json(true)
         },
       )

+ 1 - 1
packages/opencode/src/share/share-next.ts

@@ -6,7 +6,7 @@ import { Session } from "@/session"
 import { MessageV2 } from "@/session/message-v2"
 import { Storage } from "@/storage/storage"
 import { Log } from "@/util/log"
-import type * as SDK from "@opencode-ai/sdk"
+import type * as SDK from "@opencode-ai/sdk/v2"
 
 export namespace ShareNext {
   const log = Log.create({ service: "share-next" })

+ 51 - 51
packages/sdk/js/openapi.json

@@ -181,7 +181,7 @@
         }
       }
     },
-    "/pty/{id}": {
+    "/pty/{ptyID}": {
       "get": {
         "operationId": "pty.get",
         "parameters": [
@@ -194,7 +194,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "ptyID",
             "schema": {
               "type": "string"
             },
@@ -237,7 +237,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "ptyID",
             "schema": {
               "type": "string"
             },
@@ -306,7 +306,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "ptyID",
             "schema": {
               "type": "string"
             },
@@ -338,7 +338,7 @@
         }
       }
     },
-    "/pty/{id}/connect": {
+    "/pty/{ptyID}/connect": {
       "get": {
         "operationId": "pty.connect",
         "parameters": [
@@ -351,7 +351,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "ptyID",
             "schema": {
               "type": "string"
             },
@@ -751,7 +751,7 @@
         }
       }
     },
-    "/session/{id}": {
+    "/session/{sessionID}": {
       "get": {
         "operationId": "session.get",
         "parameters": [
@@ -764,7 +764,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string",
               "pattern": "^ses.*"
@@ -818,7 +818,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string",
               "pattern": "^ses.*"
@@ -872,7 +872,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -928,7 +928,7 @@
         }
       }
     },
-    "/session/{id}/children": {
+    "/session/{sessionID}/children": {
       "get": {
         "operationId": "session.children",
         "parameters": [
@@ -941,7 +941,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string",
               "pattern": "^ses.*"
@@ -987,7 +987,7 @@
         }
       }
     },
-    "/session/{id}/todo": {
+    "/session/{sessionID}/todo": {
       "get": {
         "operationId": "session.todo",
         "parameters": [
@@ -1000,7 +1000,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1046,7 +1046,7 @@
         }
       }
     },
-    "/session/{id}/init": {
+    "/session/{sessionID}/init": {
       "post": {
         "operationId": "session.init",
         "parameters": [
@@ -1059,7 +1059,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1124,7 +1124,7 @@
         }
       }
     },
-    "/session/{id}/fork": {
+    "/session/{sessionID}/fork": {
       "post": {
         "operationId": "session.fork",
         "parameters": [
@@ -1137,7 +1137,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string",
               "pattern": "^ses.*"
@@ -1175,7 +1175,7 @@
         }
       }
     },
-    "/session/{id}/abort": {
+    "/session/{sessionID}/abort": {
       "post": {
         "operationId": "session.abort",
         "parameters": [
@@ -1188,7 +1188,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1230,7 +1230,7 @@
         }
       }
     },
-    "/session/{id}/share": {
+    "/session/{sessionID}/share": {
       "post": {
         "operationId": "session.share",
         "parameters": [
@@ -1243,7 +1243,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1296,7 +1296,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string",
               "pattern": "^ses.*"
@@ -1339,7 +1339,7 @@
         }
       }
     },
-    "/session/{id}/diff": {
+    "/session/{sessionID}/diff": {
       "get": {
         "operationId": "session.diff",
         "parameters": [
@@ -1352,7 +1352,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1406,7 +1406,7 @@
         }
       }
     },
-    "/session/{id}/summarize": {
+    "/session/{sessionID}/summarize": {
       "post": {
         "operationId": "session.summarize",
         "parameters": [
@@ -1419,7 +1419,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1480,7 +1480,7 @@
         }
       }
     },
-    "/session/{id}/message": {
+    "/session/{sessionID}/message": {
       "get": {
         "operationId": "session.messages",
         "parameters": [
@@ -1493,7 +1493,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1569,7 +1569,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1689,7 +1689,7 @@
         }
       }
     },
-    "/session/{id}/message/{messageID}": {
+    "/session/{sessionID}/message/{messageID}": {
       "get": {
         "operationId": "session.message",
         "parameters": [
@@ -1702,7 +1702,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1766,7 +1766,7 @@
         }
       }
     },
-    "/session/{id}/prompt_async": {
+    "/session/{sessionID}/prompt_async": {
       "post": {
         "operationId": "session.prompt_async",
         "parameters": [
@@ -1779,7 +1779,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1880,7 +1880,7 @@
         }
       }
     },
-    "/session/{id}/command": {
+    "/session/{sessionID}/command": {
       "post": {
         "operationId": "session.command",
         "parameters": [
@@ -1893,7 +1893,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -1976,7 +1976,7 @@
         }
       }
     },
-    "/session/{id}/shell": {
+    "/session/{sessionID}/shell": {
       "post": {
         "operationId": "session.shell",
         "parameters": [
@@ -1989,7 +1989,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -2062,7 +2062,7 @@
         }
       }
     },
-    "/session/{id}/revert": {
+    "/session/{sessionID}/revert": {
       "post": {
         "operationId": "session.revert",
         "parameters": [
@@ -2075,7 +2075,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -2137,7 +2137,7 @@
         }
       }
     },
-    "/session/{id}/unrevert": {
+    "/session/{sessionID}/unrevert": {
       "post": {
         "operationId": "session.unrevert",
         "parameters": [
@@ -2150,7 +2150,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -2192,9 +2192,9 @@
         }
       }
     },
-    "/session/{id}/permissions/{permissionID}": {
+    "/session/{sessionID}/permissions/{permissionID}": {
       "post": {
-        "operationId": "postSession:idPermissions:permissionID",
+        "operationId": "permission.respond",
         "parameters": [
           {
             "in": "query",
@@ -2205,7 +2205,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "sessionID",
             "schema": {
               "type": "string"
             },
@@ -2597,7 +2597,7 @@
         }
       }
     },
-    "/provider/{id}/oauth/authorize": {
+    "/provider/{providerID}/oauth/authorize": {
       "post": {
         "operationId": "provider.oauth.authorize",
         "parameters": [
@@ -2610,7 +2610,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "providerID",
             "schema": {
               "type": "string"
             },
@@ -2659,7 +2659,7 @@
         }
       }
     },
-    "/provider/{id}/oauth/callback": {
+    "/provider/{providerID}/oauth/callback": {
       "post": {
         "operationId": "provider.oauth.callback",
         "parameters": [
@@ -2672,7 +2672,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "providerID",
             "schema": {
               "type": "string"
             },
@@ -3942,7 +3942,7 @@
         }
       }
     },
-    "/auth/{id}": {
+    "/auth/{providerID}": {
       "put": {
         "operationId": "auth.set",
         "parameters": [
@@ -3955,7 +3955,7 @@
           },
           {
             "in": "path",
-            "name": "id",
+            "name": "providerID",
             "schema": {
               "type": "string"
             },

+ 5 - 2
packages/sdk/js/package.json

@@ -10,13 +10,16 @@
   "exports": {
     ".": "./src/index.ts",
     "./client": "./src/client.ts",
-    "./server": "./src/server.ts"
+    "./server": "./src/server.ts",
+    "./v2": "./src/v2/index.ts",
+    "./v2/client": "./src/v2/client.ts",
+    "./v2/server": "./src/v2/server.ts"
   },
   "files": [
     "dist"
   ],
   "devDependencies": {
-    "@hey-api/openapi-ts": "0.81.0",
+    "@hey-api/openapi-ts": "0.88.0",
     "@tsconfig/node22": "catalog:",
     "@types/node": "catalog:",
     "typescript": "catalog:",

+ 28 - 0
packages/sdk/js/script/build.ts

@@ -17,6 +17,7 @@ await createClient({
   output: {
     path: "./src/gen",
     tsConfigPath: path.join(dir, "tsconfig.json"),
+    clean: true,
   },
   plugins: [
     {
@@ -36,6 +37,33 @@ await createClient({
     },
   ],
 })
+await createClient({
+  input: "./openapi.json",
+  output: {
+    path: "./src/v2/gen",
+    tsConfigPath: path.join(dir, "tsconfig.json"),
+    clean: true,
+  },
+  plugins: [
+    {
+      name: "@hey-api/typescript",
+      exportFromIndex: false,
+    },
+    {
+      name: "@hey-api/sdk",
+      instance: "OpencodeClient",
+      exportFromIndex: false,
+      auth: false,
+      paramsStructure: "flat",
+    },
+    {
+      name: "@hey-api/client-fetch",
+      exportFromIndex: false,
+      baseUrl: "http://localhost:4096",
+    },
+  ],
+})
+
 await $`bun prettier --write src/gen`
 await $`rm -rf dist`
 await $`bun tsc`

+ 7 - 6
packages/sdk/js/src/client.ts

@@ -1,19 +1,20 @@
 export * from "./gen/types.gen.js"
-export { type Config as OpencodeClientConfig, OpencodeClient }
 
 import { createClient } from "./gen/client/client.gen.js"
 import { type Config } from "./gen/client/types.gen.js"
 import { OpencodeClient } from "./gen/sdk.gen.js"
+export { type Config as OpencodeClientConfig, OpencodeClient }
 
 export function createOpencodeClient(config?: Config & { directory?: string }) {
   if (!config?.fetch) {
+    const customFetch: any = (req: any) => {
+      // @ts-ignore
+      req.timeout = false
+      return fetch(req)
+    }
     config = {
       ...config,
-      fetch: (req) => {
-        // @ts-ignore
-        req.timeout = false
-        return fetch(req)
-      },
+      fetch: customFetch,
     }
   }
 

+ 6 - 10
packages/sdk/js/src/gen/client.gen.ts

@@ -1,7 +1,7 @@
 // This file is auto-generated by @hey-api/openapi-ts
 
-import type { ClientOptions } from "./types.gen.js"
-import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from "./client/index.js"
+import { type ClientOptions, type Config, createClient, createConfig } from "./client/index.js"
+import type { ClientOptions as ClientOptions2 } from "./types.gen.js"
 
 /**
  * The `createClientConfig()` function will be called on client initialization
@@ -11,12 +11,8 @@ import { type Config, type ClientOptions as DefaultClientOptions, createClient,
  * `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 type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (
+  override?: Config<ClientOptions & T>,
+) => Config<Required<ClientOptions> & T>
 
-export const client = createClient(
-  createConfig<ClientOptions>({
-    baseUrl: "http://localhost:4096",
-  }),
-)
+export const client = createClient(createConfig<ClientOptions2>({ baseUrl: "http://localhost:4096" }))

+ 100 - 34
packages/sdk/js/src/gen/client/client.gen.ts

@@ -1,6 +1,8 @@
 // This file is auto-generated by @hey-api/openapi-ts
 
 import { createSseClient } from "../core/serverSentEvents.gen.js"
+import type { HttpMethod } from "../core/types.gen.js"
+import { getValidRequestBody } from "../core/utils.gen.js"
 import type { Client, Config, RequestOptions, ResolvedRequestOptions } from "./types.gen.js"
 import {
   buildUrl,
@@ -49,12 +51,12 @@ export const createClient = (config: Config = {}): Client => {
       await opts.requestValidator(opts)
     }
 
-    if (opts.body && opts.bodySerializer) {
+    if (opts.body !== undefined && opts.bodySerializer) {
       opts.serializedBody = opts.bodySerializer(opts.body)
     }
 
     // remove Content-Type header if body is empty to avoid sending invalid requests
-    if (opts.serializedBody === undefined || opts.serializedBody === "") {
+    if (opts.body === undefined || opts.serializedBody === "") {
       opts.headers.delete("Content-Type")
     }
 
@@ -69,12 +71,12 @@ export const createClient = (config: Config = {}): Client => {
     const requestInit: ReqInit = {
       redirect: "follow",
       ...opts,
-      body: opts.serializedBody,
+      body: getValidRequestBody(opts),
     }
 
     let request = new Request(url, requestInit)
 
-    for (const fn of interceptors.request._fns) {
+    for (const fn of interceptors.request.fns) {
       if (fn) {
         request = await fn(request, opts)
       }
@@ -83,9 +85,37 @@ export const createClient = (config: Config = {}): Client => {
     // 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)
+    let response: Response
 
-    for (const fn of interceptors.response._fns) {
+    try {
+      response = await _fetch(request)
+    } catch (error) {
+      // Handle fetch exceptions (AbortError, network errors, etc.)
+      let finalError = error
+
+      for (const fn of interceptors.error.fns) {
+        if (fn) {
+          finalError = (await fn(error, undefined as any, request, opts)) as unknown
+        }
+      }
+
+      finalError = finalError || ({} as unknown)
+
+      if (opts.throwOnError) {
+        throw finalError
+      }
+
+      // Return error response
+      return opts.responseStyle === "data"
+        ? undefined
+        : {
+            error: finalError,
+            request,
+            response: undefined as any,
+          }
+    }
+
+    for (const fn of interceptors.response.fns) {
       if (fn) {
         response = await fn(response, request, opts)
       }
@@ -97,18 +127,36 @@ export const createClient = (config: Config = {}): Client => {
     }
 
     if (response.ok) {
+      const parseAs =
+        (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json"
+
       if (response.status === 204 || response.headers.get("Content-Length") === "0") {
+        let emptyData: any
+        switch (parseAs) {
+          case "arrayBuffer":
+          case "blob":
+          case "text":
+            emptyData = await response[parseAs]()
+            break
+          case "formData":
+            emptyData = new FormData()
+            break
+          case "stream":
+            emptyData = response.body
+            break
+          case "json":
+          default:
+            emptyData = {}
+            break
+        }
         return opts.responseStyle === "data"
-          ? {}
+          ? emptyData
           : {
-              data: {},
+              data: emptyData,
               ...result,
             }
       }
 
-      const parseAs =
-        (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json"
-
       let data: any
       switch (parseAs) {
         case "arrayBuffer":
@@ -157,7 +205,7 @@ export const createClient = (config: Config = {}): Client => {
     const error = jsonError ?? textError
     let finalError = error
 
-    for (const fn of interceptors.error._fns) {
+    for (const fn of interceptors.error.fns) {
       if (fn) {
         finalError = (await fn(error, response, request, opts)) as string
       }
@@ -178,35 +226,53 @@ export const createClient = (config: Config = {}): Client => {
         }
   }
 
-  const makeMethod = (method: Required<Config>["method"]) => {
-    const fn = (options: RequestOptions) => request({ ...options, method })
-    fn.sse = async (options: RequestOptions) => {
-      const { opts, url } = await beforeRequest(options)
-      return createSseClient({
-        ...opts,
-        body: opts.body as BodyInit | null | undefined,
-        headers: opts.headers as unknown as Record<string, string>,
-        method,
-        url,
-      })
-    }
-    return fn
+  const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) => request({ ...options, method })
+
+  const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
+    const { opts, url } = await beforeRequest(options)
+    return createSseClient({
+      ...opts,
+      body: opts.body as BodyInit | null | undefined,
+      headers: opts.headers as unknown as Record<string, string>,
+      method,
+      onRequest: async (url, init) => {
+        let request = new Request(url, init)
+        for (const fn of interceptors.request.fns) {
+          if (fn) {
+            request = await fn(request, opts)
+          }
+        }
+        return request
+      },
+      url,
+    })
   }
 
   return {
     buildUrl,
-    connect: makeMethod("CONNECT"),
-    delete: makeMethod("DELETE"),
-    get: makeMethod("GET"),
+    connect: makeMethodFn("CONNECT"),
+    delete: makeMethodFn("DELETE"),
+    get: makeMethodFn("GET"),
     getConfig,
-    head: makeMethod("HEAD"),
+    head: makeMethodFn("HEAD"),
     interceptors,
-    options: makeMethod("OPTIONS"),
-    patch: makeMethod("PATCH"),
-    post: makeMethod("POST"),
-    put: makeMethod("PUT"),
+    options: makeMethodFn("OPTIONS"),
+    patch: makeMethodFn("PATCH"),
+    post: makeMethodFn("POST"),
+    put: makeMethodFn("PUT"),
     request,
     setConfig,
-    trace: makeMethod("TRACE"),
+    sse: {
+      connect: makeSseFn("CONNECT"),
+      delete: makeSseFn("DELETE"),
+      get: makeSseFn("GET"),
+      head: makeSseFn("HEAD"),
+      options: makeSseFn("OPTIONS"),
+      patch: makeSseFn("PATCH"),
+      post: makeSseFn("POST"),
+      put: makeSseFn("PUT"),
+      trace: makeSseFn("TRACE"),
+    },
+    trace: makeMethodFn("TRACE"),
   } as Client
 }

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

@@ -8,6 +8,7 @@ export {
   urlSearchParamsBodySerializer,
 } from "../core/bodySerializer.gen.js"
 export { buildClientParams } from "../core/params.gen.js"
+export { serializeQueryKeyValue } from "../core/queryKeySerializer.gen.js"
 export { createClient } from "./client.gen.js"
 export type {
   Client,
@@ -15,7 +16,6 @@ export type {
   Config,
   CreateClientConfig,
   Options,
-  OptionsLegacyParser,
   RequestOptions,
   RequestResult,
   ResolvedRequestOptions,

+ 6 - 26
packages/sdk/js/src/gen/client/types.gen.ts

@@ -20,7 +20,7 @@ export interface Config<T extends ClientOptions = ClientOptions>
    *
    * @default globalThis.fetch
    */
-  fetch?: (request: Request) => ReturnType<typeof fetch>
+  fetch?: typeof fetch
   /**
    * Please don't use the Fetch client for Next.js applications. The `next`
    * options won't have any effect.
@@ -128,7 +128,7 @@ export interface ClientOptions {
   throwOnError?: boolean
 }
 
-type MethodFnBase = <
+type MethodFn = <
   TData = unknown,
   TError = unknown,
   ThrowOnError extends boolean = false,
@@ -137,7 +137,7 @@ type MethodFnBase = <
   options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
 ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
 
-type MethodFnServerSentEvents = <
+type SseFn = <
   TData = unknown,
   TError = unknown,
   ThrowOnError extends boolean = false,
@@ -146,10 +146,6 @@ type MethodFnServerSentEvents = <
   options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
 ) => Promise<ServerSentEventsResult<TData, TError>>
 
-type MethodFn = MethodFnBase & {
-  sse: MethodFnServerSentEvents
-}
-
 type RequestFn = <
   TData = unknown,
   TError = unknown,
@@ -168,10 +164,10 @@ type BuildUrlFn = <
     url: string
   },
 >(
-  options: Pick<TData, "url"> & Options<TData>,
+  options: TData & Options<TData>,
 ) => string
 
-export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
+export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
   interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>
 }
 
@@ -203,20 +199,4 @@ export type Options<
   TResponse = unknown,
   TResponseStyle extends ResponseStyle = "fields",
 > = OmitKeys<RequestOptions<TResponse, 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<unknown, TResponseStyle, ThrowOnError>, "body" | "headers" | "url"> & TData
-    : OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, "body" | "url"> &
-        TData &
-        Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, "headers">
-  : TData extends { headers?: any }
-    ? OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, "headers" | "url"> &
-        TData &
-        Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, "body">
-    : OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, "url"> & TData
+  ([TData] extends [never] ? unknown : Omit<TData, "url">)

+ 43 - 41
packages/sdk/js/src/gen/client/utils.gen.ts

@@ -7,7 +7,7 @@ import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } fr
 import { getUrl } from "../core/utils.gen.js"
 import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen.js"
 
-export const createQuerySerializer = <T = unknown>({ allowReserved, array, object }: QuerySerializerOptions = {}) => {
+export const createQuerySerializer = <T = unknown>({ parameters = {}, ...args }: QuerySerializerOptions = {}) => {
   const querySerializer = (queryParams: T) => {
     const search: string[] = []
     if (queryParams && typeof queryParams === "object") {
@@ -18,29 +18,31 @@ export const createQuerySerializer = <T = unknown>({ allowReserved, array, objec
           continue
         }
 
+        const options = parameters[name] || args
+
         if (Array.isArray(value)) {
           const serializedArray = serializeArrayParam({
-            allowReserved,
+            allowReserved: options.allowReserved,
             explode: true,
             name,
             style: "form",
             value,
-            ...array,
+            ...options.array,
           })
           if (serializedArray) search.push(serializedArray)
         } else if (typeof value === "object") {
           const serializedObject = serializeObjectParam({
-            allowReserved,
+            allowReserved: options.allowReserved,
             explode: true,
             name,
             style: "deepObject",
             value: value as Record<string, unknown>,
-            ...object,
+            ...options.object,
           })
           if (serializedObject) search.push(serializedObject)
         } else {
           const serializedPrimitive = serializePrimitiveParam({
-            allowReserved,
+            allowReserved: options.allowReserved,
             name,
             value: value as string,
           })
@@ -162,14 +164,22 @@ export const mergeConfigs = (a: Config, b: Config): Config => {
   return config
 }
 
+const headersEntries = (headers: Headers): Array<[string, string]> => {
+  const entries: Array<[string, string]> = []
+  headers.forEach((value, key) => {
+    entries.push([key, value])
+  })
+  return entries
+}
+
 export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
   const mergedHeaders = new Headers()
   for (const header of headers) {
-    if (!header || typeof header !== "object") {
+    if (!header) {
       continue
     }
 
-    const iterator = header instanceof Headers ? header.entries() : Object.entries(header)
+    const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header)
 
     for (const [key, value] of iterator) {
       if (value === null) {
@@ -200,61 +210,53 @@ type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Pr
 type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>
 
 class Interceptors<Interceptor> {
-  _fns: (Interceptor | null)[]
+  fns: Array<Interceptor | null> = []
 
-  constructor() {
-    this._fns = []
+  clear(): void {
+    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)
+  eject(id: number | Interceptor): void {
+    const index = this.getInterceptorIndex(id)
+    if (this.fns[index]) {
+      this.fns[index] = null
     }
   }
-  exists(id: number | Interceptor) {
+
+  exists(id: number | Interceptor): boolean {
     const index = this.getInterceptorIndex(id)
-    return !!this._fns[index]
+    return Boolean(this.fns[index])
   }
 
-  eject(id: number | Interceptor) {
-    const index = this.getInterceptorIndex(id)
-    if (this._fns[index]) {
-      this._fns[index] = null
+  getInterceptorIndex(id: number | Interceptor): number {
+    if (typeof id === "number") {
+      return this.fns[id] ? id : -1
     }
+    return this.fns.indexOf(id)
   }
 
-  update(id: number | Interceptor, fn: Interceptor) {
+  update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false {
     const index = this.getInterceptorIndex(id)
-    if (this._fns[index]) {
-      this._fns[index] = fn
+    if (this.fns[index]) {
+      this.fns[index] = fn
       return id
-    } else {
-      return false
     }
+    return false
   }
 
-  use(fn: Interceptor) {
-    this._fns = [...this._fns, fn]
-    return this._fns.length - 1
+  use(fn: Interceptor): number {
+    this.fns.push(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">
+  error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>
+  request: Interceptors<ReqInterceptor<Req, Options>>
+  response: Interceptors<ResInterceptor<Res, Req, Options>>
 }
 
-// do not add `Middleware` as return type so we can use _fns internally
-export const createInterceptors = <Req, Res, Err, Options>() => ({
+export const createInterceptors = <Req, Res, Err, Options>(): Middleware<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>>(),

+ 11 - 3
packages/sdk/js/src/gen/core/bodySerializer.gen.ts

@@ -6,10 +6,18 @@ export type QuerySerializer = (query: Record<string, unknown>) => string
 
 export type BodySerializer = (body: any) => any
 
-export interface QuerySerializerOptions {
+type QuerySerializerOptionsObject = {
   allowReserved?: boolean
-  array?: SerializerOptions<ArrayStyle>
-  object?: SerializerOptions<ObjectStyle>
+  array?: Partial<SerializerOptions<ArrayStyle>>
+  object?: Partial<SerializerOptions<ObjectStyle>>
+}
+
+export type QuerySerializerOptions = QuerySerializerOptionsObject & {
+  /**
+   * Per-parameter serialization overrides. When provided, these settings
+   * override the global array/object settings for specific parameter names.
+   */
+  parameters?: Record<string, QuerySerializerOptionsObject>
 }
 
 const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {

+ 34 - 9
packages/sdk/js/src/gen/core/params.gen.ts

@@ -23,6 +23,17 @@ export type Field =
       key?: string
       map?: string
     }
+  | {
+      /**
+       * 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 `in` is omitted, `map` aliases `key` to the transport layer.
+       */
+      map: Slot
+    }
 
 export interface Fields {
   allowExtra?: Partial<Record<Slot, boolean>>
@@ -41,10 +52,14 @@ const extraPrefixes = Object.entries(extraPrefixesMap)
 
 type KeyMap = Map<
   string,
-  {
-    in: Slot
-    map?: string
-  }
+  | {
+      in: Slot
+      map?: string
+    }
+  | {
+      in?: never
+      map: Slot
+    }
 >
 
 const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
@@ -60,6 +75,10 @@ const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
           map: config.map,
         })
       }
+    } else if ("key" in config) {
+      map.set(config.key, {
+        map: config.map,
+      })
     } else if (config.args) {
       buildKeyMap(config.args, map)
     }
@@ -108,7 +127,9 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
       if (config.key) {
         const field = map.get(config.key)!
         const name = field.map || config.key
-        ;(params[field.in] as Record<string, unknown>)[name] = arg
+        if (field.in) {
+          ;(params[field.in] as Record<string, unknown>)[name] = arg
+        }
       } else {
         params.body = arg
       }
@@ -117,16 +138,20 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
         const field = map.get(key)
 
         if (field) {
-          const name = field.map || key
-          ;(params[field.in] as Record<string, unknown>)[name] = value
+          if (field.in) {
+            const name = field.map || key
+            ;(params[field.in] as Record<string, unknown>)[name] = value
+          } else {
+            params[field.map] = 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 ?? {})) {
+          } else if ("allowExtra" in config && config.allowExtra) {
+            for (const [slot, allowed] of Object.entries(config.allowExtra)) {
               if (allowed) {
                 ;(params[slot as Slot] as Record<string, unknown>)[key] = value
                 break

+ 111 - 0
packages/sdk/js/src/gen/core/queryKeySerializer.gen.ts

@@ -0,0 +1,111 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+/**
+ * JSON-friendly union that mirrors what Pinia Colada can hash.
+ */
+export type JsonValue = null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue }
+
+/**
+ * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
+ */
+export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
+  if (value === undefined || typeof value === "function" || typeof value === "symbol") {
+    return undefined
+  }
+  if (typeof value === "bigint") {
+    return value.toString()
+  }
+  if (value instanceof Date) {
+    return value.toISOString()
+  }
+  return value
+}
+
+/**
+ * Safely stringifies a value and parses it back into a JsonValue.
+ */
+export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
+  try {
+    const json = JSON.stringify(input, queryKeyJsonReplacer)
+    if (json === undefined) {
+      return undefined
+    }
+    return JSON.parse(json) as JsonValue
+  } catch {
+    return undefined
+  }
+}
+
+/**
+ * Detects plain objects (including objects with a null prototype).
+ */
+const isPlainObject = (value: unknown): value is Record<string, unknown> => {
+  if (value === null || typeof value !== "object") {
+    return false
+  }
+  const prototype = Object.getPrototypeOf(value as object)
+  return prototype === Object.prototype || prototype === null
+}
+
+/**
+ * Turns URLSearchParams into a sorted JSON object for deterministic keys.
+ */
+const serializeSearchParams = (params: URLSearchParams): JsonValue => {
+  const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b))
+  const result: Record<string, JsonValue> = {}
+
+  for (const [key, value] of entries) {
+    const existing = result[key]
+    if (existing === undefined) {
+      result[key] = value
+      continue
+    }
+
+    if (Array.isArray(existing)) {
+      ;(existing as string[]).push(value)
+    } else {
+      result[key] = [existing, value]
+    }
+  }
+
+  return result
+}
+
+/**
+ * Normalizes any accepted value into a JSON-friendly shape for query keys.
+ */
+export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => {
+  if (value === null) {
+    return null
+  }
+
+  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
+    return value
+  }
+
+  if (value === undefined || typeof value === "function" || typeof value === "symbol") {
+    return undefined
+  }
+
+  if (typeof value === "bigint") {
+    return value.toString()
+  }
+
+  if (value instanceof Date) {
+    return value.toISOString()
+  }
+
+  if (Array.isArray(value)) {
+    return stringifyToJsonValue(value)
+  }
+
+  if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) {
+    return serializeSearchParams(value)
+  }
+
+  if (isPlainObject(value)) {
+    return stringifyToJsonValue(value)
+  }
+
+  return undefined
+}

+ 28 - 1
packages/sdk/js/src/gen/core/serverSentEvents.gen.ts

@@ -4,6 +4,17 @@ import type { Config } from "./types.gen.js"
 
 export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method"> &
   Pick<Config, "method" | "responseTransformer" | "responseValidator"> & {
+    /**
+     * Fetch API implementation. You can use this option to provide a custom
+     * fetch instance.
+     *
+     * @default globalThis.fetch
+     */
+    fetch?: typeof fetch
+    /**
+     * Implementing clients can call request interceptors inside this hook.
+     */
+    onRequest?: (url: string, init: RequestInit) => Promise<Request>
     /**
      * Callback invoked when a network or parsing error occurs during streaming.
      *
@@ -21,6 +32,7 @@ export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method
      * @returns Nothing (void).
      */
     onSseEvent?: (event: StreamEvent<TData>) => void
+    serializedBody?: RequestInit["body"]
     /**
      * Default retry delay in milliseconds.
      *
@@ -64,6 +76,7 @@ export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unkn
 }
 
 export const createSseClient = <TData = unknown>({
+  onRequest,
   onSseError,
   onSseEvent,
   responseTransformer,
@@ -99,7 +112,21 @@ export const createSseClient = <TData = unknown>({
       }
 
       try {
-        const response = await fetch(url, { ...options, headers, signal })
+        const requestInit: RequestInit = {
+          redirect: "follow",
+          ...options,
+          body: options.serializedBody,
+          headers,
+          signal,
+        }
+        let request = new Request(url, requestInit)
+        if (onRequest) {
+          request = await onRequest(url, requestInit)
+        }
+        // fetch must be assigned here, otherwise it would throw the error:
+        // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
+        const _fetch = options.fetch ?? globalThis.fetch
+        const response = await _fetch(request)
 
         if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`)
 

+ 7 - 12
packages/sdk/js/src/gen/core/types.gen.ts

@@ -3,24 +3,19 @@
 import type { Auth, AuthToken } from "./auth.gen.js"
 import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen.js"
 
-export interface Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never> {
+export type HttpMethod = "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"
+
+export type Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never, SseFn = 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
-}
+} & {
+  [K in HttpMethod]: MethodFn
+} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } })
 
 export interface Config {
   /**
@@ -47,7 +42,7 @@ export interface Config {
    *
    * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
    */
-  method?: "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE"
+  method?: Uppercase<HttpMethod>
   /**
    * A function for serializing request query parameters. By default, arrays
    * will be exploded in form style, objects will be exploded in deepObject

+ 29 - 1
packages/sdk/js/src/gen/core/utils.gen.ts

@@ -1,6 +1,6 @@
 // This file is auto-generated by @hey-api/openapi-ts
 
-import type { QuerySerializer } from "./bodySerializer.gen.js"
+import type { BodySerializer, QuerySerializer } from "./bodySerializer.gen.js"
 import {
   type ArraySeparatorStyle,
   serializeArrayParam,
@@ -107,3 +107,31 @@ export const getUrl = ({
   }
   return url
 }
+
+export function getValidRequestBody(options: {
+  body?: unknown
+  bodySerializer?: BodySerializer | null
+  serializedBody?: unknown
+}) {
+  const hasBody = options.body !== undefined
+  const isSerializedBody = hasBody && options.bodySerializer
+
+  if (isSerializedBody) {
+    if ("serializedBody" in options) {
+      const hasSerializedBody = options.serializedBody !== undefined && options.serializedBody !== ""
+
+      return hasSerializedBody ? options.serializedBody : null
+    }
+
+    // not all clients implement a serializedBody property (i.e. client-axios)
+    return options.body !== "" ? options.body : null
+  }
+
+  // plain/text body
+  if (hasBody) {
+    return options.body
+  }
+
+  // no body was provided
+  return undefined
+}

File diff suppressed because it is too large
+ 289 - 271
packages/sdk/js/src/gen/sdk.gen.ts


+ 93 - 88
packages/sdk/js/src/gen/types.gen.ts

@@ -1,5 +1,9 @@
 // This file is auto-generated by @hey-api/openapi-ts
 
+export type ClientOptions = {
+  baseUrl: `${string}://${string}` | (string & {})
+}
+
 export type EventServerInstanceDisposed = {
   type: "server.instance.disposed"
   properties: {
@@ -622,22 +626,20 @@ export type EventTuiCommandExecute = {
   type: "tui.command.execute"
   properties: {
     command:
-      | (
-          | "session.list"
-          | "session.new"
-          | "session.share"
-          | "session.interrupt"
-          | "session.compact"
-          | "session.page.up"
-          | "session.page.down"
-          | "session.half.page.up"
-          | "session.half.page.down"
-          | "session.first"
-          | "session.last"
-          | "prompt.clear"
-          | "prompt.submit"
-          | "agent.cycle"
-        )
+      | "session.list"
+      | "session.new"
+      | "session.share"
+      | "session.interrupt"
+      | "session.compact"
+      | "session.page.up"
+      | "session.page.down"
+      | "session.half.page.up"
+      | "session.half.page.down"
+      | "session.first"
+      | "session.last"
+      | "prompt.clear"
+      | "prompt.submit"
+      | "agent.cycle"
       | string
   }
 }
@@ -977,7 +979,9 @@ export type AgentConfig = {
   permission?: {
     edit?: "ask" | "allow" | "deny"
     bash?:
-      | ("ask" | "allow" | "deny")
+      | "ask"
+      | "allow"
+      | "deny"
       | {
           [key: string]: "ask" | "allow" | "deny"
         }
@@ -993,12 +997,17 @@ export type AgentConfig = {
         [key: string]: boolean
       }
     | boolean
-    | ("subagent" | "primary" | "all")
+    | "subagent"
+    | "primary"
+    | "all"
+    | string
     | number
     | {
         edit?: "ask" | "allow" | "deny"
         bash?:
-          | ("ask" | "allow" | "deny")
+          | "ask"
+          | "allow"
+          | "deny"
           | {
               [key: string]: "ask" | "allow" | "deny"
             }
@@ -1074,7 +1083,7 @@ export type ProviderConfig = {
      * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.
      */
     timeout?: number | false
-    [key: string]: unknown | string | boolean | (number | false) | undefined
+    [key: string]: unknown | string | boolean | number | false | undefined
   }
 }
 
@@ -1302,7 +1311,9 @@ export type Config = {
   permission?: {
     edit?: "ask" | "allow" | "deny"
     bash?:
-      | ("ask" | "allow" | "deny")
+      | "ask"
+      | "allow"
+      | "deny"
       | {
           [key: string]: "ask" | "allow" | "deny"
         }
@@ -1760,12 +1771,12 @@ export type PtyCreateResponse = PtyCreateResponses[keyof PtyCreateResponses]
 export type PtyRemoveData = {
   body?: never
   path: {
-    id: string
+    ptyID: string
   }
   query?: {
     directory?: string
   }
-  url: "/pty/{id}"
+  url: "/pty/{ptyID}"
 }
 
 export type PtyRemoveErrors = {
@@ -1789,12 +1800,12 @@ export type PtyRemoveResponse = PtyRemoveResponses[keyof PtyRemoveResponses]
 export type PtyGetData = {
   body?: never
   path: {
-    id: string
+    ptyID: string
   }
   query?: {
     directory?: string
   }
-  url: "/pty/{id}"
+  url: "/pty/{ptyID}"
 }
 
 export type PtyGetErrors = {
@@ -1824,12 +1835,12 @@ export type PtyUpdateData = {
     }
   }
   path: {
-    id: string
+    ptyID: string
   }
   query?: {
     directory?: string
   }
-  url: "/pty/{id}"
+  url: "/pty/{ptyID}"
 }
 
 export type PtyUpdateErrors = {
@@ -1853,12 +1864,12 @@ export type PtyUpdateResponse = PtyUpdateResponses[keyof PtyUpdateResponses]
 export type PtyConnectData = {
   body?: never
   path: {
-    id: string
+    ptyID: string
   }
   query?: {
     directory?: string
   }
-  url: "/pty/{id}/connect"
+  url: "/pty/{ptyID}/connect"
 }
 
 export type PtyConnectErrors = {
@@ -2114,12 +2125,12 @@ export type SessionStatusResponse = SessionStatusResponses[keyof SessionStatusRe
 export type SessionDeleteData = {
   body?: never
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}"
+  url: "/session/{sessionID}"
 }
 
 export type SessionDeleteErrors = {
@@ -2147,12 +2158,12 @@ export type SessionDeleteResponse = SessionDeleteResponses[keyof SessionDeleteRe
 export type SessionGetData = {
   body?: never
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}"
+  url: "/session/{sessionID}"
 }
 
 export type SessionGetErrors = {
@@ -2182,12 +2193,12 @@ export type SessionUpdateData = {
     title?: string
   }
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}"
+  url: "/session/{sessionID}"
 }
 
 export type SessionUpdateErrors = {
@@ -2215,12 +2226,12 @@ export type SessionUpdateResponse = SessionUpdateResponses[keyof SessionUpdateRe
 export type SessionChildrenData = {
   body?: never
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/children"
+  url: "/session/{sessionID}/children"
 }
 
 export type SessionChildrenErrors = {
@@ -2251,12 +2262,12 @@ export type SessionTodoData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/todo"
+  url: "/session/{sessionID}/todo"
 }
 
 export type SessionTodoErrors = {
@@ -2291,12 +2302,12 @@ export type SessionInitData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/init"
+  url: "/session/{sessionID}/init"
 }
 
 export type SessionInitErrors = {
@@ -2326,12 +2337,12 @@ export type SessionForkData = {
     messageID?: string
   }
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/fork"
+  url: "/session/{sessionID}/fork"
 }
 
 export type SessionForkResponses = {
@@ -2346,12 +2357,12 @@ export type SessionForkResponse = SessionForkResponses[keyof SessionForkResponse
 export type SessionAbortData = {
   body?: never
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/abort"
+  url: "/session/{sessionID}/abort"
 }
 
 export type SessionAbortErrors = {
@@ -2379,12 +2390,12 @@ export type SessionAbortResponse = SessionAbortResponses[keyof SessionAbortRespo
 export type SessionUnshareData = {
   body?: never
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/share"
+  url: "/session/{sessionID}/share"
 }
 
 export type SessionUnshareErrors = {
@@ -2412,12 +2423,12 @@ export type SessionUnshareResponse = SessionUnshareResponses[keyof SessionUnshar
 export type SessionShareData = {
   body?: never
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/share"
+  url: "/session/{sessionID}/share"
 }
 
 export type SessionShareErrors = {
@@ -2448,13 +2459,13 @@ export type SessionDiffData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
     messageID?: string
   }
-  url: "/session/{id}/diff"
+  url: "/session/{sessionID}/diff"
 }
 
 export type SessionDiffErrors = {
@@ -2488,12 +2499,12 @@ export type SessionSummarizeData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/summarize"
+  url: "/session/{sessionID}/summarize"
 }
 
 export type SessionSummarizeErrors = {
@@ -2524,13 +2535,13 @@ export type SessionMessagesData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
     limit?: number
   }
-  url: "/session/{id}/message"
+  url: "/session/{sessionID}/message"
 }
 
 export type SessionMessagesErrors = {
@@ -2577,12 +2588,12 @@ export type SessionPromptData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/message"
+  url: "/session/{sessionID}/message"
 }
 
 export type SessionPromptErrors = {
@@ -2616,7 +2627,7 @@ export type SessionMessageData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
     /**
      * Message ID
      */
@@ -2625,7 +2636,7 @@ export type SessionMessageData = {
   query?: {
     directory?: string
   }
-  url: "/session/{id}/message/{messageID}"
+  url: "/session/{sessionID}/message/{messageID}"
 }
 
 export type SessionMessageErrors = {
@@ -2672,12 +2683,12 @@ export type SessionPromptAsyncData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/prompt_async"
+  url: "/session/{sessionID}/prompt_async"
 }
 
 export type SessionPromptAsyncErrors = {
@@ -2714,12 +2725,12 @@ export type SessionCommandData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/command"
+  url: "/session/{sessionID}/command"
 }
 
 export type SessionCommandErrors = {
@@ -2760,12 +2771,12 @@ export type SessionShellData = {
     /**
      * Session ID
      */
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/shell"
+  url: "/session/{sessionID}/shell"
 }
 
 export type SessionShellErrors = {
@@ -2796,12 +2807,12 @@ export type SessionRevertData = {
     partID?: string
   }
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/revert"
+  url: "/session/{sessionID}/revert"
 }
 
 export type SessionRevertErrors = {
@@ -2829,12 +2840,12 @@ export type SessionRevertResponse = SessionRevertResponses[keyof SessionRevertRe
 export type SessionUnrevertData = {
   body?: never
   path: {
-    id: string
+    sessionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/unrevert"
+  url: "/session/{sessionID}/unrevert"
 }
 
 export type SessionUnrevertErrors = {
@@ -2859,21 +2870,21 @@ export type SessionUnrevertResponses = {
 
 export type SessionUnrevertResponse = SessionUnrevertResponses[keyof SessionUnrevertResponses]
 
-export type PostSessionIdPermissionsPermissionIdData = {
+export type PermissionRespondData = {
   body?: {
     response: "once" | "always" | "reject"
   }
   path: {
-    id: string
+    sessionID: string
     permissionID: string
   }
   query?: {
     directory?: string
   }
-  url: "/session/{id}/permissions/{permissionID}"
+  url: "/session/{sessionID}/permissions/{permissionID}"
 }
 
-export type PostSessionIdPermissionsPermissionIdErrors = {
+export type PermissionRespondErrors = {
   /**
    * Bad request
    */
@@ -2884,18 +2895,16 @@ export type PostSessionIdPermissionsPermissionIdErrors = {
   404: NotFoundError
 }
 
-export type PostSessionIdPermissionsPermissionIdError =
-  PostSessionIdPermissionsPermissionIdErrors[keyof PostSessionIdPermissionsPermissionIdErrors]
+export type PermissionRespondError = PermissionRespondErrors[keyof PermissionRespondErrors]
 
-export type PostSessionIdPermissionsPermissionIdResponses = {
+export type PermissionRespondResponses = {
   /**
    * Permission processed successfully
    */
   200: boolean
 }
 
-export type PostSessionIdPermissionsPermissionIdResponse =
-  PostSessionIdPermissionsPermissionIdResponses[keyof PostSessionIdPermissionsPermissionIdResponses]
+export type PermissionRespondResponse = PermissionRespondResponses[keyof PermissionRespondResponses]
 
 export type CommandListData = {
   body?: never
@@ -3041,12 +3050,12 @@ export type ProviderOauthAuthorizeData = {
     /**
      * Provider ID
      */
-    id: string
+    providerID: string
   }
   query?: {
     directory?: string
   }
-  url: "/provider/{id}/oauth/authorize"
+  url: "/provider/{providerID}/oauth/authorize"
 }
 
 export type ProviderOauthAuthorizeErrors = {
@@ -3082,12 +3091,12 @@ export type ProviderOauthCallbackData = {
     /**
      * Provider ID
      */
-    id: string
+    providerID: string
   }
   query?: {
     directory?: string
   }
-  url: "/provider/{id}/oauth/callback"
+  url: "/provider/{providerID}/oauth/callback"
 }
 
 export type ProviderOauthCallbackErrors = {
@@ -3791,12 +3800,12 @@ export type TuiControlResponseResponse = TuiControlResponseResponses[keyof TuiCo
 export type AuthSetData = {
   body?: Auth
   path: {
-    id: string
+    providerID: string
   }
   query?: {
     directory?: string
   }
-  url: "/auth/{id}"
+  url: "/auth/{providerID}"
 }
 
 export type AuthSetErrors = {
@@ -3834,7 +3843,3 @@ export type EventSubscribeResponses = {
 }
 
 export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]
-
-export type ClientOptions = {
-  baseUrl: `${string}://${string}` | (string & {})
-}

+ 30 - 0
packages/sdk/js/src/v2/client.ts

@@ -0,0 +1,30 @@
+export * from "./gen/types.gen.js"
+
+import { createClient } from "./gen/client/client.gen.js"
+import { type Config } from "./gen/client/types.gen.js"
+import { OpencodeClient } from "./gen/sdk.gen.js"
+export { type Config as OpencodeClientConfig, OpencodeClient }
+
+export function createOpencodeClient(config?: Config & { directory?: string }) {
+  if (!config?.fetch) {
+    const customFetch: any = (req: any) => {
+      // @ts-ignore
+      req.timeout = false
+      return fetch(req)
+    }
+    config = {
+      ...config,
+      fetch: customFetch,
+    }
+  }
+
+  if (config?.directory) {
+    config.headers = {
+      ...config.headers,
+      "x-opencode-directory": config.directory,
+    }
+  }
+
+  const client = createClient(config)
+  return new OpencodeClient({ client })
+}

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

@@ -0,0 +1,16 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import { type ClientOptions, type Config, createClient, createConfig } from './client/index.js';
+import type { ClientOptions as ClientOptions2 } from './types.gen.js';
+
+/**
+ * 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 = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
+
+export const client = createClient(createConfig<ClientOptions2>({ baseUrl: 'http://localhost:4096' }));

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

@@ -0,0 +1,301 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import { createSseClient } from '../core/serverSentEvents.gen.js';
+import type { HttpMethod } from '../core/types.gen.js';
+import { getValidRequestBody } from '../core/utils.gen.js';
+import type {
+  Client,
+  Config,
+  RequestOptions,
+  ResolvedRequestOptions,
+} from './types.gen.js';
+import {
+  buildUrl,
+  createConfig,
+  createInterceptors,
+  getParseAs,
+  mergeConfigs,
+  mergeHeaders,
+  setAuthParams,
+} from './utils.gen.js';
+
+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,
+    ResolvedRequestOptions
+  >();
+
+  const beforeRequest = async (options: RequestOptions) => {
+    const opts = {
+      ..._config,
+      ...options,
+      fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
+      headers: mergeHeaders(_config.headers, options.headers),
+      serializedBody: undefined,
+    };
+
+    if (opts.security) {
+      await setAuthParams({
+        ...opts,
+        security: opts.security,
+      });
+    }
+
+    if (opts.requestValidator) {
+      await opts.requestValidator(opts);
+    }
+
+    if (opts.body !== undefined && opts.bodySerializer) {
+      opts.serializedBody = opts.bodySerializer(opts.body);
+    }
+
+    // remove Content-Type header if body is empty to avoid sending invalid requests
+    if (opts.body === undefined || opts.serializedBody === '') {
+      opts.headers.delete('Content-Type');
+    }
+
+    const url = buildUrl(opts);
+
+    return { opts, url };
+  };
+
+  const request: Client['request'] = async (options) => {
+    // @ts-expect-error
+    const { opts, url } = await beforeRequest(options);
+    const requestInit: ReqInit = {
+      redirect: 'follow',
+      ...opts,
+      body: getValidRequestBody(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: Response;
+
+    try {
+      response = await _fetch(request);
+    } catch (error) {
+      // Handle fetch exceptions (AbortError, network errors, etc.)
+      let finalError = error;
+
+      for (const fn of interceptors.error.fns) {
+        if (fn) {
+          finalError = (await fn(
+            error,
+            undefined as any,
+            request,
+            opts,
+          )) as unknown;
+        }
+      }
+
+      finalError = finalError || ({} as unknown);
+
+      if (opts.throwOnError) {
+        throw finalError;
+      }
+
+      // Return error response
+      return opts.responseStyle === 'data'
+        ? undefined
+        : {
+            error: finalError,
+            request,
+            response: undefined as any,
+          };
+    }
+
+    for (const fn of interceptors.response.fns) {
+      if (fn) {
+        response = await fn(response, request, opts);
+      }
+    }
+
+    const result = {
+      request,
+      response,
+    };
+
+    if (response.ok) {
+      const parseAs =
+        (opts.parseAs === 'auto'
+          ? getParseAs(response.headers.get('Content-Type'))
+          : opts.parseAs) ?? 'json';
+
+      if (
+        response.status === 204 ||
+        response.headers.get('Content-Length') === '0'
+      ) {
+        let emptyData: any;
+        switch (parseAs) {
+          case 'arrayBuffer':
+          case 'blob':
+          case 'text':
+            emptyData = await response[parseAs]();
+            break;
+          case 'formData':
+            emptyData = new FormData();
+            break;
+          case 'stream':
+            emptyData = response.body;
+            break;
+          case 'json':
+          default:
+            emptyData = {};
+            break;
+        }
+        return opts.responseStyle === 'data'
+          ? emptyData
+          : {
+              data: emptyData,
+              ...result,
+            };
+      }
+
+      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,
+        };
+  };
+
+  const makeMethodFn =
+    (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
+      request({ ...options, method });
+
+  const makeSseFn =
+    (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
+      const { opts, url } = await beforeRequest(options);
+      return createSseClient({
+        ...opts,
+        body: opts.body as BodyInit | null | undefined,
+        headers: opts.headers as unknown as Record<string, string>,
+        method,
+        onRequest: async (url, init) => {
+          let request = new Request(url, init);
+          for (const fn of interceptors.request.fns) {
+            if (fn) {
+              request = await fn(request, opts);
+            }
+          }
+          return request;
+        },
+        url,
+      });
+    };
+
+  return {
+    buildUrl,
+    connect: makeMethodFn('CONNECT'),
+    delete: makeMethodFn('DELETE'),
+    get: makeMethodFn('GET'),
+    getConfig,
+    head: makeMethodFn('HEAD'),
+    interceptors,
+    options: makeMethodFn('OPTIONS'),
+    patch: makeMethodFn('PATCH'),
+    post: makeMethodFn('POST'),
+    put: makeMethodFn('PUT'),
+    request,
+    setConfig,
+    sse: {
+      connect: makeSseFn('CONNECT'),
+      delete: makeSseFn('DELETE'),
+      get: makeSseFn('GET'),
+      head: makeSseFn('HEAD'),
+      options: makeSseFn('OPTIONS'),
+      patch: makeSseFn('PATCH'),
+      post: makeSseFn('POST'),
+      put: makeSseFn('PUT'),
+      trace: makeSseFn('TRACE'),
+    },
+    trace: makeMethodFn('TRACE'),
+  } as Client;
+};

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

@@ -0,0 +1,25 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+export type { Auth } from '../core/auth.gen.js';
+export type { QuerySerializerOptions } from '../core/bodySerializer.gen.js';
+export {
+  formDataBodySerializer,
+  jsonBodySerializer,
+  urlSearchParamsBodySerializer,
+} from '../core/bodySerializer.gen.js';
+export { buildClientParams } from '../core/params.gen.js';
+export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen.js';
+export { createClient } from './client.gen.js';
+export type {
+  Client,
+  ClientOptions,
+  Config,
+  CreateClientConfig,
+  Options,
+  RequestOptions,
+  RequestResult,
+  ResolvedRequestOptions,
+  ResponseStyle,
+  TDataShape,
+} from './types.gen.js';
+export { createConfig, mergeHeaders } from './utils.gen.js';

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

@@ -0,0 +1,241 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import type { Auth } from '../core/auth.gen.js';
+import type {
+  ServerSentEventsOptions,
+  ServerSentEventsResult,
+} from '../core/serverSentEvents.gen.js';
+import type {
+  Client as CoreClient,
+  Config as CoreConfig,
+} from '../core/types.gen.js';
+import type { Middleware } from './utils.gen.js';
+
+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?: 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<
+  TData = unknown,
+  TResponseStyle extends ResponseStyle = 'fields',
+  ThrowOnError extends boolean = boolean,
+  Url extends string = string,
+> extends Config<{
+      responseStyle: TResponseStyle;
+      throwOnError: ThrowOnError;
+    }>,
+    Pick<
+      ServerSentEventsOptions<TData>,
+      | 'onSseError'
+      | 'onSseEvent'
+      | 'sseDefaultRetryDelay'
+      | 'sseMaxRetryAttempts'
+      | 'sseMaxRetryDelay'
+    > {
+  /**
+   * 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 interface ResolvedRequestOptions<
+  TResponseStyle extends ResponseStyle = 'fields',
+  ThrowOnError extends boolean = boolean,
+  Url extends string = string,
+> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
+  serializedBody?: string;
+}
+
+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<TData, TResponseStyle, ThrowOnError>, 'method'>,
+) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
+
+type SseFn = <
+  TData = unknown,
+  TError = unknown,
+  ThrowOnError extends boolean = false,
+  TResponseStyle extends ResponseStyle = 'fields',
+>(
+  options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
+) => Promise<ServerSentEventsResult<TData, TError>>;
+
+type RequestFn = <
+  TData = unknown,
+  TError = unknown,
+  ThrowOnError extends boolean = false,
+  TResponseStyle extends ResponseStyle = 'fields',
+>(
+  options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> &
+    Pick<
+      Required<RequestOptions<TData, 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: TData & Options<TData>,
+) => string;
+
+export type Client = CoreClient<
+  RequestFn,
+  Config,
+  MethodFn,
+  BuildUrlFn,
+  SseFn
+> & {
+  interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>;
+};
+
+/**
+ * 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,
+  TResponse = unknown,
+  TResponseStyle extends ResponseStyle = 'fields',
+> = OmitKeys<
+  RequestOptions<TResponse, TResponseStyle, ThrowOnError>,
+  'body' | 'path' | 'query' | 'url'
+> &
+  ([TData] extends [never] ? unknown : Omit<TData, 'url'>);

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

@@ -0,0 +1,332 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import { getAuthToken } from '../core/auth.gen.js';
+import type { QuerySerializerOptions } from '../core/bodySerializer.gen.js';
+import { jsonBodySerializer } from '../core/bodySerializer.gen.js';
+import {
+  serializeArrayParam,
+  serializeObjectParam,
+  serializePrimitiveParam,
+} from '../core/pathSerializer.gen.js';
+import { getUrl } from '../core/utils.gen.js';
+import type { Client, ClientOptions, Config, RequestOptions } from './types.gen.js';
+
+export const createQuerySerializer = <T = unknown>({
+  parameters = {},
+  ...args
+}: 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;
+        }
+
+        const options = parameters[name] || args;
+
+        if (Array.isArray(value)) {
+          const serializedArray = serializeArrayParam({
+            allowReserved: options.allowReserved,
+            explode: true,
+            name,
+            style: 'form',
+            value,
+            ...options.array,
+          });
+          if (serializedArray) search.push(serializedArray);
+        } else if (typeof value === 'object') {
+          const serializedObject = serializeObjectParam({
+            allowReserved: options.allowReserved,
+            explode: true,
+            name,
+            style: 'deepObject',
+            value: value as Record<string, unknown>,
+            ...options.object,
+          });
+          if (serializedObject) search.push(serializedObject);
+        } else {
+          const serializedPrimitive = serializePrimitiveParam({
+            allowReserved: options.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;
+};
+
+const checkForExistence = (
+  options: Pick<RequestOptions, 'auth' | 'query'> & {
+    headers: Headers;
+  },
+  name?: string,
+): boolean => {
+  if (!name) {
+    return false;
+  }
+  if (
+    options.headers.has(name) ||
+    options.query?.[name] ||
+    options.headers.get('Cookie')?.includes(`${name}=`)
+  ) {
+    return true;
+  }
+  return false;
+};
+
+export const setAuthParams = async ({
+  security,
+  ...options
+}: Pick<Required<RequestOptions>, 'security'> &
+  Pick<RequestOptions, 'auth' | 'query'> & {
+    headers: Headers;
+  }) => {
+  for (const auth of security) {
+    if (checkForExistence(options, auth.name)) {
+      continue;
+    }
+
+    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;
+    }
+  }
+};
+
+export const buildUrl: Client['buildUrl'] = (options) =>
+  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,
+  });
+
+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;
+};
+
+const headersEntries = (headers: Headers): Array<[string, string]> => {
+  const entries: Array<[string, string]> = [];
+  headers.forEach((value, key) => {
+    entries.push([key, value]);
+  });
+  return entries;
+};
+
+export const mergeHeaders = (
+  ...headers: Array<Required<Config>['headers'] | undefined>
+): Headers => {
+  const mergedHeaders = new Headers();
+  for (const header of headers) {
+    if (!header) {
+      continue;
+    }
+
+    const iterator =
+      header instanceof Headers
+        ? headersEntries(header)
+        : 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: Array<Interceptor | null> = [];
+
+  clear(): void {
+    this.fns = [];
+  }
+
+  eject(id: number | Interceptor): void {
+    const index = this.getInterceptorIndex(id);
+    if (this.fns[index]) {
+      this.fns[index] = null;
+    }
+  }
+
+  exists(id: number | Interceptor): boolean {
+    const index = this.getInterceptorIndex(id);
+    return Boolean(this.fns[index]);
+  }
+
+  getInterceptorIndex(id: number | Interceptor): number {
+    if (typeof id === 'number') {
+      return this.fns[id] ? id : -1;
+    }
+    return this.fns.indexOf(id);
+  }
+
+  update(
+    id: number | Interceptor,
+    fn: Interceptor,
+  ): number | Interceptor | false {
+    const index = this.getInterceptorIndex(id);
+    if (this.fns[index]) {
+      this.fns[index] = fn;
+      return id;
+    }
+    return false;
+  }
+
+  use(fn: Interceptor): number {
+    this.fns.push(fn);
+    return this.fns.length - 1;
+  }
+}
+
+export interface Middleware<Req, Res, Err, Options> {
+  error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>;
+  request: Interceptors<ReqInterceptor<Req, Options>>;
+  response: Interceptors<ResInterceptor<Res, Req, Options>>;
+}
+
+export const createInterceptors = <Req, Res, Err, Options>(): Middleware<
+  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,
+});

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

@@ -0,0 +1,42 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+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;
+};

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

@@ -0,0 +1,100 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import type {
+  ArrayStyle,
+  ObjectStyle,
+  SerializerOptions,
+} from './pathSerializer.gen.js';
+
+export type QuerySerializer = (query: Record<string, unknown>) => string;
+
+export type BodySerializer = (body: any) => any;
+
+type QuerySerializerOptionsObject = {
+  allowReserved?: boolean;
+  array?: Partial<SerializerOptions<ArrayStyle>>;
+  object?: Partial<SerializerOptions<ObjectStyle>>;
+};
+
+export type QuerySerializerOptions = QuerySerializerOptionsObject & {
+  /**
+   * Per-parameter serialization overrides. When provided, these settings
+   * override the global array/object settings for specific parameter names.
+   */
+  parameters?: Record<string, QuerySerializerOptionsObject>;
+};
+
+const serializeFormDataPair = (
+  data: FormData,
+  key: string,
+  value: unknown,
+): void => {
+  if (typeof value === 'string' || value instanceof Blob) {
+    data.append(key, value);
+  } else if (value instanceof Date) {
+    data.append(key, value.toISOString());
+  } 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();
+  },
+};

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

@@ -0,0 +1,176 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+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;
+    }
+  | {
+      /**
+       * 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 `in` is omitted, `map` aliases `key` to the transport layer.
+       */
+      map: Slot;
+    };
+
+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;
+    }
+  | {
+      in?: never;
+      map: Slot;
+    }
+>;
+
+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 ('key' in config) {
+      map.set(config.key, {
+        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;
+        if (field.in) {
+          (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) {
+          if (field.in) {
+            const name = field.map || key;
+            (params[field.in] as Record<string, unknown>)[name] = value;
+          } else {
+            params[field.map] = 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 if ('allowExtra' in config && config.allowExtra) {
+            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;
+};

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

@@ -0,0 +1,181 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+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;
+};

+ 136 - 0
packages/sdk/js/src/v2/gen/core/queryKeySerializer.gen.ts

@@ -0,0 +1,136 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+/**
+ * JSON-friendly union that mirrors what Pinia Colada can hash.
+ */
+export type JsonValue =
+  | null
+  | string
+  | number
+  | boolean
+  | JsonValue[]
+  | { [key: string]: JsonValue };
+
+/**
+ * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
+ */
+export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
+  if (
+    value === undefined ||
+    typeof value === 'function' ||
+    typeof value === 'symbol'
+  ) {
+    return undefined;
+  }
+  if (typeof value === 'bigint') {
+    return value.toString();
+  }
+  if (value instanceof Date) {
+    return value.toISOString();
+  }
+  return value;
+};
+
+/**
+ * Safely stringifies a value and parses it back into a JsonValue.
+ */
+export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
+  try {
+    const json = JSON.stringify(input, queryKeyJsonReplacer);
+    if (json === undefined) {
+      return undefined;
+    }
+    return JSON.parse(json) as JsonValue;
+  } catch {
+    return undefined;
+  }
+};
+
+/**
+ * Detects plain objects (including objects with a null prototype).
+ */
+const isPlainObject = (value: unknown): value is Record<string, unknown> => {
+  if (value === null || typeof value !== 'object') {
+    return false;
+  }
+  const prototype = Object.getPrototypeOf(value as object);
+  return prototype === Object.prototype || prototype === null;
+};
+
+/**
+ * Turns URLSearchParams into a sorted JSON object for deterministic keys.
+ */
+const serializeSearchParams = (params: URLSearchParams): JsonValue => {
+  const entries = Array.from(params.entries()).sort(([a], [b]) =>
+    a.localeCompare(b),
+  );
+  const result: Record<string, JsonValue> = {};
+
+  for (const [key, value] of entries) {
+    const existing = result[key];
+    if (existing === undefined) {
+      result[key] = value;
+      continue;
+    }
+
+    if (Array.isArray(existing)) {
+      (existing as string[]).push(value);
+    } else {
+      result[key] = [existing, value];
+    }
+  }
+
+  return result;
+};
+
+/**
+ * Normalizes any accepted value into a JSON-friendly shape for query keys.
+ */
+export const serializeQueryKeyValue = (
+  value: unknown,
+): JsonValue | undefined => {
+  if (value === null) {
+    return null;
+  }
+
+  if (
+    typeof value === 'string' ||
+    typeof value === 'number' ||
+    typeof value === 'boolean'
+  ) {
+    return value;
+  }
+
+  if (
+    value === undefined ||
+    typeof value === 'function' ||
+    typeof value === 'symbol'
+  ) {
+    return undefined;
+  }
+
+  if (typeof value === 'bigint') {
+    return value.toString();
+  }
+
+  if (value instanceof Date) {
+    return value.toISOString();
+  }
+
+  if (Array.isArray(value)) {
+    return stringifyToJsonValue(value);
+  }
+
+  if (
+    typeof URLSearchParams !== 'undefined' &&
+    value instanceof URLSearchParams
+  ) {
+    return serializeSearchParams(value);
+  }
+
+  if (isPlainObject(value)) {
+    return stringifyToJsonValue(value);
+  }
+
+  return undefined;
+};

+ 264 - 0
packages/sdk/js/src/v2/gen/core/serverSentEvents.gen.ts

@@ -0,0 +1,264 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import type { Config } from './types.gen.js';
+
+export type ServerSentEventsOptions<TData = unknown> = Omit<
+  RequestInit,
+  'method'
+> &
+  Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & {
+    /**
+     * Fetch API implementation. You can use this option to provide a custom
+     * fetch instance.
+     *
+     * @default globalThis.fetch
+     */
+    fetch?: typeof fetch;
+    /**
+     * Implementing clients can call request interceptors inside this hook.
+     */
+    onRequest?: (url: string, init: RequestInit) => Promise<Request>;
+    /**
+     * Callback invoked when a network or parsing error occurs during streaming.
+     *
+     * This option applies only if the endpoint returns a stream of events.
+     *
+     * @param error The error that occurred.
+     */
+    onSseError?: (error: unknown) => void;
+    /**
+     * Callback invoked when an event is streamed from the server.
+     *
+     * This option applies only if the endpoint returns a stream of events.
+     *
+     * @param event Event streamed from the server.
+     * @returns Nothing (void).
+     */
+    onSseEvent?: (event: StreamEvent<TData>) => void;
+    serializedBody?: RequestInit['body'];
+    /**
+     * Default retry delay in milliseconds.
+     *
+     * This option applies only if the endpoint returns a stream of events.
+     *
+     * @default 3000
+     */
+    sseDefaultRetryDelay?: number;
+    /**
+     * Maximum number of retry attempts before giving up.
+     */
+    sseMaxRetryAttempts?: number;
+    /**
+     * Maximum retry delay in milliseconds.
+     *
+     * Applies only when exponential backoff is used.
+     *
+     * This option applies only if the endpoint returns a stream of events.
+     *
+     * @default 30000
+     */
+    sseMaxRetryDelay?: number;
+    /**
+     * Optional sleep function for retry backoff.
+     *
+     * Defaults to using `setTimeout`.
+     */
+    sseSleepFn?: (ms: number) => Promise<void>;
+    url: string;
+  };
+
+export interface StreamEvent<TData = unknown> {
+  data: TData;
+  event?: string;
+  id?: string;
+  retry?: number;
+}
+
+export type ServerSentEventsResult<
+  TData = unknown,
+  TReturn = void,
+  TNext = unknown,
+> = {
+  stream: AsyncGenerator<
+    TData extends Record<string, unknown> ? TData[keyof TData] : TData,
+    TReturn,
+    TNext
+  >;
+};
+
+export const createSseClient = <TData = unknown>({
+  onRequest,
+  onSseError,
+  onSseEvent,
+  responseTransformer,
+  responseValidator,
+  sseDefaultRetryDelay,
+  sseMaxRetryAttempts,
+  sseMaxRetryDelay,
+  sseSleepFn,
+  url,
+  ...options
+}: ServerSentEventsOptions): ServerSentEventsResult<TData> => {
+  let lastEventId: string | undefined;
+
+  const sleep =
+    sseSleepFn ??
+    ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
+
+  const createStream = async function* () {
+    let retryDelay: number = sseDefaultRetryDelay ?? 3000;
+    let attempt = 0;
+    const signal = options.signal ?? new AbortController().signal;
+
+    while (true) {
+      if (signal.aborted) break;
+
+      attempt++;
+
+      const headers =
+        options.headers instanceof Headers
+          ? options.headers
+          : new Headers(options.headers as Record<string, string> | undefined);
+
+      if (lastEventId !== undefined) {
+        headers.set('Last-Event-ID', lastEventId);
+      }
+
+      try {
+        const requestInit: RequestInit = {
+          redirect: 'follow',
+          ...options,
+          body: options.serializedBody,
+          headers,
+          signal,
+        };
+        let request = new Request(url, requestInit);
+        if (onRequest) {
+          request = await onRequest(url, requestInit);
+        }
+        // fetch must be assigned here, otherwise it would throw the error:
+        // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
+        const _fetch = options.fetch ?? globalThis.fetch;
+        const response = await _fetch(request);
+
+        if (!response.ok)
+          throw new Error(
+            `SSE failed: ${response.status} ${response.statusText}`,
+          );
+
+        if (!response.body) throw new Error('No body in SSE response');
+
+        const reader = response.body
+          .pipeThrough(new TextDecoderStream())
+          .getReader();
+
+        let buffer = '';
+
+        const abortHandler = () => {
+          try {
+            reader.cancel();
+          } catch {
+            // noop
+          }
+        };
+
+        signal.addEventListener('abort', abortHandler);
+
+        try {
+          while (true) {
+            const { done, value } = await reader.read();
+            if (done) break;
+            buffer += value;
+
+            const chunks = buffer.split('\n\n');
+            buffer = chunks.pop() ?? '';
+
+            for (const chunk of chunks) {
+              const lines = chunk.split('\n');
+              const dataLines: Array<string> = [];
+              let eventName: string | undefined;
+
+              for (const line of lines) {
+                if (line.startsWith('data:')) {
+                  dataLines.push(line.replace(/^data:\s*/, ''));
+                } else if (line.startsWith('event:')) {
+                  eventName = line.replace(/^event:\s*/, '');
+                } else if (line.startsWith('id:')) {
+                  lastEventId = line.replace(/^id:\s*/, '');
+                } else if (line.startsWith('retry:')) {
+                  const parsed = Number.parseInt(
+                    line.replace(/^retry:\s*/, ''),
+                    10,
+                  );
+                  if (!Number.isNaN(parsed)) {
+                    retryDelay = parsed;
+                  }
+                }
+              }
+
+              let data: unknown;
+              let parsedJson = false;
+
+              if (dataLines.length) {
+                const rawData = dataLines.join('\n');
+                try {
+                  data = JSON.parse(rawData);
+                  parsedJson = true;
+                } catch {
+                  data = rawData;
+                }
+              }
+
+              if (parsedJson) {
+                if (responseValidator) {
+                  await responseValidator(data);
+                }
+
+                if (responseTransformer) {
+                  data = await responseTransformer(data);
+                }
+              }
+
+              onSseEvent?.({
+                data,
+                event: eventName,
+                id: lastEventId,
+                retry: retryDelay,
+              });
+
+              if (dataLines.length) {
+                yield data as any;
+              }
+            }
+          }
+        } finally {
+          signal.removeEventListener('abort', abortHandler);
+          reader.releaseLock();
+        }
+
+        break; // exit loop on normal completion
+      } catch (error) {
+        // connection failed or aborted; retry after delay
+        onSseError?.(error);
+
+        if (
+          sseMaxRetryAttempts !== undefined &&
+          attempt >= sseMaxRetryAttempts
+        ) {
+          break; // stop after firing error
+        }
+
+        // exponential backoff: double retry each attempt, cap at 30s
+        const backoff = Math.min(
+          retryDelay * 2 ** (attempt - 1),
+          sseMaxRetryDelay ?? 30000,
+        );
+        await sleep(backoff);
+      }
+    }
+  };
+
+  const stream = createStream();
+
+  return { stream };
+};

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

@@ -0,0 +1,118 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import type { Auth, AuthToken } from './auth.gen.js';
+import type {
+  BodySerializer,
+  QuerySerializer,
+  QuerySerializerOptions,
+} from './bodySerializer.gen.js';
+
+export type HttpMethod =
+  | 'connect'
+  | 'delete'
+  | 'get'
+  | 'head'
+  | 'options'
+  | 'patch'
+  | 'post'
+  | 'put'
+  | 'trace';
+
+export type Client<
+  RequestFn = never,
+  Config = unknown,
+  MethodFn = never,
+  BuildUrlFn = never,
+  SseFn = never,
+> = {
+  /**
+   * Returns the final request URL.
+   */
+  buildUrl: BuildUrlFn;
+  getConfig: () => Config;
+  request: RequestFn;
+  setConfig: (config: Config) => Config;
+} & {
+  [K in HttpMethod]: MethodFn;
+} & ([SseFn] extends [never]
+    ? { sse?: never }
+    : { sse: { [K in HttpMethod]: SseFn } });
+
+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?: Uppercase<HttpMethod>;
+  /**
+   * 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];
+};

+ 143 - 0
packages/sdk/js/src/v2/gen/core/utils.gen.ts

@@ -0,0 +1,143 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import type { BodySerializer, QuerySerializer } from './bodySerializer.gen.js';
+import {
+  type ArraySeparatorStyle,
+  serializeArrayParam,
+  serializeObjectParam,
+  serializePrimitiveParam,
+} from './pathSerializer.gen.js';
+
+export interface PathSerializer {
+  path: Record<string, unknown>;
+  url: string;
+}
+
+export const PATH_PARAM_RE = /\{[^{}]+\}/g;
+
+export 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 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 function getValidRequestBody(options: {
+  body?: unknown;
+  bodySerializer?: BodySerializer | null;
+  serializedBody?: unknown;
+}) {
+  const hasBody = options.body !== undefined;
+  const isSerializedBody = hasBody && options.bodySerializer;
+
+  if (isSerializedBody) {
+    if ('serializedBody' in options) {
+      const hasSerializedBody =
+        options.serializedBody !== undefined && options.serializedBody !== '';
+
+      return hasSerializedBody ? options.serializedBody : null;
+    }
+
+    // not all clients implement a serializedBody property (i.e. client-axios)
+    return options.body !== '' ? options.body : null;
+  }
+
+  // plain/text body
+  if (hasBody) {
+    return options.body;
+  }
+
+  // no body was provided
+  return undefined;
+}

File diff suppressed because it is too large
+ 4 - 0
packages/sdk/js/src/v2/gen/sdk.gen.ts


+ 3748 - 0
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -0,0 +1,3748 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+export type ClientOptions = {
+    baseUrl: `${string}://${string}` | (string & {});
+};
+
+export type EventServerInstanceDisposed = {
+    type: 'server.instance.disposed';
+    properties: {
+        directory: string;
+    };
+};
+
+export type EventInstallationUpdated = {
+    type: 'installation.updated';
+    properties: {
+        version: string;
+    };
+};
+
+export type EventInstallationUpdateAvailable = {
+    type: 'installation.update-available';
+    properties: {
+        version: string;
+    };
+};
+
+export type EventLspClientDiagnostics = {
+    type: 'lsp.client.diagnostics';
+    properties: {
+        serverID: string;
+        path: string;
+    };
+};
+
+export type EventLspUpdated = {
+    type: 'lsp.updated';
+    properties: {
+        [key: string]: unknown;
+    };
+};
+
+export type FileDiff = {
+    file: string;
+    before: string;
+    after: string;
+    additions: number;
+    deletions: number;
+};
+
+export type UserMessage = {
+    id: string;
+    sessionID: string;
+    role: 'user';
+    time: {
+        created: number;
+    };
+    summary?: {
+        title?: string;
+        body?: string;
+        diffs: Array<FileDiff>;
+    };
+    agent: string;
+    model: {
+        providerID: string;
+        modelID: string;
+    };
+    system?: string;
+    tools?: {
+        [key: string]: boolean;
+    };
+};
+
+export type ProviderAuthError = {
+    name: 'ProviderAuthError';
+    data: {
+        providerID: string;
+        message: string;
+    };
+};
+
+export type UnknownError = {
+    name: 'UnknownError';
+    data: {
+        message: string;
+    };
+};
+
+export type MessageOutputLengthError = {
+    name: 'MessageOutputLengthError';
+    data: {
+        [key: string]: unknown;
+    };
+};
+
+export type MessageAbortedError = {
+    name: 'MessageAbortedError';
+    data: {
+        message: string;
+    };
+};
+
+export type ApiError = {
+    name: 'APIError';
+    data: {
+        message: string;
+        statusCode?: number;
+        isRetryable: boolean;
+        responseHeaders?: {
+            [key: string]: string;
+        };
+        responseBody?: string;
+    };
+};
+
+export type AssistantMessage = {
+    id: string;
+    sessionID: string;
+    role: 'assistant';
+    time: {
+        created: number;
+        completed?: number;
+    };
+    error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError;
+    parentID: string;
+    modelID: string;
+    providerID: string;
+    mode: string;
+    path: {
+        cwd: string;
+        root: string;
+    };
+    summary?: boolean;
+    cost: number;
+    tokens: {
+        input: number;
+        output: number;
+        reasoning: number;
+        cache: {
+            read: number;
+            write: number;
+        };
+    };
+    finish?: string;
+};
+
+export type Message = UserMessage | AssistantMessage;
+
+export type EventMessageUpdated = {
+    type: 'message.updated';
+    properties: {
+        info: Message;
+    };
+};
+
+export type EventMessageRemoved = {
+    type: 'message.removed';
+    properties: {
+        sessionID: string;
+        messageID: string;
+    };
+};
+
+export type TextPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'text';
+    text: string;
+    synthetic?: boolean;
+    ignored?: boolean;
+    time?: {
+        start: number;
+        end?: number;
+    };
+    metadata?: {
+        [key: string]: unknown;
+    };
+};
+
+export type ReasoningPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'reasoning';
+    text: string;
+    metadata?: {
+        [key: string]: unknown;
+    };
+    time: {
+        start: number;
+        end?: number;
+    };
+};
+
+export type FilePartSourceText = {
+    value: string;
+    start: number;
+    end: number;
+};
+
+export type FileSource = {
+    text: FilePartSourceText;
+    type: 'file';
+    path: string;
+};
+
+export type Range = {
+    start: {
+        line: number;
+        character: number;
+    };
+    end: {
+        line: number;
+        character: number;
+    };
+};
+
+export type SymbolSource = {
+    text: FilePartSourceText;
+    type: 'symbol';
+    path: string;
+    range: Range;
+    name: string;
+    kind: number;
+};
+
+export type FilePartSource = FileSource | SymbolSource;
+
+export type FilePart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'file';
+    mime: string;
+    filename?: string;
+    url: string;
+    source?: FilePartSource;
+};
+
+export type ToolStatePending = {
+    status: 'pending';
+    input: {
+        [key: string]: unknown;
+    };
+    raw: string;
+};
+
+export type ToolStateRunning = {
+    status: 'running';
+    input: {
+        [key: string]: unknown;
+    };
+    title?: string;
+    metadata?: {
+        [key: string]: unknown;
+    };
+    time: {
+        start: number;
+    };
+};
+
+export type ToolStateCompleted = {
+    status: 'completed';
+    input: {
+        [key: string]: unknown;
+    };
+    output: string;
+    title: string;
+    metadata: {
+        [key: string]: unknown;
+    };
+    time: {
+        start: number;
+        end: number;
+        compacted?: number;
+    };
+    attachments?: Array<FilePart>;
+};
+
+export type ToolStateError = {
+    status: 'error';
+    input: {
+        [key: string]: unknown;
+    };
+    error: string;
+    metadata?: {
+        [key: string]: unknown;
+    };
+    time: {
+        start: number;
+        end: number;
+    };
+};
+
+export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError;
+
+export type ToolPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'tool';
+    callID: string;
+    tool: string;
+    state: ToolState;
+    metadata?: {
+        [key: string]: unknown;
+    };
+};
+
+export type StepStartPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'step-start';
+    snapshot?: string;
+};
+
+export type StepFinishPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'step-finish';
+    reason: string;
+    snapshot?: string;
+    cost: number;
+    tokens: {
+        input: number;
+        output: number;
+        reasoning: number;
+        cache: {
+            read: number;
+            write: number;
+        };
+    };
+};
+
+export type SnapshotPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'snapshot';
+    snapshot: string;
+};
+
+export type PatchPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'patch';
+    hash: string;
+    files: Array<string>;
+};
+
+export type AgentPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'agent';
+    name: string;
+    source?: {
+        value: string;
+        start: number;
+        end: number;
+    };
+};
+
+export type RetryPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'retry';
+    attempt: number;
+    error: ApiError;
+    time: {
+        created: number;
+    };
+};
+
+export type CompactionPart = {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'compaction';
+    auto: boolean;
+};
+
+export type Part = TextPart | {
+    id: string;
+    sessionID: string;
+    messageID: string;
+    type: 'subtask';
+    prompt: string;
+    description: string;
+    agent: string;
+} | ReasoningPart | FilePart | ToolPart | StepStartPart | StepFinishPart | SnapshotPart | PatchPart | AgentPart | RetryPart | CompactionPart;
+
+export type EventMessagePartUpdated = {
+    type: 'message.part.updated';
+    properties: {
+        part: Part;
+        delta?: string;
+    };
+};
+
+export type EventMessagePartRemoved = {
+    type: 'message.part.removed';
+    properties: {
+        sessionID: string;
+        messageID: string;
+        partID: string;
+    };
+};
+
+export type Permission = {
+    id: string;
+    type: string;
+    pattern?: string | Array<string>;
+    sessionID: string;
+    messageID: string;
+    callID?: string;
+    title: string;
+    metadata: {
+        [key: string]: unknown;
+    };
+    time: {
+        created: number;
+    };
+};
+
+export type EventPermissionUpdated = {
+    type: 'permission.updated';
+    properties: Permission;
+};
+
+export type EventPermissionReplied = {
+    type: 'permission.replied';
+    properties: {
+        sessionID: string;
+        permissionID: string;
+        response: string;
+    };
+};
+
+export type SessionStatus = {
+    type: 'idle';
+} | {
+    type: 'retry';
+    attempt: number;
+    message: string;
+    next: number;
+} | {
+    type: 'busy';
+};
+
+export type EventSessionStatus = {
+    type: 'session.status';
+    properties: {
+        sessionID: string;
+        status: SessionStatus;
+    };
+};
+
+export type EventSessionIdle = {
+    type: 'session.idle';
+    properties: {
+        sessionID: string;
+    };
+};
+
+export type EventSessionCompacted = {
+    type: 'session.compacted';
+    properties: {
+        sessionID: string;
+    };
+};
+
+export type EventFileEdited = {
+    type: 'file.edited';
+    properties: {
+        file: string;
+    };
+};
+
+export type Todo = {
+    /**
+     * Brief description of the task
+     */
+    content: string;
+    /**
+     * Current status of the task: pending, in_progress, completed, cancelled
+     */
+    status: string;
+    /**
+     * Priority level of the task: high, medium, low
+     */
+    priority: string;
+    /**
+     * Unique identifier for the todo item
+     */
+    id: string;
+};
+
+export type EventTodoUpdated = {
+    type: 'todo.updated';
+    properties: {
+        sessionID: string;
+        todos: Array<Todo>;
+    };
+};
+
+export type EventCommandExecuted = {
+    type: 'command.executed';
+    properties: {
+        name: string;
+        sessionID: string;
+        arguments: string;
+        messageID: string;
+    };
+};
+
+export type Session = {
+    id: string;
+    projectID: string;
+    directory: string;
+    parentID?: string;
+    summary?: {
+        additions: number;
+        deletions: number;
+        files: number;
+        diffs?: Array<FileDiff>;
+    };
+    share?: {
+        url: string;
+    };
+    title: string;
+    version: string;
+    time: {
+        created: number;
+        updated: number;
+        compacting?: number;
+    };
+    revert?: {
+        messageID: string;
+        partID?: string;
+        snapshot?: string;
+        diff?: string;
+    };
+};
+
+export type EventSessionCreated = {
+    type: 'session.created';
+    properties: {
+        info: Session;
+    };
+};
+
+export type EventSessionUpdated = {
+    type: 'session.updated';
+    properties: {
+        info: Session;
+    };
+};
+
+export type EventSessionDeleted = {
+    type: 'session.deleted';
+    properties: {
+        info: Session;
+    };
+};
+
+export type EventSessionDiff = {
+    type: 'session.diff';
+    properties: {
+        sessionID: string;
+        diff: Array<FileDiff>;
+    };
+};
+
+export type EventSessionError = {
+    type: 'session.error';
+    properties: {
+        sessionID?: string;
+        error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError;
+    };
+};
+
+export type EventFileWatcherUpdated = {
+    type: 'file.watcher.updated';
+    properties: {
+        file: string;
+        event: 'add' | 'change' | 'unlink';
+    };
+};
+
+export type EventVcsBranchUpdated = {
+    type: 'vcs.branch.updated';
+    properties: {
+        branch?: string;
+    };
+};
+
+export type EventTuiPromptAppend = {
+    type: 'tui.prompt.append';
+    properties: {
+        text: string;
+    };
+};
+
+export type EventTuiCommandExecute = {
+    type: 'tui.command.execute';
+    properties: {
+        command: 'session.list' | 'session.new' | 'session.share' | 'session.interrupt' | 'session.compact' | 'session.page.up' | 'session.page.down' | 'session.half.page.up' | 'session.half.page.down' | 'session.first' | 'session.last' | 'prompt.clear' | 'prompt.submit' | 'agent.cycle' | string;
+    };
+};
+
+export type EventTuiToastShow = {
+    type: 'tui.toast.show';
+    properties: {
+        title?: string;
+        message: string;
+        variant: 'info' | 'success' | 'warning' | 'error';
+        /**
+         * Duration in milliseconds
+         */
+        duration?: number;
+    };
+};
+
+export type Pty = {
+    id: string;
+    title: string;
+    command: string;
+    args: Array<string>;
+    cwd: string;
+    status: 'running' | 'exited';
+    pid: number;
+};
+
+export type EventPtyCreated = {
+    type: 'pty.created';
+    properties: {
+        info: Pty;
+    };
+};
+
+export type EventPtyUpdated = {
+    type: 'pty.updated';
+    properties: {
+        info: Pty;
+    };
+};
+
+export type EventPtyExited = {
+    type: 'pty.exited';
+    properties: {
+        id: string;
+        exitCode: number;
+    };
+};
+
+export type EventPtyDeleted = {
+    type: 'pty.deleted';
+    properties: {
+        id: string;
+    };
+};
+
+export type EventServerConnected = {
+    type: 'server.connected';
+    properties: {
+        [key: string]: unknown;
+    };
+};
+
+export type Event = EventServerInstanceDisposed | EventInstallationUpdated | EventInstallationUpdateAvailable | EventLspClientDiagnostics | EventLspUpdated | EventMessageUpdated | EventMessageRemoved | EventMessagePartUpdated | EventMessagePartRemoved | EventPermissionUpdated | EventPermissionReplied | EventSessionStatus | EventSessionIdle | EventSessionCompacted | EventFileEdited | EventTodoUpdated | EventCommandExecuted | EventSessionCreated | EventSessionUpdated | EventSessionDeleted | EventSessionDiff | EventSessionError | EventFileWatcherUpdated | EventVcsBranchUpdated | EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventPtyCreated | EventPtyUpdated | EventPtyExited | EventPtyDeleted | EventServerConnected;
+
+export type GlobalEvent = {
+    directory: string;
+    payload: Event;
+};
+
+export type Project = {
+    id: string;
+    worktree: string;
+    vcsDir?: string;
+    vcs?: 'git';
+    time: {
+        created: number;
+        initialized?: number;
+    };
+};
+
+export type BadRequestError = {
+    data: unknown;
+    errors: Array<{
+        [key: string]: unknown;
+    }>;
+    success: false;
+};
+
+export type NotFoundError = {
+    name: 'NotFoundError';
+    data: {
+        message: string;
+    };
+};
+
+/**
+ * Custom keybind configurations
+ */
+export type KeybindsConfig = {
+    /**
+     * Leader key for keybind combinations
+     */
+    leader?: string;
+    /**
+     * Exit the application
+     */
+    app_exit?: string;
+    /**
+     * Open external editor
+     */
+    editor_open?: string;
+    /**
+     * List available themes
+     */
+    theme_list?: string;
+    /**
+     * Toggle sidebar
+     */
+    sidebar_toggle?: string;
+    /**
+     * Toggle session scrollbar
+     */
+    scrollbar_toggle?: string;
+    /**
+     * Toggle username visibility
+     */
+    username_toggle?: string;
+    /**
+     * View status
+     */
+    status_view?: string;
+    /**
+     * Export session to editor
+     */
+    session_export?: string;
+    /**
+     * Create a new session
+     */
+    session_new?: string;
+    /**
+     * List all sessions
+     */
+    session_list?: string;
+    /**
+     * Show session timeline
+     */
+    session_timeline?: string;
+    /**
+     * Share current session
+     */
+    session_share?: string;
+    /**
+     * Unshare current session
+     */
+    session_unshare?: string;
+    /**
+     * Interrupt current session
+     */
+    session_interrupt?: string;
+    /**
+     * Compact the session
+     */
+    session_compact?: string;
+    /**
+     * Scroll messages up by one page
+     */
+    messages_page_up?: string;
+    /**
+     * Scroll messages down by one page
+     */
+    messages_page_down?: string;
+    /**
+     * Scroll messages up by half page
+     */
+    messages_half_page_up?: string;
+    /**
+     * Scroll messages down by half page
+     */
+    messages_half_page_down?: string;
+    /**
+     * Navigate to first message
+     */
+    messages_first?: string;
+    /**
+     * Navigate to last message
+     */
+    messages_last?: string;
+    /**
+     * Navigate to last user message
+     */
+    messages_last_user?: string;
+    /**
+     * Copy message
+     */
+    messages_copy?: string;
+    /**
+     * Undo message
+     */
+    messages_undo?: string;
+    /**
+     * Redo message
+     */
+    messages_redo?: string;
+    /**
+     * Toggle code block concealment in messages
+     */
+    messages_toggle_conceal?: string;
+    /**
+     * Toggle tool details visibility
+     */
+    tool_details?: string;
+    /**
+     * List available models
+     */
+    model_list?: string;
+    /**
+     * Next recently used model
+     */
+    model_cycle_recent?: string;
+    /**
+     * Previous recently used model
+     */
+    model_cycle_recent_reverse?: string;
+    /**
+     * List available commands
+     */
+    command_list?: string;
+    /**
+     * List agents
+     */
+    agent_list?: string;
+    /**
+     * Next agent
+     */
+    agent_cycle?: string;
+    /**
+     * Previous agent
+     */
+    agent_cycle_reverse?: string;
+    /**
+     * Clear input field
+     */
+    input_clear?: string;
+    /**
+     * Forward delete
+     */
+    input_forward_delete?: string;
+    /**
+     * Paste from clipboard
+     */
+    input_paste?: string;
+    /**
+     * Submit input
+     */
+    input_submit?: string;
+    /**
+     * Insert newline in input
+     */
+    input_newline?: string;
+    /**
+     * Previous history item
+     */
+    history_previous?: string;
+    /**
+     * Next history item
+     */
+    history_next?: string;
+    /**
+     * Next child session
+     */
+    session_child_cycle?: string;
+    /**
+     * Previous child session
+     */
+    session_child_cycle_reverse?: string;
+    /**
+     * Suspend terminal
+     */
+    terminal_suspend?: string;
+};
+
+export type AgentConfig = {
+    model?: string;
+    temperature?: number;
+    top_p?: number;
+    prompt?: string;
+    tools?: {
+        [key: string]: boolean;
+    };
+    disable?: boolean;
+    /**
+     * Description of when to use the agent
+     */
+    description?: string;
+    mode?: 'subagent' | 'primary' | 'all';
+    /**
+     * Hex color code for the agent (e.g., #FF5733)
+     */
+    color?: string;
+    /**
+     * Maximum number of agentic iterations before forcing text-only response
+     */
+    maxSteps?: number;
+    permission?: {
+        edit?: 'ask' | 'allow' | 'deny';
+        bash?: 'ask' | 'allow' | 'deny' | {
+            [key: string]: 'ask' | 'allow' | 'deny';
+        };
+        webfetch?: 'ask' | 'allow' | 'deny';
+        doom_loop?: 'ask' | 'allow' | 'deny';
+        external_directory?: 'ask' | 'allow' | 'deny';
+    };
+    [key: string]: unknown | string | number | {
+        [key: string]: boolean;
+    } | boolean | 'subagent' | 'primary' | 'all' | string | number | {
+        edit?: 'ask' | 'allow' | 'deny';
+        bash?: 'ask' | 'allow' | 'deny' | {
+            [key: string]: 'ask' | 'allow' | 'deny';
+        };
+        webfetch?: 'ask' | 'allow' | 'deny';
+        doom_loop?: 'ask' | 'allow' | 'deny';
+        external_directory?: 'ask' | 'allow' | 'deny';
+    } | undefined;
+};
+
+export type ProviderConfig = {
+    api?: string;
+    name?: string;
+    env?: Array<string>;
+    id?: string;
+    npm?: string;
+    models?: {
+        [key: string]: {
+            id?: string;
+            name?: string;
+            release_date?: string;
+            attachment?: boolean;
+            reasoning?: boolean;
+            temperature?: boolean;
+            tool_call?: boolean;
+            cost?: {
+                input: number;
+                output: number;
+                cache_read?: number;
+                cache_write?: number;
+                context_over_200k?: {
+                    input: number;
+                    output: number;
+                    cache_read?: number;
+                    cache_write?: number;
+                };
+            };
+            limit?: {
+                context: number;
+                output: number;
+            };
+            modalities?: {
+                input: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>;
+                output: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>;
+            };
+            experimental?: boolean;
+            status?: 'alpha' | 'beta' | 'deprecated';
+            options?: {
+                [key: string]: unknown;
+            };
+            headers?: {
+                [key: string]: string;
+            };
+            provider?: {
+                npm: string;
+            };
+        };
+    };
+    whitelist?: Array<string>;
+    blacklist?: Array<string>;
+    options?: {
+        apiKey?: string;
+        baseURL?: string;
+        /**
+         * GitHub Enterprise URL for copilot authentication
+         */
+        enterpriseUrl?: string;
+        /**
+         * Enable promptCacheKey for this provider (default false)
+         */
+        setCacheKey?: boolean;
+        /**
+         * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.
+         */
+        timeout?: number | false;
+        [key: string]: unknown | string | boolean | number | false | undefined;
+    };
+};
+
+export type McpLocalConfig = {
+    /**
+     * Type of MCP server connection
+     */
+    type: 'local';
+    /**
+     * Command and arguments to run the MCP server
+     */
+    command: Array<string>;
+    /**
+     * Environment variables to set when running the MCP server
+     */
+    environment?: {
+        [key: string]: string;
+    };
+    /**
+     * Enable or disable the MCP server on startup
+     */
+    enabled?: boolean;
+    /**
+     * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.
+     */
+    timeout?: number;
+};
+
+export type McpOAuthConfig = {
+    /**
+     * OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.
+     */
+    clientId?: string;
+    /**
+     * OAuth client secret (if required by the authorization server)
+     */
+    clientSecret?: string;
+    /**
+     * OAuth scopes to request during authorization
+     */
+    scope?: string;
+};
+
+export type McpRemoteConfig = {
+    /**
+     * Type of MCP server connection
+     */
+    type: 'remote';
+    /**
+     * URL of the remote MCP server
+     */
+    url: string;
+    /**
+     * Enable or disable the MCP server on startup
+     */
+    enabled?: boolean;
+    /**
+     * Headers to send with the request
+     */
+    headers?: {
+        [key: string]: string;
+    };
+    /**
+     * OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.
+     */
+    oauth?: McpOAuthConfig | false;
+    /**
+     * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.
+     */
+    timeout?: number;
+};
+
+/**
+ * @deprecated Always uses stretch layout.
+ */
+export type LayoutConfig = 'auto' | 'stretch';
+
+export type Config = {
+    /**
+     * JSON schema reference for configuration validation
+     */
+    $schema?: string;
+    /**
+     * Theme name to use for the interface
+     */
+    theme?: string;
+    keybinds?: KeybindsConfig;
+    /**
+     * TUI specific settings
+     */
+    tui?: {
+        /**
+         * TUI scroll speed
+         */
+        scroll_speed?: number;
+        /**
+         * Scroll acceleration settings
+         */
+        scroll_acceleration?: {
+            /**
+             * Enable scroll acceleration
+             */
+            enabled: boolean;
+        };
+        /**
+         * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column
+         */
+        diff_style?: 'auto' | 'stacked';
+    };
+    /**
+     * Command configuration, see https://opencode.ai/docs/commands
+     */
+    command?: {
+        [key: string]: {
+            template: string;
+            description?: string;
+            agent?: string;
+            model?: string;
+            subtask?: boolean;
+        };
+    };
+    watcher?: {
+        ignore?: Array<string>;
+    };
+    plugin?: Array<string>;
+    snapshot?: boolean;
+    /**
+     * Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing
+     */
+    share?: 'manual' | 'auto' | 'disabled';
+    /**
+     * @deprecated Use 'share' field instead. Share newly created sessions automatically
+     */
+    autoshare?: boolean;
+    /**
+     * Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications
+     */
+    autoupdate?: boolean | 'notify';
+    /**
+     * Disable providers that are loaded automatically
+     */
+    disabled_providers?: Array<string>;
+    /**
+     * When set, ONLY these providers will be enabled. All other providers will be ignored
+     */
+    enabled_providers?: Array<string>;
+    /**
+     * Model to use in the format of provider/model, eg anthropic/claude-2
+     */
+    model?: string;
+    /**
+     * Small model to use for tasks like title generation in the format of provider/model
+     */
+    small_model?: string;
+    /**
+     * Custom username to display in conversations instead of system username
+     */
+    username?: string;
+    /**
+     * @deprecated Use `agent` field instead.
+     */
+    mode?: {
+        build?: AgentConfig;
+        plan?: AgentConfig;
+        [key: string]: AgentConfig | undefined;
+    };
+    /**
+     * Agent configuration, see https://opencode.ai/docs/agent
+     */
+    agent?: {
+        plan?: AgentConfig;
+        build?: AgentConfig;
+        general?: AgentConfig;
+        explore?: AgentConfig;
+        [key: string]: AgentConfig | undefined;
+    };
+    /**
+     * Custom provider configurations and model overrides
+     */
+    provider?: {
+        [key: string]: ProviderConfig;
+    };
+    /**
+     * MCP (Model Context Protocol) server configurations
+     */
+    mcp?: {
+        [key: string]: McpLocalConfig | McpRemoteConfig;
+    };
+    formatter?: false | {
+        [key: string]: {
+            disabled?: boolean;
+            command?: Array<string>;
+            environment?: {
+                [key: string]: string;
+            };
+            extensions?: Array<string>;
+        };
+    };
+    lsp?: false | {
+        [key: string]: {
+            disabled: true;
+        } | {
+            command: Array<string>;
+            extensions?: Array<string>;
+            disabled?: boolean;
+            env?: {
+                [key: string]: string;
+            };
+            initialization?: {
+                [key: string]: unknown;
+            };
+        };
+    };
+    /**
+     * Additional instruction files or patterns to include
+     */
+    instructions?: Array<string>;
+    layout?: LayoutConfig;
+    permission?: {
+        edit?: 'ask' | 'allow' | 'deny';
+        bash?: 'ask' | 'allow' | 'deny' | {
+            [key: string]: 'ask' | 'allow' | 'deny';
+        };
+        webfetch?: 'ask' | 'allow' | 'deny';
+        doom_loop?: 'ask' | 'allow' | 'deny';
+        external_directory?: 'ask' | 'allow' | 'deny';
+    };
+    tools?: {
+        [key: string]: boolean;
+    };
+    enterprise?: {
+        /**
+         * Enterprise URL
+         */
+        url?: string;
+    };
+    experimental?: {
+        hook?: {
+            file_edited?: {
+                [key: string]: Array<{
+                    command: Array<string>;
+                    environment?: {
+                        [key: string]: string;
+                    };
+                }>;
+            };
+            session_completed?: Array<{
+                command: Array<string>;
+                environment?: {
+                    [key: string]: string;
+                };
+            }>;
+        };
+        /**
+         * Number of retries for chat completions on failure
+         */
+        chatMaxRetries?: number;
+        disable_paste_summary?: boolean;
+        /**
+         * Enable the batch tool
+         */
+        batch_tool?: boolean;
+        /**
+         * Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)
+         */
+        openTelemetry?: boolean;
+        /**
+         * Tools that should only be available to primary agents.
+         */
+        primary_tools?: Array<string>;
+    };
+};
+
+export type ToolIds = Array<string>;
+
+export type ToolListItem = {
+    id: string;
+    description: string;
+    parameters: unknown;
+};
+
+export type ToolList = Array<ToolListItem>;
+
+export type Path = {
+    state: string;
+    config: string;
+    worktree: string;
+    directory: string;
+};
+
+export type VcsInfo = {
+    branch: string;
+};
+
+export type TextPartInput = {
+    id?: string;
+    type: 'text';
+    text: string;
+    synthetic?: boolean;
+    ignored?: boolean;
+    time?: {
+        start: number;
+        end?: number;
+    };
+    metadata?: {
+        [key: string]: unknown;
+    };
+};
+
+export type FilePartInput = {
+    id?: string;
+    type: 'file';
+    mime: string;
+    filename?: string;
+    url: string;
+    source?: FilePartSource;
+};
+
+export type AgentPartInput = {
+    id?: string;
+    type: 'agent';
+    name: string;
+    source?: {
+        value: string;
+        start: number;
+        end: number;
+    };
+};
+
+export type SubtaskPartInput = {
+    id?: string;
+    type: 'subtask';
+    prompt: string;
+    description: string;
+    agent: string;
+};
+
+export type Command = {
+    name: string;
+    description?: string;
+    agent?: string;
+    model?: string;
+    template: string;
+    subtask?: boolean;
+};
+
+export type Model = {
+    id: string;
+    providerID: string;
+    api: {
+        id: string;
+        url: string;
+        npm: string;
+    };
+    name: string;
+    capabilities: {
+        temperature: boolean;
+        reasoning: boolean;
+        attachment: boolean;
+        toolcall: boolean;
+        input: {
+            text: boolean;
+            audio: boolean;
+            image: boolean;
+            video: boolean;
+            pdf: boolean;
+        };
+        output: {
+            text: boolean;
+            audio: boolean;
+            image: boolean;
+            video: boolean;
+            pdf: boolean;
+        };
+    };
+    cost: {
+        input: number;
+        output: number;
+        cache: {
+            read: number;
+            write: number;
+        };
+        experimentalOver200K?: {
+            input: number;
+            output: number;
+            cache: {
+                read: number;
+                write: number;
+            };
+        };
+    };
+    limit: {
+        context: number;
+        output: number;
+    };
+    status: 'alpha' | 'beta' | 'deprecated' | 'active';
+    options: {
+        [key: string]: unknown;
+    };
+    headers: {
+        [key: string]: string;
+    };
+};
+
+export type Provider = {
+    id: string;
+    name: string;
+    source: 'env' | 'config' | 'custom' | 'api';
+    env: Array<string>;
+    key?: string;
+    options: {
+        [key: string]: unknown;
+    };
+    models: {
+        [key: string]: Model;
+    };
+};
+
+export type ProviderAuthMethod = {
+    type: 'oauth' | 'api';
+    label: string;
+};
+
+export type ProviderAuthAuthorization = {
+    url: string;
+    method: 'auto' | 'code';
+    instructions: string;
+};
+
+export type Symbol = {
+    name: string;
+    kind: number;
+    location: {
+        uri: string;
+        range: Range;
+    };
+};
+
+export type FileNode = {
+    name: string;
+    path: string;
+    absolute: string;
+    type: 'file' | 'directory';
+    ignored: boolean;
+};
+
+export type FileContent = {
+    type: 'text';
+    content: string;
+    diff?: string;
+    patch?: {
+        oldFileName: string;
+        newFileName: string;
+        oldHeader?: string;
+        newHeader?: string;
+        hunks: Array<{
+            oldStart: number;
+            oldLines: number;
+            newStart: number;
+            newLines: number;
+            lines: Array<string>;
+        }>;
+        index?: string;
+    };
+    encoding?: 'base64';
+    mimeType?: string;
+};
+
+export type File = {
+    path: string;
+    added: number;
+    removed: number;
+    status: 'added' | 'deleted' | 'modified';
+};
+
+export type Agent = {
+    name: string;
+    description?: string;
+    mode: 'subagent' | 'primary' | 'all';
+    builtIn: boolean;
+    topP?: number;
+    temperature?: number;
+    color?: string;
+    permission: {
+        edit: 'ask' | 'allow' | 'deny';
+        bash: {
+            [key: string]: 'ask' | 'allow' | 'deny';
+        };
+        webfetch?: 'ask' | 'allow' | 'deny';
+        doom_loop?: 'ask' | 'allow' | 'deny';
+        external_directory?: 'ask' | 'allow' | 'deny';
+    };
+    model?: {
+        modelID: string;
+        providerID: string;
+    };
+    prompt?: string;
+    tools: {
+        [key: string]: boolean;
+    };
+    options: {
+        [key: string]: unknown;
+    };
+    maxSteps?: number;
+};
+
+export type McpStatusConnected = {
+    status: 'connected';
+};
+
+export type McpStatusDisabled = {
+    status: 'disabled';
+};
+
+export type McpStatusFailed = {
+    status: 'failed';
+    error: string;
+};
+
+export type McpStatusNeedsAuth = {
+    status: 'needs_auth';
+};
+
+export type McpStatusNeedsClientRegistration = {
+    status: 'needs_client_registration';
+    error: string;
+};
+
+export type McpStatus = McpStatusConnected | McpStatusDisabled | McpStatusFailed | McpStatusNeedsAuth | McpStatusNeedsClientRegistration;
+
+export type LspStatus = {
+    id: string;
+    name: string;
+    root: string;
+    status: 'connected' | 'error';
+};
+
+export type FormatterStatus = {
+    name: string;
+    extensions: Array<string>;
+    enabled: boolean;
+};
+
+export type OAuth = {
+    type: 'oauth';
+    refresh: string;
+    access: string;
+    expires: number;
+    enterpriseUrl?: string;
+};
+
+export type ApiAuth = {
+    type: 'api';
+    key: string;
+};
+
+export type WellKnownAuth = {
+    type: 'wellknown';
+    key: string;
+    token: string;
+};
+
+export type Auth = OAuth | ApiAuth | WellKnownAuth;
+
+export type GlobalEventData = {
+    body?: never;
+    path?: never;
+    query?: never;
+    url: '/global/event';
+};
+
+export type GlobalEventResponses = {
+    /**
+     * Event stream
+     */
+    200: GlobalEvent;
+};
+
+export type GlobalEventResponse = GlobalEventResponses[keyof GlobalEventResponses];
+
+export type ProjectListData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/project';
+};
+
+export type ProjectListResponses = {
+    /**
+     * List of projects
+     */
+    200: Array<Project>;
+};
+
+export type ProjectListResponse = ProjectListResponses[keyof ProjectListResponses];
+
+export type ProjectCurrentData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/project/current';
+};
+
+export type ProjectCurrentResponses = {
+    /**
+     * Current project
+     */
+    200: Project;
+};
+
+export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses];
+
+export type PtyListData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/pty';
+};
+
+export type PtyListResponses = {
+    /**
+     * List of sessions
+     */
+    200: Array<Pty>;
+};
+
+export type PtyListResponse = PtyListResponses[keyof PtyListResponses];
+
+export type PtyCreateData = {
+    body?: {
+        command?: string;
+        args?: Array<string>;
+        cwd?: string;
+        title?: string;
+        env?: {
+            [key: string]: string;
+        };
+    };
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/pty';
+};
+
+export type PtyCreateErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type PtyCreateError = PtyCreateErrors[keyof PtyCreateErrors];
+
+export type PtyCreateResponses = {
+    /**
+     * Created session
+     */
+    200: Pty;
+};
+
+export type PtyCreateResponse = PtyCreateResponses[keyof PtyCreateResponses];
+
+export type PtyRemoveData = {
+    body?: never;
+    path: {
+        ptyID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/pty/{ptyID}';
+};
+
+export type PtyRemoveErrors = {
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type PtyRemoveError = PtyRemoveErrors[keyof PtyRemoveErrors];
+
+export type PtyRemoveResponses = {
+    /**
+     * Session removed
+     */
+    200: boolean;
+};
+
+export type PtyRemoveResponse = PtyRemoveResponses[keyof PtyRemoveResponses];
+
+export type PtyGetData = {
+    body?: never;
+    path: {
+        ptyID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/pty/{ptyID}';
+};
+
+export type PtyGetErrors = {
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type PtyGetError = PtyGetErrors[keyof PtyGetErrors];
+
+export type PtyGetResponses = {
+    /**
+     * Session info
+     */
+    200: Pty;
+};
+
+export type PtyGetResponse = PtyGetResponses[keyof PtyGetResponses];
+
+export type PtyUpdateData = {
+    body?: {
+        title?: string;
+        size?: {
+            rows: number;
+            cols: number;
+        };
+    };
+    path: {
+        ptyID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/pty/{ptyID}';
+};
+
+export type PtyUpdateErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type PtyUpdateError = PtyUpdateErrors[keyof PtyUpdateErrors];
+
+export type PtyUpdateResponses = {
+    /**
+     * Updated session
+     */
+    200: Pty;
+};
+
+export type PtyUpdateResponse = PtyUpdateResponses[keyof PtyUpdateResponses];
+
+export type PtyConnectData = {
+    body?: never;
+    path: {
+        ptyID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/pty/{ptyID}/connect';
+};
+
+export type PtyConnectErrors = {
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type PtyConnectError = PtyConnectErrors[keyof PtyConnectErrors];
+
+export type PtyConnectResponses = {
+    /**
+     * Connected session
+     */
+    200: boolean;
+};
+
+export type PtyConnectResponse = PtyConnectResponses[keyof PtyConnectResponses];
+
+export type ConfigGetData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/config';
+};
+
+export type ConfigGetResponses = {
+    /**
+     * Get config info
+     */
+    200: Config;
+};
+
+export type ConfigGetResponse = ConfigGetResponses[keyof ConfigGetResponses];
+
+export type ConfigUpdateData = {
+    body?: Config;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/config';
+};
+
+export type ConfigUpdateErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type ConfigUpdateError = ConfigUpdateErrors[keyof ConfigUpdateErrors];
+
+export type ConfigUpdateResponses = {
+    /**
+     * Successfully updated config
+     */
+    200: Config;
+};
+
+export type ConfigUpdateResponse = ConfigUpdateResponses[keyof ConfigUpdateResponses];
+
+export type ToolIdsData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/experimental/tool/ids';
+};
+
+export type ToolIdsErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type ToolIdsError = ToolIdsErrors[keyof ToolIdsErrors];
+
+export type ToolIdsResponses = {
+    /**
+     * Tool IDs
+     */
+    200: ToolIds;
+};
+
+export type ToolIdsResponse = ToolIdsResponses[keyof ToolIdsResponses];
+
+export type ToolListData = {
+    body?: never;
+    path?: never;
+    query: {
+        directory?: string;
+        provider: string;
+        model: string;
+    };
+    url: '/experimental/tool';
+};
+
+export type ToolListErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type ToolListError = ToolListErrors[keyof ToolListErrors];
+
+export type ToolListResponses = {
+    /**
+     * Tools
+     */
+    200: ToolList;
+};
+
+export type ToolListResponse = ToolListResponses[keyof ToolListResponses];
+
+export type InstanceDisposeData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/instance/dispose';
+};
+
+export type InstanceDisposeResponses = {
+    /**
+     * Instance disposed
+     */
+    200: boolean;
+};
+
+export type InstanceDisposeResponse = InstanceDisposeResponses[keyof InstanceDisposeResponses];
+
+export type PathGetData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/path';
+};
+
+export type PathGetResponses = {
+    /**
+     * Path
+     */
+    200: Path;
+};
+
+export type PathGetResponse = PathGetResponses[keyof PathGetResponses];
+
+export type VcsGetData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/vcs';
+};
+
+export type VcsGetResponses = {
+    /**
+     * VCS info
+     */
+    200: VcsInfo;
+};
+
+export type VcsGetResponse = VcsGetResponses[keyof VcsGetResponses];
+
+export type SessionListData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/session';
+};
+
+export type SessionListResponses = {
+    /**
+     * List of sessions
+     */
+    200: Array<Session>;
+};
+
+export type SessionListResponse = SessionListResponses[keyof SessionListResponses];
+
+export type SessionCreateData = {
+    body?: {
+        parentID?: string;
+        title?: string;
+    };
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/session';
+};
+
+export type SessionCreateErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type SessionCreateError = SessionCreateErrors[keyof SessionCreateErrors];
+
+export type SessionCreateResponses = {
+    /**
+     * Successfully created session
+     */
+    200: Session;
+};
+
+export type SessionCreateResponse = SessionCreateResponses[keyof SessionCreateResponses];
+
+export type SessionStatusData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/session/status';
+};
+
+export type SessionStatusErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type SessionStatusError = SessionStatusErrors[keyof SessionStatusErrors];
+
+export type SessionStatusResponses = {
+    /**
+     * Get session status
+     */
+    200: {
+        [key: string]: SessionStatus;
+    };
+};
+
+export type SessionStatusResponse = SessionStatusResponses[keyof SessionStatusResponses];
+
+export type SessionDeleteData = {
+    body?: never;
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}';
+};
+
+export type SessionDeleteErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionDeleteError = SessionDeleteErrors[keyof SessionDeleteErrors];
+
+export type SessionDeleteResponses = {
+    /**
+     * Successfully deleted session
+     */
+    200: boolean;
+};
+
+export type SessionDeleteResponse = SessionDeleteResponses[keyof SessionDeleteResponses];
+
+export type SessionGetData = {
+    body?: never;
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}';
+};
+
+export type SessionGetErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionGetError = SessionGetErrors[keyof SessionGetErrors];
+
+export type SessionGetResponses = {
+    /**
+     * Get session
+     */
+    200: Session;
+};
+
+export type SessionGetResponse = SessionGetResponses[keyof SessionGetResponses];
+
+export type SessionUpdateData = {
+    body?: {
+        title?: string;
+    };
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}';
+};
+
+export type SessionUpdateErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionUpdateError = SessionUpdateErrors[keyof SessionUpdateErrors];
+
+export type SessionUpdateResponses = {
+    /**
+     * Successfully updated session
+     */
+    200: Session;
+};
+
+export type SessionUpdateResponse = SessionUpdateResponses[keyof SessionUpdateResponses];
+
+export type SessionChildrenData = {
+    body?: never;
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/children';
+};
+
+export type SessionChildrenErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionChildrenError = SessionChildrenErrors[keyof SessionChildrenErrors];
+
+export type SessionChildrenResponses = {
+    /**
+     * List of children
+     */
+    200: Array<Session>;
+};
+
+export type SessionChildrenResponse = SessionChildrenResponses[keyof SessionChildrenResponses];
+
+export type SessionTodoData = {
+    body?: never;
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/todo';
+};
+
+export type SessionTodoErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionTodoError = SessionTodoErrors[keyof SessionTodoErrors];
+
+export type SessionTodoResponses = {
+    /**
+     * Todo list
+     */
+    200: Array<Todo>;
+};
+
+export type SessionTodoResponse = SessionTodoResponses[keyof SessionTodoResponses];
+
+export type SessionInitData = {
+    body?: {
+        modelID: string;
+        providerID: string;
+        messageID: string;
+    };
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/init';
+};
+
+export type SessionInitErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionInitError = SessionInitErrors[keyof SessionInitErrors];
+
+export type SessionInitResponses = {
+    /**
+     * 200
+     */
+    200: boolean;
+};
+
+export type SessionInitResponse = SessionInitResponses[keyof SessionInitResponses];
+
+export type SessionForkData = {
+    body?: {
+        messageID?: string;
+    };
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/fork';
+};
+
+export type SessionForkResponses = {
+    /**
+     * 200
+     */
+    200: Session;
+};
+
+export type SessionForkResponse = SessionForkResponses[keyof SessionForkResponses];
+
+export type SessionAbortData = {
+    body?: never;
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/abort';
+};
+
+export type SessionAbortErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionAbortError = SessionAbortErrors[keyof SessionAbortErrors];
+
+export type SessionAbortResponses = {
+    /**
+     * Aborted session
+     */
+    200: boolean;
+};
+
+export type SessionAbortResponse = SessionAbortResponses[keyof SessionAbortResponses];
+
+export type SessionUnshareData = {
+    body?: never;
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/share';
+};
+
+export type SessionUnshareErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionUnshareError = SessionUnshareErrors[keyof SessionUnshareErrors];
+
+export type SessionUnshareResponses = {
+    /**
+     * Successfully unshared session
+     */
+    200: Session;
+};
+
+export type SessionUnshareResponse = SessionUnshareResponses[keyof SessionUnshareResponses];
+
+export type SessionShareData = {
+    body?: never;
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/share';
+};
+
+export type SessionShareErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionShareError = SessionShareErrors[keyof SessionShareErrors];
+
+export type SessionShareResponses = {
+    /**
+     * Successfully shared session
+     */
+    200: Session;
+};
+
+export type SessionShareResponse = SessionShareResponses[keyof SessionShareResponses];
+
+export type SessionDiffData = {
+    body?: never;
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+        messageID?: string;
+    };
+    url: '/session/{sessionID}/diff';
+};
+
+export type SessionDiffErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionDiffError = SessionDiffErrors[keyof SessionDiffErrors];
+
+export type SessionDiffResponses = {
+    /**
+     * List of diffs
+     */
+    200: Array<FileDiff>;
+};
+
+export type SessionDiffResponse = SessionDiffResponses[keyof SessionDiffResponses];
+
+export type SessionSummarizeData = {
+    body?: {
+        providerID: string;
+        modelID: string;
+    };
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/summarize';
+};
+
+export type SessionSummarizeErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionSummarizeError = SessionSummarizeErrors[keyof SessionSummarizeErrors];
+
+export type SessionSummarizeResponses = {
+    /**
+     * Summarized session
+     */
+    200: boolean;
+};
+
+export type SessionSummarizeResponse = SessionSummarizeResponses[keyof SessionSummarizeResponses];
+
+export type SessionMessagesData = {
+    body?: never;
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+        limit?: number;
+    };
+    url: '/session/{sessionID}/message';
+};
+
+export type SessionMessagesErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionMessagesError = SessionMessagesErrors[keyof SessionMessagesErrors];
+
+export type SessionMessagesResponses = {
+    /**
+     * List of messages
+     */
+    200: Array<{
+        info: Message;
+        parts: Array<Part>;
+    }>;
+};
+
+export type SessionMessagesResponse = SessionMessagesResponses[keyof SessionMessagesResponses];
+
+export type SessionPromptData = {
+    body?: {
+        messageID?: string;
+        model?: {
+            providerID: string;
+            modelID: string;
+        };
+        agent?: string;
+        noReply?: boolean;
+        system?: string;
+        tools?: {
+            [key: string]: boolean;
+        };
+        parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>;
+    };
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/message';
+};
+
+export type SessionPromptErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionPromptError = SessionPromptErrors[keyof SessionPromptErrors];
+
+export type SessionPromptResponses = {
+    /**
+     * Created message
+     */
+    200: {
+        info: AssistantMessage;
+        parts: Array<Part>;
+    };
+};
+
+export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptResponses];
+
+export type SessionMessageData = {
+    body?: never;
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+        /**
+         * Message ID
+         */
+        messageID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/message/{messageID}';
+};
+
+export type SessionMessageErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionMessageError = SessionMessageErrors[keyof SessionMessageErrors];
+
+export type SessionMessageResponses = {
+    /**
+     * Message
+     */
+    200: {
+        info: Message;
+        parts: Array<Part>;
+    };
+};
+
+export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses];
+
+export type SessionPromptAsyncData = {
+    body?: {
+        messageID?: string;
+        model?: {
+            providerID: string;
+            modelID: string;
+        };
+        agent?: string;
+        noReply?: boolean;
+        system?: string;
+        tools?: {
+            [key: string]: boolean;
+        };
+        parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>;
+    };
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/prompt_async';
+};
+
+export type SessionPromptAsyncErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionPromptAsyncError = SessionPromptAsyncErrors[keyof SessionPromptAsyncErrors];
+
+export type SessionPromptAsyncResponses = {
+    /**
+     * Prompt accepted
+     */
+    204: void;
+};
+
+export type SessionPromptAsyncResponse = SessionPromptAsyncResponses[keyof SessionPromptAsyncResponses];
+
+export type SessionCommandData = {
+    body?: {
+        messageID?: string;
+        agent?: string;
+        model?: string;
+        arguments: string;
+        command: string;
+    };
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/command';
+};
+
+export type SessionCommandErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionCommandError = SessionCommandErrors[keyof SessionCommandErrors];
+
+export type SessionCommandResponses = {
+    /**
+     * Created message
+     */
+    200: {
+        info: AssistantMessage;
+        parts: Array<Part>;
+    };
+};
+
+export type SessionCommandResponse = SessionCommandResponses[keyof SessionCommandResponses];
+
+export type SessionShellData = {
+    body?: {
+        agent: string;
+        model?: {
+            providerID: string;
+            modelID: string;
+        };
+        command: string;
+    };
+    path: {
+        /**
+         * Session ID
+         */
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/shell';
+};
+
+export type SessionShellErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionShellError = SessionShellErrors[keyof SessionShellErrors];
+
+export type SessionShellResponses = {
+    /**
+     * Created message
+     */
+    200: AssistantMessage;
+};
+
+export type SessionShellResponse = SessionShellResponses[keyof SessionShellResponses];
+
+export type SessionRevertData = {
+    body?: {
+        messageID: string;
+        partID?: string;
+    };
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/revert';
+};
+
+export type SessionRevertErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionRevertError = SessionRevertErrors[keyof SessionRevertErrors];
+
+export type SessionRevertResponses = {
+    /**
+     * Updated session
+     */
+    200: Session;
+};
+
+export type SessionRevertResponse = SessionRevertResponses[keyof SessionRevertResponses];
+
+export type SessionUnrevertData = {
+    body?: never;
+    path: {
+        sessionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/unrevert';
+};
+
+export type SessionUnrevertErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type SessionUnrevertError = SessionUnrevertErrors[keyof SessionUnrevertErrors];
+
+export type SessionUnrevertResponses = {
+    /**
+     * Updated session
+     */
+    200: Session;
+};
+
+export type SessionUnrevertResponse = SessionUnrevertResponses[keyof SessionUnrevertResponses];
+
+export type PermissionRespondData = {
+    body?: {
+        response: 'once' | 'always' | 'reject';
+    };
+    path: {
+        sessionID: string;
+        permissionID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/session/{sessionID}/permissions/{permissionID}';
+};
+
+export type PermissionRespondErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type PermissionRespondError = PermissionRespondErrors[keyof PermissionRespondErrors];
+
+export type PermissionRespondResponses = {
+    /**
+     * Permission processed successfully
+     */
+    200: boolean;
+};
+
+export type PermissionRespondResponse = PermissionRespondResponses[keyof PermissionRespondResponses];
+
+export type CommandListData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/command';
+};
+
+export type CommandListResponses = {
+    /**
+     * List of commands
+     */
+    200: Array<Command>;
+};
+
+export type CommandListResponse = CommandListResponses[keyof CommandListResponses];
+
+export type ConfigProvidersData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/config/providers';
+};
+
+export type ConfigProvidersResponses = {
+    /**
+     * List of providers
+     */
+    200: {
+        providers: Array<Provider>;
+        default: {
+            [key: string]: string;
+        };
+    };
+};
+
+export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses];
+
+export type ProviderListData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/provider';
+};
+
+export type ProviderListResponses = {
+    /**
+     * List of providers
+     */
+    200: {
+        all: Array<{
+            api?: string;
+            name: string;
+            env: Array<string>;
+            id: string;
+            npm?: string;
+            models: {
+                [key: string]: {
+                    id: string;
+                    name: string;
+                    release_date: string;
+                    attachment: boolean;
+                    reasoning: boolean;
+                    temperature: boolean;
+                    tool_call: boolean;
+                    cost?: {
+                        input: number;
+                        output: number;
+                        cache_read?: number;
+                        cache_write?: number;
+                        context_over_200k?: {
+                            input: number;
+                            output: number;
+                            cache_read?: number;
+                            cache_write?: number;
+                        };
+                    };
+                    limit: {
+                        context: number;
+                        output: number;
+                    };
+                    modalities?: {
+                        input: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>;
+                        output: Array<'text' | 'audio' | 'image' | 'video' | 'pdf'>;
+                    };
+                    experimental?: boolean;
+                    status?: 'alpha' | 'beta' | 'deprecated';
+                    options: {
+                        [key: string]: unknown;
+                    };
+                    headers?: {
+                        [key: string]: string;
+                    };
+                    provider?: {
+                        npm: string;
+                    };
+                };
+            };
+        }>;
+        default: {
+            [key: string]: string;
+        };
+        connected: Array<string>;
+    };
+};
+
+export type ProviderListResponse = ProviderListResponses[keyof ProviderListResponses];
+
+export type ProviderAuthData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/provider/auth';
+};
+
+export type ProviderAuthResponses = {
+    /**
+     * Provider auth methods
+     */
+    200: {
+        [key: string]: Array<ProviderAuthMethod>;
+    };
+};
+
+export type ProviderAuthResponse = ProviderAuthResponses[keyof ProviderAuthResponses];
+
+export type ProviderOauthAuthorizeData = {
+    body?: {
+        /**
+         * Auth method index
+         */
+        method: number;
+    };
+    path: {
+        /**
+         * Provider ID
+         */
+        providerID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/provider/{providerID}/oauth/authorize';
+};
+
+export type ProviderOauthAuthorizeErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type ProviderOauthAuthorizeError = ProviderOauthAuthorizeErrors[keyof ProviderOauthAuthorizeErrors];
+
+export type ProviderOauthAuthorizeResponses = {
+    /**
+     * Authorization URL and method
+     */
+    200: ProviderAuthAuthorization;
+};
+
+export type ProviderOauthAuthorizeResponse = ProviderOauthAuthorizeResponses[keyof ProviderOauthAuthorizeResponses];
+
+export type ProviderOauthCallbackData = {
+    body?: {
+        /**
+         * Auth method index
+         */
+        method: number;
+        /**
+         * OAuth authorization code
+         */
+        code?: string;
+    };
+    path: {
+        /**
+         * Provider ID
+         */
+        providerID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/provider/{providerID}/oauth/callback';
+};
+
+export type ProviderOauthCallbackErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type ProviderOauthCallbackError = ProviderOauthCallbackErrors[keyof ProviderOauthCallbackErrors];
+
+export type ProviderOauthCallbackResponses = {
+    /**
+     * OAuth callback processed successfully
+     */
+    200: boolean;
+};
+
+export type ProviderOauthCallbackResponse = ProviderOauthCallbackResponses[keyof ProviderOauthCallbackResponses];
+
+export type FindTextData = {
+    body?: never;
+    path?: never;
+    query: {
+        directory?: string;
+        pattern: string;
+    };
+    url: '/find';
+};
+
+export type FindTextResponses = {
+    /**
+     * Matches
+     */
+    200: Array<{
+        path: {
+            text: string;
+        };
+        lines: {
+            text: string;
+        };
+        line_number: number;
+        absolute_offset: number;
+        submatches: Array<{
+            match: {
+                text: string;
+            };
+            start: number;
+            end: number;
+        }>;
+    }>;
+};
+
+export type FindTextResponse = FindTextResponses[keyof FindTextResponses];
+
+export type FindFilesData = {
+    body?: never;
+    path?: never;
+    query: {
+        directory?: string;
+        query: string;
+        dirs?: 'true' | 'false';
+    };
+    url: '/find/file';
+};
+
+export type FindFilesResponses = {
+    /**
+     * File paths
+     */
+    200: Array<string>;
+};
+
+export type FindFilesResponse = FindFilesResponses[keyof FindFilesResponses];
+
+export type FindSymbolsData = {
+    body?: never;
+    path?: never;
+    query: {
+        directory?: string;
+        query: string;
+    };
+    url: '/find/symbol';
+};
+
+export type FindSymbolsResponses = {
+    /**
+     * Symbols
+     */
+    200: Array<Symbol>;
+};
+
+export type FindSymbolsResponse = FindSymbolsResponses[keyof FindSymbolsResponses];
+
+export type FileListData = {
+    body?: never;
+    path?: never;
+    query: {
+        directory?: string;
+        path: string;
+    };
+    url: '/file';
+};
+
+export type FileListResponses = {
+    /**
+     * Files and directories
+     */
+    200: Array<FileNode>;
+};
+
+export type FileListResponse = FileListResponses[keyof FileListResponses];
+
+export type FileReadData = {
+    body?: never;
+    path?: never;
+    query: {
+        directory?: string;
+        path: string;
+    };
+    url: '/file/content';
+};
+
+export type FileReadResponses = {
+    /**
+     * File content
+     */
+    200: FileContent;
+};
+
+export type FileReadResponse = FileReadResponses[keyof FileReadResponses];
+
+export type FileStatusData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/file/status';
+};
+
+export type FileStatusResponses = {
+    /**
+     * File status
+     */
+    200: Array<File>;
+};
+
+export type FileStatusResponse = FileStatusResponses[keyof FileStatusResponses];
+
+export type AppLogData = {
+    body?: {
+        /**
+         * Service name for the log entry
+         */
+        service: string;
+        /**
+         * Log level
+         */
+        level: 'debug' | 'info' | 'error' | 'warn';
+        /**
+         * Log message
+         */
+        message: string;
+        /**
+         * Additional metadata for the log entry
+         */
+        extra?: {
+            [key: string]: unknown;
+        };
+    };
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/log';
+};
+
+export type AppLogErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type AppLogError = AppLogErrors[keyof AppLogErrors];
+
+export type AppLogResponses = {
+    /**
+     * Log entry written successfully
+     */
+    200: boolean;
+};
+
+export type AppLogResponse = AppLogResponses[keyof AppLogResponses];
+
+export type AppAgentsData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/agent';
+};
+
+export type AppAgentsResponses = {
+    /**
+     * List of agents
+     */
+    200: Array<Agent>;
+};
+
+export type AppAgentsResponse = AppAgentsResponses[keyof AppAgentsResponses];
+
+export type McpStatusData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/mcp';
+};
+
+export type McpStatusResponses = {
+    /**
+     * MCP server status
+     */
+    200: {
+        [key: string]: McpStatus;
+    };
+};
+
+export type McpStatusResponse = McpStatusResponses[keyof McpStatusResponses];
+
+export type McpAddData = {
+    body?: {
+        name: string;
+        config: McpLocalConfig | McpRemoteConfig;
+    };
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/mcp';
+};
+
+export type McpAddErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type McpAddError = McpAddErrors[keyof McpAddErrors];
+
+export type McpAddResponses = {
+    /**
+     * MCP server added successfully
+     */
+    200: {
+        [key: string]: McpStatus;
+    };
+};
+
+export type McpAddResponse = McpAddResponses[keyof McpAddResponses];
+
+export type McpAuthRemoveData = {
+    body?: never;
+    path: {
+        name: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/mcp/{name}/auth';
+};
+
+export type McpAuthRemoveErrors = {
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type McpAuthRemoveError = McpAuthRemoveErrors[keyof McpAuthRemoveErrors];
+
+export type McpAuthRemoveResponses = {
+    /**
+     * OAuth credentials removed
+     */
+    200: {
+        success: true;
+    };
+};
+
+export type McpAuthRemoveResponse = McpAuthRemoveResponses[keyof McpAuthRemoveResponses];
+
+export type McpAuthStartData = {
+    body?: never;
+    path: {
+        name: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/mcp/{name}/auth';
+};
+
+export type McpAuthStartErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type McpAuthStartError = McpAuthStartErrors[keyof McpAuthStartErrors];
+
+export type McpAuthStartResponses = {
+    /**
+     * OAuth flow started
+     */
+    200: {
+        /**
+         * URL to open in browser for authorization
+         */
+        authorizationUrl: string;
+    };
+};
+
+export type McpAuthStartResponse = McpAuthStartResponses[keyof McpAuthStartResponses];
+
+export type McpAuthCallbackData = {
+    body?: {
+        /**
+         * Authorization code from OAuth callback
+         */
+        code: string;
+    };
+    path: {
+        name: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/mcp/{name}/auth/callback';
+};
+
+export type McpAuthCallbackErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type McpAuthCallbackError = McpAuthCallbackErrors[keyof McpAuthCallbackErrors];
+
+export type McpAuthCallbackResponses = {
+    /**
+     * OAuth authentication completed
+     */
+    200: McpStatus;
+};
+
+export type McpAuthCallbackResponse = McpAuthCallbackResponses[keyof McpAuthCallbackResponses];
+
+export type McpAuthAuthenticateData = {
+    body?: never;
+    path: {
+        name: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/mcp/{name}/auth/authenticate';
+};
+
+export type McpAuthAuthenticateErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+    /**
+     * Not found
+     */
+    404: NotFoundError;
+};
+
+export type McpAuthAuthenticateError = McpAuthAuthenticateErrors[keyof McpAuthAuthenticateErrors];
+
+export type McpAuthAuthenticateResponses = {
+    /**
+     * OAuth authentication completed
+     */
+    200: McpStatus;
+};
+
+export type McpAuthAuthenticateResponse = McpAuthAuthenticateResponses[keyof McpAuthAuthenticateResponses];
+
+export type LspStatusData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/lsp';
+};
+
+export type LspStatusResponses = {
+    /**
+     * LSP server status
+     */
+    200: Array<LspStatus>;
+};
+
+export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses];
+
+export type FormatterStatusData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/formatter';
+};
+
+export type FormatterStatusResponses = {
+    /**
+     * Formatter status
+     */
+    200: Array<FormatterStatus>;
+};
+
+export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses];
+
+export type TuiAppendPromptData = {
+    body?: {
+        text: string;
+    };
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/append-prompt';
+};
+
+export type TuiAppendPromptErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type TuiAppendPromptError = TuiAppendPromptErrors[keyof TuiAppendPromptErrors];
+
+export type TuiAppendPromptResponses = {
+    /**
+     * Prompt processed successfully
+     */
+    200: boolean;
+};
+
+export type TuiAppendPromptResponse = TuiAppendPromptResponses[keyof TuiAppendPromptResponses];
+
+export type TuiOpenHelpData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/open-help';
+};
+
+export type TuiOpenHelpResponses = {
+    /**
+     * Help dialog opened successfully
+     */
+    200: boolean;
+};
+
+export type TuiOpenHelpResponse = TuiOpenHelpResponses[keyof TuiOpenHelpResponses];
+
+export type TuiOpenSessionsData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/open-sessions';
+};
+
+export type TuiOpenSessionsResponses = {
+    /**
+     * Session dialog opened successfully
+     */
+    200: boolean;
+};
+
+export type TuiOpenSessionsResponse = TuiOpenSessionsResponses[keyof TuiOpenSessionsResponses];
+
+export type TuiOpenThemesData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/open-themes';
+};
+
+export type TuiOpenThemesResponses = {
+    /**
+     * Theme dialog opened successfully
+     */
+    200: boolean;
+};
+
+export type TuiOpenThemesResponse = TuiOpenThemesResponses[keyof TuiOpenThemesResponses];
+
+export type TuiOpenModelsData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/open-models';
+};
+
+export type TuiOpenModelsResponses = {
+    /**
+     * Model dialog opened successfully
+     */
+    200: boolean;
+};
+
+export type TuiOpenModelsResponse = TuiOpenModelsResponses[keyof TuiOpenModelsResponses];
+
+export type TuiSubmitPromptData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/submit-prompt';
+};
+
+export type TuiSubmitPromptResponses = {
+    /**
+     * Prompt submitted successfully
+     */
+    200: boolean;
+};
+
+export type TuiSubmitPromptResponse = TuiSubmitPromptResponses[keyof TuiSubmitPromptResponses];
+
+export type TuiClearPromptData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/clear-prompt';
+};
+
+export type TuiClearPromptResponses = {
+    /**
+     * Prompt cleared successfully
+     */
+    200: boolean;
+};
+
+export type TuiClearPromptResponse = TuiClearPromptResponses[keyof TuiClearPromptResponses];
+
+export type TuiExecuteCommandData = {
+    body?: {
+        command: string;
+    };
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/execute-command';
+};
+
+export type TuiExecuteCommandErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type TuiExecuteCommandError = TuiExecuteCommandErrors[keyof TuiExecuteCommandErrors];
+
+export type TuiExecuteCommandResponses = {
+    /**
+     * Command executed successfully
+     */
+    200: boolean;
+};
+
+export type TuiExecuteCommandResponse = TuiExecuteCommandResponses[keyof TuiExecuteCommandResponses];
+
+export type TuiShowToastData = {
+    body?: {
+        title?: string;
+        message: string;
+        variant: 'info' | 'success' | 'warning' | 'error';
+        /**
+         * Duration in milliseconds
+         */
+        duration?: number;
+    };
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/show-toast';
+};
+
+export type TuiShowToastResponses = {
+    /**
+     * Toast notification shown successfully
+     */
+    200: boolean;
+};
+
+export type TuiShowToastResponse = TuiShowToastResponses[keyof TuiShowToastResponses];
+
+export type TuiPublishData = {
+    body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/publish';
+};
+
+export type TuiPublishErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type TuiPublishError = TuiPublishErrors[keyof TuiPublishErrors];
+
+export type TuiPublishResponses = {
+    /**
+     * Event published successfully
+     */
+    200: boolean;
+};
+
+export type TuiPublishResponse = TuiPublishResponses[keyof TuiPublishResponses];
+
+export type TuiControlNextData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/control/next';
+};
+
+export type TuiControlNextResponses = {
+    /**
+     * Next TUI request
+     */
+    200: {
+        path: string;
+        body: unknown;
+    };
+};
+
+export type TuiControlNextResponse = TuiControlNextResponses[keyof TuiControlNextResponses];
+
+export type TuiControlResponseData = {
+    body?: unknown;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/tui/control/response';
+};
+
+export type TuiControlResponseResponses = {
+    /**
+     * Response submitted successfully
+     */
+    200: boolean;
+};
+
+export type TuiControlResponseResponse = TuiControlResponseResponses[keyof TuiControlResponseResponses];
+
+export type AuthSetData = {
+    body?: Auth;
+    path: {
+        providerID: string;
+    };
+    query?: {
+        directory?: string;
+    };
+    url: '/auth/{providerID}';
+};
+
+export type AuthSetErrors = {
+    /**
+     * Bad request
+     */
+    400: BadRequestError;
+};
+
+export type AuthSetError = AuthSetErrors[keyof AuthSetErrors];
+
+export type AuthSetResponses = {
+    /**
+     * Successfully set authentication credentials
+     */
+    200: boolean;
+};
+
+export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses];
+
+export type EventSubscribeData = {
+    body?: never;
+    path?: never;
+    query?: {
+        directory?: string;
+    };
+    url: '/event';
+};
+
+export type EventSubscribeResponses = {
+    /**
+     * Event stream
+     */
+    200: Event;
+};
+
+export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses];

+ 21 - 0
packages/sdk/js/src/v2/index.ts

@@ -0,0 +1,21 @@
+export * from "./client.js"
+export * from "./server.js"
+
+import { createOpencodeClient } from "./client.js"
+import { createOpencodeServer } from "./server.js"
+import type { ServerOptions } from "./server.js"
+
+export async function createOpencode(options?: ServerOptions) {
+  const server = await createOpencodeServer({
+    ...options,
+  })
+
+  const client = createOpencodeClient({
+    baseUrl: server.url,
+  })
+
+  return {
+    client,
+    server,
+  }
+}

+ 120 - 0
packages/sdk/js/src/v2/server.ts

@@ -0,0 +1,120 @@
+import { spawn } from "node:child_process"
+import { type Config } from "./gen/types.gen.js"
+
+export type ServerOptions = {
+  hostname?: string
+  port?: number
+  signal?: AbortSignal
+  timeout?: number
+  config?: Config
+}
+
+export type TuiOptions = {
+  project?: string
+  model?: string
+  session?: string
+  agent?: string
+  signal?: AbortSignal
+  config?: Config
+}
+
+export async function createOpencodeServer(options?: ServerOptions) {
+  options = Object.assign(
+    {
+      hostname: "127.0.0.1",
+      port: 4096,
+      timeout: 5000,
+    },
+    options ?? {},
+  )
+
+  const proc = spawn(`opencode`, [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`], {
+    signal: options.signal,
+    env: {
+      ...process.env,
+      OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}),
+    },
+  })
+
+  const url = await new Promise<string>((resolve, reject) => {
+    const id = setTimeout(() => {
+      reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`))
+    }, options.timeout)
+    let output = ""
+    proc.stdout?.on("data", (chunk) => {
+      output += chunk.toString()
+      const lines = output.split("\n")
+      for (const line of lines) {
+        if (line.startsWith("opencode server listening")) {
+          const match = line.match(/on\s+(https?:\/\/[^\s]+)/)
+          if (!match) {
+            throw new Error(`Failed to parse server url from output: ${line}`)
+          }
+          clearTimeout(id)
+          resolve(match[1]!)
+          return
+        }
+      }
+    })
+    proc.stderr?.on("data", (chunk) => {
+      output += chunk.toString()
+    })
+    proc.on("exit", (code) => {
+      clearTimeout(id)
+      let msg = `Server exited with code ${code}`
+      if (output.trim()) {
+        msg += `\nServer output: ${output}`
+      }
+      reject(new Error(msg))
+    })
+    proc.on("error", (error) => {
+      clearTimeout(id)
+      reject(error)
+    })
+    if (options.signal) {
+      options.signal.addEventListener("abort", () => {
+        clearTimeout(id)
+        reject(new Error("Aborted"))
+      })
+    }
+  })
+
+  return {
+    url,
+    close() {
+      proc.kill()
+    },
+  }
+}
+
+export function createOpencodeTui(options?: TuiOptions) {
+  const args = []
+
+  if (options?.project) {
+    args.push(`--project=${options.project}`)
+  }
+  if (options?.model) {
+    args.push(`--model=${options.model}`)
+  }
+  if (options?.session) {
+    args.push(`--session=${options.session}`)
+  }
+  if (options?.agent) {
+    args.push(`--agent=${options.agent}`)
+  }
+
+  const proc = spawn(`opencode`, args, {
+    signal: options?.signal,
+    stdio: "inherit",
+    env: {
+      ...process.env,
+      OPENCODE_CONFIG_CONTENT: JSON.stringify(options?.config ?? {}),
+    },
+  })
+
+  return {
+    close() {
+      proc.kill()
+    },
+  }
+}

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