Просмотр исходного кода

Merge branch 'dev' of https://github.com/sst/opencode into dev

David Hill 3 месяцев назад
Родитель
Сommit
7088bfabd7
33 измененных файлов с 541 добавлено и 241 удалено
  1. 21 21
      bun.lock
  2. 1 1
      packages/console/app/package.json
  3. 1 1
      packages/console/core/package.json
  4. 1 1
      packages/console/function/package.json
  5. 1 1
      packages/console/mail/package.json
  6. 1 1
      packages/desktop/package.json
  7. 3 1
      packages/desktop/src/index.tsx
  8. 1 1
      packages/function/package.json
  9. 3 3
      packages/opencode/package.json
  10. 1 0
      packages/opencode/src/auth/index.ts
  11. 193 148
      packages/opencode/src/cli/cmd/auth.ts
  12. 1 0
      packages/opencode/src/cli/cmd/tui/app.tsx
  13. 9 4
      packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx
  14. 1 1
      packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx
  15. 42 6
      packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
  16. 19 12
      packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
  17. 6 3
      packages/opencode/src/cli/cmd/web.ts
  18. 1 0
      packages/opencode/src/config/config.ts
  19. 1 1
      packages/opencode/src/plugin/index.ts
  20. 52 8
      packages/opencode/src/provider/provider.ts
  21. 38 1
      packages/opencode/src/server/server.ts
  22. 9 2
      packages/opencode/src/session/index.ts
  23. 4 1
      packages/opencode/src/session/summary.ts
  24. 33 10
      packages/opencode/src/storage/storage.ts
  25. 1 1
      packages/plugin/package.json
  26. 59 2
      packages/plugin/src/index.ts
  27. 1 1
      packages/sdk/js/package.json
  28. 7 2
      packages/sdk/js/src/gen/sdk.gen.ts
  29. 26 3
      packages/sdk/js/src/gen/types.gen.ts
  30. 1 1
      packages/slack/package.json
  31. 1 1
      packages/ui/package.json
  32. 1 1
      packages/web/package.json
  33. 1 1
      sdks/vscode/package.json

+ 21 - 21
bun.lock

