Compare commits

..

7 Commits

Author SHA1 Message Date
Aiden Cline
090f638318 ignore 2025-12-09 16:29:55 -06:00
Aiden Cline
8f3f87943a fmt 2025-12-09 16:29:33 -06:00
Aiden Cline
7ac3f9182b fixes 2025-12-09 16:23:18 -06:00
Aiden Cline
cda2138f24 fixes 2025-12-09 16:22:36 -06:00
Aiden Cline
c4f8c6c005 fix 2025-12-09 15:59:03 -06:00
Aiden Cline
8304bd418f Merge branch 'dev' into interleaved-thinking 2025-12-09 15:54:28 -06:00
Aiden Cline
366b6e4fbb wip 2025-12-09 15:36:53 -06:00
1238 changed files with 9524 additions and 2256 deletions

View File

@@ -1,4 +1,4 @@
name: generate
name: format
on:
push:
@@ -8,10 +8,14 @@ on:
branches-ignore:
- production
workflow_dispatch:
workflow_run:
workflows: ["sdk"]
types:
- completed
jobs:
generate:
format:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.event.workflow_run.conclusion == 'success'
permissions:
contents: write
steps:
@@ -25,14 +29,9 @@ jobs:
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Generate SDK
- name: run
run: |
bun ./packages/sdk/js/script/build.ts
(cd packages/opencode && bun dev generate > ../sdk/openapi.json)
bun x prettier --write packages/sdk/openapi.json
- name: Format
run: ./script/format.ts
./script/format.ts
env:
CI: true
PUSH_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}

View File

