Compare commits

..
Author SHA1 Message Date
opencode a76ad48563 release: v1.0.87
Update Nix Hashes / update (push) Has been cancelled
2025-11-21 21:58:10 +00:00
Dax Raad 00f991162f if finish reason is unknown, continue 2025-11-21 16:51:32 -05:00
Frank d6cdd24fad doc: update gpt pricing 2025-11-21 15:48:54 -05:00
GitHub Action c9473756df chore: format code 2025-11-21 20:33:42 +00:00
Ivan Starkovopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
b5d0c56b4c fix: make bash tool respect $SHELL (#3494)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-11-21 14:33:04 -06:00
opencode 96a2f5268c release: v1.0.86
Update Nix Hashes / update (push) Has been cancelled
2025-11-21 20:32:47 +00:00
Aiden Cline 5083f9c9c2 bump bun to 1.3.3 2025-11-21 13:56:46 -06:00
Frank e34df15ff5 update logic for local and dev providers 2025-11-21 13:31:33 -05:00
GitHub Action 26ec87803a chore: format code 2025-11-21 17:57:33 +00:00
Frank 037e8d4555 wip: zen 2025-11-21 12:50:51 -05:00
Adam 08a366c4dc feat(install): better install script visuals, custom progress bar and next steps (#4589) 2025-11-21 07:29:52 -06:00
GitHub Action 670e1523e0 ignore: update download stats 2025-11-21 2025-11-21 12:04:49 +00:00
Patrick Wolf 416f2964b5 fix: preserve agent context during compaction (#4556) 2025-11-21 02:13:10 -06:00
e018e16898 fix(cli): ensure clean exit on provider/model errors (#4223)
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-21 02:01:19 -06:00
37 changed files with 290 additions and 146 deletions
+1
View File
@@ -146,3 +146,4 @@
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) | | 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) | | 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) | | 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
+12 -12
View File
@@ -41,7 +41,7 @@
}, },
"packages/console/core": { "packages/console/core": {
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.0.85", "version": "1.0.87",
"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",
@@ -68,7 +68,7 @@
}, },
"packages/console/function": { "packages/console/function": {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.0.85", "version": "1.0.87",
"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",
@@ -92,7 +92,7 @@
}, },
"packages/console/mail": { "packages/console/mail": {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.0.85", "version": "1.0.87",
"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",
@@ -116,7 +116,7 @@
}, },
"packages/desktop": { "packages/desktop": {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"version": "1.0.85", "version": "1.0.87",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
@@ -156,7 +156,7 @@
}, },
"packages/function": { "packages/function": {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.0.85", "version": "1.0.87",
"dependencies": { "dependencies": {
"@octokit/auth-app": "8.0.1", "@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0", "@octokit/rest": "22.0.0",
@@ -172,7 +172,7 @@
}, },
"packages/opencode": { "packages/opencode": {
"name": "opencode", "name": "opencode",
"version": "1.0.85", "version": "1.0.87",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -251,7 +251,7 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.0.85", "version": "1.0.87",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"zod": "catalog:", "zod": "catalog:",
@@ -271,7 +271,7 @@
}, },
"packages/sdk/js": { "packages/sdk/js": {
"name": "@opencode-ai/sdk", "name": "@opencode-ai/sdk",
"version": "1.0.85", "version": "1.0.87",
"devDependencies": { "devDependencies": {
"@hey-api/openapi-ts": "0.81.0", "@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:", "@tsconfig/node22": "catalog:",
@@ -282,7 +282,7 @@
}, },
"packages/slack": { "packages/slack": {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.0.85", "version": "1.0.87",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1", "@slack/bolt": "^3.17.1",
@@ -295,7 +295,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.0.85", "version": "1.0.87",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
@@ -325,7 +325,7 @@
}, },
"packages/util": { "packages/util": {
"name": "@opencode-ai/util", "name": "@opencode-ai/util",
"version": "1.0.85", "version": "1.0.87",
"dependencies": { "dependencies": {
"zod": "catalog:", "zod": "catalog:",
}, },
@@ -335,7 +335,7 @@
}, },
"packages/web": { "packages/web": {
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"version": "1.0.85", "version": "1.0.87",
"dependencies": { "dependencies": {
"@astrojs/cloudflare": "12.6.3", "@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1", "@astrojs/markdown-remark": "6.3.1",
+7 -4
View File
@@ -97,8 +97,12 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
], ],
}) })
const ZEN_MODELS1 = new sst.Secret("ZEN_MODELS1") const ZEN_MODELS = [
const ZEN_MODELS2 = new sst.Secret("ZEN_MODELS2") new sst.Secret("ZEN_MODELS1"),
new sst.Secret("ZEN_MODELS2"),
new sst.Secret("ZEN_MODELS3"),
new sst.Secret("ZEN_MODELS4"),
]
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY") const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", { const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
properties: { value: auth.url.apply((url) => url!) }, properties: { value: auth.url.apply((url) => url!) },
@@ -132,11 +136,10 @@ new sst.cloudflare.x.SolidStart("Console", {
AUTH_API_URL, AUTH_API_URL,
STRIPE_WEBHOOK_SECRET, STRIPE_WEBHOOK_SECRET,
STRIPE_SECRET_KEY, STRIPE_SECRET_KEY,
ZEN_MODELS1,
ZEN_MODELS2,
EMAILOCTOPUS_API_KEY, EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID, AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY, AWS_SES_SECRET_ACCESS_KEY,
...ZEN_MODELS,
...($dev ...($dev
? [ ? [
new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!), new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!),
+124 -8
View File
@@ -2,9 +2,8 @@
set -euo pipefail set -euo pipefail
APP=opencode APP=opencode
MUTED='\033[0;2m'
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
ORANGE='\033[38;2;255;140;0m' ORANGE='\033[38;2;255;140;0m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
@@ -45,6 +44,11 @@ case "$filename" in
;; ;;
esac esac
if ! command -v unzip >/dev/null 2>&1; then
echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
exit 1
fi
INSTALL_DIR=$HOME/.opencode/bin INSTALL_DIR=$HOME/.opencode/bin
mkdir -p "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"
@@ -67,8 +71,8 @@ print_message() {
local color="" local color=""
case $level in case $level in
info) color="${GREEN}" ;; info) color="${NC}" ;;
warning) color="${YELLOW}" ;; warning) color="${NC}" ;;
error) color="${RED}" ;; error) color="${RED}" ;;
esac esac
@@ -86,18 +90,113 @@ check_version() {
installed_version=$(echo $installed_version | awk '{print $2}') installed_version=$(echo $installed_version | awk '{print $2}')
if [[ "$installed_version" != "$specific_version" ]]; then if [[ "$installed_version" != "$specific_version" ]]; then
print_message info "Installed version: ${YELLOW}$installed_version." print_message info "${MUTED}Installed version: ${NC}$installed_version."
else else
print_message info "Version ${YELLOW}$specific_version${GREEN} already installed" print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
exit 0 exit 0
fi fi
fi fi
} }
unbuffered_sed() {
if echo | sed -u -e "" >/dev/null 2>&1; then
sed -nu "$@"
elif echo | sed -l -e "" >/dev/null 2>&1; then
sed -nl "$@"
else
local pad="$(printf "\n%512s" "")"
sed -ne "s/$/\\${pad}/" "$@"
fi
}
print_progress() {
local bytes="$1"
local length="$2"
[ "$length" -gt 0 ] || return 0
local width=50
local percent=$(( bytes * 100 / length ))
[ "$percent" -gt 100 ] && percent=100
local on=$(( percent * width / 100 ))
local off=$(( width - on ))
local filled=$(printf "%*s" "$on" "")
filled=${filled// /■}
local empty=$(printf "%*s" "$off" "")
empty=${empty// /・}
printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
}
download_with_progress() {
local url="$1"
local output="$2"
if [ -t 2 ]; then
exec 4>&2
else
exec 4>/dev/null
fi
local tmp_dir=${TMPDIR:-/tmp}
local basename="${tmp_dir}/opencode_install_$$"
local tracefile="${basename}.trace"
rm -f "$tracefile"
mkfifo "$tracefile"
# Hide cursor
printf "\033[?25l" >&4
trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
(
curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
) &
local curl_pid=$!
unbuffered_sed \
-e 'y/ACDEGHLNORTV/acdeghlnortv/' \
-e '/^0000: content-length:/p' \
-e '/^<= recv data/p' \
"$tracefile" | \
{
local length=0
local bytes=0
while IFS=" " read -r -a line; do
[ "${#line[@]}" -lt 2 ] && continue
local tag="${line[0]} ${line[1]}"
if [ "$tag" = "0000: content-length:" ]; then
length="${line[2]}"
length=$(echo "$length" | tr -d '\r')
bytes=0
elif [ "$tag" = "<= recv" ]; then
local size="${line[3]}"
bytes=$(( bytes + size ))
if [ "$length" -gt 0 ]; then
print_progress "$bytes" "$length"
fi
fi
done
}
wait $curl_pid
local ret=$?
echo "" >&4
return $ret
}
download_and_install() { download_and_install() {
print_message info "Downloading ${ORANGE}opencode ${GREEN}version: ${YELLOW}$specific_version ${GREEN}..." print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
mkdir -p opencodetmp && cd opencodetmp mkdir -p opencodetmp && cd opencodetmp
if [[ "$os" == "windows" ]] || ! download_with_progress "$url" "$filename"; then
# Fallback to standard curl on Windows or if custom progress fails
curl -# -L -o "$filename" "$url" curl -# -L -o "$filename" "$url"
fi
unzip -q "$filename" unzip -q "$filename"
mv opencode "$INSTALL_DIR" mv opencode "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/opencode" chmod 755 "${INSTALL_DIR}/opencode"
@@ -117,7 +216,7 @@ add_to_path() {
elif [[ -w $config_file ]]; then elif [[ -w $config_file ]]; then
echo -e "\n# opencode" >> "$config_file" echo -e "\n# opencode" >> "$config_file"
echo "$command" >> "$config_file" echo "$command" >> "$config_file"
print_message info "Successfully added ${ORANGE}opencode ${GREEN}to \$PATH in $config_file" print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file"
else else
print_message warning "Manually add the directory to $config_file (or similar):" print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " $command" print_message info " $command"
@@ -191,3 +290,20 @@ if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "$INSTALL_DIR" >> $GITHUB_PATH echo "$INSTALL_DIR" >> $GITHUB_PATH
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH" print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
fi fi
echo -e ""
echo -e "${MUTED}  ${NC} ▄ "
echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█"
echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀"
echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
echo -e ""
echo -e ""
echo -e "${MUTED}To get started, navigate to a project and run:${NC}"
echo -e "opencode ${MUTED}Use free models${NC}"
echo -e "opencode auth login ${MUTED}Add paid provider API keys${NC}"
echo -e "opencode help ${MUTED}List commands and options${NC}"
echo -e ""
echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
echo -e ""
echo -e ""
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "AI-powered development tool", "description": "AI-powered development tool",
"private": true, "private": true,
"type": "module", "type": "module",
"packageManager": "bun@1.3.2", "packageManager": "bun@1.3.3",
"scripts": { "scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"typecheck": "bun turbo typecheck", "typecheck": "bun turbo typecheck",
+1 -1
View File
@@ -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": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", "build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start", "start": "vinxi start",
"version": "1.0.85" "version": "1.0.87"
}, },
"dependencies": { "dependencies": {
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
+1 -1
View File
@@ -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.85", "version": "1.0.87",
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
+10 -9
View File
@@ -11,20 +11,21 @@ const root = path.resolve(process.cwd(), "..", "..", "..")
// read the secret // read the secret
const ret = await $`bun sst secret list`.cwd(root).text() const ret = await $`bun sst secret list`.cwd(root).text()
const value1 = ret const lines = ret.split("\n")
.split("\n") const value1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
.find((line) => line.startsWith("ZEN_MODELS1")) const value2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
?.split("=")[1] const value3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
const value2 = ret const value4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
.split("\n")
.find((line) => line.startsWith("ZEN_MODELS2"))
?.split("=")[1]
if (!value1) throw new Error("ZEN_MODELS1 not found") if (!value1) throw new Error("ZEN_MODELS1 not found")
if (!value2) throw new Error("ZEN_MODELS2 not found") if (!value2) throw new Error("ZEN_MODELS2 not found")
if (!value3) throw new Error("ZEN_MODELS3 not found")
if (!value4) throw new Error("ZEN_MODELS4 not found")
// validate value // validate value
ZenData.validate(JSON.parse(value1 + value2)) ZenData.validate(JSON.parse(value1 + value2 + value3 + value4))
// update the secret // update the secret
await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}` await $`bun sst secret set ZEN_MODELS1 ${value1} --stage ${stage}`
await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}` await $`bun sst secret set ZEN_MODELS2 ${value2} --stage ${stage}`
await $`bun sst secret set ZEN_MODELS3 ${value3} --stage ${stage}`
await $`bun sst secret set ZEN_MODELS4 ${value4} --stage ${stage}`
+17 -12
View File
@@ -9,21 +9,20 @@ const root = path.resolve(process.cwd(), "..", "..", "..")
const models = await $`bun sst secret list`.cwd(root).text() const models = await $`bun sst secret list`.cwd(root).text()
// read the line starting with "ZEN_MODELS" // read the line starting with "ZEN_MODELS"
const oldValue1 = models const lines = models.split("\n")
.split("\n") const oldValue1 = lines.find((line) => line.startsWith("ZEN_MODELS1"))?.split("=")[1]
.find((line) => line.startsWith("ZEN_MODELS1")) const oldValue2 = lines.find((line) => line.startsWith("ZEN_MODELS2"))?.split("=")[1]
?.split("=")[1] const oldValue3 = lines.find((line) => line.startsWith("ZEN_MODELS3"))?.split("=")[1]
const oldValue2 = models const oldValue4 = lines.find((line) => line.startsWith("ZEN_MODELS4"))?.split("=")[1]
.split("\n")
.find((line) => line.startsWith("ZEN_MODELS2"))
?.split("=")[1]
if (!oldValue1) throw new Error("ZEN_MODELS1 not found") if (!oldValue1) throw new Error("ZEN_MODELS1 not found")
if (!oldValue2) throw new Error("ZEN_MODELS2 not found") if (!oldValue2) throw new Error("ZEN_MODELS2 not found")
if (!oldValue3) throw new Error("ZEN_MODELS3 not found")
if (!oldValue4) throw new Error("ZEN_MODELS4 not found")
// store the prettified json to a temp file // store the prettified json to a temp file
const filename = `models-${Date.now()}.json` const filename = `models-${Date.now()}.json`
const tempFile = Bun.file(path.join(os.tmpdir(), filename)) const tempFile = Bun.file(path.join(os.tmpdir(), filename))
await tempFile.write(JSON.stringify(JSON.parse(oldValue1 + oldValue2), null, 2)) await tempFile.write(JSON.stringify(JSON.parse(oldValue1 + oldValue2 + oldValue3 + oldValue4), null, 2))
console.log("tempFile", tempFile.name) console.log("tempFile", tempFile.name)
// open temp file in vim and read the file on close // open temp file in vim and read the file on close
@@ -32,6 +31,12 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
ZenData.validate(JSON.parse(newValue)) ZenData.validate(JSON.parse(newValue))
// update the secret // update the secret
const mid = Math.floor(newValue.length / 2) const chunk = Math.ceil(newValue.length / 4)
await $`bun sst secret set ZEN_MODELS1 ${newValue.slice(0, mid)}` const newValue1 = newValue.slice(0, chunk)
await $`bun sst secret set ZEN_MODELS2 ${newValue.slice(mid)}` const newValue2 = newValue.slice(chunk, chunk * 2)
const newValue3 = newValue.slice(chunk * 2, chunk * 3)
const newValue4 = newValue.slice(chunk * 3)
await $`bun sst secret set ZEN_MODELS1 ${newValue1}`
await $`bun sst secret set ZEN_MODELS2 ${newValue2}`
await $`bun sst secret set ZEN_MODELS3 ${newValue3}`
await $`bun sst secret set ZEN_MODELS4 ${newValue4}`
+3 -1
View File
@@ -53,7 +53,9 @@ export namespace ZenData {
}) })
export const list = fn(z.void(), () => { export const list = fn(z.void(), () => {
const json = JSON.parse(Resource.ZEN_MODELS1.value + Resource.ZEN_MODELS2.value) const json = JSON.parse(
Resource.ZEN_MODELS1.value + Resource.ZEN_MODELS2.value + Resource.ZEN_MODELS3.value + Resource.ZEN_MODELS4.value,
)
return ModelsSchema.parse(json) return ModelsSchema.parse(json)
}) })
} }
+8
View File
@@ -94,6 +94,14 @@ declare module "sst" {
type: "sst.sst.Secret" type: "sst.sst.Secret"
value: string value: string
} }
ZEN_MODELS3: {
type: "sst.sst.Secret"
value: string
}
ZEN_MODELS4: {
type: "sst.sst.Secret"
value: string
}
} }
} }
// cloudflare // cloudflare
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.0.85", "version": "1.0.87",
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"private": true, "private": true,
"type": "module", "type": "module",
+8
View File
@@ -94,6 +94,14 @@ declare module "sst" {
type: "sst.sst.Secret" type: "sst.sst.Secret"
value: string value: string
} }
ZEN_MODELS3: {
type: "sst.sst.Secret"
value: string
}
ZEN_MODELS4: {
type: "sst.sst.Secret"
value: string
}
} }
} }
// cloudflare // cloudflare
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.0.85", "version": "1.0.87",
"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",
+8
View File
@@ -94,6 +94,14 @@ declare module "sst" {
type: "sst.sst.Secret" type: "sst.sst.Secret"
value: string value: string
} }
ZEN_MODELS3: {
type: "sst.sst.Secret"
value: string
}
ZEN_MODELS4: {
type: "sst.sst.Secret"
value: string
}
} }
} }
// cloudflare // cloudflare
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"version": "1.0.85", "version": "1.0.87",
"description": "", "description": "",
"type": "module", "type": "module",
"scripts": { "scripts": {
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode" id = "opencode"
name = "OpenCode" name = "OpenCode"
description = "The AI coding agent built for the terminal" description = "The AI coding agent built for the terminal"
version = "1.0.85" version = "1.0.87"
schema_version = 1 schema_version = 1
authors = ["Anomaly"] authors = ["Anomaly"]
repository = "https://github.com/sst/opencode" repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg" icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64] [agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.85/opencode-darwin-arm64.zip" archive = "https://github.com/sst/opencode/releases/download/v1.0.87/opencode-darwin-arm64.zip"
cmd = "./opencode" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64] [agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.85/opencode-darwin-x64.zip" archive = "https://github.com/sst/opencode/releases/download/v1.0.87/opencode-darwin-x64.zip"
cmd = "./opencode" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64] [agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.85/opencode-linux-arm64.zip" archive = "https://github.com/sst/opencode/releases/download/v1.0.87/opencode-linux-arm64.zip"
cmd = "./opencode" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64] [agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.85/opencode-linux-x64.zip" archive = "https://github.com/sst/opencode/releases/download/v1.0.87/opencode-linux-x64.zip"
cmd = "./opencode" cmd = "./opencode"
args = ["acp"] args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64] [agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.85/opencode-windows-x64.zip" archive = "https://github.com/sst/opencode/releases/download/v1.0.87/opencode-windows-x64.zip"
cmd = "./opencode.exe" cmd = "./opencode.exe"
args = ["acp"] args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.0.85", "version": "1.0.87",
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"private": true, "private": true,
"type": "module", "type": "module",
+8
View File
@@ -94,6 +94,14 @@ declare module "sst" {
type: "sst.sst.Secret" type: "sst.sst.Secret"
value: string value: string
} }
ZEN_MODELS3: {
type: "sst.sst.Secret"
value: string
}
ZEN_MODELS4: {
type: "sst.sst.Secret"
value: string
}
} }
} }
// cloudflare // cloudflare
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.0.85", "version": "1.0.87",
"name": "opencode", "name": "opencode",
"type": "module", "type": "module",
"private": true, "private": true,
+8 -65
View File
@@ -599,77 +599,20 @@ export namespace Provider {
const provider = s.providers[providerID] const provider = s.providers[providerID]
if (!provider) { if (!provider) {
let suggestions: string[] = [] const availableProviders = Object.keys(s.providers)
const normalize = (str: string) => str.toLowerCase().replace(/[^a-z0-9]/g, "") const matches = fuzzysort.go(providerID, availableProviders, { limit: 3, threshold: -10000 })
const levenshtein = (a: string, b: string) => { const suggestions = matches.map((m) => m.target)
const m = a.length,
n = b.length
const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0))
for (let i = 0; i <= m; i++) dp[i][0] = i
for (let j = 0; j <= n; j++) dp[0][j] = j
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
const cost = a[i - 1] === b[j - 1] ? 0 : 1
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost)
}
}
return dp[m][n]
}
if (!modelID || modelID.trim() === "") {
// Treat single-token input as an unqualified model; search across all providers' models.
const q = normalize(providerID)
const entries: { combo: string; norm: string }[] = []
for (const [pid, prov] of Object.entries(s.providers)) {
for (const mid of Object.keys(prov.info.models)) {
entries.push({ combo: pid + "/" + mid, norm: normalize(mid) })
}
}
const byNorm = fuzzysort.go(q, entries as any, { limit: 5, key: "norm" }).map((r: any) => r.obj.combo)
const combos = entries.map((e) => e.combo)
const byRaw = fuzzysort.go(providerID, combos, { limit: 5 }).map((r) => r.target)
let merged = Array.from(new Set([...byNorm, ...byRaw]))
if (merged.length === 0) {
// fallback to edit distance on normalized mid
const scored = entries
.map((e) => ({ combo: e.combo, d: levenshtein(q, e.norm) }))
.sort((a, b) => a.d - b.d)
.slice(0, 3)
.map((x) => x.combo)
merged = scored
}
suggestions = merged.slice(0, 3)
} else {
const pcands = Object.keys(s.providers)
const corpus = pcands.map((raw) => ({ raw, norm: normalize(raw) }))
const q = normalize(providerID)
const hits = fuzzysort.go(q, corpus as any, { limit: 5, key: "norm" })
let ranked = hits.map((r: any) => r.obj.raw)
if (ranked.length === 0) {
ranked = pcands
.map((p) => ({ p, d: levenshtein(q, normalize(p)) }))
.sort((a, b) => a.d - b.d)
.slice(0, 3)
.map((x) => x.p)
}
const providerSuggestions = ranked.map((r) => r + "/" + modelID)
suggestions = providerSuggestions
}
throw new ModelNotFoundError({ providerID, modelID, suggestions }) throw new ModelNotFoundError({ providerID, modelID, suggestions })
} }
const info = provider.info.models[modelID] const info = provider.info.models[modelID]
if (!info) { if (!info) {
const candidates = Object.keys(provider.info.models) const availableModels = Object.keys(provider.info.models)
// Normalize punctuation differences like '-' vs '.' by stripping non-alphanumerics const matches = fuzzysort.go(modelID, availableModels, { limit: 3, threshold: -10000 })
const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, "") const suggestions = matches.map((m) => m.target)
const corpus = candidates.map((raw) => ({ raw, norm: normalize(raw) }))
const query = normalize(modelID)
const results = fuzzysort.go(query, corpus as any, { limit: 5, key: "norm" })
const ranked = results.map((r) => ("obj" in r ? (r as any).obj.raw : (r as any).target)) as string[]
const fallback = fuzzysort.go(modelID, candidates, { limit: 5 }).map((r) => r.target)
const merged = Array.from(new Set([...ranked, ...fallback]))
const suggestions = merged.slice(0, 3).map((m) => providerID + "/" + m)
throw new ModelNotFoundError({ providerID, modelID, suggestions }) throw new ModelNotFoundError({ providerID, modelID, suggestions })
} }
const sdk = await getSDK(provider.info, info) const sdk = await getSDK(provider.info, info)
try { try {
+2 -2
View File
@@ -147,7 +147,7 @@ export namespace ProviderTransform {
result["promptCacheKey"] = sessionID result["promptCacheKey"] = sessionID
} }
if (providerID === "google" || (providerID === "opencode" && modelID.includes("gemini-3"))) { if (providerID === "google" || (providerID.startsWith("opencode") && modelID.includes("gemini-3"))) {
result["thinkingConfig"] = { result["thinkingConfig"] = {
includeThoughts: true, includeThoughts: true,
} }
@@ -166,7 +166,7 @@ export namespace ProviderTransform {
result["textVerbosity"] = "low" result["textVerbosity"] = "low"
} }
if (providerID === "opencode") { if (providerID.startsWith("opencode")) {
result["promptCacheKey"] = sessionID result["promptCacheKey"] = sessionID
result["include"] = ["reasoning.encrypted_content"] result["include"] = ["reasoning.encrypted_content"]
result["reasoningSummary"] = "auto" result["reasoningSummary"] = "auto"
+10
View File
@@ -813,8 +813,18 @@ export namespace Server {
async (c) => { async (c) => {
const id = c.req.valid("param").id const id = c.req.valid("param").id
const body = c.req.valid("json") const body = c.req.valid("json")
const msgs = await Session.messages({ sessionID: id })
let currentAgent = "build"
for (let i = msgs.length - 1; i >= 0; i--) {
const info = msgs[i].info
if (info.role === "user") {
currentAgent = info.agent || "build"
break
}
}
await SessionCompaction.create({ await SessionCompaction.create({
sessionID: id, sessionID: id,
agent: currentAgent,
model: { model: {
providerID: body.providerID, providerID: body.providerID,
modelID: body.modelID, modelID: body.modelID,
+5 -3
View File
@@ -92,6 +92,7 @@ export namespace SessionCompaction {
providerID: string providerID: string
modelID: string modelID: string
} }
agent: string
abort: AbortSignal abort: AbortSignal
}) { }) {
const model = await Provider.getModel(input.model.providerID, input.model.modelID) const model = await Provider.getModel(input.model.providerID, input.model.modelID)
@@ -101,7 +102,7 @@ export namespace SessionCompaction {
role: "assistant", role: "assistant",
parentID: input.parentID, parentID: input.parentID,
sessionID: input.sessionID, sessionID: input.sessionID,
mode: "build", mode: input.agent,
summary: true, summary: true,
path: { path: {
cwd: Instance.directory, cwd: Instance.directory,
@@ -199,7 +200,7 @@ export namespace SessionCompaction {
time: { time: {
created: Date.now(), created: Date.now(),
}, },
agent: "build", agent: input.agent,
model: input.model, model: input.model,
}) })
await Session.updatePart({ await Session.updatePart({
@@ -222,6 +223,7 @@ export namespace SessionCompaction {
export const create = fn( export const create = fn(
z.object({ z.object({
sessionID: Identifier.schema("session"), sessionID: Identifier.schema("session"),
agent: z.string(),
model: z.object({ model: z.object({
providerID: z.string(), providerID: z.string(),
modelID: z.string(), modelID: z.string(),
@@ -233,7 +235,7 @@ export namespace SessionCompaction {
role: "user", role: "user",
model: input.model, model: input.model,
sessionID: input.sessionID, sessionID: input.sessionID,
agent: "build", agent: input.agent,
time: { time: {
created: Date.now(), created: Date.now(),
}, },
+7 -1
View File
@@ -265,7 +265,11 @@ export namespace SessionPrompt {
} }
if (!lastUser) throw new Error("No user message found in stream. This should never happen.") if (!lastUser) throw new Error("No user message found in stream. This should never happen.")
if (lastAssistant?.finish && lastAssistant.finish !== "tool-calls" && lastUser.id < lastAssistant.id) { if (
lastAssistant?.finish &&
!["tool-calls", "unknown"].includes(lastAssistant.finish) &&
lastUser.id < lastAssistant.id
) {
log.info("exiting loop", { sessionID }) log.info("exiting loop", { sessionID })
break break
} }
@@ -398,6 +402,7 @@ export namespace SessionPrompt {
messages: msgs, messages: msgs,
parentID: lastUser.id, parentID: lastUser.id,
abort, abort,
agent: lastUser.agent,
model: { model: {
providerID: model.providerID, providerID: model.providerID,
modelID: model.modelID, modelID: model.modelID,
@@ -416,6 +421,7 @@ export namespace SessionPrompt {
) { ) {
await SessionCompaction.create({ await SessionCompaction.create({
sessionID, sessionID,
agent: lastUser.agent,
model: lastUser.model, model: lastUser.model,
}) })
continue continue
+1 -1
View File
@@ -183,7 +183,7 @@ export const BashTool = Tool.define("bash", {
} }
const proc = spawn(params.command, { const proc = spawn(params.command, {
shell: true, shell: process.env.SHELL ?? true, // $SHELL if set, otherwise default shell
cwd: Instance.directory, cwd: Instance.directory,
env: { env: {
...process.env, ...process.env,
+1 -1
View File
@@ -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.85", "version": "1.0.87",
"type": "module", "type": "module",
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit", "typecheck": "tsgo --noEmit",
+1 -1
View File
@@ -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.85", "version": "1.0.87",
"type": "module", "type": "module",
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit", "typecheck": "tsgo --noEmit",
+6
View File
@@ -90,4 +90,10 @@ class Resource:
class ZEN_MODELS2: class ZEN_MODELS2:
type: str type: str
value: str value: str
class ZEN_MODELS3:
type: str
value: str
class ZEN_MODELS4:
type: str
value: str
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.0.85", "version": "1.0.87",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "bun run src/index.ts", "dev": "bun run src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.0.85", "version": "1.0.87",
"type": "module", "type": "module",
"exports": { "exports": {
".": "./src/components/index.ts", ".": "./src/components/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/util", "name": "@opencode-ai/util",
"version": "1.0.85", "version": "1.0.87",
"private": true, "private": true,
"type": "module", "type": "module",
"exports": { "exports": {
+9
View File
@@ -0,0 +1,9 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"type": "module", "type": "module",
"version": "1.0.85", "version": "1.0.87",
"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",
+4 -4
View File
@@ -133,10 +133,10 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | | Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 |
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
| GPT 5.1 | $1.25 | $10.00 | $0.125 | - | | GPT 5.1 | $1.07 | $8.50 | $0.107 | - |
| GPT 5.1 Codex | $1.25 | $10.00 | $0.125 | - | | GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - |
| GPT 5 | $1.25 | $10.00 | $0.125 | - | | GPT 5 | $1.07 | $8.50 | $0.107 | - |
| GPT 5 Codex | $1.25 | $10.00 | $0.125 | - | | GPT 5 Codex | $1.07 | $8.50 | $0.107 | - |
| GPT 5 Nano | Free | Free | Free | - | | GPT 5 Nano | Free | Free | Free | - |
You might notice _Claude Haiku 3.5_ in your usage history. This is a [low cost model](/docs/config/#models) that's used to generate the titles of your sessions. You might notice _Claude Haiku 3.5_ in your usage history. This is a [low cost model](/docs/config/#models) that's used to generate the titles of your sessions.
+1 -1
View File
@@ -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.85", "version": "1.0.87",
"publisher": "sst-dev", "publisher": "sst-dev",
"repository": { "repository": {
"type": "git", "type": "git",
+8
View File
@@ -116,6 +116,14 @@ declare module "sst" {
type: "sst.sst.Secret" type: "sst.sst.Secret"
value: string value: string
} }
ZEN_MODELS3: {
type: "sst.sst.Secret"
value: string
}
ZEN_MODELS4: {
type: "sst.sst.Secret"
value: string
}
} }
} }
/// <reference path="sst-env.d.ts" /> /// <reference path="sst-env.d.ts" />