@@ -39,7 +39,7 @@
     },
     },
     "packages/console/core": {
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
       "name": "@opencode-ai/console-core",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
         "@jsx-email/render": "1.1.1",
@@ -66,7 +66,7 @@
     },
     },
     "packages/console/function": {
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
       "name": "@opencode-ai/console-function",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/openai": "2.0.2",
         "@ai-sdk/openai": "2.0.2",
@@ -90,7 +90,7 @@
     },
     },
     "packages/console/mail": {
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
       "name": "@opencode-ai/console-mail",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
         "@jsx-email/cli": "1.4.3",
@@ -111,7 +111,7 @@
     },
     },
     "packages/desktop": {
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
       "name": "@opencode-ai/desktop",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
@@ -150,7 +150,7 @@
     },
     },
     "packages/function": {
     "packages/function": {
       "name": "@opencode-ai/function",
       "name": "@opencode-ai/function",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "22.0.0",
         "@octokit/rest": "22.0.0",
@@ -166,7 +166,7 @@
     },
     },
     "packages/opencode": {
     "packages/opencode": {
       "name": "opencode",
       "name": "opencode",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "bin": {
       "bin": {
         "opencode": "./bin/opencode",
         "opencode": "./bin/opencode",
       },
       },
@@ -184,8 +184,8 @@
         "@opencode-ai/plugin": "workspace:*",
         "@opencode-ai/plugin": "workspace:*",
         "@opencode-ai/script": "workspace:*",
         "@opencode-ai/script": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
-        "@opentui/core": "0.1.34",
-        "@opentui/solid": "0.1.34",
+        "@opentui/core": "0.1.33",
+        "@opentui/solid": "0.1.33",
         "@parcel/watcher": "2.5.1",
         "@parcel/watcher": "2.5.1",
         "@pierre/precision-diffs": "catalog:",
         "@pierre/precision-diffs": "catalog:",
         "@solid-primitives/event-bus": "1.1.2",
         "@solid-primitives/event-bus": "1.1.2",
@@ -243,7 +243,7 @@
     },
     },
     "packages/plugin": {
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
       "name": "@opencode-ai/plugin",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "zod": "catalog:",
         "zod": "catalog:",
@@ -263,7 +263,7 @@
     },
     },
     "packages/sdk/js": {
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
       "name": "@opencode-ai/sdk",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "devDependencies": {
       "devDependencies": {
         "@hey-api/openapi-ts": "0.81.0",
         "@hey-api/openapi-ts": "0.81.0",
         "@tsconfig/node22": "catalog:",
         "@tsconfig/node22": "catalog:",
@@ -274,7 +274,7 @@
     },
     },
     "packages/slack": {
     "packages/slack": {
       "name": "@opencode-ai/slack",
       "name": "@opencode-ai/slack",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
         "@slack/bolt": "^3.17.1",
@@ -287,7 +287,7 @@
     },
     },
     "packages/ui": {
     "packages/ui": {
       "name": "@opencode-ai/ui",
       "name": "@opencode-ai/ui",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
@@ -317,7 +317,7 @@
     },
     },
     "packages/web": {
     "packages/web": {
       "name": "@opencode-ai/web",
       "name": "@opencode-ai/web",
-      "version": "1.0.20",
+      "version": "1.0.23",
       "dependencies": {
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
         "@astrojs/markdown-remark": "6.3.1",
@@ -961,21 +961,21 @@
 
 
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
 
 
-    "@opentui/core": ["@opentui/[email protected]4", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.34", "@opentui/core-darwin-x64": "0.1.34", "@opentui/core-linux-arm64": "0.1.34", "@opentui/core-linux-x64": "0.1.34", "@opentui/core-win32-arm64": "0.1.34", "@opentui/core-win32-x64": "0.1.34", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-rsqEbHXIFL6JEZs/2dCHn7efnJaGByqpI3mMtt+cJvyt7ZiGU9y+JwryFb9rE8KZMtwsUWN1ECz58ufy6iJvzA=="],
+    "@opentui/core": ["@opentui/[email protected]3", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.33", "@opentui/core-darwin-x64": "0.1.33", "@opentui/core-linux-arm64": "0.1.33", "@opentui/core-linux-x64": "0.1.33", "@opentui/core-win32-arm64": "0.1.33", "@opentui/core-win32-x64": "0.1.33", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vwHdrPIqnsY6YnG2JTNhenHSsx+HUPYrQTBZdmEfCj9ROGVzKgUKbSDH1xGK2OtSNRb2KVBg4XaMpq0bie6afQ=="],
 
 
-    "@opentui/core-darwin-arm64": ["@opentui/[email protected]4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P/Pw66vJ1W5pIVg7D5bUlMPBTarXh0S/conHRaeybBZoO+8G04A6x9ufeaD/L4HCE0iR0huSoHGDB1VxZUL2Zg=="],
+    "@opentui/core-darwin-arm64": ["@opentui/[email protected]3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JBvzcP2V7fT9KxFAMenHRd/t72qPP5IL5kzge2uok1T7t2nw3Wa+CWI5s6FYP42p2b1W9qZkv5Fno5gA7OAYuQ=="],
 
 
-    "@opentui/core-darwin-x64": ["@opentui/[email protected]4", "", { "os": "darwin", "cpu": "x64" }, "sha512-JKfDC2qI1AmY4u504FKfrSdP0qOJIn+rI7kj0C0ydpvj1Wd2c6ImOsbnny70372Uq/m3EXxPE3Hq/66DL4P94A=="],
+    "@opentui/core-darwin-x64": ["@opentui/[email protected]3", "", { "os": "darwin", "cpu": "x64" }, "sha512-x7DY6VCkAky10z/2o4UkkuNW/nIvoX7uAh3dJOHWZCLbiKywSFvFk3QZVVcH5BMk4tOOophYTzika4s4HpaeMg=="],
 
 
-    "@opentui/core-linux-arm64": ["@opentui/[email protected]4", "", { "os": "linux", "cpu": "arm64" }, "sha512-E1xAuz0xx7lmh7tZmexP/4Aceyzmpuo4c9UoNd844Aweu/AlmjsmaOMOBLA77I94RSbEuGKJt9WAPyiSZbgwVw=="],
+    "@opentui/core-linux-arm64": ["@opentui/[email protected]3", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBc1EdkVxsLBtqGjXM2BYpBJLa57ogcrSADSZbc5cQkPu0muSGzUwBbVnVZJUjWEfk6n5jcd4dDmLezVoQga0A=="],
 
 
-    "@opentui/core-linux-x64": ["@opentui/[email protected]4", "", { "os": "linux", "cpu": "x64" }, "sha512-VZxgdOUR8h2l3LUPex0A02pLsw9+P4RouL7sJ2Ul/sXvvi/b2ptzJvGQluynV6yHa2etYklZWDyWyMJmF8OKzw=="],
+    "@opentui/core-linux-x64": ["@opentui/[email protected]3", "", { "os": "linux", "cpu": "x64" }, "sha512-3oVL5mrLlKLUc1lc4v7xS3BJ9N7PnnimbGwAvlnVpfaAygotAs1XkPcjsUe6ItMnSJyi0FWiDHUE2+GiDtM5Nw=="],
 
 
-    "@opentui/core-win32-arm64": ["@opentui/[email protected]4", "", { "os": "win32", "cpu": "arm64" }, "sha512-4HXGcYdAHodhm0VnL3nn9uYFvmUhKHiN2vSMDy5KO2NZ49O1IXcS001g/TKryv0hcK1kIUBkq+RH/0vrieCAJw=="],
+    "@opentui/core-win32-arm64": ["@opentui/[email protected]3", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q68v7wssE+r0OG1KIGfi7m3fnu8KOK4ZNg9ML6EwE47VF9/bqgUe+6fPiXh5mmHzTwof7nAOdXCf052av5/upQ=="],
 
 
-    "@opentui/core-win32-x64": ["@opentui/[email protected]4", "", { "os": "win32", "cpu": "x64" }, "sha512-ptuIL6QO7LVFGI6ouZ01fw+AQfjJC+DURjsqiQhoaS/iunFefZY0q83V7ZWgv0nYlhRm+E2yWjRNNzCySJlTaQ=="],
+    "@opentui/core-win32-x64": ["@opentui/[email protected]3", "", { "os": "win32", "cpu": "x64" }, "sha512-PvuchmUnbMCUXXMzfle/WTzhNGIdJ6RGCCoclx3YVUyNUVuUicPf42OEV+td2m81/Hr3CgcLn98HYX1TLIzPrw=="],
 
 
-    "@opentui/solid": ["@opentui/[email protected]4", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.34", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-GPT+EeC6vcDnb4aUJ2K4t01GlbNoMZUfMTiIif55JSjXTKURzdDLL4mOhxar1+iJqwubYHEu/nC1GkTiGWIJoQ=="],
+    "@opentui/solid": ["@opentui/[email protected]3", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.33", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-bWSALdGJ2j51zwZ2gK1ZIBxFgauHq+V1ejEnyd4XamYMdWfpAKU+AUWDVLbpx1T9XG1oAnycJZfYX7BsZdVOOg=="],
 
 
     "@oslojs/asn1": ["@oslojs/[email protected]", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
     "@oslojs/asn1": ["@oslojs/[email protected]", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
 
 

+ 1 - 1
packages/console/app/package.json

@@ -7,7 +7,7 @@
     "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
     "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
     "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
     "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
     "start": "vinxi start",
     "start": "vinxi start",
-    "version": "1.0.20"
+    "version": "1.0.23"
   },
   },
   "dependencies": {
   "dependencies": {
     "@ibm/plex": "6.4.1",
     "@ibm/plex": "6.4.1",

+ 1 - 1
packages/console/core/package.json

@@ -1,7 +1,7 @@
 {
 {
   "$schema": "https://json.schemastore.org/package.json",
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/console-core",
   "name": "@opencode-ai/console-core",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "private": true,
   "private": true,
   "type": "module",
   "type": "module",
   "dependencies": {
   "dependencies": {

+ 1 - 1
packages/console/function/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@opencode-ai/console-function",
   "name": "@opencode-ai/console-function",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "$schema": "https://json.schemastore.org/package.json",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "private": true,
   "type": "module",
   "type": "module",

+ 1 - 1
packages/console/mail/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@opencode-ai/console-mail",
   "name": "@opencode-ai/console-mail",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "dependencies": {
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",
     "@jsx-email/cli": "1.4.3",

+ 1 - 1
packages/desktop/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@opencode-ai/desktop",
   "name": "@opencode-ai/desktop",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "description": "",
   "description": "",
   "type": "module",
   "type": "module",
   "scripts": {
   "scripts": {

+ 3 - 1
packages/desktop/src/index.tsx

@@ -12,7 +12,9 @@ import Home from "@/pages"
 const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
 const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
 const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
 const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
 
 
-const url = new URLSearchParams(document.location.search).get("url") || `http://${host}:${port}`
+const url =
+  new URLSearchParams(document.location.search).get("url") ||
+  (location.hostname.includes("opencode.ai") ? `http://${host}:${port}` : "/")
 
 
 const root = document.getElementById("root")
 const root = document.getElementById("root")
 if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
 if (import.meta.env.DEV && !(root instanceof HTMLElement)) {

+ 1 - 1
packages/function/package.json

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

+ 3 - 3
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
 {
   "$schema": "https://json.schemastore.org/package.json",
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "name": "opencode",
   "name": "opencode",
   "type": "module",
   "type": "module",
   "private": true,
   "private": true,
@@ -54,8 +54,8 @@
     "@opencode-ai/plugin": "workspace:*",
     "@opencode-ai/plugin": "workspace:*",
     "@opencode-ai/script": "workspace:*",
     "@opencode-ai/script": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",
-    "@opentui/core": "0.1.34",
-    "@opentui/solid": "0.1.34",
+    "@opentui/core": "0.1.33",
+    "@opentui/solid": "0.1.33",
     "@parcel/watcher": "2.5.1",
     "@parcel/watcher": "2.5.1",
     "@solid-primitives/event-bus": "1.1.2",
     "@solid-primitives/event-bus": "1.1.2",
     "@pierre/precision-diffs": "catalog:",
     "@pierre/precision-diffs": "catalog:",

+ 1 - 0
packages/opencode/src/auth/index.ts

@@ -10,6 +10,7 @@ export namespace Auth {
       refresh: z.string(),
       refresh: z.string(),
       access: z.string(),
       access: z.string(),
       expires: z.number(),
       expires: z.number(),
+      enterpriseUrl: z.string().optional(),
     })
     })
     .meta({ ref: "OAuth" })
     .meta({ ref: "OAuth" })
 
 

+ 193 - 148
packages/opencode/src/cli/cmd/auth.ts

@@ -102,178 +102,223 @@ export const AuthLoginCommand = cmd({
           prompts.outro("Done")
           prompts.outro("Done")
           return
           return
         }
         }
-        await ModelsDev.refresh().catch(() => {})
-        const providers = await ModelsDev.get()
-        const priority: Record<string, number> = {
-          opencode: 0,
-          anthropic: 1,
-          "github-copilot": 2,
-          openai: 3,
-          google: 4,
-          openrouter: 5,
-          vercel: 6,
-        }
-        let provider = await prompts.autocomplete({
-          message: "Select provider",
-          maxItems: 8,
-          options: [
-            ...pipe(
-              providers,
-              values(),
-              sortBy(
-                (x) => priority[x.id] ?? 99,
-                (x) => x.name ?? x.id,
-              ),
-              map((x) => ({
-                label: x.name,
-                value: x.id,
-                hint: priority[x.id] <= 1 ? "recommended" : undefined,
-              })),
+      await ModelsDev.refresh().catch(() => {})
+      const providers = await ModelsDev.get()
+      const priority: Record<string, number> = {
+        opencode: 0,
+        anthropic: 1,
+        "github-copilot": 2,
+        openai: 3,
+        google: 4,
+        openrouter: 5,
+        vercel: 6,
+      }
+      let provider = await prompts.autocomplete({
+        message: "Select provider",
+        maxItems: 8,
+        options: [
+          ...pipe(
+            providers,
+            values(),
+            sortBy(
+              (x) => priority[x.id] ?? 99,
+              (x) => x.name ?? x.id,
             ),
             ),
-            {
-              value: "other",
-              label: "Other",
-            },
-          ],
-        })
+            map((x) => ({
+              label: x.name,
+              value: x.id,
+              hint: priority[x.id] <= 1 ? "recommended" : undefined,
+            })),
+          ),
+          {
+            value: "other",
+            label: "Other",
+          },
+        ],
+      })
 
 
-        if (prompts.isCancel(provider)) throw new UI.CancelledError()
+      if (prompts.isCancel(provider)) throw new UI.CancelledError()
 
 
-        const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
-        if (plugin && plugin.auth) {
-          let index = 0
-          if (plugin.auth.methods.length > 1) {
-            const method = await prompts.select({
-              message: "Login method",
-              options: [
-                ...plugin.auth.methods.map((x, index) => ({
-                  label: x.label,
-                  value: index.toString(),
-                })),
-              ],
-            })
-            if (prompts.isCancel(method)) throw new UI.CancelledError()
-            index = parseInt(method)
-          }
-          const method = plugin.auth.methods[index]
-          if (method.type === "oauth") {
-            await new Promise((resolve) => setTimeout(resolve, 10))
-            const authorize = await method.authorize()
+      const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
+      if (plugin && plugin.auth) {
+        let index = 0
+        if (plugin.auth.methods.length > 1) {
+          const method = await prompts.select({
+            message: "Login method",
+            options: [
+              ...plugin.auth.methods.map((x, index) => ({
+                label: x.label,
+                value: index.toString(),
+              })),
+            ],
+          })
+          if (prompts.isCancel(method)) throw new UI.CancelledError()
+          index = parseInt(method)
+        }
+        const method = plugin.auth.methods[index]
 
 
-            if (authorize.url) {
-              prompts.log.info("Go to: " + authorize.url)
+        // Handle prompts for all auth types
+        await new Promise((resolve) => setTimeout(resolve, 10))
+        const inputs: Record<string, string> = {}
+        if (method.prompts) {
+          for (const prompt of method.prompts) {
+            if (prompt.condition && !prompt.condition(inputs)) {
+              continue
             }
             }
+            if (prompt.type === "select") {
+              const value = await prompts.select({
+                message: prompt.message,
+                options: prompt.options,
+              })
+              if (prompts.isCancel(value)) throw new UI.CancelledError()
+              inputs[prompt.key] = value
+            } else {
+              const value = await prompts.text({
+                message: prompt.message,
+                placeholder: prompt.placeholder,
+                validate: prompt.validate ? (v) => prompt.validate!(v ?? "") : undefined,
+              })
+              if (prompts.isCancel(value)) throw new UI.CancelledError()
+              inputs[prompt.key] = value
+            }
+          }
+        }
 
 
-            if (authorize.method === "auto") {
-              if (authorize.instructions) {
-                prompts.log.info(authorize.instructions)
-              }
-              const spinner = prompts.spinner()
-              spinner.start("Waiting for authorization...")
-              const result = await authorize.callback()
-              if (result.type === "failed") {
-                spinner.stop("Failed to authorize", 1)
+        if (method.type === "oauth") {
+          const authorize = await method.authorize(inputs)
+
+          if (authorize.url) {
+            prompts.log.info("Go to: " + authorize.url)
+          }
+
+          if (authorize.method === "auto") {
+            if (authorize.instructions) {
+              prompts.log.info(authorize.instructions)
+            }
+            const spinner = prompts.spinner()
+            spinner.start("Waiting for authorization...")
+            const result = await authorize.callback()
+            if (result.type === "failed") {
+              spinner.stop("Failed to authorize", 1)
+            }
+            if (result.type === "success") {
+              const saveProvider = result.provider ?? provider
+              if ("refresh" in result) {
+                const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
+                await Auth.set(saveProvider, {
+                  type: "oauth",
+                  refresh,
+                  access,
+                  expires,
+                  ...extraFields,
+                })
               }
               }
-              if (result.type === "success") {
-                if ("refresh" in result) {
-                  await Auth.set(provider, {
-                    type: "oauth",
-                    refresh: result.refresh,
-                    access: result.access,
-                    expires: result.expires,
-                  })
-                }
-                if ("key" in result) {
-                  await Auth.set(provider, {
-                    type: "api",
-                    key: result.key,
-                  })
-                }
-                spinner.stop("Login successful")
+              if ("key" in result) {
+                await Auth.set(saveProvider, {
+                  type: "api",
+                  key: result.key,
+                })
               }
               }
+              spinner.stop("Login successful")
             }
             }
+          }
 
 
-            if (authorize.method === "code") {
-              const code = await prompts.text({
-                message: "Paste the authorization code here: ",
-                validate: (x) => (x && x.length > 0 ? undefined : "Required"),
-              })
-              if (prompts.isCancel(code)) throw new UI.CancelledError()
-              const result = await authorize.callback(code)
-              if (result.type === "failed") {
-                prompts.log.error("Failed to authorize")
+          if (authorize.method === "code") {
+            const code = await prompts.text({
+              message: "Paste the authorization code here: ",
+              validate: (x) => (x && x.length > 0 ? undefined : "Required"),
+            })
+            if (prompts.isCancel(code)) throw new UI.CancelledError()
+            const result = await authorize.callback(code)
+            if (result.type === "failed") {
+              prompts.log.error("Failed to authorize")
+            }
+            if (result.type === "success") {
+              const saveProvider = result.provider ?? provider
+              if ("refresh" in result) {
+                const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
+                await Auth.set(saveProvider, {
+                  type: "oauth",
+                  refresh,
+                  access,
+                  expires,
+                  ...extraFields,
+                })
               }
               }
-              if (result.type === "success") {
-                if ("refresh" in result) {
-                  await Auth.set(provider, {
-                    type: "oauth",
-                    refresh: result.refresh,
-                    access: result.access,
-                    expires: result.expires,
-                  })
-                }
-                if ("key" in result) {
-                  await Auth.set(provider, {
-                    type: "api",
-                    key: result.key,
-                  })
-                }
-                prompts.log.success("Login successful")
+              if ("key" in result) {
+                await Auth.set(saveProvider, {
+                  type: "api",
+                  key: result.key,
+                })
               }
               }
+              prompts.log.success("Login successful")
             }
             }
-            prompts.outro("Done")
-            return
           }
           }
-        }
 
 
-        if (provider === "other") {
-          provider = await prompts.text({
-            message: "Enter provider id",
-            validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
-          })
-          if (prompts.isCancel(provider)) throw new UI.CancelledError()
-          provider = provider.replace(/^@ai-sdk\//, "")
-          if (prompts.isCancel(provider)) throw new UI.CancelledError()
-          prompts.log.warn(
-            `This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
-          )
-        }
-
-        if (provider === "amazon-bedrock") {
-          prompts.log.info(
-            "Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID",
-          )
-          prompts.outro("Done")
-          return
-        }
-
-        if (provider === "google-vertex") {
-          prompts.log.info(
-            "Google Cloud Vertex AI uses Application Default Credentials. Set GOOGLE_APPLICATION_CREDENTIALS or run 'gcloud auth application-default login'. Optionally set GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION (or VERTEX_LOCATION)",
-          )
           prompts.outro("Done")
           prompts.outro("Done")
           return
           return
         }
         }
 
 
-        if (provider === "opencode") {
-          prompts.log.info("Create an api key at https://opencode.ai/auth")
-        }
-
-        if (provider === "vercel") {
-          prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token")
+        if (method.type === "api") {
+          if (method.authorize) {
+            const result = await method.authorize(inputs)
+            if (result.type === "failed") {
+              prompts.log.error("Failed to authorize")
+            }
+            if (result.type === "success") {
+              const saveProvider = result.provider ?? provider
+              await Auth.set(saveProvider, {
+                type: "api",
+                key: result.key,
+              })
+              prompts.log.success("Login successful")
+            }
+            prompts.outro("Done")
+            return
+          }
         }
         }
+      }
 
 
-        const key = await prompts.password({
-          message: "Enter your API key",
-          validate: (x) => (x && x.length > 0 ? undefined : "Required"),
-        })
-        if (prompts.isCancel(key)) throw new UI.CancelledError()
-        await Auth.set(provider, {
-          type: "api",
-          key,
+      if (provider === "other") {
+        provider = await prompts.text({
+          message: "Enter provider id",
+          validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
         })
         })
+        if (prompts.isCancel(provider)) throw new UI.CancelledError()
+        provider = provider.replace(/^@ai-sdk\//, "")
+        if (prompts.isCancel(provider)) throw new UI.CancelledError()
+        prompts.log.warn(
+          `This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
+        )
+      }
 
 
+      if (provider === "amazon-bedrock") {
+        prompts.log.info(
+          "Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID",
+        )
         prompts.outro("Done")
         prompts.outro("Done")
+        return
+      }
+
+      if (provider === "opencode") {
+        prompts.log.info("Create an api key at https://opencode.ai/auth")
+      }
+
+      if (provider === "vercel") {
+        prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token")
+      }
+
+      const key = await prompts.password({
+        message: "Enter your API key",
+        validate: (x) => (x && x.length > 0 ? undefined : "Required"),
+      })
+      if (prompts.isCancel(key)) throw new UI.CancelledError()
+      await Auth.set(provider, {
+        type: "api",
+        key,
+      })
+
+      prompts.outro("Done")
       },
       },
     })
     })
   },
   },

+ 1 - 0
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -358,6 +358,7 @@ function App() {
 
 
   event.on(SessionApi.Event.Deleted.type, (evt) => {
   event.on(SessionApi.Event.Deleted.type, (evt) => {
     if (route.data.type === "session" && route.data.sessionID === evt.properties.info.id) {
     if (route.data.type === "session" && route.data.sessionID === evt.properties.info.id) {
+      dialog.clear()
       route.navigate({ type: "home" })
       route.navigate({ type: "home" })
       toast.show({
       toast.show({
         variant: "info",
         variant: "info",

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

@@ -22,15 +22,16 @@ export type CommandOption = DialogSelectOption & {
 
 
 function init() {
 function init() {
   const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
   const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
+  const [suspendCount, setSuspendCount] = createSignal(0)
   const dialog = useDialog()
   const dialog = useDialog()
   const keybind = useKeybind()
   const keybind = useKeybind()
   const options = createMemo(() => {
   const options = createMemo(() => {
     return registrations().flatMap((x) => x())
     return registrations().flatMap((x) => x())
   })
   })
+  const suspended = () => suspendCount() > 0
 
 
-  let keybinds = true
   useKeyboard((evt) => {
   useKeyboard((evt) => {
-    if (!keybinds) return
+    if (suspended()) return
     for (const option of options()) {
     for (const option of options()) {
       if (option.keybind && keybind.match(option.keybind, evt)) {
       if (option.keybind && keybind.match(option.keybind, evt)) {
         evt.preventDefault()
         evt.preventDefault()
@@ -50,8 +51,9 @@ function init() {
       }
       }
     },
     },
     keybinds(enabled: boolean) {
     keybinds(enabled: boolean) {
-      keybinds = enabled
+      setSuspendCount((count) => count + (enabled ? -1 : 1))
     },
     },
+    suspended,
     show() {
     show() {
       dialog.replace(() => <DialogCommand options={options()} />)
       dialog.replace(() => <DialogCommand options={options()} />)
     },
     },
@@ -83,7 +85,10 @@ export function CommandProvider(props: ParentProps) {
   const keybind = useKeybind()
   const keybind = useKeybind()
 
 
   useKeyboard((evt) => {
   useKeyboard((evt) => {
-    if (keybind.match("command_list", evt) && dialog.stack.length === 0) {
+    if (value.suspended()) return
+    if (dialog.stack.length > 0) return
+    if (evt.defaultPrevented) return
+    if (keybind.match("command_list", evt)) {
       evt.preventDefault()
       evt.preventDefault()
       dialog.replace(() => <DialogCommand options={value.options} />)
       dialog.replace(() => <DialogCommand options={value.options} />)
       return
       return

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

@@ -72,7 +72,7 @@ export function DialogSessionList() {
                 },
                 },
               })
               })
               setToDelete(undefined)
               setToDelete(undefined)
-              dialog.clear()
+              // dialog.clear()
               return
               return
             }
             }
             setToDelete(option.value)
             setToDelete(option.value)

+ 42 - 6
packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

@@ -54,6 +54,12 @@ export function Autocomplete(props: {
 
 
     const val = props.input().getTextRange(store.index + 1, props.input().cursorOffset + 1)
     const val = props.input().getTextRange(store.index + 1, props.input().cursorOffset + 1)
 
 
+    // If the filter contains a space, hide the autocomplete
+    if (val.includes(" ")) {
+      hide()
+      return undefined
+    }
+
     return val
     return val
   })
   })
 
 
@@ -373,15 +379,45 @@ export function Autocomplete(props: {
         return store.visible
         return store.visible
       },
       },
       onInput() {
       onInput() {
-        if (store.visible && props.input().cursorOffset <= store.index) hide()
+        if (store.visible) {
+          if (props.input().cursorOffset <= store.index) {
+            hide()
+            return
+          }
+          // Check if a space was typed after the trigger character
+          const currentText = props.input().getTextRange(store.index + 1, props.input().cursorOffset + 1)
+          if (currentText.includes(" ")) {
+            hide()
+          }
+        }
       },
       },
       onKeyDown(e: KeyEvent) {
       onKeyDown(e: KeyEvent) {
         if (store.visible) {
         if (store.visible) {
-          if (e.name === "up") move(-1)
-          if (e.name === "down") move(1)
-          if (e.name === "escape") hide()
-          if (e.name === "return" || e.name === "tab") select()
-          if (["up", "down", "return", "tab", "escape"].includes(e.name)) e.preventDefault()
+          const name = e.name?.toLowerCase()
+          const ctrlOnly = e.ctrl && !e.meta && !e.shift
+          const isNavUp = name === "up" || (ctrlOnly && name === "p")
+          const isNavDown = name === "down" || (ctrlOnly && name === "n")
+
+          if (isNavUp) {
+            move(-1)
+            e.preventDefault()
+            return
+          }
+          if (isNavDown) {
+            move(1)
+            e.preventDefault()
+            return
+          }
+          if (name === "escape") {
+            hide()
+            e.preventDefault()
+            return
+          }
+          if (name === "return" || name === "tab") {
+            select()
+            e.preventDefault()
+            return
+          }
         }
         }
         if (!store.visible) {
         if (!store.visible) {
           if (e.name === "@") {
           if (e.name === "@") {

+ 19 - 12
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -115,15 +115,11 @@ export function Prompt(props: PromptProps) {
       {
       {
         title: "Clear prompt",
         title: "Clear prompt",
         value: "prompt.clear",
         value: "prompt.clear",
-        disabled: true,
         category: "Prompt",
         category: "Prompt",
+        disabled: true,
         onSelect: (dialog) => {
         onSelect: (dialog) => {
           input.extmarks.clear()
           input.extmarks.clear()
-          setStore("prompt", {
-            input: "",
-            parts: [],
-          })
-          setStore("extmarkToPartIndex", new Map())
+          input.clear()
           dialog.clear()
           dialog.clear()
         },
         },
       },
       },
@@ -156,16 +152,27 @@ export function Prompt(props: PromptProps) {
           }
           }
         },
         },
       },
       },
+      {
+        title: "Interrupt session",
+        value: "session.interrupt",
+        keybind: "session_interrupt",
+        category: "Session",
+        disabled: true,
+        onSelect: (dialog) => {
+          if (!props.sessionID) return
+          sdk.client.session.abort({
+            path: {
+              id: props.sessionID,
+            },
+          })
+          dialog.clear()
+        },
+      },
     ]
     ]
   })
   })
 
 
   sdk.event.on(TuiEvent.PromptAppend.type, (evt) => {
   sdk.event.on(TuiEvent.PromptAppend.type, (evt) => {
-    setStore(
-      "prompt",
-      produce((draft) => {
-        draft.input += evt.properties.text
-      }),
-    )
+    input.insertText(evt.properties.text)
   })
   })
 
 
   createEffect(() => {
   createEffect(() => {

+ 6 - 3
packages/opencode/src/cli/cmd/web.ts

@@ -26,12 +26,15 @@ export const WebCommand = cmd({
       port,
       port,
       hostname,
       hostname,
     })
     })
-    const url = `https://desktop.dev.opencode.ai?url=${server.url}`
     UI.empty()
     UI.empty()
     UI.println(UI.logo("  "))
     UI.println(UI.logo("  "))
     UI.empty()
     UI.empty()
-    UI.println(UI.Style.TEXT_INFO_BOLD + "  Web interface:    ", UI.Style.TEXT_NORMAL, url)
-    open(url).catch(() => {})
+    UI.println(
+      UI.Style.TEXT_INFO_BOLD + "  Web interface:    ",
+      UI.Style.TEXT_NORMAL,
+      server.url.toString(),
+    )
+    open(server.url.toString()).catch(() => {})
     await new Promise(() => {})
     await new Promise(() => {})
     await server.stop()
     await server.stop()
   },
   },

+ 1 - 0
packages/opencode/src/config/config.ts

@@ -574,6 +574,7 @@ export namespace Config {
                 .object({
                 .object({
                   apiKey: z.string().optional(),
                   apiKey: z.string().optional(),
                   baseURL: z.string().optional(),
                   baseURL: z.string().optional(),
+                  enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
                   timeout: z
                   timeout: z
                     .union([
                     .union([
                       z
                       z

+ 1 - 1
packages/opencode/src/plugin/index.ts

@@ -28,7 +28,7 @@ export namespace Plugin {
     }
     }
     const plugins = [...(config.plugin ?? [])]
     const plugins = [...(config.plugin ?? [])]
     if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
     if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
-      plugins.push("[email protected].3")
+      plugins.push("[email protected].4")
       plugins.push("[email protected]")
       plugins.push("[email protected]")
     }
     }
     for (let plugin of plugins) {
     for (let plugin of plugins) {

+ 52 - 8
packages/opencode/src/provider/provider.ts

@@ -283,6 +283,18 @@ export namespace Provider {
 
 
     const configProviders = Object.entries(config.provider ?? {})
     const configProviders = Object.entries(config.provider ?? {})
 
 
+    // Add GitHub Copilot Enterprise provider that inherits from GitHub Copilot
+    if (database["github-copilot"]) {
+      const githubCopilot = database["github-copilot"]
+      database["github-copilot-enterprise"] = {
+        ...githubCopilot,
+        id: "github-copilot-enterprise",
+        name: "GitHub Copilot Enterprise",
+        // Enterprise uses a different API endpoint - will be set dynamically based on auth
+        api: undefined,
+      }
+    }
+
     for (const [providerID, provider] of configProviders) {
     for (const [providerID, provider] of configProviders) {
       const existing = database[providerID]
       const existing = database[providerID]
       const parsed: ModelsDev.Provider = {
       const parsed: ModelsDev.Provider = {
@@ -378,14 +390,44 @@ export namespace Provider {
       if (!plugin.auth) continue
       if (!plugin.auth) continue
       const providerID = plugin.auth.provider
       const providerID = plugin.auth.provider
       if (disabled.has(providerID)) continue
       if (disabled.has(providerID)) continue
+
+      // For github-copilot plugin, check if auth exists for either github-copilot or github-copilot-enterprise
+      let hasAuth = false
       const auth = await Auth.get(providerID)
       const auth = await Auth.get(providerID)
-      if (!auth) continue
+      if (auth) hasAuth = true
+
+      // Special handling for github-copilot: also check for enterprise auth
+      if (providerID === "github-copilot" && !hasAuth) {
+        const enterpriseAuth = await Auth.get("github-copilot-enterprise")
+        if (enterpriseAuth) hasAuth = true
+      }
+
+      if (!hasAuth) continue
       if (!plugin.auth.loader) continue
       if (!plugin.auth.loader) continue
-      const options = await plugin.auth.loader(
-        () => Auth.get(providerID) as any,
-        database[plugin.auth.provider],
-      )
-      mergeProvider(plugin.auth.provider, options ?? {}, "custom")
+
+      // Load for the main provider if auth exists
+      if (auth) {
+        const options = await plugin.auth.loader(
+          () => Auth.get(providerID) as any,
+          database[plugin.auth.provider],
+        )
+        mergeProvider(plugin.auth.provider, options ?? {}, "custom")
+      }
+
+      // If this is github-copilot plugin, also register for github-copilot-enterprise if auth exists
+      if (providerID === "github-copilot") {
+        const enterpriseProviderID = "github-copilot-enterprise"
+        if (!disabled.has(enterpriseProviderID)) {
+          const enterpriseAuth = await Auth.get(enterpriseProviderID)
+          if (enterpriseAuth) {
+            const enterpriseOptions = await plugin.auth.loader(
+              () => Auth.get(enterpriseProviderID) as any,
+              database[enterpriseProviderID],
+            )
+            mergeProvider(enterpriseProviderID, enterpriseOptions ?? {}, "custom")
+          }
+        }
+      }
     }
     }
 
 
     // load config
     // load config
@@ -458,7 +500,8 @@ export namespace Provider {
           : installedPath
           : installedPath
       const mod = await import(modPath)
       const mod = await import(modPath)
       if (options["timeout"] !== undefined && options["timeout"] !== null) {
       if (options["timeout"] !== undefined && options["timeout"] !== null) {
-        // Only override fetch if user explicitly sets timeout
+        // Preserve custom fetch if it exists, wrap it with timeout logic
+        const customFetch = options["fetch"]
         options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
         options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
           const { signal, ...rest } = init ?? {}
           const { signal, ...rest } = init ?? {}
 
 
@@ -468,7 +511,8 @@ export namespace Provider {
 
 
           const combined = signals.length > 1 ? AbortSignal.any(signals) : signals[0]
           const combined = signals.length > 1 ? AbortSignal.any(signals) : signals[0]
 
 
-          return fetch(input, {
+          const fetchFn = customFetch ?? fetch
+          return fetchFn(input, {
             ...rest,
             ...rest,
             signal: combined,
             signal: combined,
             // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
             // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682

+ 38 - 1
packages/opencode/src/server/server.ts

@@ -10,6 +10,7 @@ import {
 import { Hono } from "hono"
 import { Hono } from "hono"
 import { cors } from "hono/cors"
 import { cors } from "hono/cors"
 import { stream, streamSSE } from "hono/streaming"
 import { stream, streamSSE } from "hono/streaming"
+import { proxy } from "hono/proxy"
 import { Session } from "../session"
 import { Session } from "../session"
 import z from "zod"
 import z from "zod"
 import { Provider } from "../provider/provider"
 import { Provider } from "../provider/provider"
@@ -757,6 +758,34 @@ export namespace Server {
           return c.json(messages)
           return c.json(messages)
         },
         },
       )
       )
+      .get(
+        "/session/:id/diff",
+        describeRoute({
+          description: "Get the diff for this session",
+          operationId: "session.diff",
+          responses: {
+            200: {
+              description: "List of diffs",
+              content: {
+                "application/json": {
+                  schema: resolver(Snapshot.FileDiff.array()),
+                },
+              },
+            },
+            ...errors(400, 404),
+          },
+        }),
+        validator(
+          "param",
+          z.object({
+            id: z.string().meta({ description: "Session ID" }),
+          }),
+        ),
+        async (c) => {
+          const diff = await Session.diff(c.req.valid("param").id)
+          return c.json(diff)
+        },
+      )
       .get(
       .get(
         "/session/:id/message/:messageID",
         "/session/:id/message/:messageID",
         describeRoute({
         describeRoute({
@@ -1696,7 +1725,15 @@ export namespace Server {
             })
             })
           })
           })
         },
         },
-      ),
+      )
+      .all("/*", async (c) => {
+        return proxy(`https://desktop.dev.opencode.ai${c.req.path}`, {
+          ...c.req,
+          headers: {
+            host: "desktop.dev.opencode.ai",
+          },
+        })
+      }),
   )
   )
 
 
   export async function openapi() {
   export async function openapi() {

+ 9 - 2
packages/opencode/src/session/index.ts

@@ -15,8 +15,8 @@ import { MessageV2 } from "./message-v2"
 import { Instance } from "../project/instance"
 import { Instance } from "../project/instance"
 import { SessionPrompt } from "./prompt"
 import { SessionPrompt } from "./prompt"
 import { fn } from "@/util/fn"
 import { fn } from "@/util/fn"
-import { Snapshot } from "@/snapshot"
 import { Command } from "../command"
 import { Command } from "../command"
+import { Snapshot } from "@/snapshot"
 
 
 export namespace Session {
 export namespace Session {
   const log = Log.create({ service: "session" })
   const log = Log.create({ service: "session" })
@@ -42,7 +42,9 @@ export namespace Session {
       parentID: Identifier.schema("session").optional(),
       parentID: Identifier.schema("session").optional(),
       summary: z
       summary: z
         .object({
         .object({
-          diffs: Snapshot.FileDiff.array(),
+          additions: z.number(),
+          deletions: z.number(),
+          diffs: Snapshot.FileDiff.array().optional(),
         })
         })
         .optional(),
         .optional(),
       share: z
       share: z
@@ -258,6 +260,11 @@ export namespace Session {
     return result
     return result
   }
   }
 
 
+  export const diff = fn(Identifier.schema("session"), async (sessionID) => {
+    const diffs = await Storage.read<Snapshot.FileDiff[]>(["session_diff", sessionID])
+    return diffs ?? []
+  })
+
   export const messages = fn(Identifier.schema("session"), async (sessionID) => {
   export const messages = fn(Identifier.schema("session"), async (sessionID) => {
     const result = [] as MessageV2.WithParts[]
     const result = [] as MessageV2.WithParts[]
     for (const p of await Storage.list(["message", sessionID])) {
     for (const p of await Storage.list(["message", sessionID])) {

+ 4 - 1
packages/opencode/src/session/summary.ts

@@ -11,6 +11,7 @@ import { SystemPrompt } from "./system"
 import { Log } from "@/util/log"
 import { Log } from "@/util/log"
 import path from "path"
 import path from "path"
 import { Instance } from "@/project/instance"
 import { Instance } from "@/project/instance"
+import { Storage } from "@/storage/storage"
 
 
 export namespace SessionSummary {
 export namespace SessionSummary {
   const log = Log.create({ service: "session.summary" })
   const log = Log.create({ service: "session.summary" })
@@ -44,9 +45,11 @@ export namespace SessionSummary {
     )
     )
     await Session.update(input.sessionID, (draft) => {
     await Session.update(input.sessionID, (draft) => {
       draft.summary = {
       draft.summary = {
-        diffs,
+        additions: diffs.reduce((sum, x) => sum + x.additions, 0),
+        deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
       }
       }
     })
     })
+    await Storage.write(["session_diff", input.sessionID], diffs)
   }
   }
 
 
   async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) {
   async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) {

+ 33 - 10
packages/opencode/src/storage/storage.ts

@@ -85,7 +85,9 @@ export namespace Storage {
             const session = await Bun.file(sessionFile).json()
             const session = await Bun.file(sessionFile).json()
             await Bun.write(dest, JSON.stringify(session))
             await Bun.write(dest, JSON.stringify(session))
             log.info(`migrating messages for session ${session.id}`)
             log.info(`migrating messages for session ${session.id}`)
-            for await (const msgFile of new Bun.Glob(`storage/session/message/${session.id}/*.json`).scan({
+            for await (const msgFile of new Bun.Glob(
+              `storage/session/message/${session.id}/*.json`,
+            ).scan({
               cwd: fullProjectDir,
               cwd: fullProjectDir,
               absolute: true,
               absolute: true,
             })) {
             })) {
@@ -98,12 +100,12 @@ export namespace Storage {
               await Bun.write(dest, JSON.stringify(message))
               await Bun.write(dest, JSON.stringify(message))
 
 
               log.info(`migrating parts for message ${message.id}`)
               log.info(`migrating parts for message ${message.id}`)
-              for await (const partFile of new Bun.Glob(`storage/session/part/${session.id}/${message.id}/*.json`).scan(
-                {
-                  cwd: fullProjectDir,
-                  absolute: true,
-                },
-              )) {
+              for await (const partFile of new Bun.Glob(
+                `storage/session/part/${session.id}/${message.id}/*.json`,
+              ).scan({
+                cwd: fullProjectDir,
+                absolute: true,
+              })) {
                 const dest = path.join(dir, "part", message.id, path.basename(partFile))
                 const dest = path.join(dir, "part", message.id, path.basename(partFile))
                 const part = await Bun.file(partFile).json()
                 const part = await Bun.file(partFile).json()
                 log.info("copying", {
                 log.info("copying", {
@@ -117,6 +119,29 @@ export namespace Storage {
         }
         }
       }
       }
     },
     },
+    async (dir) => {
+      for await (const item of new Bun.Glob("session/*/*.json").scan({
+        cwd: dir,
+        absolute: true,
+      })) {
+        const session = await Bun.file(item).json()
+        if (!session.projectID) continue
+        if (!session.summary?.diffs) continue
+        const { diffs } = session.summary
+        await Bun.file(path.join(dir, "session_diff", session.id + ".json")).write(
+          JSON.stringify(diffs),
+        )
+        await Bun.file(path.join(dir, "session", session.projectID, session.id + ".json")).write(
+          JSON.stringify({
+            ...session,
+            summary: {
+              additions: diffs.reduce((sum: any, x: any) => sum + x.additions, 0),
+              deletions: diffs.reduce((sum: any, x: any) => sum + x.deletions, 0),
+            },
+          }),
+        )
+      }
+    },
   ]
   ]
 
 
   const state = lazy(async () => {
   const state = lazy(async () => {
@@ -128,9 +153,7 @@ export namespace Storage {
     for (let index = migration; index < MIGRATIONS.length; index++) {
     for (let index = migration; index < MIGRATIONS.length; index++) {
       log.info("running migration", { index })
       log.info("running migration", { index })
       const migration = MIGRATIONS[index]
       const migration = MIGRATIONS[index]
-      await migration(dir).catch((e) => {
-        log.error("failed to run migration", { error: e, index })
-      })
+      await migration(dir).catch(() => log.error("failed to run migration", { index }))
       await Bun.write(path.join(dir, "migration"), (index + 1).toString())
       await Bun.write(path.join(dir, "migration"), (index + 1).toString())
     }
     }
     return {
     return {

+ 1 - 1
packages/plugin/package.json

@@ -1,7 +1,7 @@
 {
 {
   "$schema": "https://json.schemastore.org/package.json",
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/plugin",
   "name": "@opencode-ai/plugin",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "type": "module",
   "type": "module",
   "scripts": {
   "scripts": {
     "typecheck": "tsgo --noEmit",
     "typecheck": "tsgo --noEmit",

+ 59 - 2
packages/plugin/src/index.ts

@@ -39,13 +39,35 @@ export interface Hooks {
       | {
       | {
           type: "oauth"
           type: "oauth"
           label: string
           label: string
-          authorize(): Promise<
+          prompts?: Array<
+            | {
+                type: "text"
+                key: string
+                message: string
+                placeholder?: string
+                validate?: (value: string) => string | undefined
+                condition?: (inputs: Record<string, string>) => boolean
+              }
+            | {
+                type: "select"
+                key: string
+                message: string
+                options: Array<{
+                  label: string
+                  value: string
+                  hint?: string
+                }>
+                condition?: (inputs: Record<string, string>) => boolean
+              }
+          >
+          authorize(inputs?: Record<string, string>): Promise<
             { url: string; instructions: string } & (
             { url: string; instructions: string } & (
               | {
               | {
                   method: "auto"
                   method: "auto"
                   callback(): Promise<
                   callback(): Promise<
                     | ({
                     | ({
                         type: "success"
                         type: "success"
+                        provider?: string
                       } & (
                       } & (
                         | {
                         | {
                             refresh: string
                             refresh: string
@@ -64,6 +86,7 @@ export interface Hooks {
                   callback(code: string): Promise<
                   callback(code: string): Promise<
                     | ({
                     | ({
                         type: "success"
                         type: "success"
+                        provider?: string
                       } & (
                       } & (
                         | {
                         | {
                             refresh: string
                             refresh: string
@@ -80,7 +103,41 @@ export interface Hooks {
             )
             )
           >
           >
         }
         }
-      | { type: "api"; label: string }
+      | {
+          type: "api"
+          label: string
+          prompts?: Array<
+            | {
+                type: "text"
+                key: string
+                message: string
+                placeholder?: string
+                validate?: (value: string) => string | undefined
+                condition?: (inputs: Record<string, string>) => boolean
+              }
+            | {
+                type: "select"
+                key: string
+                message: string
+                options: Array<{
+                  label: string
+                  value: string
+                  hint?: string
+                }>
+                condition?: (inputs: Record<string, string>) => boolean
+              }
+          >
+          authorize?(inputs?: Record<string, string>): Promise<
+            | {
+                type: "success"
+                key: string
+                provider?: string
+              }
+            | {
+                type: "failed"
+              }
+          >
+        }
     )[]
     )[]
   }
   }
   /**
   /**

+ 1 - 1
packages/sdk/js/package.json

@@ -1,7 +1,7 @@
 {
 {
   "$schema": "https://json.schemastore.org/package.json",
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/sdk",
   "name": "@opencode-ai/sdk",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "type": "module",
   "type": "module",
   "scripts": {
   "scripts": {
     "typecheck": "tsgo --noEmit",
     "typecheck": "tsgo --noEmit",

+ 7 - 2
packages/sdk/js/src/gen/sdk.gen.ts

@@ -55,6 +55,7 @@ import type {
   SessionShareErrors,
   SessionShareErrors,
   SessionDiffData,
   SessionDiffData,
   SessionDiffResponses,
   SessionDiffResponses,
+  SessionDiffErrors,
   SessionSummarizeData,
   SessionSummarizeData,
   SessionSummarizeResponses,
   SessionSummarizeResponses,
   SessionSummarizeErrors,
   SessionSummarizeErrors,
@@ -475,12 +476,16 @@ class Session extends _HeyApiClient {
   }
   }
 
 
   /**
   /**
-   * Get the diff that resulted from this user message
+   * Get the diff for this session
    */
    */
   public diff<ThrowOnError extends boolean = false>(
   public diff<ThrowOnError extends boolean = false>(
     options: Options<SessionDiffData, ThrowOnError>,
     options: Options<SessionDiffData, ThrowOnError>,
   ) {
   ) {
-    return (options.client ?? this._client).get<SessionDiffResponses, unknown, ThrowOnError>({
+    return (options.client ?? this._client).get<
+      SessionDiffResponses,
+      SessionDiffErrors,
+      ThrowOnError
+    >({
       url: "/session/{id}/diff",
       url: "/session/{id}/diff",
       ...options,
       ...options,
     })
     })

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

@@ -163,7 +163,7 @@ export type KeybindsConfig = {
    */
    */
   history_previous?: string
   history_previous?: string
   /**
   /**
-   * Previous history item
+   * Next history item
    */
    */
   history_next?: string
   history_next?: string
   /**
   /**
@@ -405,6 +405,10 @@ export type Config = {
       options?: {
       options?: {
         apiKey?: string
         apiKey?: string
         baseURL?: string
         baseURL?: string
+        /**
+         * GitHub Enterprise URL for copilot authentication
+         */
+        enterpriseUrl?: string
         /**
         /**
          * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.
          * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.
          */
          */
@@ -527,7 +531,9 @@ export type Session = {
   directory: string
   directory: string
   parentID?: string
   parentID?: string
   summary?: {
   summary?: {
-    diffs: Array<FileDiff>
+    additions: number
+    deletions: number
+    diffs?: Array<FileDiff>
   }
   }
   share?: {
   share?: {
     url: string
     url: string
@@ -1133,6 +1139,7 @@ export type OAuth = {
   refresh: string
   refresh: string
   access: string
   access: string
   expires: number
   expires: number
+  enterpriseUrl?: string
 }
 }
 
 
 export type ApiAuth = {
 export type ApiAuth = {
@@ -1882,6 +1889,9 @@ export type SessionShareResponse = SessionShareResponses[keyof SessionShareRespo
 export type SessionDiffData = {
 export type SessionDiffData = {
   body?: never
   body?: never
   path: {
   path: {
+    /**
+     * Session ID
+     */
     id: string
     id: string
   }
   }
   query?: {
   query?: {
@@ -1891,9 +1901,22 @@ export type SessionDiffData = {
   url: "/session/{id}/diff"
   url: "/session/{id}/diff"
 }
 }
 
 
+export type SessionDiffErrors = {
+  /**
+   * Bad request
+   */
+  400: BadRequestError
+  /**
+   * Not found
+   */
+  404: NotFoundError
+}
+
+export type SessionDiffError = SessionDiffErrors[keyof SessionDiffErrors]
+
 export type SessionDiffResponses = {
 export type SessionDiffResponses = {
   /**
   /**
-   * Successfully retrieved diff
+   * List of diffs
    */
    */
   200: Array<FileDiff>
   200: Array<FileDiff>
 }
 }

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@opencode-ai/slack",
   "name": "@opencode-ai/slack",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "type": "module",
   "type": "module",
   "scripts": {
   "scripts": {
     "dev": "bun run src/index.ts",
     "dev": "bun run src/index.ts",

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@opencode-ai/ui",
   "name": "@opencode-ai/ui",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "type": "module",
   "type": "module",
   "exports": {
   "exports": {
     ".": "./src/components/index.ts",
     ".": "./src/components/index.ts",

+ 1 - 1
packages/web/package.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "@opencode-ai/web",
   "name": "@opencode-ai/web",
   "type": "module",
   "type": "module",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "scripts": {
   "scripts": {
     "dev": "astro dev",
     "dev": "astro dev",
     "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
     "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",

+ 1 - 1
sdks/vscode/package.json

@@ -2,7 +2,7 @@
   "name": "opencode",
   "name": "opencode",
   "displayName": "opencode",
   "displayName": "opencode",
   "description": "opencode for VS Code",
   "description": "opencode for VS Code",
-  "version": "1.0.20",
+  "version": "1.0.23",
   "publisher": "sst-dev",
   "publisher": "sst-dev",
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",