@@ -2,15 +2,11 @@ name: publish
run-name: "${{ format('release {0}', inputs.bump) }}"
on:
push:
branches:
- dev
- snapshot-*
workflow_dispatch:
inputs:
bump:
description: "Bump major, minor, or patch"
required: false
required: true
type: choice
options:
- major
@@ -24,7 +20,6 @@ on:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
id-token: write
contents: write
packages: write
@@ -39,13 +34,20 @@ jobs:
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: ">=1.24.0"
cache: true
cache-dependency-path: go.sum
- uses: ./.github/actions/setup-bun
- name: Setup SSH for AUR
if: inputs.bump || inputs.version
- name: Install makepkg
run: |
sudo apt-get update
sudo apt-get install -y pacman-package-manager
- name: Setup SSH for AUR
run: |
mkdir -p ~/.ssh
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
@@ -54,9 +56,12 @@ jobs:
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
- name: Install OpenCode
if: inputs.bump || inputs.version
run: curl -fsSL https://opencode.ai/install | bash
- name: Setup npm auth
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -64,24 +69,19 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Publish
run: |
./script/publish.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
OPENCODE_VERSION: ${{ inputs.version }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
AUR_KEY: ${{ secrets.AUR_KEY }}
OPENCODE_CHANNEL: latest
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: false
AUR_KEY: ${{ secrets.AUR_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
publish-tauri:
if: false # inputs.bump || inputs.version
continue-on-error: true
strategy:
fail-fast: false

43
.github/workflows/sdk.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: sdk
on:
push:
branches-ignore:
- production
pull_request:
branches-ignore:
- production
workflow_dispatch:
jobs:
format:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: run
run: |
bun ./packages/sdk/js/script/build.ts
(cd packages/opencode && bun dev generate > ../sdk/openapi.json)
bun x prettier --write packages/sdk/js/src/openapi.ts
if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit"
exit 0
fi
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add -A
git commit -m "chore: regen sdk"
git push origin HEAD:${PUSH_BRANCH} --no-verify
env:
CI: true
PUSH_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}

38
.github/workflows/snapshot.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: snapshot
on:
workflow_dispatch:
push:
branches:
- dev
- test-bedrock
- v0
- otui-diffs
- snapshot-*
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
publish:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: ">=1.24.0"
cache: true
cache-dependency-path: go.sum
- uses: ./.github/actions/setup-bun
- name: Publish
run: |
./script/publish.ts
env:
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -2,8 +2,8 @@ name: "sync-zed-extension"
on:
workflow_dispatch:
# release:
# types: [published]
release:
types: [published]
jobs:
zed:

View File

@@ -30,7 +30,7 @@ scoop bucket add extras; scoop install extras/opencode # Windows
choco install opencode # Windows
brew install opencode # macOS and Linux
paru -S opencode-bin # Arch Linux
mise use -g ubi:sst/opencode # Any OS
mise use --pin -g ubi:sst/opencode # Any OS
nix run nixpkgs#opencode # or github:sst/opencode for latest dev branch
```

View File

@@ -165,4 +165,3 @@
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |

View File

@@ -20,7 +20,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -48,7 +48,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -75,7 +75,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -99,7 +99,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -123,7 +123,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -168,7 +168,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -196,7 +196,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -212,7 +212,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.143",
"version": "1.0.138",
"bin": {
"opencode": "./bin/opencode",
},
@@ -241,9 +241,9 @@
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.2",
"@opentui/core": "0.1.60",
"@opentui/solid": "0.1.60",
"@openrouter/ai-sdk-provider": "1.2.8",
"@opentui/core": "0.1.59",
"@opentui/solid": "0.1.59",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -304,7 +304,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -324,7 +324,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.143",
"version": "1.0.138",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@tsconfig/node22": "catalog:",
@@ -335,7 +335,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -348,7 +348,7 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@opencode-ai/desktop": "workspace:*",
"@tauri-apps/api": "^2",
@@ -370,7 +370,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -402,7 +402,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"zod": "catalog:",
},
@@ -413,7 +413,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.143",
"version": "1.0.138",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -1141,27 +1141,27 @@
"@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"],
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.5.2", "", { "dependencies": { "@openrouter/sdk": "^0.1.27" }, "peerDependencies": { "@toon-format/toon": "^2.0.0", "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" }, "optionalPeers": ["@toon-format/toon"] }, "sha512-3Th0vmJ9pjnwcPc2H1f59Mb0LFvwaREZAScfOQIpUxAHjZ7ZawVKDP27qgsteZPmMYqccNMy4r4Y3kgUnNcKAg=="],
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.8", "", { "dependencies": { "@openrouter/sdk": "^0.1.8" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-pQT8AzZBKg9f4bkt4doF486ZlhK0XjKkevrLkiqYgfh1Jplovieu28nK4Y+xy3sF18/mxjqh9/2y6jh01qzLrA=="],
"@openrouter/sdk": ["@openrouter/sdk@0.1.27", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentui/core": ["@opentui/core@0.1.60", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.60", "@opentui/core-darwin-x64": "0.1.60", "@opentui/core-linux-arm64": "0.1.60", "@opentui/core-linux-x64": "0.1.60", "@opentui/core-win32-arm64": "0.1.60", "@opentui/core-win32-x64": "0.1.60", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-28jphd0AJo48uvEuKXcT9pJhgAu8I2rEJhPt25cc5ipJ2iw/eDk1uoxrbID80MPDqgOEzN21vXmzXwCd6ao+hg=="],
"@opentui/core": ["@opentui/core@0.1.59", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.59", "@opentui/core-darwin-x64": "0.1.59", "@opentui/core-linux-arm64": "0.1.59", "@opentui/core-linux-x64": "0.1.59", "@opentui/core-win32-arm64": "0.1.59", "@opentui/core-win32-x64": "0.1.59", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vOtEvIulvfCOWJy0EfKAPzAMtDTmC+S0boGYrefjLqIp7tp+bbVJuXVh/8bz6GQTPmbQC6MIk6bv/ij3pdUVkA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.60", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N4feqnOBDA4O4yocpat5vOiV06HqJVwJGx8rEZE9DiOtl1i+1cPQ1Lx6+zWdLhbrVBJ0ENhb7Azox8sXkm/+5Q=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.59", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JQWq7W/wkmTujW/2/Ig0d7S+701rul87LSW5txQ+GM4o6EWchqHrELwo6jcZpczsyOEj4fXxI2O8l4OVYyMa9A=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.60", "", { "os": "darwin", "cpu": "x64" }, "sha512-+z3q4WaoIs7ANU8+eTFlvnfCjAS81rk81TOdZm4TJ53Ti3/B+yheWtnV/mLpLLhvZDz2VUVxxRmfDrGMnJb4fQ=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.59", "", { "os": "darwin", "cpu": "x64" }, "sha512-GzafWzMP9Lt4AzUwQAk02lxgITgfvvo33OLCN265LtQBO8w23u0eB7Fjs9W+nmtcvzXtB9q6HuA0PvP9a3OioA=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.60", "", { "os": "linux", "cpu": "arm64" }, "sha512-/Q65sjqVGB9ygJ6lStI8n1X6RyfmJZC8XofRGEuFiMLiWcWC/xoBtztdL8LAIvHQy42y2+pl9zIiW0fWSQ0wjw=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.59", "", { "os": "linux", "cpu": "arm64" }, "sha512-QMMFg3dr2v43g3jICgzNFYQyU4YL3zHw733MVJINC+c882+qiQ8l0utTFoVEx/iRYeBzFvMVrKZ4f6G8fFrtrw=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.60", "", { "os": "linux", "cpu": "x64" }, "sha512-AegF+g7OguIpjZKN+PS55sc3ZFY6fj+fLwfETbSRGw6NqX+aiwpae0Y3gXX1s298Yq5yQEzMXnARTCJTGH4uzg=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.59", "", { "os": "linux", "cpu": "x64" }, "sha512-XSblVjhW/7+Xs+/o+xJHwHn74nw9j69mnPAFiNdH0d8ilP4j09nUYHZOvQ89sHZaMYeSIuJEciHnh/qP0n5QXQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.60", "", { "os": "win32", "cpu": "arm64" }, "sha512-fbkq8MOZJgT3r9q3JWqsfVxRpQ1SlbmhmvB35BzukXnZBK8eA178wbSadGH6irMDrkSIYye9WYddHI/iXjmgVQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.59", "", { "os": "win32", "cpu": "arm64" }, "sha512-GU5pPUcTpYmeOUYKpQgAPx0VKBMrfz5LNZlK8gm/jlo2CbLrIW7QLMWCoxncVZmNYqYJeG+KUZkmXYe5KLPXCQ=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.60", "", { "os": "win32", "cpu": "x64" }, "sha512-OebCL7f9+CKodBw0G+NvKIcc74bl6/sBEHfb73cACdJDJKh+T3C3Vt9H3kQQ0m1C8wRAqX6rh706OArk1pUb2A=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.59", "", { "os": "win32", "cpu": "x64" }, "sha512-InIawEI0TOG8MBBpavMq31WBRBjJ6XPuqFcsDnjqDJcXrRbNkguRW3PNXEwlyaU4tXHfYOsdlPpRtsysS8X/bQ=="],
"@opentui/solid": ["@opentui/solid@0.1.60", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.60", "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-pn91stzAHNGWaNL6h39q55bq3G1/DLqxKtT3wVsRAV68dHfPpwmqikX1nEJZK8OU84ZTPS9Ly9fz8po2Mot2uQ=="],
"@opentui/solid": ["@opentui/solid@0.1.59", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.59", "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-O88a/+YHkHlDC4IxbrfWD2ZWlpkpu4oXC2FCLTK8taaUAnLYoybxdrMpv1+o8u8KoWXOoZmEHdntdO9O4abHnQ=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1765270179,
"narHash": "sha256-g2a4MhRKu4ymR4xwo+I+auTknXt/+j37Lnf0Mvfl1rE=",
"lastModified": 1764947035,
"narHash": "sha256-EYHSjVM4Ox4lvCXUMiKKs2vETUSL5mx+J2FfutM7T9w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "677fbe97984e7af3175b6c121f3c39ee5c8d62c9",
"rev": "a672be65651c80d3f592a89b3945466584a22069",
"type": "github"
},
"original": {

View File

@@ -1,3 +1,3 @@
{
"nodeModules": "sha256-JT8J+Nd2kk0x46BcyotmBbM39tuKOW7VzXfOV3R3sqQ="
"nodeModules": "sha256-lM/7mkrPHz5E6YOMjWspfRhKjwav9ANrLt9kYlpPkEI="
}

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.0.143",
"version": "1.0.138",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View File

@@ -588,7 +588,7 @@ export async function handler(
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))),
.where(eq(KeyTable.id, authInfo.apiKeyId)),
)
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.143",
"version": "1.0.138",
"description": "",
"type": "module",
"exports": {
@@ -8,7 +8,7 @@
"./vite": "./vite.js"
},
"scripts": {
"typecheck": "tsgo -b",
"typecheck": "tsgo --noEmit",
"start": "vite",
"dev": "vite",
"build": "vite build",

View File

@@ -1,5 +1,5 @@
import { useFilteredList } from "@opencode-ai/ui/hooks"
import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match, createSignal } from "solid-js"
import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match } from "solid-js"
import { createStore } from "solid-js/store"
import { createFocusSignal } from "@solid-primitives/active-element"
import { useLocal } from "@/context/local"
@@ -14,44 +14,14 @@ import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Select } from "@opencode-ai/ui/select"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { type IconName } from "@opencode-ai/ui/icons/provider"
interface PromptInputProps {
class?: string
ref?: (el: HTMLDivElement) => void
}
const PLACEHOLDERS = [
"Fix a TODO in the codebase",
"What is the tech stack of this project?",
"Fix broken tests",
"Explain how authentication works",
"Find and fix security vulnerabilities",
"Add unit tests for the user service",
"Refactor this function to be more readable",
"What does this error mean?",
"Help me debug this issue",
"Generate API documentation",
"Optimize database queries",
"Add input validation",
"Create a new component for...",
"How do I deploy this project?",
"Review my code for best practices",
"Add error handling to this function",
"Explain this regex pattern",
"Convert this to TypeScript",
"Add logging throughout the codebase",
"What dependencies are outdated?",
"Help me write a migration script",
"Implement caching for this endpoint",
"Add pagination to this list",
"Create a CLI command for...",
"How do environment variables work here?",
]
export const PromptInput: Component<PromptInputProps> = (props) => {
const navigate = useNavigate()
const sdk = useSDK()
@@ -66,15 +36,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
popoverIsOpen: false,
})
const [placeholder, setPlaceholder] = createSignal(Math.floor(Math.random() * PLACEHOLDERS.length))
onMount(() => {
const interval = setInterval(() => {
setPlaceholder((prev) => (prev + 1) % PLACEHOLDERS.length)
}, 6500)
onCleanup(() => clearInterval(interval))
})
createEffect(() => {
session.id
editorRef.focus()
@@ -107,7 +68,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const handleFileSelect = (path: string | undefined) => {
if (!path) return
addPart({ type: "file", path, content: "@" + path, start: 0, end: 0 })
addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
}
const { flat, active, onInput, onKeyDown, refetch } = useFilteredList<string>({
@@ -442,7 +403,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
/>
<Show when={!session.prompt.dirty()}>
<div class="absolute top-0 left-0 px-5 py-3 text-14-regular text-text-weak pointer-events-none">
Ask anything... "{PLACEHOLDERS[placeholder()]}"
Plan and build anything
</div>
</Show>
</div>
@@ -488,7 +449,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
{(i) => (
<div class="w-full flex items-center justify-between gap-x-3">
<div class="flex items-center gap-x-2.5 text-text-muted grow min-w-0">
<ProviderIcon name={i.provider.id as IconName} class="size-6 p-0.5 shrink-0" />
<img src={`https://models.dev/logos/${i.provider.id}.svg`} class="size-6 p-0.5 shrink-0" />
<div class="flex gap-x-3 items-baseline flex-[1_0_0]">
<span class="text-14-medium text-text-strong overflow-hidden text-ellipsis">{i.name}</span>
<Show when={false}>

View File

@@ -18,38 +18,6 @@ import { Binary } from "@opencode-ai/util/binary"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSDK } from "./global-sdk"
const PASTEL_COLORS = [
"#FCEAFD", // pastel pink
"#FFDFBA", // pastel peach
"#FFFFBA", // pastel yellow
"#BAFFC9", // pastel green
"#EAF6FD", // pastel blue
"#EFEAFD", // pastel lavender
"#FEC8D8", // pastel rose
"#D4F0F0", // pastel cyan
"#FDF0EA", // pastel coral
"#C1E1C1", // pastel mint
]
function pickAvailableColor(usedColors: Set<string>) {
const available = PASTEL_COLORS.filter((c) => !usedColors.has(c))
if (available.length === 0) return PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)]
return available[Math.floor(Math.random() * available.length)]
}
async function ensureProjectColor(
project: Project,
sdk: ReturnType<typeof useGlobalSDK>,
usedColors: Set<string>,
): Promise<Project> {
if (project.icon?.color) return project
const color = pickAvailableColor(usedColors)
usedColors.add(color)
const updated = { ...project, icon: { ...project.icon, color } }
sdk.client.project.update({ projectID: project.id, icon: { color } })
return updated
}
type State = {
ready: boolean
provider: Provider[]
@@ -124,20 +92,17 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
if (directory === "global") {
switch (event.type) {
case "project.updated": {
const usedColors = new Set(globalStore.projects.map((p) => p.icon?.color).filter(Boolean) as string[])
ensureProjectColor(event.properties, sdk, usedColors).then((project) => {
const result = Binary.search(globalStore.projects, project.id, (s) => s.id)
if (result.found) {
setGlobalStore("projects", result.index, reconcile(project))
return
}
setGlobalStore(
"projects",
produce((draft) => {
draft.splice(result.index, 0, project)
}),
)
})
const result = Binary.search(globalStore.projects, event.properties.id, (s) => s.id)
if (result.found) {
setGlobalStore("projects", result.index, reconcile(event.properties))
break
}
setGlobalStore(
"projects",
produce((draft) => {
draft.splice(result.index, 0, event.properties)
}),
)
break
}
}
@@ -215,15 +180,14 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
})
Promise.all([
sdk.client.project.list().then(async (x) => {
const filtered = x.data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs)
const usedColors = new Set(filtered.map((p) => p.icon?.color).filter(Boolean) as string[])
const projects = await Promise.all(filtered.map((p) => ensureProjectColor(p, sdk, usedColors)))
sdk.client.project.list().then((x) =>
setGlobalStore(
"projects",
projects.sort((a, b) => a.id.localeCompare(b.id)),
)
}),
x
.data!.filter((x) => !x.worktree.includes("opencode-test") && x.vcs)
.sort((a, b) => a.id.localeCompare(b.id)),
),
),
]).then(() => setGlobalStore("ready", true))
return {

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.0.143",
"version": "1.0.138",
"private": true,
"type": "module",
"scripts": {

View File

@@ -8,7 +8,6 @@ import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } fro
import { Share } from "~/core/share"
import { Logo, Mark } from "@opencode-ai/ui/logo"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { createDefaultOptions } from "@opencode-ai/ui/pierre"
import { iife } from "@opencode-ai/util/iife"
import { Binary } from "@opencode-ai/util/binary"
@@ -22,7 +21,6 @@ import { Tabs } from "@opencode-ai/ui/tabs"
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
import { Diff as SSRDiff } from "@opencode-ai/ui/diff-ssr"
import { clientOnly } from "@solidjs/start"
import { type IconName } from "@opencode-ai/ui/icons/provider"
const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
@@ -212,7 +210,10 @@ export default function () {
<div class="text-12-mono text-text-base">v{info().version}</div>
</div>
<div class="flex gap-2 items-center">
<ProviderIcon name={provider() as IconName} class="size-3.5 shrink-0 text-icon-strong-base" />
<img
src={`https://models.dev/logos/${provider()}.svg`}
class="size-3.5 shrink-0 dark:invert"
/>
<div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div>
</div>
<div class="text-12-regular text-text-weaker">

View File

@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.0.143"
version = "1.0.138"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-linux-arm64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-linux-x64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.143/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]

View File

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

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.143",
"version": "1.0.138",
"name": "opencode",
"type": "module",
"private": true,
@@ -9,12 +9,7 @@
"test": "bun test",
"build": "./script/build.ts",
"dev": "bun run --conditions=browser ./src/index.ts",
"random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'",
"clean": "echo 'Cleaning up...' && rm -rf node_modules dist",
"lint": "echo 'Running lint checks...' && bun test --coverage",
"format": "echo 'Formatting code...' && bun run --prettier --write src/**/*.ts",
"docs": "echo 'Generating documentation...' && find src -name '*.ts' -exec echo 'Processing: {}' \\;",
"deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'"
"random": "echo 'Random script updated at $(date)'"
},
"bin": {
"opencode": "./bin/opencode"
@@ -70,9 +65,9 @@
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.2",
"@opentui/core": "0.1.60",
"@opentui/solid": "0.1.60",
"@openrouter/ai-sdk-provider": "1.2.8",
"@opentui/core": "0.1.59",
"@opentui/solid": "0.1.59",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",

View File

@@ -35,21 +35,26 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
2,
),
)
const tags = [Script.channel]
const tasks = Object.entries(binaries).map(async ([name]) => {
if (process.platform !== "win32") {
await $`chmod 755 -R .`.cwd(`./dist/${name}`)
for (const [name] of Object.entries(binaries)) {
try {
process.chdir(`./dist/${name}`)
if (process.platform !== "win32") {
await $`chmod 755 -R .`
}
await $`bun publish --access public --tag ${Script.channel}`
} finally {
process.chdir(dir)
}
await $`bun pm pack`.cwd(`./dist/${name}`)
for (const tag of tags) {
await $`npm publish *.tgz --access public --tag ${tag}`.cwd(`./dist/${name}`)
}
await $`cd ./dist/${pkg.name} && bun publish --access public --tag ${Script.channel}`
if (!Script.preview) {
const major = Script.version.split(".")[0]
const majorTag = `latest-${major}`
for (const [name] of Object.entries(binaries)) {
await $`cd dist/${name} && npm dist-tag add ${name}@${Script.version} ${majorTag}`
}
})
await Promise.all(tasks)
for (const tag of tags) {
await $`cd ./dist/${pkg.name} && bun pm pack && npm publish *.tgz --access public --tag ${tag}`
await $`cd ./dist/${pkg.name} && npm dist-tag add ${pkg.name}-ai@${Script.version} ${majorTag}`
}
if (!Script.preview) {

View File

@@ -252,7 +252,7 @@ export namespace Agent {
},
},
temperature: 0.3,
messages: [
prompt: [
...system.map(
(item): ModelMessage => ({
role: "system",

View File

@@ -4,215 +4,131 @@ import { UI } from "../ui"
import { Global } from "../../global"
import { Agent } from "../../agent/agent"
import path from "path"
import fs from "fs/promises"
import matter from "gray-matter"
import { Instance } from "../../project/instance"
import { EOL } from "os"
import type { Argv } from "yargs"
type AgentMode = "all" | "primary" | "subagent"
const AVAILABLE_TOOLS = [
"bash",
"read",
"write",
"edit",
"list",
"glob",
"grep",
"webfetch",
"task",
"todowrite",
"todoread",
]
const AgentCreateCommand = cmd({
command: "create",
describe: "create a new agent",
builder: (yargs: Argv) =>
yargs
.option("path", {
type: "string",
describe: "directory path to generate the agent file",
})
.option("description", {
type: "string",
describe: "what the agent should do",
})
.option("mode", {
type: "string",
describe: "agent mode",
choices: ["all", "primary", "subagent"] as const,
})
.option("tools", {
type: "string",
describe: `comma-separated list of tools to enable (default: all). Available: "${AVAILABLE_TOOLS.join(", ")}"`,
}),
async handler(args) {
async handler() {
await Instance.provide({
directory: process.cwd(),
async fn() {
const cliPath = args.path
const cliDescription = args.description
const cliMode = args.mode as AgentMode | undefined
const cliTools = args.tools
const isFullyNonInteractive = cliPath && cliDescription && cliMode && cliTools !== undefined
if (!isFullyNonInteractive) {
UI.empty()
prompts.intro("Create agent")
}
UI.empty()
prompts.intro("Create agent")
const project = Instance.project
// Determine scope/path
let targetPath: string
if (cliPath) {
targetPath = path.join(cliPath, "agent")
} else {
let scope: "global" | "project" = "global"
if (project.vcs === "git") {
const scopeResult = await prompts.select({
message: "Location",
options: [
{
label: "Current project",
value: "project" as const,
hint: Instance.worktree,
},
{
label: "Global",
value: "global" as const,
hint: Global.Path.config,
},
],
})
if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
scope = scopeResult
}
targetPath = path.join(
scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
"agent",
)
}
// Get description
let description: string
if (cliDescription) {
description = cliDescription
} else {
const query = await prompts.text({
message: "Description",
placeholder: "What should this agent do?",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
let scope: "global" | "project" = "global"
if (project.vcs === "git") {
const scopeResult = await prompts.select({
message: "Location",
options: [
{
label: "Current project",
value: "project" as const,
hint: Instance.worktree,
},
{
label: "Global",
value: "global" as const,
hint: Global.Path.config,
},
],
})
if (prompts.isCancel(query)) throw new UI.CancelledError()
description = query
if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
scope = scopeResult
}
// Generate agent
const query = await prompts.text({
message: "Description",
placeholder: "What should this agent do?",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(query)) throw new UI.CancelledError()
const spinner = prompts.spinner()
spinner.start("Generating agent configuration...")
const generated = await Agent.generate({ description }).catch((error) => {
const generated = await Agent.generate({ description: query }).catch((error) => {
spinner.stop(`LLM failed to generate agent: ${error.message}`, 1)
if (isFullyNonInteractive) process.exit(1)
throw new UI.CancelledError()
})
spinner.stop(`Agent ${generated.identifier} generated`)
// Select tools
let selectedTools: string[]
if (cliTools !== undefined) {
selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS
} else {
const result = await prompts.multiselect({
message: "Select tools to enable",
options: AVAILABLE_TOOLS.map((tool) => ({
label: tool,
value: tool,
})),
initialValues: AVAILABLE_TOOLS,
})
if (prompts.isCancel(result)) throw new UI.CancelledError()
selectedTools = result
}
const availableTools = [
"bash",
"read",
"write",
"edit",
"list",
"glob",
"grep",
"webfetch",
"task",
"todowrite",
"todoread",
]
// Get mode
let mode: AgentMode
if (cliMode) {
mode = cliMode
} else {
const modeResult = await prompts.select({
message: "Agent mode",
options: [
{
label: "All",
value: "all" as const,
hint: "Can function in both primary and subagent roles",
},
{
label: "Primary",
value: "primary" as const,
hint: "Acts as a primary/main agent",
},
{
label: "Subagent",
value: "subagent" as const,
hint: "Can be used as a subagent by other agents",
},
],
initialValue: "all" as const,
})
if (prompts.isCancel(modeResult)) throw new UI.CancelledError()
mode = modeResult
}
const selectedTools = await prompts.multiselect({
message: "Select tools to enable",
options: availableTools.map((tool) => ({
label: tool,
value: tool,
})),
initialValues: availableTools,
})
if (prompts.isCancel(selectedTools)) throw new UI.CancelledError()
const modeResult = await prompts.select({
message: "Agent mode",
options: [
{
label: "All",
value: "all" as const,
hint: "Can function in both primary and subagent roles",
},
{
label: "Primary",
value: "primary" as const,
hint: "Acts as a primary/main agent",
},
{
label: "Subagent",
value: "subagent" as const,
hint: "Can be used as a subagent by other agents",
},
],
initialValue: "all",
})
if (prompts.isCancel(modeResult)) throw new UI.CancelledError()
// Build tools config
const tools: Record<string, boolean> = {}
for (const tool of AVAILABLE_TOOLS) {
for (const tool of availableTools) {
if (!selectedTools.includes(tool)) {
tools[tool] = false
}
}
// Build frontmatter
const frontmatter: {
description: string
mode: AgentMode
tools?: Record<string, boolean>
} = {
const frontmatter: any = {
description: generated.whenToUse,
mode,
mode: modeResult,
}
if (Object.keys(tools).length > 0) {
frontmatter.tools = tools
}
// Write file
const content = matter.stringify(generated.systemPrompt, frontmatter)
const filePath = path.join(targetPath, `${generated.identifier}.md`)
await fs.mkdir(targetPath, { recursive: true })
const file = Bun.file(filePath)
if (await file.exists()) {
if (isFullyNonInteractive) {
console.error(`Error: Agent file already exists: ${filePath}`)
process.exit(1)
}
prompts.log.error(`Agent file already exists: ${filePath}`)
throw new UI.CancelledError()
}
const filePath = path.join(
scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
`agent`,
`${generated.identifier}.md`,
)
await Bun.write(filePath, content)
if (isFullyNonInteractive) {
console.log(filePath)
} else {
prompts.log.success(`Agent created: ${filePath}`)
prompts.outro("Done")
}
prompts.log.success(`Agent created: ${filePath}`)
prompts.outro("Done")
},
})
},

View File

@@ -403,12 +403,12 @@ export const GithubRunCommand = cmd({
let appToken: string
let octoRest: Octokit
let octoGraph: typeof graphql
let commentId: number
let gitConfig: string
let session: { id: string; title: string; version: string }
let shareId: string | undefined
let exitCode = 0
type PromptFiles = Awaited<ReturnType<typeof getUserPrompt>>["promptFiles"]
const triggerCommentId = payload.comment.id
try {
const actionToken = isMock ? args.token! : await getOidcToken()
@@ -422,7 +422,8 @@ export const GithubRunCommand = cmd({
await configureGit(appToken)
await assertPermissions()
await addReaction("eyes")
const comment = await createComment()
commentId = comment.data.id
// Setup opencode session
const repoData = await fetchRepo()
@@ -454,8 +455,7 @@ export const GithubRunCommand = cmd({
await pushToLocalBranch(summary, uncommittedChanges)
}
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`))
await createComment(`${response}${footer({ image: !hasShared })}`)
await removeReaction()
await updateComment(`${response}${footer({ image: !hasShared })}`)
}
// Fork PR
else {
@@ -469,8 +469,7 @@ export const GithubRunCommand = cmd({
await pushToForkBranch(summary, prData, uncommittedChanges)
}
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`))
await createComment(`${response}${footer({ image: !hasShared })}`)
await removeReaction()
await updateComment(`${response}${footer({ image: !hasShared })}`)
}
}
// Issue
@@ -490,11 +489,9 @@ export const GithubRunCommand = cmd({
summary,
`${response}\n\nCloses #${issueId}${footer({ image: true })}`,
)
await createComment(`Created PR #${pr}${footer({ image: true })}`)
await removeReaction()
await updateComment(`Created PR #${pr}${footer({ image: true })}`)
} else {
await createComment(`${response}${footer({ image: true })}`)
await removeReaction()
await updateComment(`${response}${footer({ image: true })}`)
}
}
} catch (e: any) {
@@ -506,8 +503,7 @@ export const GithubRunCommand = cmd({
} else if (e instanceof Error) {
msg = e.message
}
await createComment(`${msg}${footer()}`)
await removeReaction()
await updateComment(`${msg}${footer()}`)
core.setFailed(msg)
// Also output the clean error message for the action to capture
//core.setOutput("prepare_error", e.message);
@@ -935,41 +931,24 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
}
async function addReaction(reaction: "eyes") {
console.log("Adding reaction...")
return await octoRest.rest.reactions.createForIssueComment({
owner,
repo,
comment_id: triggerCommentId,
content: reaction,
})
}
async function removeReaction() {
console.log("Removing reaction...")
const reactions = await octoRest.rest.reactions.listForIssueComment({
owner,
repo,
comment_id: triggerCommentId,
})
const eyesReaction = reactions.data.find((r) => r.content === "eyes")
if (!eyesReaction) return
await octoRest.rest.reactions.deleteForIssueComment({
owner,
repo,
comment_id: triggerCommentId,
reaction_id: eyesReaction.id,
})
}
async function createComment(body: string) {
async function createComment() {
console.log("Creating comment...")
return await octoRest.rest.issues.createComment({
owner,
repo,
issue_number: issueId,
body: `[Working...](${runUrl})`,
})
}
async function updateComment(body: string) {
if (!commentId) return
console.log("Updating comment...")
return await octoRest.rest.issues.updateComment({
owner,
repo,
comment_id: commentId,
body,
})
}
@@ -1050,7 +1029,7 @@ query($owner: String!, $repo: String!, $number: Int!) {
const comments = (issue.comments?.nodes || [])
.filter((c) => {
const id = parseInt(c.databaseId)
return id !== payload.comment.id
return id !== commentId && id !== payload.comment.id
})
.map((c) => ` - ${c.author.login} at ${c.createdAt}: ${c.body}`)
@@ -1169,7 +1148,7 @@ query($owner: String!, $repo: String!, $number: Int!) {
const comments = (pr.comments?.nodes || [])
.filter((c) => {
const id = parseInt(c.databaseId)
return id !== payload.comment.id
return id !== commentId && id !== payload.comment.id
})
.map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`)

View File

@@ -144,7 +144,7 @@ export function tui(input: { url: string; args: Args; onExit?: () => Promise<voi
targetFps: 60,
gatherStats: false,
exitOnCtrlC: false,
useKittyKeyboard: {},
useKittyKeyboard: true,
},
)
})

View File

@@ -199,7 +199,7 @@ export function Prompt(props: PromptProps) {
const content = await Editor.open({ value, renderer })
if (!content) return
input.setText(content)
input.setText(content, { history: false })
// Update positions for nonTextParts based on their location in new content
// Filter out parts whose virtual text was deleted
@@ -390,7 +390,7 @@ export function Prompt(props: PromptProps) {
input.blur()
},
set(prompt) {
input.setText(prompt.input)
input.setText(prompt.input, { history: false })
setStore("prompt", prompt)
restoreExtmarksFromParts(prompt.parts)
input.gotoBufferEnd()
@@ -410,11 +410,6 @@ export function Prompt(props: PromptProps) {
if (props.disabled) return
if (autocomplete.visible) return
if (!store.prompt.input) return
const trimmed = store.prompt.input.trim()
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
exit()
return
}
const selectedModel = local.model.current()
if (!selectedModel) {
promptModelWarning()
@@ -688,6 +683,17 @@ export function Prompt(props: PromptProps) {
setStore("extmarkToPartIndex", new Map())
return
}
if (keybind.match("input_forward_delete", e) && store.prompt.input !== "") {
const cursorOffset = input.cursorOffset
if (cursorOffset < input.plainText.length) {
const text = input.plainText
const newText = text.slice(0, cursorOffset) + text.slice(cursorOffset + 1)
input.setText(newText)
input.cursorOffset = cursorOffset
}
e.preventDefault()
return
}
if (keybind.match("app_exit", e)) {
await exit()
return
@@ -714,7 +720,7 @@ export function Prompt(props: PromptProps) {
const item = history.move(direction, input.plainText)
if (item) {
input.setText(item.input)
input.setText(item.input, { history: false })
setStore("prompt", item)
restoreExtmarksFromParts(item.parts)
e.preventDefault()

View File

@@ -17,7 +17,7 @@
"darkAccent": "#FFF7F1",
"darkRed": "#e06c75",
"darkOrange": "#EC5B2B",
"darkBlue": "#6ba1e6",
"darkGreen": "#7fd88f",
"darkCyan": "#56b6c2",
"darkYellow": "#e5c07b",
"lightStep1": "#ffffff",
@@ -36,7 +36,7 @@
"lightAccent": "#c94d24",
"lightRed": "#d1383d",
"lightOrange": "#EC5B2B",
"lightBlue": "#0062d1",
"lightGreen": "#3d9a57",
"lightCyan": "#318795",
"lightYellow": "#b0851f"
},
@@ -62,8 +62,8 @@
"light": "lightOrange"
},
"success": {
"dark": "darkBlue",
"light": "lightBlue"
"dark": "darkGreen",
"light": "lightGreen"
},
"info": {
"dark": "darkCyan",
@@ -102,8 +102,8 @@
"light": "lightStep6"
},
"diffAdded": {
"dark": "#6ba1e6",
"light": "#0062d1"
"dark": "#4fd6be",
"light": "#1e725c"
},
"diffRemoved": {
"dark": "#c53b53",
@@ -118,16 +118,16 @@
"light": "#7086b5"
},
"diffHighlightAdded": {
"dark": "#6ba1e6",
"light": "#0062d1"
"dark": "#b8db87",
"light": "#4db380"
},
"diffHighlightRemoved": {
"dark": "#e26a75",
"light": "#f52a65"
},
"diffAddedBg": {
"dark": "#1a2a3d",
"light": "#e0edfa"
"dark": "#20303b",
"light": "#d5e5d5"
},
"diffRemovedBg": {
"dark": "#37222c",
@@ -142,8 +142,8 @@
"light": "lightStep3"
},
"diffAddedLineNumberBg": {
"dark": "#162535",
"light": "#d0e5f5"
"dark": "#1b2b34",
"light": "#c5d5c5"
},
"diffRemovedLineNumberBg": {
"dark": "#2d1f26",
@@ -166,8 +166,8 @@
"light": "lightCyan"
},
"markdownCode": {
"dark": "darkBlue",
"light": "lightBlue"
"dark": "darkGreen",
"light": "lightGreen"
},
"markdownBlockQuote": {
"dark": "#FFF7F1",
@@ -222,8 +222,8 @@
"light": "lightRed"
},
"syntaxString": {
"dark": "darkBlue",
"light": "lightBlue"
"dark": "darkGreen",
"light": "lightGreen"
},
"syntaxNumber": {
"dark": "#FFF7F1",

View File

@@ -10,7 +10,7 @@ export function Footer() {
const { theme } = useTheme()
const sync = useSync()
const route = useRoute()
const mcp = createMemo(() => Object.values(sync.data.mcp).filter((x) => x.status === "connected").length)
const mcp = createMemo(() => Object.keys(sync.data.mcp))
const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed"))
const lsp = createMemo(() => Object.keys(sync.data.lsp))
const permissions = createMemo(() => {
@@ -66,7 +66,7 @@ export function Footer() {
<text fg={theme.text}>
<span style={{ fg: theme.success }}></span> {lsp().length} LSP
</text>
<Show when={mcp()}>
<Show when={mcp().length}>
<text fg={theme.text}>
<Switch>
<Match when={mcpError()}>
@@ -76,7 +76,7 @@ export function Footer() {
<span style={{ fg: theme.success }}> </span>
</Match>
</Switch>
{mcp()} MCP
{mcp().length} MCP
</text>
</Show>
<text fg={theme.textMuted}>/status</text>

View File

@@ -1057,7 +1057,6 @@ function UserMessage(props: {
</Show>
}
>
<span> </span>
<span style={{ bg: theme.accent, fg: theme.backgroundPanel, bold: true }}> QUEUED </span>
</Show>
</text>

View File

@@ -464,6 +464,7 @@ export namespace Config {
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
input_forward_delete: z.string().optional().default("ctrl+d").describe("Forward delete"),
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
input_submit: z.string().optional().default("return").describe("Submit input"),
input_newline: z.string().optional().default("shift+return,ctrl+j").describe("Insert newline in input"),

View File

@@ -11,6 +11,8 @@ export namespace Flag {
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT")
// Experimental
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
@@ -18,8 +20,6 @@ export namespace Flag {
export const OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT = truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT")
export const OPENCODE_ENABLE_EXA =
truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS")
function truthy(key: string) {
const value = process.env[key]?.toLowerCase()

View File

@@ -184,8 +184,7 @@ export namespace Installation {
return reg.endsWith("/") ? reg.slice(0, -1) : reg
})
const [major] = VERSION.split(".").map((x) => Number(x))
// const channel = CHANNEL === "latest" ? `latest-${major}` : CHANNEL
const channel = CHANNEL
const channel = CHANNEL === "latest" ? `latest-${major}` : CHANNEL
return fetch(`${registry}/opencode-ai/${channel}`)
.then((res) => {
if (!res.ok) throw new Error(res.statusText)

View File

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

View File

@@ -107,7 +107,7 @@ export namespace Project {
await migrateFromGlobal(id, worktree)
}
}
if (Flag.OPENCODE_EXPERIMENTAL) discover(existing)
discover(existing)
const result: Info = {
...existing,
worktree,
@@ -129,7 +129,7 @@ export namespace Project {
export async function discover(input: Info) {
if (input.vcs !== "git") return
if (input.icon?.url) return
if (input.icon) return
const glob = new Bun.Glob("**/{favicon}.{ico,png,svg,jpg,jpeg,webp}")
const matches = await Array.fromAsync(
glob.scan({
@@ -198,24 +198,11 @@ export namespace Project {
icon: Info.shape.icon.optional(),
}),
async (input) => {
const result = await Storage.update<Info>(["project", input.projectID], (draft) => {
return await Storage.update<Info>(["project", input.projectID], (draft) => {
if (input.name !== undefined) draft.name = input.name
if (input.icon !== undefined) {
draft.icon = {
...draft.icon,
}
if (input.icon.url !== undefined) draft.icon.url = input.icon.url
if (input.icon.color !== undefined) draft.icon.color = input.icon.color
}
if (input.icon !== undefined) draft.icon = input.icon
draft.time.updated = Date.now()
})
GlobalBus.emit("event", {
payload: {
type: Event.Updated.type,
properties: result,
},
})
return result
},
)
}

View File

@@ -12,7 +12,6 @@ export namespace ModelsDev {
export const Model = z.object({
id: z.string(),
name: z.string(),
family: z.string().optional(),
release_date: z.string(),
attachment: z.boolean(),
reasoning: z.boolean(),

View File

@@ -330,7 +330,6 @@ export namespace Provider {
npm: z.string(),
}),
name: z.string(),
family: z.string().optional(),
capabilities: z.object({
temperature: z.boolean(),
reasoning: z.boolean(),
@@ -408,7 +407,6 @@ export namespace Provider {
id: model.id,
providerID: provider.id,
name: model.name,
family: model.family,
api: {
id: model.id,
url: provider.api!,

View File

@@ -74,23 +74,23 @@ export namespace ProviderTransform {
return result
}
if (
model.providerID === "deepseek" ||
model.api.id.toLowerCase().includes("deepseek") ||
(model.capabilities.interleaved &&
typeof model.capabilities.interleaved === "object" &&
model.capabilities.interleaved.field === "reasoning_content")
) {
// DeepSeek: Handle reasoning_content for tool call continuations
// - With tool calls: Include reasoning_content in providerOptions so model can continue reasoning
// - Without tool calls: Strip reasoning (new turn doesn't need previous reasoning)
// See: https://api-docs.deepseek.com/guides/thinking_mode
if (model.providerID === "deepseek" || model.api.id.toLowerCase().includes("deepseek")) {
return msgs.map((msg) => {
if (msg.role === "assistant" && Array.isArray(msg.content)) {
const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning")
const hasToolCalls = msg.content.some((part: any) => part.type === "tool-call")
const reasoningText = reasoningParts.map((part: any) => part.text).join("")
// Filter out reasoning parts from content
const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning")
// Include reasoning_content directly on the message for all assistant messages
if (reasoningText) {
// If this message has tool calls and reasoning, include reasoning_content
// so DeepSeek can continue reasoning after tool execution
if (hasToolCalls && reasoningText) {
return {
...msg,
content: filteredContent,
@@ -104,12 +104,12 @@ export namespace ProviderTransform {
}
}
// For final answers (no tool calls), just strip reasoning
return {
...msg,
content: filteredContent,
}
}
return msg
})
}
@@ -212,26 +212,22 @@ export namespace ProviderTransform {
): Record<string, any> {
const result: Record<string, any> = {}
// switch to providerID later, for now use this
if (model.api.npm === "@openrouter/ai-sdk-provider") {
result["usage"] = {
include: true,
}
if (model.api.id.includes("gemini-3")) {
result["reasoning"] = { effort: "high" }
}
}
if (model.providerID === "baseten") {
result["chat_template_args"] = { enable_thinking: true }
}
if (model.providerID === "openai" || providerOptions?.setCacheKey) {
result["promptCacheKey"] = sessionID
}
if (model.api.npm === "@ai-sdk/google" || model.api.npm === "@ai-sdk/google-vertex") {
if (
model.providerID === "google" ||
(model.providerID.startsWith("opencode") && model.api.id.includes("gemini-3"))
) {
result["thinkingConfig"] = {
thinkingLevel: "high",
includeThoughts: true,
}
}
@@ -277,7 +273,23 @@ export namespace ProviderTransform {
return options
}
export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
export function providerOptions(model: Provider.Model, options: { [x: string]: any }, messages: ModelMessage[]) {
if (model.capabilities.interleaved && typeof model.capabilities.interleaved === "object") {
const cot = []
const assistantMessages = messages.filter((msg) => msg.role === "assistant")
for (const msg of assistantMessages) {
for (const part of msg.content) {
if (typeof part === "string") {
continue
}
if (part.type === "reasoning") {
cot.push(part)
}
}
}
options[model.capabilities.interleaved.field] = cot
}
switch (model.api.npm) {
case "@ai-sdk/openai":
case "@ai-sdk/azure":

View File

@@ -1460,15 +1460,12 @@ export namespace Server {
}
}
const connected = await Provider.list()
const providers = Object.assign(
mapValues(filteredProviders, (x) => Provider.fromModelsDevProvider(x)),
connected,
)
const providers = mapValues(filteredProviders, (x) => Provider.fromModelsDevProvider(x))
const connected = await Provider.list().then((x) => Object.keys(x))
return c.json({
all: Object.values(providers),
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
connected: Object.keys(connected),
connected,
})
},
)

View File

@@ -143,6 +143,7 @@ export namespace SessionCompaction {
providerOptions: ProviderTransform.providerOptions(
model,
pipe({}, mergeDeep(ProviderTransform.options(model, input.sessionID)), mergeDeep(model.options)),
[],
),
headers: model.headers,
abortSignal: input.abort,

View File

@@ -593,7 +593,7 @@ export namespace SessionPrompt {
OUTPUT_TOKEN_MAX,
),
abortSignal: abort,
providerOptions: ProviderTransform.providerOptions(model, params.options),
providerOptions: ProviderTransform.providerOptions(model, params.options, messages),
stopWhen: stepCountIs(1),
temperature: params.temperature,
topP: params.topP,
@@ -1231,8 +1231,8 @@ export namespace SessionPrompt {
},
}
await Session.updatePart(part)
const shell = process.env["SHELL"] ?? (process.platform === "win32" ? process.env["COMSPEC"] || "cmd.exe" : "bash")
const shellName = path.basename(shell).toLowerCase()
const shell = process.env["SHELL"] ?? "bash"
const shellName = path.basename(shell)
const invocations: Record<string, { args: string[] }> = {
nu: {
@@ -1262,14 +1262,6 @@ export namespace SessionPrompt {
`,
],
},
// Windows cmd.exe
"cmd.exe": {
args: ["/c", input.command],
},
// Windows PowerShell
"powershell.exe": {
args: ["-NoProfile", "-Command", input.command],
},
// Fallback: any shell that doesn't match those above
"": {
args: ["-c", "-l", `${input.command}`],
@@ -1281,7 +1273,7 @@ export namespace SessionPrompt {
const proc = spawn(shell, args, {
cwd: Instance.directory,
detached: process.platform !== "win32",
detached: true,
stdio: ["ignore", "pipe", "pipe"],
env: {
...process.env,
@@ -1473,7 +1465,7 @@ export namespace SessionPrompt {
await generateText({
// use higher # for reasoning models since reasoning tokens eat up a lot of the budget
maxOutputTokens: small.capabilities.reasoning ? 3000 : 20,
providerOptions: ProviderTransform.providerOptions(small, options),
providerOptions: ProviderTransform.providerOptions(small, options, []),
messages: [
...SystemPrompt.title(small.providerID).map(
(x): ModelMessage => ({

View File

@@ -91,7 +91,7 @@ export namespace SessionSummary {
if (textPart && !userMsg.summary?.title) {
const result = await generateText({
maxOutputTokens: small.capabilities.reasoning ? 1500 : 20,
providerOptions: ProviderTransform.providerOptions(small, options),
providerOptions: ProviderTransform.providerOptions(small, options, []),
messages: [
...SystemPrompt.title(small.providerID).map(
(x): ModelMessage => ({
@@ -144,7 +144,7 @@ export namespace SessionSummary {
const result = await generateText({
model: language,
maxOutputTokens: 100,
providerOptions: ProviderTransform.providerOptions(small, options),
providerOptions: ProviderTransform.providerOptions(small, options, []),
messages: [
...SystemPrompt.summarize(small.providerID).map(
(x): ModelMessage => ({

View File

@@ -17,7 +17,7 @@ import path from "path"
import { iife } from "@/util/iife"
const MAX_OUTPUT_LENGTH = Flag.OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH || 30_000
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT || 2 * 60 * 1000
const SIGKILL_TIMEOUT_MS = 200
export const log = Log.create({ service: "bash-tool" })

View File

@@ -17,48 +17,14 @@ Before executing the command, please follow these steps:
- Capture the output of the command.
Usage notes:
- The command argument is required.
- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes).
If not specified, commands will timeout after 120000ms (2 minutes).
- It is very helpful if you write a clear, concise description of what this command
does in 5-10 words.
- If the output exceeds 30000 characters, output will be truncated before being
returned to you.
- You can use the `run_in_background` parameter to run the command in the background,
which allows you to continue working while the command runs. You can monitor the output
using the Bash tool as it becomes available. You do not need to use '&' at the end of
the command when using this parameter.
- Avoid using Bash with the `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or
`echo` commands, unless explicitly instructed or when these commands are truly necessary
for the task. Instead, always prefer using the dedicated tools for these commands:
- File search: Use Glob (NOT find or ls)
- Content search: Use Grep (NOT grep or rg)
- Read files: Use Read (NOT cat/head/tail)
- Edit files: Use Edit (NOT sed/awk)
- Write files: Use Write (NOT echo >/cat <<EOF)
- Communication: Output text directly (NOT echo/printf)
- When issuing multiple commands:
- If the commands are independent and can run in parallel, make multiple Bash tool
calls in a single message. For example, if you need to run "git status" and "git diff",
send a single message with two Bash tool calls in parallel.
- If the commands depend on each other and must run sequentially, use a single Bash
call with '&&' to chain them together (e.g., `git add . && git commit -m "message" &&
git push`). For instance, if one operation must complete before another starts (like
mkdir before cp, Write before Bash for git operations, or git add before git commit),
run these operations sequentially instead.
- Use ';' only when you need to run commands sequentially but don't care if earlier
commands fail
- DO NOT use newlines to separate commands (newlines are ok in quoted strings)
- Try to maintain your current working directory throughout the session by using
absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly
requests it.
<good-example>
pytest /foo/bar/tests
</good-example>
<bad-example>
cd /foo/bar && pytest tests
</bad-example>
- The command argument is required.
- You can specify an optional timeout in milliseconds. If not specified, commands will timeout after 120000ms (2 minutes). Use the `timeout` parameter to control execution time.
- The `workdir` parameter specifies the working directory for the command. Defaults to the current working directory. Prefer setting `workdir` over using `cd` in your commands.
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
- If the output exceeds 30000 characters, output will be truncated before being returned to you.
- VERY IMPORTANT: You MUST avoid using search commands like `find` and `grep`. Instead use Grep, Glob, or Task to search. You MUST avoid read tools like `cat`, `head`, `tail`, and `ls`, and use Read and List to read files.
- If you _still_ need to run `grep`, STOP. ALWAYS USE ripgrep at `rg` (or /usr/bin/rg) first, which all opencode users have pre-installed.
- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings).
# Working Directory

View File

@@ -2,7 +2,7 @@
- Searches file contents using regular expressions
- Supports full regex syntax (eg. "log.*Error", "function\s+\w+", etc.)
- Filter files by pattern with the include parameter (eg. "*.js", "*.{ts,tsx}")
- Returns file paths and line numbers with at least one match sorted by modification time
- Returns file paths with at least one match sorted by modification time
- Use this tool when you need to find files containing specific patterns
- If you need to identify/count the number of matches within files, use the Bash tool with `rg` (ripgrep) directly. Do NOT use `grep`.
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Task tool instead

View File

@@ -1,33 +1,27 @@
import { $ } from "bun"
import * as fs from "fs/promises"
import { realpathSync } from "fs"
import os from "os"
import path from "path"
// Strip null bytes from paths (defensive fix for CI environment issues)
function sanitizePath(p: string): string {
return p.replace(/\0/g, "")
}
type TmpDirOptions<T> = {
git?: boolean
init?: (dir: string) => Promise<T>
dispose?: (dir: string) => Promise<T>
}
export async function tmpdir<T>(options?: TmpDirOptions<T>) {
const dirpath = sanitizePath(path.join(os.tmpdir(), "opencode-test-" + Math.random().toString(36).slice(2)))
await fs.mkdir(dirpath, { recursive: true })
const dirpath = path.join(os.tmpdir(), "opencode-test-" + Math.random().toString(36).slice(2))
await $`mkdir -p ${dirpath}`.quiet()
if (options?.git) {
await $`git init`.cwd(dirpath).quiet()
await $`git commit --allow-empty -m "root commit ${dirpath}"`.cwd(dirpath).quiet()
}
const extra = await options?.init?.(dirpath)
const realpath = sanitizePath(await fs.realpath(dirpath))
const result = {
[Symbol.asyncDispose]: async () => {
await options?.dispose?.(dirpath)
// await fs.rm(dirpath, { recursive: true, force: true })
await $`rm -rf ${dirpath}`.quiet()
},
path: realpath,
path: realpathSync(dirpath),
extra: extra as T,
}
return result

View File

@@ -2,14 +2,12 @@
// xdg-basedir reads env vars at import time, so we must set these first
import os from "os"
import path from "path"
import fs from "fs/promises"
const dir = path.join(os.tmpdir(), "opencode-test-data-" + process.pid)
await fs.mkdir(dir, { recursive: true })
process.env["XDG_DATA_HOME"] = path.join(dir, "share")
process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
process.env["XDG_STATE_HOME"] = path.join(dir, "state")
const testDataDir = path.join(os.tmpdir(), "opencode-test-data-" + process.pid)
process.env["XDG_DATA_HOME"] = testDataDir
process.env["XDG_CACHE_HOME"] = path.join(testDataDir, "cache")
process.env["XDG_CONFIG_HOME"] = path.join(testDataDir, "config")
process.env["XDG_STATE_HOME"] = path.join(testDataDir, "state")
// Clear provider env vars to ensure clean test state
delete process.env["ANTHROPIC_API_KEY"]

View File

@@ -158,6 +158,54 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBe("Let me think about this...")
})
test("DeepSeek without tool calls strips reasoning from content", () => {
const msgs = [
{
role: "assistant",
content: [
{ type: "reasoning", text: "Let me think about this..." },
{ type: "text", text: "Final answer" },
],
},
] as any[]
const result = ProviderTransform.message(msgs, {
id: "deepseek/deepseek-chat",
providerID: "deepseek",
api: {
id: "deepseek-chat",
url: "https://api.deepseek.com",
npm: "@ai-sdk/openai-compatible",
},
name: "DeepSeek Chat",
capabilities: {
temperature: true,
reasoning: true,
attachment: false,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: {
input: 0.001,
output: 0.002,
cache: { read: 0.0001, write: 0.0002 },
},
limit: {
context: 128000,
output: 8192,
},
status: "active",
options: {},
headers: {},
})
expect(result).toHaveLength(1)
expect(result[0].content).toEqual([{ type: "text", text: "Final answer" }])
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBeUndefined()
})
test("DeepSeek model ID containing 'deepseek' matches (case insensitive)", () => {
const msgs = [
{

View File

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

View File

@@ -17,5 +17,5 @@ for (const [key, value] of Object.entries(pkg.exports)) {
}
}
await Bun.write("package.json", JSON.stringify(pkg, null, 2))
await $`bun pm pack && npm publish *.tgz --tag ${Script.channel} --access public`
await $`bun publish --tag ${Script.channel} --access public`
await Bun.write("package.json", JSON.stringify(original, null, 2))

View File

@@ -13,21 +13,10 @@ if (process.versions.bun !== expectedBunVersion) {
throw new Error(`This script requires bun@${expectedBunVersion}, but you are using bun@${process.versions.bun}`)
}
const env = {
OPENCODE_CHANNEL: process.env["OPENCODE_CHANNEL"],
OPENCODE_BUMP: process.env["OPENCODE_BUMP"],
OPENCODE_VERSION: process.env["OPENCODE_VERSION"],
}
const CHANNEL = await (async () => {
if (env.OPENCODE_CHANNEL) return env.OPENCODE_CHANNEL
if (env.OPENCODE_BUMP) return "latest"
if (env.OPENCODE_VERSION && !env.OPENCODE_VERSION.startsWith("0.0.0-")) return "latest"
return await $`git branch --show-current`.text().then((x) => x.trim())
})()
const CHANNEL = process.env["OPENCODE_CHANNEL"] ?? (await $`git branch --show-current`.text().then((x) => x.trim()))
const IS_PREVIEW = CHANNEL !== "latest"
const VERSION = await (async () => {
if (env.OPENCODE_VERSION) return env.OPENCODE_VERSION
if (process.env["OPENCODE_VERSION"]) return process.env["OPENCODE_VERSION"]
if (IS_PREVIEW) return `0.0.0-${CHANNEL}-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
const version = await fetch("https://registry.npmjs.org/opencode-ai/latest")
.then((res) => {
@@ -36,7 +25,7 @@ const VERSION = await (async () => {
})
.then((data: any) => data.version)
const [major, minor, patch] = version.split(".").map((x: string) => Number(x) || 0)
const t = env.OPENCODE_BUMP?.toLowerCase()
const t = process.env["OPENCODE_BUMP"]?.toLowerCase()
if (t === "major") return `${major + 1}.0.0`
if (t === "minor") return `${major}.${minor + 1}.0`
return `${major}.${minor}.${patch + 1}`

8963
packages/sdk/js/openapi.json Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -41,4 +41,3 @@ await $`bun prettier --write src/gen`
await $`bun prettier --write src/v2`
await $`rm -rf dist`
await $`bun tsc`
await $`rm openapi.json`

View File

@@ -19,6 +19,5 @@ for (const [key, value] of Object.entries(pkg.exports)) {
}
}
await Bun.write("package.json", JSON.stringify(pkg, null, 2))
await $`bun pm pack`
await $`npm publish *.tgz --tag ${Script.channel} --access public`
await $`bun publish --tag ${Script.channel} --access public`
await Bun.write("package.json", JSON.stringify(original, null, 2))

View File

@@ -927,6 +927,10 @@ export type KeybindsConfig = {
* Clear input field
*/
input_clear?: string
/**
* Forward delete
*/
input_forward_delete?: string
/**
* Paste from clipboard
*/
@@ -1035,7 +1039,6 @@ export type ProviderConfig = {
[key: string]: {
id?: string
name?: string
family?: string
release_date?: string
attachment?: boolean
reasoning?: boolean
@@ -1462,7 +1465,6 @@ export type Model = {
npm: string
}
name: string
family?: string
capabilities: {
temperature: boolean
reasoning: boolean
@@ -3029,7 +3031,6 @@ export type ProviderListResponses = {
[key: string]: {
id: string
name: string
family?: string
release_date: string
attachment: boolean
reasoning: boolean

View File

@@ -2788,9 +2788,6 @@
"name": {
"type": "string"
},
"family": {
"type": "string"
},
"release_date": {
"type": "string"
},
@@ -2806,25 +2803,6 @@
"tool_call": {
"type": "boolean"
},
"interleaved": {
"anyOf": [
{
"type": "boolean",
"const": true
},
{
"type": "object",
"properties": {
"field": {
"type": "string",
"enum": ["reasoning_content", "reasoning_details"]
}
},
"required": ["field"],
"additionalProperties": false
}
]
},
"cost": {
"type": "object",
"properties": {
@@ -7137,6 +7115,11 @@
"default": "ctrl+c",
"type": "string"
},
"input_forward_delete": {
"description": "Forward delete",
"default": "ctrl+d",
"type": "string"
},
"input_paste": {
"description": "Paste from clipboard",
"default": "ctrl+v",
@@ -7303,9 +7286,6 @@
"name": {
"type": "string"
},
"family": {
"type": "string"
},
"release_date": {
"type": "string"
},
@@ -7321,25 +7301,6 @@
"tool_call": {
"type": "boolean"
},
"interleaved": {
"anyOf": [
{
"type": "boolean",
"const": true
},
{
"type": "object",
"properties": {
"field": {
"type": "string",
"enum": ["reasoning_content", "reasoning_details"]
}
},
"required": ["field"],
"additionalProperties": false
}
]
},
"cost": {
"type": "object",
"properties": {
@@ -8293,9 +8254,6 @@
"name": {
"type": "string"
},
"family": {
"type": "string"
},
"capabilities": {
"type": "object",
"properties": {
@@ -8352,26 +8310,9 @@
}
},
"required": ["text", "audio", "image", "video", "pdf"]
},
"interleaved": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "object",
"properties": {
"field": {
"type": "string",
"enum": ["reasoning_content", "reasoning_details"]
}
},
"required": ["field"]
}
]
}
},
"required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output", "interleaved"]
"required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output"]
},
"cost": {
"type": "object",

View File

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

View File

@@ -1,10 +1,9 @@
{
"name": "@opencode-ai/tauri",
"private": true,
"version": "1.0.143",
"version": "1.0.138",
"type": "module",
"scripts": {
"typecheck": "tsgo -b",
"predev": "bun ./scripts/predev.ts",
"dev": "vite",
"build": "bun run typecheck && vite build",

View File

@@ -108,30 +108,29 @@ pub fn run() {
tauri::async_runtime::spawn(async move {
let port = get_sidecar_port();
let socket_connected = is_server_running(port).await;
let should_spawn_sidecar = !is_server_running(port).await;
let should_spawn_sidecar = if socket_connected {
let res = app
.dialog()
.message(
"OpenCode Server is already running, would you like to restart it?",
)
.buttons(MessageDialogButtons::YesNo)
.blocking_show_with_result();
// if server_running {
// let res = app
// .dialog()
// .message(
// "OpenCode Server is already running, would you like to restart it?",
// )
// .buttons(MessageDialogButtons::YesNo)
// .blocking_show_with_result();
// match res {
// MessageDialogResult::Yes => {
// if let Err(e) = find_and_kill_process_on_port(port) {
// eprintln!("Failed to kill process on port {}: {}", port, e);
// }
// true
// }
// _ => false,
// }
// } else {
// true
// };
match res {
MessageDialogResult::Yes => {
if let Err(e) = find_and_kill_process_on_port(port) {
eprintln!("Failed to kill process on port {}: {}", port, e);
}
true
}
_ => false,
}
} else {
true
};
let child = if should_spawn_sidecar {
let child = spawn_sidecar(&app, port);

View File

@@ -20,9 +20,7 @@ export async function runUpdater(onDownloadEvent?: (progress: DownloadEvent) =>
return false
}
const shouldUpdate = await ask(
`Version ${update.version} of OpenCode has been downloaded, would you like to install it and relaunch?`,
)
const shouldUpdate = await ask(`Version ${update.version} of OpenCode is available, would you like to install it?`)
if (!shouldUpdate) return
try {
@@ -32,7 +30,8 @@ export async function runUpdater(onDownloadEvent?: (progress: DownloadEvent) =>
return false
}
await relaunch()
const shouldRestart = await ask("Update installed successfully, would you like to restart OpenCode?")
if (shouldRestart) await relaunch()
return true
}

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.0.143",
"version": "1.0.138",
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",
@@ -10,8 +10,6 @@
"./context/*": "./src/context/*.tsx",
"./styles": "./src/styles/index.css",
"./styles/tailwind": "./src/styles/tailwind/index.css",
"./icons/provider": "./src/components/provider-icons/types.ts",
"./icons/file-type": "./src/components/file-icons/types.ts",
"./fonts/*": "./src/assets/fonts/*"
},
"scripts": {

View File

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

View File

Before

Width:  |  Height:  |  Size: 110 B

After

Width:  |  Height:  |  Size: 110 B

View File

Before

Width:  |  Height:  |  Size: 746 B

After

Width:  |  Height:  |  Size: 746 B

View File

Before

Width:  |  Height:  |  Size: 758 B

After

Width:  |  Height:  |  Size: 758 B

View File

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

View File

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 511 B

View File

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 511 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 297 B

View File

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

View File

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 775 B

View File

Before

Width:  |  Height:  |  Size: 151 B

After

Width:  |  Height:  |  Size: 151 B

View File

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 509 B

View File

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 263 B

View File

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 823 B

View File

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

View File

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 431 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 776 B

View File

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 776 B

View File

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 579 B

View File

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 418 B

View File

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 281 B

View File

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 415 B

View File

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 517 B

View File

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 686 B

View File

Before

Width:  |  Height:  |  Size: 577 B

After

Width:  |  Height:  |  Size: 577 B

View File

Before

Width:  |  Height:  |  Size: 185 B

After

Width:  |  Height:  |  Size: 185 B

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 532 B

After

Width:  |  Height:  |  Size: 532 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 384 B

After

Width:  |  Height:  |  Size: 384 B

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