summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Hill <[email protected]>2025-11-04 21:36:46 +0000
committerDavid Hill <[email protected]>2025-11-04 21:36:46 +0000
commit7088bfabd773e2f076aab1c9d2468c04feff0570 (patch)
tree67edc3c55bbf4110c8f6c2c12be2f9a0a4ba463a
parentdbdbfb85431eb12b3159992b301fdba54b026ab8 (diff)
parentfe94bb8e50ed9625e553cc7bd79d3a02889c2979 (diff)
downloadopencode-7088bfabd773e2f076aab1c9d2468c04feff0570.tar.gz
opencode-7088bfabd773e2f076aab1c9d2468c04feff0570.zip
Merge branch 'dev' of https://github.com/sst/opencode into dev
-rw-r--r--bun.lock42
-rw-r--r--packages/console/app/package.json2
-rw-r--r--packages/console/core/package.json2
-rw-r--r--packages/console/function/package.json2
-rw-r--r--packages/console/mail/package.json2
-rw-r--r--packages/desktop/package.json2
-rw-r--r--packages/desktop/src/index.tsx4
-rw-r--r--packages/function/package.json2
-rw-r--r--packages/opencode/package.json6
-rw-r--r--packages/opencode/src/auth/index.ts1
-rw-r--r--packages/opencode/src/cli/cmd/auth.ts341
-rw-r--r--packages/opencode/src/cli/cmd/tui/app.tsx1
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx13
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx2
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx48
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx31
-rw-r--r--packages/opencode/src/cli/cmd/web.ts9
-rw-r--r--packages/opencode/src/config/config.ts1
-rw-r--r--packages/opencode/src/plugin/index.ts2
-rw-r--r--packages/opencode/src/provider/provider.ts60
-rw-r--r--packages/opencode/src/server/server.ts39
-rw-r--r--packages/opencode/src/session/index.ts11
-rw-r--r--packages/opencode/src/session/summary.ts5
-rw-r--r--packages/opencode/src/storage/storage.ts43
-rw-r--r--packages/plugin/package.json2
-rw-r--r--packages/plugin/src/index.ts61
-rw-r--r--packages/sdk/js/package.json2
-rw-r--r--packages/sdk/js/src/gen/sdk.gen.ts9
-rw-r--r--packages/sdk/js/src/gen/types.gen.ts29
-rw-r--r--packages/slack/package.json2
-rw-r--r--packages/ui/package.json2
-rw-r--r--packages/web/package.json2
-rw-r--r--sdks/vscode/package.json2
33 files changed, 541 insertions, 241 deletions
diff --git a/bun.lock b/bun.lock
index e49c70c63..e18ad9507 100644
--- a/bun.lock
+++ b/bun.lock
@@ -39,7 +39,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -66,7 +66,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -90,7 +90,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -111,7 +111,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -150,7 +150,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -166,7 +166,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.0.20",
+ "version": "1.0.23",
"bin": {
"opencode": "./bin/opencode",
},
@@ -184,8 +184,8 @@
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "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",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -243,7 +243,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -263,7 +263,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.0.20",
+ "version": "1.0.23",
"devDependencies": {
"@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:",
@@ -274,7 +274,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -287,7 +287,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -317,7 +317,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -961,21 +961,21 @@
"@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
- "@opentui/core": ["@opentui/[email protected]", "", { "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]", "", { "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]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P/Pw66vJ1W5pIVg7D5bUlMPBTarXh0S/conHRaeybBZoO+8G04A6x9ufeaD/L4HCE0iR0huSoHGDB1VxZUL2Zg=="],
+ "@opentui/core-darwin-arm64": ["@opentui/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JBvzcP2V7fT9KxFAMenHRd/t72qPP5IL5kzge2uok1T7t2nw3Wa+CWI5s6FYP42p2b1W9qZkv5Fno5gA7OAYuQ=="],
- "@opentui/core-darwin-x64": ["@opentui/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-JKfDC2qI1AmY4u504FKfrSdP0qOJIn+rI7kj0C0ydpvj1Wd2c6ImOsbnny70372Uq/m3EXxPE3Hq/66DL4P94A=="],
+ "@opentui/core-darwin-x64": ["@opentui/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-x7DY6VCkAky10z/2o4UkkuNW/nIvoX7uAh3dJOHWZCLbiKywSFvFk3QZVVcH5BMk4tOOophYTzika4s4HpaeMg=="],
- "@opentui/core-linux-arm64": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-E1xAuz0xx7lmh7tZmexP/4Aceyzmpuo4c9UoNd844Aweu/AlmjsmaOMOBLA77I94RSbEuGKJt9WAPyiSZbgwVw=="],
+ "@opentui/core-linux-arm64": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBc1EdkVxsLBtqGjXM2BYpBJLa57ogcrSADSZbc5cQkPu0muSGzUwBbVnVZJUjWEfk6n5jcd4dDmLezVoQga0A=="],
- "@opentui/core-linux-x64": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-VZxgdOUR8h2l3LUPex0A02pLsw9+P4RouL7sJ2Ul/sXvvi/b2ptzJvGQluynV6yHa2etYklZWDyWyMJmF8OKzw=="],
+ "@opentui/core-linux-x64": ["@opentui/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-3oVL5mrLlKLUc1lc4v7xS3BJ9N7PnnimbGwAvlnVpfaAygotAs1XkPcjsUe6ItMnSJyi0FWiDHUE2+GiDtM5Nw=="],
- "@opentui/core-win32-arm64": ["@opentui/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-4HXGcYdAHodhm0VnL3nn9uYFvmUhKHiN2vSMDy5KO2NZ49O1IXcS001g/TKryv0hcK1kIUBkq+RH/0vrieCAJw=="],
+ "@opentui/core-win32-arm64": ["@opentui/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q68v7wssE+r0OG1KIGfi7m3fnu8KOK4ZNg9ML6EwE47VF9/bqgUe+6fPiXh5mmHzTwof7nAOdXCf052av5/upQ=="],
- "@opentui/core-win32-x64": ["@opentui/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-ptuIL6QO7LVFGI6ouZ01fw+AQfjJC+DURjsqiQhoaS/iunFefZY0q83V7ZWgv0nYlhRm+E2yWjRNNzCySJlTaQ=="],
+ "@opentui/core-win32-x64": ["@opentui/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-PvuchmUnbMCUXXMzfle/WTzhNGIdJ6RGCCoclx3YVUyNUVuUicPf42OEV+td2m81/Hr3CgcLn98HYX1TLIzPrw=="],
- "@opentui/solid": ["@opentui/[email protected]", "", { "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]", "", { "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=="],
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index ad25ada72..7e06d8248 100644
--- a/packages/console/app/package.json
+++ b/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",
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
- "version": "1.0.20"
+ "version": "1.0.23"
},
"dependencies": {
"@ibm/plex": "6.4.1",
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index 5dd902e76..c1ab23476 100644
--- a/packages/console/core/package.json
+++ b/packages/console/core/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
- "version": "1.0.20",
+ "version": "1.0.23",
"private": true,
"type": "module",
"dependencies": {
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index 564d5b0c6..0c6e02886 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
- "version": "1.0.20",
+ "version": "1.0.23",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json
index 7d1d54ab7..30e772b6b 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
- "version": "1.0.20",
+ "version": "1.0.23",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index 4364e34d7..341884453 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
- "version": "1.0.20",
+ "version": "1.0.23",
"description": "",
"type": "module",
"scripts": {
diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx
index 0d631a5a0..9fe5da2f6 100644
--- a/packages/desktop/src/index.tsx
+++ b/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 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")
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
diff --git a/packages/function/package.json b/packages/function/package.json
index 4861ea8e2..eeb842ca3 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
- "version": "1.0.20",
+ "version": "1.0.23",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 927ac9d23..3e4f5c02c 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "1.0.20",
+ "version": "1.0.23",
"name": "opencode",
"type": "module",
"private": true,
@@ -54,8 +54,8 @@
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "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",
"@solid-primitives/event-bus": "1.1.2",
"@pierre/precision-diffs": "catalog:",
diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts
index 6d90c9325..883b9acc6 100644
--- a/packages/opencode/src/auth/index.ts
+++ b/packages/opencode/src/auth/index.ts
@@ -10,6 +10,7 @@ export namespace Auth {
refresh: z.string(),
access: z.string(),
expires: z.number(),
+ enterpriseUrl: z.string().optional(),
})
.meta({ ref: "OAuth" })
diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts
index 763d82b3f..aa833e977 100644
--- a/packages/opencode/src/cli/cmd/auth.ts
+++ b/packages/opencode/src/cli/cmd/auth.ts
@@ -102,178 +102,223 @@ export const AuthLoginCommand = cmd({
prompts.outro("Done")
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")
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")
+ 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")
},
})
},
diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index 7938ec86f..e57ef8cd6 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -358,6 +358,7 @@ function App() {
event.on(SessionApi.Event.Deleted.type, (evt) => {
if (route.data.type === "session" && route.data.sessionID === evt.properties.info.id) {
+ dialog.clear()
route.navigate({ type: "home" })
toast.show({
variant: "info",
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx
index 66a312e5b..b9ba4a9ba 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx
@@ -22,15 +22,16 @@ export type CommandOption = DialogSelectOption & {
function init() {
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
+ const [suspendCount, setSuspendCount] = createSignal(0)
const dialog = useDialog()
const keybind = useKeybind()
const options = createMemo(() => {
return registrations().flatMap((x) => x())
})
+ const suspended = () => suspendCount() > 0
- let keybinds = true
useKeyboard((evt) => {
- if (!keybinds) return
+ if (suspended()) return
for (const option of options()) {
if (option.keybind && keybind.match(option.keybind, evt)) {
evt.preventDefault()
@@ -50,8 +51,9 @@ function init() {
}
},
keybinds(enabled: boolean) {
- keybinds = enabled
+ setSuspendCount((count) => count + (enabled ? -1 : 1))
},
+ suspended,
show() {
dialog.replace(() => <DialogCommand options={options()} />)
},
@@ -83,7 +85,10 @@ export function CommandProvider(props: ParentProps) {
const keybind = useKeybind()
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()
dialog.replace(() => <DialogCommand options={value.options} />)
return
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx
index 31b1c2b5c..4a720aa15 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx
@@ -72,7 +72,7 @@ export function DialogSessionList() {
},
})
setToDelete(undefined)
- dialog.clear()
+ // dialog.clear()
return
}
setToDelete(option.value)
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
index 02fd3fccb..a4c1f33d9 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
+++ b/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)
+ // If the filter contains a space, hide the autocomplete
+ if (val.includes(" ")) {
+ hide()
+ return undefined
+ }
+
return val
})
@@ -373,15 +379,45 @@ export function Autocomplete(props: {
return store.visible
},
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) {
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 (e.name === "@") {
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index 7835539ed..785eb7e4b 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -115,15 +115,11 @@ export function Prompt(props: PromptProps) {
{
title: "Clear prompt",
value: "prompt.clear",
- disabled: true,
category: "Prompt",
+ disabled: true,
onSelect: (dialog) => {
input.extmarks.clear()
- setStore("prompt", {
- input: "",
- parts: [],
- })
- setStore("extmarkToPartIndex", new Map())
+ input.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) => {
- setStore(
- "prompt",
- produce((draft) => {
- draft.input += evt.properties.text
- }),
- )
+ input.insertText(evt.properties.text)
})
createEffect(() => {
diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts
index 4823414f7..8fc8a9915 100644
--- a/packages/opencode/src/cli/cmd/web.ts
+++ b/packages/opencode/src/cli/cmd/web.ts
@@ -26,12 +26,15 @@ export const WebCommand = cmd({
port,
hostname,
})
- const url = `https://desktop.dev.opencode.ai?url=${server.url}`
UI.empty()
UI.println(UI.logo(" "))
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 server.stop()
},
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 63a956fb3..efd31ccba 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -574,6 +574,7 @@ export namespace Config {
.object({
apiKey: z.string().optional(),
baseURL: z.string().optional(),
+ enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
timeout: z
.union([
z
diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts
index 1d433628d..a3d8d6c0e 100644
--- a/packages/opencode/src/plugin/index.ts
+++ b/packages/opencode/src/plugin/index.ts
@@ -28,7 +28,7 @@ export namespace Plugin {
}
const plugins = [...(config.plugin ?? [])]
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
- plugins.push("[email protected]")
+ plugins.push("[email protected]")
plugins.push("[email protected]")
}
for (let plugin of plugins) {
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 730ba3963..3be091df8 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -283,6 +283,18 @@ export namespace 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) {
const existing = database[providerID]
const parsed: ModelsDev.Provider = {
@@ -378,14 +390,44 @@ export namespace Provider {
if (!plugin.auth) continue
const providerID = plugin.auth.provider
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)
- 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
- 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
@@ -458,7 +500,8 @@ export namespace Provider {
: installedPath
const mod = await import(modPath)
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) => {
const { signal, ...rest } = init ?? {}
@@ -468,7 +511,8 @@ export namespace Provider {
const combined = signals.length > 1 ? AbortSignal.any(signals) : signals[0]
- return fetch(input, {
+ const fetchFn = customFetch ?? fetch
+ return fetchFn(input, {
...rest,
signal: combined,
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index 919d68287..59e066e15 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -10,6 +10,7 @@ import {
import { Hono } from "hono"
import { cors } from "hono/cors"
import { stream, streamSSE } from "hono/streaming"
+import { proxy } from "hono/proxy"
import { Session } from "../session"
import z from "zod"
import { Provider } from "../provider/provider"
@@ -758,6 +759,34 @@ export namespace Server {
},
)
.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(
"/session/:id/message/:messageID",
describeRoute({
description: "Get a message from a session",
@@ -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() {
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 71d59d84e..f8a2ba918 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -15,8 +15,8 @@ import { MessageV2 } from "./message-v2"
import { Instance } from "../project/instance"
import { SessionPrompt } from "./prompt"
import { fn } from "@/util/fn"
-import { Snapshot } from "@/snapshot"
import { Command } from "../command"
+import { Snapshot } from "@/snapshot"
export namespace Session {
const log = Log.create({ service: "session" })
@@ -42,7 +42,9 @@ export namespace Session {
parentID: Identifier.schema("session").optional(),
summary: z
.object({
- diffs: Snapshot.FileDiff.array(),
+ additions: z.number(),
+ deletions: z.number(),
+ diffs: Snapshot.FileDiff.array().optional(),
})
.optional(),
share: z
@@ -258,6 +260,11 @@ export namespace Session {
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) => {
const result = [] as MessageV2.WithParts[]
for (const p of await Storage.list(["message", sessionID])) {
diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts
index ffd0f8da7..9795a3069 100644
--- a/packages/opencode/src/session/summary.ts
+++ b/packages/opencode/src/session/summary.ts
@@ -11,6 +11,7 @@ import { SystemPrompt } from "./system"
import { Log } from "@/util/log"
import path from "path"
import { Instance } from "@/project/instance"
+import { Storage } from "@/storage/storage"
export namespace SessionSummary {
const log = Log.create({ service: "session.summary" })
@@ -44,9 +45,11 @@ export namespace SessionSummary {
)
await Session.update(input.sessionID, (draft) => {
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[] }) {
diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts
index 9eb0f8f51..66ce20d95 100644
--- a/packages/opencode/src/storage/storage.ts
+++ b/packages/opencode/src/storage/storage.ts
@@ -85,7 +85,9 @@ export namespace Storage {
const session = await Bun.file(sessionFile).json()
await Bun.write(dest, JSON.stringify(session))
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,
absolute: true,
})) {
@@ -98,12 +100,12 @@ export namespace Storage {
await Bun.write(dest, JSON.stringify(message))
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 part = await Bun.file(partFile).json()
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 () => {
@@ -128,9 +153,7 @@ export namespace Storage {
for (let index = migration; index < MIGRATIONS.length; index++) {
log.info("running migration", { 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())
}
return {
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index 897931489..0ad4afe63 100644
--- a/packages/plugin/package.json
+++ b/packages/plugin/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
- "version": "1.0.20",
+ "version": "1.0.23",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts
index 9c2647c60..f103749bd 100644
--- a/packages/plugin/src/index.ts
+++ b/packages/plugin/src/index.ts
@@ -39,13 +39,35 @@ export interface Hooks {
| {
type: "oauth"
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 } & (
| {
method: "auto"
callback(): Promise<
| ({
type: "success"
+ provider?: string
} & (
| {
refresh: string
@@ -64,6 +86,7 @@ export interface Hooks {
callback(code: string): Promise<
| ({
type: "success"
+ provider?: 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"
+ }
+ >
+ }
)[]
}
/**
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index 24023d1b7..d4de9ca17 100644
--- a/packages/sdk/js/package.json
+++ b/packages/sdk/js/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
- "version": "1.0.20",
+ "version": "1.0.23",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts
index b76b6996f..1a54da8fa 100644
--- a/packages/sdk/js/src/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/gen/sdk.gen.ts
@@ -55,6 +55,7 @@ import type {
SessionShareErrors,
SessionDiffData,
SessionDiffResponses,
+ SessionDiffErrors,
SessionSummarizeData,
SessionSummarizeResponses,
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>(
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",
...options,
})
diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts
index 97dc559db..0c0135b4f 100644
--- a/packages/sdk/js/src/gen/types.gen.ts
+++ b/packages/sdk/js/src/gen/types.gen.ts
@@ -163,7 +163,7 @@ export type KeybindsConfig = {
*/
history_previous?: string
/**
- * Previous history item
+ * Next history item
*/
history_next?: string
/**
@@ -406,6 +406,10 @@ export type Config = {
apiKey?: 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?: number | false
@@ -527,7 +531,9 @@ export type Session = {
directory: string
parentID?: string
summary?: {
- diffs: Array<FileDiff>
+ additions: number
+ deletions: number
+ diffs?: Array<FileDiff>
}
share?: {
url: string
@@ -1133,6 +1139,7 @@ export type OAuth = {
refresh: string
access: string
expires: number
+ enterpriseUrl?: string
}
export type ApiAuth = {
@@ -1882,6 +1889,9 @@ export type SessionShareResponse = SessionShareResponses[keyof SessionShareRespo
export type SessionDiffData = {
body?: never
path: {
+ /**
+ * Session ID
+ */
id: string
}
query?: {
@@ -1891,9 +1901,22 @@ export type SessionDiffData = {
url: "/session/{id}/diff"
}
+export type SessionDiffErrors = {
+ /**
+ * Bad request
+ */
+ 400: BadRequestError
+ /**
+ * Not found
+ */
+ 404: NotFoundError
+}
+
+export type SessionDiffError = SessionDiffErrors[keyof SessionDiffErrors]
+
export type SessionDiffResponses = {
/**
- * Successfully retrieved diff
+ * List of diffs
*/
200: Array<FileDiff>
}
diff --git a/packages/slack/package.json b/packages/slack/package.json
index 5e8d6bb45..f9392ef37 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
- "version": "1.0.20",
+ "version": "1.0.23",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 96cca8edf..585a249b0 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
- "version": "1.0.20",
+ "version": "1.0.23",
"type": "module",
"exports": {
".": "./src/components/index.ts",
diff --git a/packages/web/package.json b/packages/web/package.json
index d0ec79f85..2de696b3e 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
- "version": "1.0.20",
+ "version": "1.0.23",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json
index 82adb2da9..6a8ed497a 100644
--- a/sdks/vscode/package.json
+++ b/sdks/vscode/package.json
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
- "version": "1.0.20",
+ "version": "1.0.23",
"publisher": "sst-dev",
"repository": {
"type": "git",