Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fce7f05347 | ||
|
|
17ec45a670 | ||
|
|
48b79702a0 | ||
|
|
8fd5fe6a5f | ||
|
|
00637c0269 | ||
|
|
e1e356cab4 | ||
|
|
d58661d4fb | ||
|
|
09f4ef8996 | ||
|
|
1794319a4c | ||
|
|
8f53794017 | ||
|
|
48d6d72e25 | ||
|
|
5bef8e316a | ||
|
|
08f11f4da6 | ||
|
|
9e0747c9b4 | ||
|
|
66ec378680 | ||
|
|
015eda36ce | ||
|
|
e666ddb630 | ||
|
|
da7f45bd4c | ||
|
|
273e7b8379 | ||
|
|
36041c0000 | ||
|
|
b5e5d4c92f | ||
|
|
b109ab7830 | ||
|
|
b28891473f | ||
|
|
5d0122b5a9 | ||
|
|
1ab4bbc275 | ||
|
|
908350c2ea | ||
|
|
3fef490187 | ||
|
|
3ac05201c6 | ||
|
|
2d3c7a0f24 | ||
|
|
cd664a189b | ||
|
|
849f488744 | ||
|
|
5ea1042ffb | ||
|
|
71d280d570 | ||
|
|
5cfb5fdd06 | ||
|
|
30969dc33e | ||
|
|
5f282c268d | ||
|
|
d3d6e7e275 | ||
|
|
a70c66eb3f | ||
|
|
60de810d9a | ||
|
|
95309c2149 | ||
|
|
e9e8d97b0d | ||
|
|
553316af2a | ||
|
|
f27ee4674a | ||
|
|
ad91f9143a | ||
|
|
b43a35b737 | ||
|
|
03803621db | ||
|
|
81326377f2 | ||
|
|
7ed6f690e9 | ||
|
|
1f3bf56640 | ||
|
|
bbc7bdb3fd | ||
|
|
a5c01a81ff | ||
|
|
4f4694d9e3 | ||
|
|
4c82ad6280 | ||
|
|
b35265823c | ||
|
|
8ce19f8cca | ||
|
|
b5ffa997da | ||
|
|
75166a1961 | ||
|
|
6cc739701b | ||
|
|
2125dc11c7 | ||
|
|
aa1d0f6167 | ||
|
|
fdd484d2c1 | ||
|
|
cd4075faf6 | ||
|
|
33311e9950 | ||
|
|
a92b7923c2 |
@@ -3,20 +3,17 @@ description: "Setup Bun with caching and install dependencies"
|
|||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
|
- name: Mount Bun Cache
|
||||||
|
uses: useblacksmith/stickydisk@v1
|
||||||
|
with:
|
||||||
|
key: ${{ github.repository }}-bun-cache
|
||||||
|
path: ~/.bun
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version-file: package.json
|
bun-version-file: package.json
|
||||||
|
|
||||||
- name: Cache ~/.bun
|
|
||||||
id: cache-bun
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.bun
|
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('package.json') }}-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-bun-${{ hashFiles('package.json') }}-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: "Setup Git Committer"
|
||||||
|
description: "Create app token and configure git user"
|
||||||
|
inputs:
|
||||||
|
opencode-app-id:
|
||||||
|
description: "OpenCode GitHub App ID"
|
||||||
|
required: true
|
||||||
|
opencode-app-secret:
|
||||||
|
description: "OpenCode GitHub App private key"
|
||||||
|
required: true
|
||||||
|
outputs:
|
||||||
|
token:
|
||||||
|
description: "GitHub App token"
|
||||||
|
value: ${{ steps.apptoken.outputs.token }}
|
||||||
|
app-slug:
|
||||||
|
description: "GitHub App slug"
|
||||||
|
value: ${{ steps.apptoken.outputs.app-slug }}
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Create app token
|
||||||
|
id: apptoken
|
||||||
|
uses: actions/create-github-app-token@v2
|
||||||
|
with:
|
||||||
|
app-id: ${{ inputs.opencode-app-id }}
|
||||||
|
private-key: ${{ inputs.opencode-app-secret }}
|
||||||
|
|
||||||
|
- name: Configure git user
|
||||||
|
run: |
|
||||||
|
slug="${{ steps.apptoken.outputs.app-slug }}"
|
||||||
|
git config --global user.name "${slug}[bot]"
|
||||||
|
git config --global user.email "${slug}[bot]@users.noreply.github.com"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Clear checkout auth
|
||||||
|
run: |
|
||||||
|
git config --local --unset-all http.https://github.com/.extraheader || true
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Configure git remote
|
||||||
|
run: |
|
||||||
|
git remote set-url origin https://x-access-token:${{ steps.apptoken.outputs.token }}@github.com/${{ github.repository }}
|
||||||
|
shell: bash
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: beta
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [dev]
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, labeled, unlabeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
if: |
|
||||||
|
github.event_name == 'push' ||
|
||||||
|
(github.event_name == 'pull_request' &&
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'contributor'))
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Sync beta branch
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: bun script/beta.ts
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
name: containers
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- packages/containers/**
|
||||||
|
- .github/workflows/containers.yml
|
||||||
|
- package.json
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io/${{ github.repository_owner }}
|
||||||
|
TAG: "24.04"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push containers
|
||||||
|
run: bun ./packages/containers/script/build.ts --push
|
||||||
|
env:
|
||||||
|
REGISTRY: ${{ env.REGISTRY }}
|
||||||
|
TAG: ${{ env.TAG }}
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -15,14 +16,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
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
|
- name: Setup Bun
|
||||||
uses: ./.github/actions/setup-bun
|
uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- name: Setup git committer
|
||||||
|
id: committer
|
||||||
|
uses: ./.github/actions/setup-git-committer
|
||||||
|
with:
|
||||||
|
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||||
|
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||||
|
|
||||||
- name: Generate
|
- name: Generate
|
||||||
run: ./script/generate.ts
|
run: ./script/generate.ts
|
||||||
|
|
||||||
@@ -32,10 +36,8 @@ jobs:
|
|||||||
echo "No changes to commit"
|
echo "No changes to commit"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
git config --local user.email "action@github.com"
|
|
||||||
git config --local user.name "GitHub Action"
|
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore: generate"
|
git commit -m "chore: generate" --allow-empty
|
||||||
git push origin HEAD:${{ github.ref_name }} --no-verify
|
git push origin HEAD:${{ github.ref_name }} --no-verify
|
||||||
# if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then
|
# if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then
|
||||||
# echo ""
|
# echo ""
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: nix-desktop
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [dev]
|
||||||
|
paths:
|
||||||
|
- "flake.nix"
|
||||||
|
- "flake.lock"
|
||||||
|
- "nix/**"
|
||||||
|
- "packages/app/**"
|
||||||
|
- "packages/desktop/**"
|
||||||
|
- ".github/workflows/nix-desktop.yml"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "flake.nix"
|
||||||
|
- "flake.lock"
|
||||||
|
- "nix/**"
|
||||||
|
- "packages/app/**"
|
||||||
|
- "packages/desktop/**"
|
||||||
|
- ".github/workflows/nix-desktop.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nix-desktop:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- blacksmith-4vcpu-ubuntu-2404
|
||||||
|
- blacksmith-4vcpu-ubuntu-2404-arm
|
||||||
|
- macos-15-intel
|
||||||
|
- macos-latest
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Nix
|
||||||
|
uses: nixbuild/nix-quick-install-action@v34
|
||||||
|
|
||||||
|
- name: Build desktop via flake
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
nix --version
|
||||||
|
nix build .#desktop -L
|
||||||
@@ -36,14 +36,16 @@ jobs:
|
|||||||
ref: ${{ github.head_ref || github.ref_name }}
|
ref: ${{ github.head_ref || github.ref_name }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
|
|
||||||
|
- name: Setup git committer
|
||||||
|
id: committer
|
||||||
|
uses: ./.github/actions/setup-git-committer
|
||||||
|
with:
|
||||||
|
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||||
|
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||||
|
|
||||||
- name: Setup Nix
|
- name: Setup Nix
|
||||||
uses: nixbuild/nix-quick-install-action@v34
|
uses: nixbuild/nix-quick-install-action@v34
|
||||||
|
|
||||||
- name: Configure git
|
|
||||||
run: |
|
|
||||||
git config --global user.email "action@github.com"
|
|
||||||
git config --global user.name "Github Action"
|
|
||||||
|
|
||||||
- name: Pull latest changes
|
- name: Pull latest changes
|
||||||
env:
|
env:
|
||||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- ci
|
- ci
|
||||||
- dev
|
- dev
|
||||||
|
- beta
|
||||||
|
- workflow/publish-updates
|
||||||
- snapshot-*
|
- snapshot-*
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
@@ -36,15 +38,22 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- name: Install OpenCode
|
||||||
|
# if: inputs.bump || inputs.version
|
||||||
|
run: bun i -g opencode-ai
|
||||||
|
|
||||||
- id: version
|
- id: version
|
||||||
run: |
|
run: |
|
||||||
./script/version.ts
|
./script/version.ts
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||||
OPENCODE_VERSION: ${{ inputs.version }}
|
OPENCODE_VERSION: ${{ inputs.version }}
|
||||||
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
release: ${{ steps.version.outputs.release }}
|
release: ${{ steps.version.outputs.release }}
|
||||||
@@ -57,7 +66,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
|
||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
@@ -69,7 +77,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||||
GH_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -102,7 +110,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
|
||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
|
|
||||||
- uses: apple-actions/import-codesign-certs@v2
|
- uses: apple-actions/import-codesign-certs@v2
|
||||||
@@ -127,6 +134,15 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- name: Cache apt packages
|
||||||
|
if: contains(matrix.settings.host, 'ubuntu')
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.settings.target }}-apt-
|
||||||
|
|
||||||
- name: install dependencies (ubuntu only)
|
- name: install dependencies (ubuntu only)
|
||||||
if: contains(matrix.settings.host, 'ubuntu')
|
if: contains(matrix.settings.host, 'ubuntu')
|
||||||
run: |
|
run: |
|
||||||
@@ -148,10 +164,9 @@ jobs:
|
|||||||
cd packages/desktop
|
cd packages/desktop
|
||||||
bun ./scripts/prepare.ts
|
bun ./scripts/prepare.ts
|
||||||
env:
|
env:
|
||||||
OPENCODE_VERSION: ${{ needs.publish.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
|
||||||
RUST_TARGET: ${{ matrix.settings.target }}
|
RUST_TARGET: ${{ matrix.settings.target }}
|
||||||
GH_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
|
||||||
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
|
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
|
||||||
@@ -176,7 +191,7 @@ jobs:
|
|||||||
releaseDraft: true
|
releaseDraft: true
|
||||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
@@ -191,23 +206,19 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- version
|
- version
|
||||||
- build-cli
|
- build-cli
|
||||||
- build-tauri
|
# - build-tauri
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Install OpenCode
|
- uses: ./.github/actions/setup-bun
|
||||||
if: inputs.bump || inputs.version
|
|
||||||
run: bun i -g opencode-ai
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ github.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -220,19 +231,19 @@ jobs:
|
|||||||
node-version: "24"
|
node-version: "24"
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- name: Setup Git Identity
|
|
||||||
run: |
|
|
||||||
git config --global user.email "opencode@sst.dev"
|
|
||||||
git config --global user.name "opencode"
|
|
||||||
git remote set-url origin https://x-access-token:${{ secrets.SST_GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: opencode-cli
|
name: opencode-cli
|
||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
|
- name: Cache apt packages (AUR)
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-apt-aur-
|
||||||
|
|
||||||
- name: Setup SSH for AUR
|
- name: Setup SSH for AUR
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -244,11 +255,17 @@ jobs:
|
|||||||
git config --global user.name "opencode"
|
git config --global user.name "opencode"
|
||||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
||||||
|
|
||||||
|
- name: Setup git committer
|
||||||
|
id: committer
|
||||||
|
uses: ./.github/actions/setup-git-committer
|
||||||
|
with:
|
||||||
|
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||||
|
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||||
|
|
||||||
- run: ./script/publish.ts
|
- run: ./script/publish.ts
|
||||||
env:
|
env:
|
||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
||||||
NPM_CONFIG_PROVENANCE: false
|
NPM_CONFIG_PROVENANCE: false
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
||||||
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
||||||
- The default branch in this repo is `dev`.
|
- The default branch in this repo is `dev`.
|
||||||
|
- Prefer automation: execute requested actions without confirmation unless blocked by missing info or safety/irreversibility.
|
||||||
|
|
||||||
## Style Guide
|
## Style Guide
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-yAtZlh6YR78RwPt0LK/7Pk0qUm0/97+s6ghhZzuoE/0=",
|
"x86_64-linux": "sha256-gUWzUsk81miIrjg0fZQmsIQG4pZYmEHgzN6BaXI+lfc=",
|
||||||
"aarch64-linux": "sha256-6j81rdjQ7Wps9bvfw+mmdwW5p01qUOwX40UZltCTe3Y=",
|
"aarch64-linux": "sha256-gwEG75ha/ojTO2iAObTmLTtEkXIXJ7BThzfI5CqlJh8=",
|
||||||
"aarch64-darwin": "sha256-pDM8M/QMWR6Go5pz3XXsJqcJDHAlHrx2Faijjkzcngo=",
|
"aarch64-darwin": "sha256-20RGG2GkUItCzD67gDdoSLfexttM8abS//FKO9bfjoM=",
|
||||||
"x86_64-darwin": "sha256-eOAPtMd1n5xYupBOevCLhY1eFy3wzGqFk/EsZocl9Y8="
|
"x86_64-darwin": "sha256-i2VawFuR1UbjPVYoybU6aJDJfFo0tcvtl1aM31Y2mTQ="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { test, expect } from "./fixtures"
|
||||||
|
import { modelVariantCycleSelector } from "./utils"
|
||||||
|
|
||||||
|
test("smoke model variant cycle updates label", async ({ page, gotoSession }) => {
|
||||||
|
await gotoSession()
|
||||||
|
|
||||||
|
await page.addStyleTag({
|
||||||
|
content: `${modelVariantCycleSelector} { display: inline-block !important; }`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const button = page.locator(modelVariantCycleSelector)
|
||||||
|
const exists = (await button.count()) > 0
|
||||||
|
test.skip(!exists, "current model has no variants")
|
||||||
|
if (!exists) return
|
||||||
|
|
||||||
|
await expect(button).toBeVisible()
|
||||||
|
|
||||||
|
const before = (await button.innerText()).trim()
|
||||||
|
await button.click()
|
||||||
|
await expect(button).not.toHaveText(before)
|
||||||
|
|
||||||
|
const after = (await button.innerText()).trim()
|
||||||
|
await button.click()
|
||||||
|
await expect(button).not.toHaveText(after)
|
||||||
|
})
|
||||||
@@ -12,6 +12,7 @@ export const terminalToggleKey = "Control+Backquote"
|
|||||||
|
|
||||||
export const promptSelector = '[data-component="prompt-input"]'
|
export const promptSelector = '[data-component="prompt-input"]'
|
||||||
export const terminalSelector = '[data-component="terminal"]'
|
export const terminalSelector = '[data-component="terminal"]'
|
||||||
|
export const modelVariantCycleSelector = '[data-action="model-variant-cycle"]'
|
||||||
|
|
||||||
export function createSdk(directory?: string) {
|
export function createSdk(directory?: string) {
|
||||||
return createOpencodeClient({ baseUrl: serverUrl, directory, throwOnError: true })
|
return createOpencodeClient({ baseUrl: serverUrl, directory, throwOnError: true })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -34,11 +34,14 @@ export const DialogSelectModelUnpaid: Component = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title={language.t("dialog.model.select.title")}>
|
<Dialog
|
||||||
<div class="flex flex-col gap-3 px-2.5 flex-1 min-h-0">
|
title={language.t("dialog.model.select.title")}
|
||||||
|
class="overflow-y-auto [&_[data-slot=dialog-body]]:overflow-visible [&_[data-slot=dialog-body]]:flex-none"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-3 px-2.5">
|
||||||
<div class="text-14-medium text-text-base px-2.5">{language.t("dialog.model.unpaid.freeModels.title")}</div>
|
<div class="text-14-medium text-text-base px-2.5">{language.t("dialog.model.unpaid.freeModels.title")}</div>
|
||||||
<List
|
<List
|
||||||
class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
|
class="[&_[data-slot=list-scroll]]:overflow-visible"
|
||||||
ref={(ref) => (listRef = ref)}
|
ref={(ref) => (listRef = ref)}
|
||||||
items={local.model.list}
|
items={local.model.list}
|
||||||
current={local.model.current()}
|
current={local.model.current()}
|
||||||
@@ -76,8 +79,6 @@ export const DialogSelectModelUnpaid: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="px-1.5 pb-1.5">
|
<div class="px-1.5 pb-1.5">
|
||||||
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
|
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
|
||||||
|
|||||||
@@ -1953,6 +1953,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
keybind={command.keybind("model.variant.cycle")}
|
keybind={command.keybind("model.variant.cycle")}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
data-action="model-variant-cycle"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
|
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
|
||||||
onClick={() => local.model.variant.cycle()}
|
onClick={() => local.model.variant.cycle()}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export function SessionHeader() {
|
|||||||
<Portal mount={mount()}>
|
<Portal mount={mount()}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="hidden md:flex w-[320px] p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
|
class="hidden md:flex w-[320px] max-w-full min-w-0 p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
|
||||||
onClick={() => command.trigger("file.open")}
|
onClick={() => command.trigger("file.open")}
|
||||||
aria-label={language.t("session.header.searchFiles")}
|
aria-label={language.t("session.header.searchFiles")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -132,12 +132,14 @@ export function Titlebar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header class="h-10 shrink-0 bg-background-base flex items-center relative" data-tauri-drag-region>
|
<header
|
||||||
|
class="h-10 shrink-0 bg-background-base relative grid grid-cols-[auto_minmax(0,1fr)_auto] items-center"
|
||||||
|
data-tauri-drag-region
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"flex items-center w-full min-w-0": true,
|
"flex items-center min-w-0": true,
|
||||||
"pl-2": !mac(),
|
"pl-2": !mac(),
|
||||||
"pr-6": !windows(),
|
|
||||||
}}
|
}}
|
||||||
onMouseDown={drag}
|
onMouseDown={drag}
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
@@ -218,20 +220,29 @@ export function Titlebar() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" data-tauri-drag-region />
|
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" data-tauri-drag-region />
|
||||||
<div class="flex-1 h-full" data-tauri-drag-region />
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="opencode-titlebar-right"
|
class="min-w-0 flex items-center justify-center pointer-events-none lg:absolute lg:inset-0 lg:flex lg:items-center lg:justify-center"
|
||||||
class="flex items-center gap-3 shrink-0 flex-1 justify-end"
|
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
/>
|
>
|
||||||
|
<div id="opencode-titlebar-center" class="pointer-events-auto w-full min-w-0 flex justify-center lg:w-fit" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"flex items-center min-w-0 justify-end": true,
|
||||||
|
"pr-6": !windows(),
|
||||||
|
}}
|
||||||
|
onMouseDown={drag}
|
||||||
|
data-tauri-drag-region
|
||||||
|
>
|
||||||
|
<div id="opencode-titlebar-right" class="flex items-center gap-3 shrink-0 justify-end" data-tauri-drag-region />
|
||||||
<Show when={windows()}>
|
<Show when={windows()}>
|
||||||
<div class="w-6 shrink-0" />
|
<div class="w-6 shrink-0" />
|
||||||
<div data-tauri-decorum-tb class="flex flex-row" />
|
<div data-tauri-decorum-tb class="flex flex-row" />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
||||||
<div id="opencode-titlebar-center" class="pointer-events-auto" />
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"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",
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# CI containers
|
||||||
|
|
||||||
|
Prebuilt images intended to speed up GitHub Actions jobs by baking in
|
||||||
|
large, slow-to-install dependencies. These are designed for Linux jobs
|
||||||
|
that can use `job.container` in workflows.
|
||||||
|
|
||||||
|
Images
|
||||||
|
|
||||||
|
- `base`: Ubuntu 24.04 with common build tools and utilities
|
||||||
|
- `bun-node`: `base` plus Bun and Node.js 24
|
||||||
|
- `rust`: `bun-node` plus Rust (stable, minimal profile)
|
||||||
|
- `tauri-linux`: `rust` plus Tauri Linux build dependencies
|
||||||
|
- `publish`: `bun-node` plus Docker CLI and AUR tooling
|
||||||
|
|
||||||
|
Build
|
||||||
|
|
||||||
|
```
|
||||||
|
REGISTRY=ghcr.io/anomalyco TAG=24.04 bun ./packages/containers/script/build.ts
|
||||||
|
REGISTRY=ghcr.io/anomalyco TAG=24.04 bun ./packages/containers/script/build.ts --push
|
||||||
|
```
|
||||||
|
|
||||||
|
Workflow usage
|
||||||
|
|
||||||
|
```
|
||||||
|
jobs:
|
||||||
|
build-cli:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ghcr.io/anomalyco/build/bun-node:24.04
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes
|
||||||
|
|
||||||
|
- These images only help Linux jobs. macOS and Windows jobs cannot run
|
||||||
|
inside Linux containers.
|
||||||
|
- `--push` publishes multi-arch (amd64 + arm64) images using Buildx.
|
||||||
|
- If a job uses Docker Buildx, the container needs access to the host
|
||||||
|
Docker daemon (or `docker-in-docker` with privileged mode).
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
jq \
|
||||||
|
openssh-client \
|
||||||
|
pkg-config \
|
||||||
|
python3 \
|
||||||
|
unzip \
|
||||||
|
xz-utils \
|
||||||
|
zip \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
ARG REGISTRY=ghcr.io/anomalyco
|
||||||
|
FROM ${REGISTRY}/build/base:24.04
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-lc"]
|
||||||
|
|
||||||
|
ARG NODE_VERSION=24.4.0
|
||||||
|
ARG BUN_VERSION=1.3.5
|
||||||
|
|
||||||
|
ENV BUN_INSTALL=/opt/bun
|
||||||
|
ENV PATH=/opt/bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
RUN set -euo pipefail; \
|
||||||
|
arch=$(uname -m); \
|
||||||
|
node_arch=x64; \
|
||||||
|
if [ "$arch" = "aarch64" ]; then node_arch=arm64; fi; \
|
||||||
|
curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${node_arch}.tar.xz" \
|
||||||
|
| tar -xJf - -C /usr/local --strip-components=1; \
|
||||||
|
corepack enable
|
||||||
|
|
||||||
|
RUN set -euo pipefail; \
|
||||||
|
curl -fsSL https://bun.sh/install | bash -s -- "bun-v${BUN_VERSION}"; \
|
||||||
|
bun --version; \
|
||||||
|
node --version; \
|
||||||
|
npm --version
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
ARG REGISTRY=ghcr.io/anomalyco
|
||||||
|
FROM ${REGISTRY}/build/bun-node:24.04
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
docker.io \
|
||||||
|
pacman-package-manager \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
ARG REGISTRY=ghcr.io/anomalyco
|
||||||
|
FROM ${REGISTRY}/build/bun-node:24.04
|
||||||
|
|
||||||
|
ARG RUST_TOOLCHAIN=stable
|
||||||
|
|
||||||
|
ENV CARGO_HOME=/opt/cargo
|
||||||
|
ENV RUSTUP_HOME=/opt/rustup
|
||||||
|
ENV PATH=/opt/cargo/bin:/opt/bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
RUN set -euo pipefail; \
|
||||||
|
curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain "${RUST_TOOLCHAIN}"; \
|
||||||
|
rustc --version; \
|
||||||
|
cargo --version
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import { $ } from "bun"
|
||||||
|
import path from "path"
|
||||||
|
import { fileURLToPath } from "url"
|
||||||
|
|
||||||
|
const rootDir = fileURLToPath(new URL("../../..", import.meta.url))
|
||||||
|
process.chdir(rootDir)
|
||||||
|
|
||||||
|
const reg = process.env.REGISTRY ?? "ghcr.io/anomalyco"
|
||||||
|
const tag = process.env.TAG ?? "24.04"
|
||||||
|
const push = process.argv.includes("--push") || process.env.PUSH === "1"
|
||||||
|
|
||||||
|
const root = path.join(rootDir, "package.json")
|
||||||
|
const pkg = await Bun.file(root).json()
|
||||||
|
const manager = pkg.packageManager ?? ""
|
||||||
|
const bun = manager.startsWith("bun@") ? manager.slice(4) : ""
|
||||||
|
if (!bun) throw new Error("packageManager must be bun@<version>")
|
||||||
|
|
||||||
|
const images = ["base", "bun-node", "rust", "tauri-linux", "publish"]
|
||||||
|
|
||||||
|
const setup = async () => {
|
||||||
|
if (!push) return
|
||||||
|
const list = await $`docker buildx ls`.text()
|
||||||
|
if (list.includes("opencode")) {
|
||||||
|
await $`docker buildx use opencode`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await $`docker buildx create --name opencode --use`
|
||||||
|
}
|
||||||
|
|
||||||
|
await setup()
|
||||||
|
|
||||||
|
const platform = "linux/amd64,linux/arm64"
|
||||||
|
|
||||||
|
for (const name of images) {
|
||||||
|
const image = `${reg}/build/${name}:${tag}`
|
||||||
|
const file = `packages/containers/${name}/Dockerfile`
|
||||||
|
if (name === "base") {
|
||||||
|
if (push) {
|
||||||
|
console.log(`docker buildx build --platform ${platform} -f ${file} -t ${image} --push .`)
|
||||||
|
await $`docker buildx build --platform ${platform} -f ${file} -t ${image} --push .`
|
||||||
|
}
|
||||||
|
if (!push) {
|
||||||
|
console.log(`docker build -f ${file} -t ${image} .`)
|
||||||
|
await $`docker build -f ${file} -t ${image} .`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name === "bun-node") {
|
||||||
|
if (push) {
|
||||||
|
console.log(
|
||||||
|
`docker buildx build --platform ${platform} -f ${file} -t ${image} --build-arg REGISTRY=${reg} --build-arg BUN_VERSION=${bun} --push .`,
|
||||||
|
)
|
||||||
|
await $`docker buildx build --platform ${platform} -f ${file} -t ${image} --build-arg REGISTRY=${reg} --build-arg BUN_VERSION=${bun} --push .`
|
||||||
|
}
|
||||||
|
if (!push) {
|
||||||
|
console.log(`docker build -f ${file} -t ${image} --build-arg REGISTRY=${reg} --build-arg BUN_VERSION=${bun} .`)
|
||||||
|
await $`docker build -f ${file} -t ${image} --build-arg REGISTRY=${reg} --build-arg BUN_VERSION=${bun} .`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name !== "base" && name !== "bun-node") {
|
||||||
|
if (push) {
|
||||||
|
console.log(
|
||||||
|
`docker buildx build --platform ${platform} -f ${file} -t ${image} --build-arg REGISTRY=${reg} --push .`,
|
||||||
|
)
|
||||||
|
await $`docker buildx build --platform ${platform} -f ${file} -t ${image} --build-arg REGISTRY=${reg} --push .`
|
||||||
|
}
|
||||||
|
if (!push) {
|
||||||
|
console.log(`docker build -f ${file} -t ${image} --build-arg REGISTRY=${reg} .`)
|
||||||
|
await $`docker build -f ${file} -t ${image} --build-arg REGISTRY=${reg} .`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (push) {
|
||||||
|
console.log(`pushed ${image}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
ARG REGISTRY=ghcr.io/anomalyco
|
||||||
|
FROM ${REGISTRY}/build/rust:24.04
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
libappindicator3-dev \
|
||||||
|
libwebkit2gtk-4.1-dev \
|
||||||
|
librsvg2-dev \
|
||||||
|
patchelf \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"extends": "@tsconfig/bun/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
|
"noUncheckedIndexedAccess": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
|
||||||
|
import { Script } from "@opencode-ai/script"
|
||||||
import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils"
|
import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils"
|
||||||
|
|
||||||
|
const pkg = await Bun.file("./package.json").json()
|
||||||
|
pkg.version = Script.version
|
||||||
|
await Bun.write("./package.json", JSON.stringify(pkg, null, 2) + "\n")
|
||||||
|
console.log(`Updated package.json version to ${Script.version}`)
|
||||||
|
|
||||||
const sidecarConfig = getCurrentSidecar()
|
const sidecarConfig = getCurrentSidecar()
|
||||||
|
|
||||||
const dir = "src-tauri/target/opencode-binaries"
|
const dir = "src-tauri/target/opencode-binaries"
|
||||||
|
|||||||
Generated
+4
-4
@@ -4914,9 +4914,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-fs"
|
||||||
version = "2.4.4"
|
version = "2.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9"
|
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dunce",
|
"dunce",
|
||||||
@@ -4936,9 +4936,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-http"
|
name = "tauri-plugin-http"
|
||||||
version = "2.5.4"
|
version = "2.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c00685aceab12643cf024f712ab0448ba8fcadf86f2391d49d2e5aa732aacc70"
|
checksum = "68bef611ccbfbce67c813959c11b23c1c084d201aa94222de9eba5f9edc3f897"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"cookie_store",
|
"cookie_store",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ tauri-plugin-process = "2"
|
|||||||
tauri-plugin-store = "2"
|
tauri-plugin-store = "2"
|
||||||
tauri-plugin-window-state = "2"
|
tauri-plugin-window-state = "2"
|
||||||
tauri-plugin-clipboard-manager = "2"
|
tauri-plugin-clipboard-manager = "2"
|
||||||
tauri-plugin-http = "2"
|
tauri-plugin-http = "2.5.6"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "0.0.0-ci-202601291718"
|
version = "1.1.45"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/anomalyco/opencode"
|
repository = "https://github.com/anomalyco/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/anomalyco/opencode/releases/download/v0.0.0-ci-202601291718/opencode-darwin-arm64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.45/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/anomalyco/opencode/releases/download/v0.0.0-ci-202601291718/opencode-darwin-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.45/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/anomalyco/opencode/releases/download/v0.0.0-ci-202601291718/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.45/opencode-linux-arm64.tar.gz"
|
||||||
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/anomalyco/opencode/releases/download/v0.0.0-ci-202601291718/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.45/opencode-linux-x64.tar.gz"
|
||||||
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/anomalyco/opencode/releases/download/v0.0.0-ci-202601291718/opencode-windows-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.45/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.12.0",
|
"@agentclientprotocol/sdk": "0.13.0",
|
||||||
"@ai-sdk/amazon-bedrock": "3.0.73",
|
"@ai-sdk/amazon-bedrock": "3.0.73",
|
||||||
"@ai-sdk/anthropic": "2.0.57",
|
"@ai-sdk/anthropic": "2.0.57",
|
||||||
"@ai-sdk/azure": "2.0.91",
|
"@ai-sdk/azure": "2.0.91",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
type ToolCallContent,
|
type ToolCallContent,
|
||||||
type ToolKind,
|
type ToolKind,
|
||||||
} from "@agentclientprotocol/sdk"
|
} from "@agentclientprotocol/sdk"
|
||||||
|
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { ACPSessionManager } from "./session"
|
import { ACPSessionManager } from "./session"
|
||||||
import type { ACPConfig } from "./types"
|
import type { ACPConfig } from "./types"
|
||||||
@@ -40,6 +41,11 @@ import { LoadAPIKeyError } from "ai"
|
|||||||
import type { Event, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
import type { Event, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
||||||
import { applyPatch } from "diff"
|
import { applyPatch } from "diff"
|
||||||
|
|
||||||
|
type ModeOption = { id: string; name: string; description?: string }
|
||||||
|
type ModelOption = { modelId: string; name: string }
|
||||||
|
|
||||||
|
const DEFAULT_VARIANT_VALUE = "default"
|
||||||
|
|
||||||
export namespace ACP {
|
export namespace ACP {
|
||||||
const log = Log.create({ service: "acp-agent" })
|
const log = Log.create({ service: "acp-agent" })
|
||||||
|
|
||||||
@@ -476,7 +482,7 @@ export namespace ACP {
|
|||||||
sessionId,
|
sessionId,
|
||||||
models: load.models,
|
models: load.models,
|
||||||
modes: load.modes,
|
modes: load.modes,
|
||||||
_meta: {},
|
_meta: load._meta,
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = MessageV2.fromError(e, {
|
const error = MessageV2.fromError(e, {
|
||||||
@@ -529,7 +535,7 @@ export namespace ACP {
|
|||||||
providerID: lastUser.model.providerID,
|
providerID: lastUser.model.providerID,
|
||||||
modelID: lastUser.model.modelID,
|
modelID: lastUser.model.modelID,
|
||||||
})
|
})
|
||||||
if (result.modes.availableModes.some((m) => m.id === lastUser.agent)) {
|
if (result.modes?.availableModes.some((m) => m.id === lastUser.agent)) {
|
||||||
result.modes.currentModeId = lastUser.agent
|
result.modes.currentModeId = lastUser.agent
|
||||||
this.sessionManager.setMode(sessionId, lastUser.agent)
|
this.sessionManager.setMode(sessionId, lastUser.agent)
|
||||||
}
|
}
|
||||||
@@ -956,27 +962,7 @@ export namespace ACP {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadSessionMode(params: LoadSessionRequest) {
|
private async loadAvailableModes(directory: string): Promise<ModeOption[]> {
|
||||||
const directory = params.cwd
|
|
||||||
const model = await defaultModel(this.config, directory)
|
|
||||||
const sessionId = params.sessionId
|
|
||||||
|
|
||||||
const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
|
|
||||||
const entries = providers.sort((a, b) => {
|
|
||||||
const nameA = a.name.toLowerCase()
|
|
||||||
const nameB = b.name.toLowerCase()
|
|
||||||
if (nameA < nameB) return -1
|
|
||||||
if (nameA > nameB) return 1
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
const availableModels = entries.flatMap((provider) => {
|
|
||||||
const models = Provider.sort(Object.values(provider.models))
|
|
||||||
return models.map((model) => ({
|
|
||||||
modelId: `${provider.id}/${model.id}`,
|
|
||||||
name: `${provider.name}/${model.name}`,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const agents = await this.config.sdk.app
|
const agents = await this.config.sdk.app
|
||||||
.agents(
|
.agents(
|
||||||
{
|
{
|
||||||
@@ -986,6 +972,56 @@ export namespace ACP {
|
|||||||
)
|
)
|
||||||
.then((resp) => resp.data!)
|
.then((resp) => resp.data!)
|
||||||
|
|
||||||
|
return agents
|
||||||
|
.filter((agent) => agent.mode !== "subagent" && !agent.hidden)
|
||||||
|
.map((agent) => ({
|
||||||
|
id: agent.name,
|
||||||
|
name: agent.name,
|
||||||
|
description: agent.description,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveModeState(
|
||||||
|
directory: string,
|
||||||
|
sessionId: string,
|
||||||
|
): Promise<{ availableModes: ModeOption[]; currentModeId?: string }> {
|
||||||
|
const availableModes = await this.loadAvailableModes(directory)
|
||||||
|
const currentModeId =
|
||||||
|
this.sessionManager.get(sessionId).modeId ||
|
||||||
|
(await (async () => {
|
||||||
|
if (!availableModes.length) return undefined
|
||||||
|
const defaultAgentName = await AgentModule.defaultAgent()
|
||||||
|
const resolvedModeId =
|
||||||
|
availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id
|
||||||
|
this.sessionManager.setMode(sessionId, resolvedModeId)
|
||||||
|
return resolvedModeId
|
||||||
|
})())
|
||||||
|
|
||||||
|
return { availableModes, currentModeId }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadSessionMode(params: LoadSessionRequest) {
|
||||||
|
const directory = params.cwd
|
||||||
|
const model = await defaultModel(this.config, directory)
|
||||||
|
const sessionId = params.sessionId
|
||||||
|
|
||||||
|
const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
|
||||||
|
const entries = sortProvidersByName(providers)
|
||||||
|
const availableVariants = modelVariantsFromProviders(entries, model)
|
||||||
|
const currentVariant = this.sessionManager.getVariant(sessionId)
|
||||||
|
if (currentVariant && !availableVariants.includes(currentVariant)) {
|
||||||
|
this.sessionManager.setVariant(sessionId, undefined)
|
||||||
|
}
|
||||||
|
const availableModels = buildAvailableModels(entries, { includeVariants: true })
|
||||||
|
const modeState = await this.resolveModeState(directory, sessionId)
|
||||||
|
const currentModeId = modeState.currentModeId
|
||||||
|
const modes = currentModeId
|
||||||
|
? {
|
||||||
|
availableModes: modeState.availableModes,
|
||||||
|
currentModeId,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
const commands = await this.config.sdk.command
|
const commands = await this.config.sdk.command
|
||||||
.list(
|
.list(
|
||||||
{
|
{
|
||||||
@@ -1006,20 +1042,6 @@ export namespace ACP {
|
|||||||
description: "compact the session",
|
description: "compact the session",
|
||||||
})
|
})
|
||||||
|
|
||||||
const availableModes = agents
|
|
||||||
.filter((agent) => agent.mode !== "subagent" && !agent.hidden)
|
|
||||||
.map((agent) => ({
|
|
||||||
id: agent.name,
|
|
||||||
name: agent.name,
|
|
||||||
description: agent.description,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const defaultAgentName = await AgentModule.defaultAgent()
|
|
||||||
const currentModeId = availableModes.find((m) => m.name === defaultAgentName)?.id ?? availableModes[0].id
|
|
||||||
|
|
||||||
// Persist the default mode so prompt() uses it immediately
|
|
||||||
this.sessionManager.setMode(sessionId, currentModeId)
|
|
||||||
|
|
||||||
const mcpServers: Record<string, Config.Mcp> = {}
|
const mcpServers: Record<string, Config.Mcp> = {}
|
||||||
for (const server of params.mcpServers) {
|
for (const server of params.mcpServers) {
|
||||||
if ("type" in server) {
|
if ("type" in server) {
|
||||||
@@ -1073,40 +1095,46 @@ export namespace ACP {
|
|||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
models: {
|
models: {
|
||||||
currentModelId: `${model.providerID}/${model.modelID}`,
|
currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, true),
|
||||||
availableModels,
|
availableModels,
|
||||||
},
|
},
|
||||||
modes: {
|
modes,
|
||||||
availableModes,
|
_meta: buildVariantMeta({
|
||||||
currentModeId,
|
model,
|
||||||
},
|
variant: this.sessionManager.getVariant(sessionId),
|
||||||
_meta: {},
|
availableVariants,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async unstable_setSessionModel(params: SetSessionModelRequest) {
|
async unstable_setSessionModel(params: SetSessionModelRequest) {
|
||||||
const session = this.sessionManager.get(params.sessionId)
|
const session = this.sessionManager.get(params.sessionId)
|
||||||
|
const providers = await this.sdk.config
|
||||||
|
.providers({ directory: session.cwd }, { throwOnError: true })
|
||||||
|
.then((x) => x.data!.providers)
|
||||||
|
|
||||||
const model = Provider.parseModel(params.modelId)
|
const selection = parseModelSelection(params.modelId, providers)
|
||||||
|
this.sessionManager.setModel(session.id, selection.model)
|
||||||
|
this.sessionManager.setVariant(session.id, selection.variant)
|
||||||
|
|
||||||
this.sessionManager.setModel(session.id, {
|
const entries = sortProvidersByName(providers)
|
||||||
providerID: model.providerID,
|
const availableVariants = modelVariantsFromProviders(entries, selection.model)
|
||||||
modelID: model.modelID,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_meta: {},
|
_meta: buildVariantMeta({
|
||||||
|
model: selection.model,
|
||||||
|
variant: selection.variant,
|
||||||
|
availableVariants,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
|
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
|
||||||
this.sessionManager.get(params.sessionId)
|
const session = this.sessionManager.get(params.sessionId)
|
||||||
await this.config.sdk.app
|
const availableModes = await this.loadAvailableModes(session.cwd)
|
||||||
.agents({}, { throwOnError: true })
|
if (!availableModes.some((mode) => mode.id === params.modeId)) {
|
||||||
.then((x) => x.data)
|
throw new Error(`Agent not found: ${params.modeId}`)
|
||||||
.then((agent) => {
|
}
|
||||||
if (!agent) throw new Error(`Agent not found: ${params.modeId}`)
|
|
||||||
})
|
|
||||||
this.sessionManager.setMode(params.sessionId, params.modeId)
|
this.sessionManager.setMode(params.sessionId, params.modeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1223,6 +1251,7 @@ export namespace ACP {
|
|||||||
providerID: model.providerID,
|
providerID: model.providerID,
|
||||||
modelID: model.modelID,
|
modelID: model.modelID,
|
||||||
},
|
},
|
||||||
|
variant: this.sessionManager.getVariant(sessionID),
|
||||||
parts,
|
parts,
|
||||||
agent,
|
agent,
|
||||||
directory,
|
directory,
|
||||||
@@ -1434,4 +1463,105 @@ export namespace ACP {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortProvidersByName<T extends { name: string }>(providers: T[]): T[] {
|
||||||
|
return [...providers].sort((a, b) => {
|
||||||
|
const nameA = a.name.toLowerCase()
|
||||||
|
const nameB = b.name.toLowerCase()
|
||||||
|
if (nameA < nameB) return -1
|
||||||
|
if (nameA > nameB) return 1
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function modelVariantsFromProviders(
|
||||||
|
providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
|
||||||
|
model: { providerID: string; modelID: string },
|
||||||
|
): string[] {
|
||||||
|
const provider = providers.find((entry) => entry.id === model.providerID)
|
||||||
|
if (!provider) return []
|
||||||
|
const modelInfo = provider.models[model.modelID]
|
||||||
|
if (!modelInfo?.variants) return []
|
||||||
|
return Object.keys(modelInfo.variants)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAvailableModels(
|
||||||
|
providers: Array<{ id: string; name: string; models: Record<string, any> }>,
|
||||||
|
options: { includeVariants?: boolean } = {},
|
||||||
|
): ModelOption[] {
|
||||||
|
const includeVariants = options.includeVariants ?? false
|
||||||
|
return providers.flatMap((provider) => {
|
||||||
|
const models = Provider.sort(Object.values(provider.models) as any)
|
||||||
|
return models.flatMap((model) => {
|
||||||
|
const base: ModelOption = {
|
||||||
|
modelId: `${provider.id}/${model.id}`,
|
||||||
|
name: `${provider.name}/${model.name}`,
|
||||||
|
}
|
||||||
|
if (!includeVariants || !model.variants) return [base]
|
||||||
|
const variants = Object.keys(model.variants).filter((variant) => variant !== DEFAULT_VARIANT_VALUE)
|
||||||
|
const variantOptions = variants.map((variant) => ({
|
||||||
|
modelId: `${provider.id}/${model.id}/${variant}`,
|
||||||
|
name: `${provider.name}/${model.name} (${variant})`,
|
||||||
|
}))
|
||||||
|
return [base, ...variantOptions]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatModelIdWithVariant(
|
||||||
|
model: { providerID: string; modelID: string },
|
||||||
|
variant: string | undefined,
|
||||||
|
availableVariants: string[],
|
||||||
|
includeVariant: boolean,
|
||||||
|
) {
|
||||||
|
const base = `${model.providerID}/${model.modelID}`
|
||||||
|
if (!includeVariant || !variant || !availableVariants.includes(variant)) return base
|
||||||
|
return `${base}/${variant}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVariantMeta(input: {
|
||||||
|
model: { providerID: string; modelID: string }
|
||||||
|
variant?: string
|
||||||
|
availableVariants: string[]
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
opencode: {
|
||||||
|
modelId: `${input.model.providerID}/${input.model.modelID}`,
|
||||||
|
variant: input.variant ?? null,
|
||||||
|
availableVariants: input.availableVariants,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseModelSelection(
|
||||||
|
modelId: string,
|
||||||
|
providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
|
||||||
|
): { model: { providerID: string; modelID: string }; variant?: string } {
|
||||||
|
const parsed = Provider.parseModel(modelId)
|
||||||
|
const provider = providers.find((p) => p.id === parsed.providerID)
|
||||||
|
if (!provider) {
|
||||||
|
return { model: parsed, variant: undefined }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if modelID exists directly
|
||||||
|
if (provider.models[parsed.modelID]) {
|
||||||
|
return { model: parsed, variant: undefined }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to extract variant from end of modelID (e.g., "claude-sonnet-4/high" -> model: "claude-sonnet-4", variant: "high")
|
||||||
|
const segments = parsed.modelID.split("/")
|
||||||
|
if (segments.length > 1) {
|
||||||
|
const candidateVariant = segments[segments.length - 1]
|
||||||
|
const baseModelId = segments.slice(0, -1).join("/")
|
||||||
|
const baseModelInfo = provider.models[baseModelId]
|
||||||
|
if (baseModelInfo?.variants && candidateVariant in baseModelInfo.variants) {
|
||||||
|
return {
|
||||||
|
model: { providerID: parsed.providerID, modelID: baseModelId },
|
||||||
|
variant: candidateVariant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { model: parsed, variant: undefined }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,18 @@ export class ACPSessionManager {
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVariant(sessionId: string) {
|
||||||
|
const session = this.get(sessionId)
|
||||||
|
return session.variant
|
||||||
|
}
|
||||||
|
|
||||||
|
setVariant(sessionId: string, variant?: string) {
|
||||||
|
const session = this.get(sessionId)
|
||||||
|
session.variant = variant
|
||||||
|
this.sessions.set(sessionId, session)
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
setMode(sessionId: string, modeId: string) {
|
setMode(sessionId: string, modeId: string) {
|
||||||
const session = this.get(sessionId)
|
const session = this.get(sessionId)
|
||||||
session.modeId = modeId
|
session.modeId = modeId
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface ACPSessionState {
|
|||||||
providerID: string
|
providerID: string
|
||||||
modelID: string
|
modelID: string
|
||||||
}
|
}
|
||||||
|
variant?: string
|
||||||
modeId?: string
|
modeId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
||||||
)
|
)
|
||||||
if (inFavorites) return false
|
if (inFavorites) return false
|
||||||
const inRecents = recentList.some(
|
const inRecents = recents.some(
|
||||||
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
||||||
)
|
)
|
||||||
if (inRecents) return false
|
if (inRecents) return false
|
||||||
|
|||||||
@@ -32,6 +32,21 @@ import { Event } from "../server/event"
|
|||||||
export namespace Config {
|
export namespace Config {
|
||||||
const log = Log.create({ service: "config" })
|
const log = Log.create({ service: "config" })
|
||||||
|
|
||||||
|
// Managed settings directory for enterprise deployments (highest priority, admin-controlled)
|
||||||
|
// These settings override all user and project settings
|
||||||
|
function getManagedConfigDir(): string {
|
||||||
|
switch (process.platform) {
|
||||||
|
case "darwin":
|
||||||
|
return "/Library/Application Support/opencode"
|
||||||
|
case "win32":
|
||||||
|
return path.join(process.env.ProgramData || "C:\\ProgramData", "opencode")
|
||||||
|
default:
|
||||||
|
return "/etc/opencode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR || getManagedConfigDir()
|
||||||
|
|
||||||
// Custom merge function that concatenates array fields instead of replacing them
|
// Custom merge function that concatenates array fields instead of replacing them
|
||||||
function mergeConfigConcatArrays(target: Info, source: Info): Info {
|
function mergeConfigConcatArrays(target: Info, source: Info): Info {
|
||||||
const merged = mergeDeep(target, source)
|
const merged = mergeDeep(target, source)
|
||||||
@@ -148,8 +163,18 @@ export namespace Config {
|
|||||||
result.plugin.push(...(await loadPlugin(dir)))
|
result.plugin.push(...(await loadPlugin(dir)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load managed config files last (highest priority) - enterprise admin-controlled
|
||||||
|
// Kept separate from directories array to avoid write operations when installing plugins
|
||||||
|
// which would fail on system directories requiring elevated permissions
|
||||||
|
// This way it only loads config file and not skills/plugins/commands
|
||||||
|
if (existsSync(managedConfigDir)) {
|
||||||
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||||
|
result = mergeConfigConcatArrays(result, await loadFile(path.join(managedConfigDir, file)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Migrate deprecated mode field to agent field
|
// Migrate deprecated mode field to agent field
|
||||||
for (const [name, mode] of Object.entries(result.mode)) {
|
for (const [name, mode] of Object.entries(result.mode ?? {})) {
|
||||||
result.agent = mergeDeep(result.agent ?? {}, {
|
result.agent = mergeDeep(result.agent ?? {}, {
|
||||||
[name]: {
|
[name]: {
|
||||||
...mode,
|
...mode,
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export namespace MCP {
|
|||||||
return client.callTool(
|
return client.callTool(
|
||||||
{
|
{
|
||||||
name: mcpTool.name,
|
name: mcpTool.name,
|
||||||
arguments: args as Record<string, unknown>,
|
arguments: (args || {}) as Record<string, unknown>,
|
||||||
},
|
},
|
||||||
CallToolResultSchema,
|
CallToolResultSchema,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import os from "os"
|
|
||||||
import { Installation } from "@/installation"
|
import { Installation } from "@/installation"
|
||||||
import { Provider } from "@/provider/provider"
|
import { Provider } from "@/provider/provider"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
@@ -9,7 +8,6 @@ import {
|
|||||||
type StreamTextResult,
|
type StreamTextResult,
|
||||||
type Tool,
|
type Tool,
|
||||||
type ToolSet,
|
type ToolSet,
|
||||||
extractReasoningMiddleware,
|
|
||||||
tool,
|
tool,
|
||||||
jsonSchema,
|
jsonSchema,
|
||||||
} from "ai"
|
} from "ai"
|
||||||
@@ -261,7 +259,6 @@ export namespace LLM {
|
|||||||
return args.params
|
return args.params
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extractReasoningMiddleware({ tagName: "think", startWithReasoning: false }),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
experimental_telemetry: {
|
experimental_telemetry: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect, describe, mock } from "bun:test"
|
import { test, expect, describe, mock, afterEach } from "bun:test"
|
||||||
import { Config } from "../../src/config/config"
|
import { Config } from "../../src/config/config"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { Auth } from "../../src/auth"
|
import { Auth } from "../../src/auth"
|
||||||
@@ -6,6 +6,23 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import { pathToFileURL } from "url"
|
import { pathToFileURL } from "url"
|
||||||
|
import { Global } from "../../src/global"
|
||||||
|
|
||||||
|
// Get managed config directory from environment (set in preload.ts)
|
||||||
|
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR!
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await fs.rm(managedConfigDir, { force: true, recursive: true }).catch(() => {})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function writeManagedSettings(settings: object, filename = "opencode.json") {
|
||||||
|
await fs.mkdir(managedConfigDir, { recursive: true })
|
||||||
|
await Bun.write(path.join(managedConfigDir, filename), JSON.stringify(settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeConfig(dir: string, config: object, name = "opencode.json") {
|
||||||
|
await Bun.write(path.join(dir, name), JSON.stringify(config))
|
||||||
|
}
|
||||||
|
|
||||||
test("loads config with defaults when no files exist", async () => {
|
test("loads config with defaults when no files exist", async () => {
|
||||||
await using tmp = await tmpdir()
|
await using tmp = await tmpdir()
|
||||||
@@ -21,14 +38,11 @@ test("loads config with defaults when no files exist", async () => {
|
|||||||
test("loads JSON config file", async () => {
|
test("loads JSON config file", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Bun.write(
|
await writeConfig(dir, {
|
||||||
path.join(dir, "opencode.json"),
|
|
||||||
JSON.stringify({
|
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
model: "test/model",
|
model: "test/model",
|
||||||
username: "testuser",
|
username: "testuser",
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
@@ -68,21 +82,19 @@ test("loads JSONC config file", async () => {
|
|||||||
test("merges multiple config files with correct precedence", async () => {
|
test("merges multiple config files with correct precedence", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Bun.write(
|
await writeConfig(
|
||||||
path.join(dir, "opencode.jsonc"),
|
dir,
|
||||||
JSON.stringify({
|
{
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
model: "base",
|
model: "base",
|
||||||
username: "base",
|
username: "base",
|
||||||
}),
|
},
|
||||||
|
"opencode.jsonc",
|
||||||
)
|
)
|
||||||
await Bun.write(
|
await writeConfig(dir, {
|
||||||
path.join(dir, "opencode.json"),
|
|
||||||
JSON.stringify({
|
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
model: "override",
|
model: "override",
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
@@ -102,13 +114,10 @@ test("handles environment variable substitution", async () => {
|
|||||||
try {
|
try {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Bun.write(
|
await writeConfig(dir, {
|
||||||
path.join(dir, "opencode.json"),
|
|
||||||
JSON.stringify({
|
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
theme: "{env:TEST_VAR}",
|
theme: "{env:TEST_VAR}",
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
@@ -169,13 +178,10 @@ test("handles file inclusion substitution", async () => {
|
|||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Bun.write(path.join(dir, "included.txt"), "test_theme")
|
await Bun.write(path.join(dir, "included.txt"), "test_theme")
|
||||||
await Bun.write(
|
await writeConfig(dir, {
|
||||||
path.join(dir, "opencode.json"),
|
|
||||||
JSON.stringify({
|
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
theme: "{file:included.txt}",
|
theme: "{file:included.txt}",
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
@@ -190,13 +196,10 @@ test("handles file inclusion substitution", async () => {
|
|||||||
test("validates config schema and throws on invalid fields", async () => {
|
test("validates config schema and throws on invalid fields", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Bun.write(
|
await writeConfig(dir, {
|
||||||
path.join(dir, "opencode.json"),
|
|
||||||
JSON.stringify({
|
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
invalid_field: "should cause error",
|
invalid_field: "should cause error",
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
@@ -225,9 +228,7 @@ test("throws error for invalid JSON", async () => {
|
|||||||
test("handles agent configuration", async () => {
|
test("handles agent configuration", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Bun.write(
|
await writeConfig(dir, {
|
||||||
path.join(dir, "opencode.json"),
|
|
||||||
JSON.stringify({
|
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
agent: {
|
agent: {
|
||||||
test_agent: {
|
test_agent: {
|
||||||
@@ -236,8 +237,7 @@ test("handles agent configuration", async () => {
|
|||||||
description: "test agent",
|
description: "test agent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
@@ -258,9 +258,7 @@ test("handles agent configuration", async () => {
|
|||||||
test("handles command configuration", async () => {
|
test("handles command configuration", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Bun.write(
|
await writeConfig(dir, {
|
||||||
path.join(dir, "opencode.json"),
|
|
||||||
JSON.stringify({
|
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
command: {
|
command: {
|
||||||
test_command: {
|
test_command: {
|
||||||
@@ -269,8 +267,7 @@ test("handles command configuration", async () => {
|
|||||||
agent: "test_agent",
|
agent: "test_agent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
@@ -894,6 +891,86 @@ test("migrates legacy write tool to edit permission", async () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Managed settings tests
|
||||||
|
// Note: preload.ts sets OPENCODE_TEST_MANAGED_CONFIG which Global.Path.managedConfig uses
|
||||||
|
|
||||||
|
test("managed settings override user settings", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await writeConfig(dir, {
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
model: "user/model",
|
||||||
|
share: "auto",
|
||||||
|
username: "testuser",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await writeManagedSettings({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
model: "managed/model",
|
||||||
|
share: "disabled",
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const config = await Config.get()
|
||||||
|
expect(config.model).toBe("managed/model")
|
||||||
|
expect(config.share).toBe("disabled")
|
||||||
|
expect(config.username).toBe("testuser")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("managed settings override project settings", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await writeConfig(dir, {
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
autoupdate: true,
|
||||||
|
disabled_providers: [],
|
||||||
|
theme: "dark",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await writeManagedSettings({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
autoupdate: false,
|
||||||
|
disabled_providers: ["openai"],
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const config = await Config.get()
|
||||||
|
expect(config.autoupdate).toBe(false)
|
||||||
|
expect(config.disabled_providers).toEqual(["openai"])
|
||||||
|
expect(config.theme).toBe("dark")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("missing managed settings file is not an error", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await writeConfig(dir, {
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
model: "user/model",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const config = await Config.get()
|
||||||
|
expect(config.model).toBe("user/model")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("migrates legacy edit tool to edit permission", async () => {
|
test("migrates legacy edit tool to edit permission", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ const testHome = path.join(dir, "home")
|
|||||||
await fs.mkdir(testHome, { recursive: true })
|
await fs.mkdir(testHome, { recursive: true })
|
||||||
process.env["OPENCODE_TEST_HOME"] = testHome
|
process.env["OPENCODE_TEST_HOME"] = testHome
|
||||||
|
|
||||||
|
// Set test managed config directory to isolate tests from system managed settings
|
||||||
|
const testManagedConfigDir = path.join(dir, "managed")
|
||||||
|
process.env["OPENCODE_TEST_MANAGED_CONFIG_DIR"] = testManagedConfigDir
|
||||||
|
|
||||||
process.env["XDG_DATA_HOME"] = path.join(dir, "share")
|
process.env["XDG_DATA_HOME"] = path.join(dir, "share")
|
||||||
process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
|
process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
|
||||||
process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
|
process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
|
||||||
|
|||||||
@@ -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": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -28,18 +28,33 @@ const Context = createContext<ReturnType<typeof init>>()
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
const [active, setActive] = createSignal<Active | undefined>()
|
const [active, setActive] = createSignal<Active | undefined>()
|
||||||
let closing = false
|
const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
|
||||||
|
const lock = { value: false }
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (timer.current === undefined) return
|
||||||
|
clearTimeout(timer.current)
|
||||||
|
timer.current = undefined
|
||||||
|
})
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
const current = active()
|
const current = active()
|
||||||
if (!current || closing) return
|
if (!current || lock.value) return
|
||||||
closing = true
|
lock.value = true
|
||||||
current.onClose?.()
|
current.onClose?.()
|
||||||
current.setClosing(true)
|
current.setClosing(true)
|
||||||
setTimeout(() => {
|
|
||||||
|
const id = current.id
|
||||||
|
if (timer.current !== undefined) {
|
||||||
|
clearTimeout(timer.current)
|
||||||
|
timer.current = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.current = setTimeout(() => {
|
||||||
|
timer.current = undefined
|
||||||
current.dispose()
|
current.dispose()
|
||||||
setActive(undefined)
|
if (active()?.id === id) setActive(undefined)
|
||||||
closing = false
|
lock.value = false
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +79,12 @@ function init() {
|
|||||||
current.dispose()
|
current.dispose()
|
||||||
setActive(undefined)
|
setActive(undefined)
|
||||||
}
|
}
|
||||||
closing = false
|
|
||||||
|
if (timer.current !== undefined) {
|
||||||
|
clearTimeout(timer.current)
|
||||||
|
timer.current = undefined
|
||||||
|
}
|
||||||
|
lock.value = false
|
||||||
|
|
||||||
const id = Math.random().toString(36).slice(2)
|
const id = Math.random().toString(36).slice(2)
|
||||||
let dispose: (() => void) | undefined
|
let dispose: (() => void) | undefined
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"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",
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ You can also access our models through the following API endpoints.
|
|||||||
| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
| GLM 4.7 Free | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||||
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||||
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||||
|
| Kimi K2.5 Free | kimi-k2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||||
| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||||
| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||||
| Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
| Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||||
@@ -120,7 +121,8 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
|
|||||||
| GLM 4.7 Free | Free | Free | Free | - |
|
| GLM 4.7 Free | Free | Free | Free | - |
|
||||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||||
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
| Kimi K2.5 Free | Free | Free | Free | - |
|
||||||
|
| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - |
|
||||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||||
@@ -153,8 +155,9 @@ Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don
|
|||||||
|
|
||||||
The free models:
|
The free models:
|
||||||
|
|
||||||
- GLM 4.7 is currently free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
|
- GLM 4.7 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
|
||||||
- MiniMax M2.1 is currently free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
|
- Kimi M2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
|
||||||
|
- MiniMax M2.1 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
|
||||||
- Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
|
- Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
|
||||||
|
|
||||||
<a href={email}>Contact us</a> if you have any questions.
|
<a href={email}>Contact us</a> if you have any questions.
|
||||||
@@ -184,9 +187,10 @@ charging you more than $20 if your balance goes below $5.
|
|||||||
|
|
||||||
All our models are hosted in the US. Our providers follow a zero-retention policy and do not use your data for model training, with the following exceptions:
|
All our models are hosted in the US. Our providers follow a zero-retention policy and do not use your data for model training, with the following exceptions:
|
||||||
|
|
||||||
- GLM 4.7: During its free period, collected data may be used to improve the model.
|
|
||||||
- MiniMax M2.1: During its free period, collected data may be used to improve the model.
|
|
||||||
- Big Pickle: During its free period, collected data may be used to improve the model.
|
- Big Pickle: During its free period, collected data may be used to improve the model.
|
||||||
|
- GLM 4.7 Free: During its free period, collected data may be used to improve the model.
|
||||||
|
- Kimi K2.5 Free: During its free period, collected data may be used to improve the model.
|
||||||
|
- MiniMax M2.1 Free: During its free period, collected data may be used to improve the model.
|
||||||
- OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
|
- OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
|
||||||
- Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
|
- Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
|
||||||
|
|
||||||
|
|||||||
Executable
+146
@@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
interface PR {
|
||||||
|
number: number
|
||||||
|
headRefName: string
|
||||||
|
headRefOid: string
|
||||||
|
createdAt: string
|
||||||
|
isDraft: boolean
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log("Fetching open contributor PRs...")
|
||||||
|
|
||||||
|
const prsResult =
|
||||||
|
await $`gh pr list --label contributor --state open --json number,headRefName,headRefOid,createdAt,isDraft,title --limit 100`.nothrow()
|
||||||
|
if (prsResult.exitCode !== 0) {
|
||||||
|
throw new Error(`Failed to fetch PRs: ${prsResult.stderr}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPRs: PR[] = JSON.parse(prsResult.stdout)
|
||||||
|
const prs = allPRs.filter((pr) => !pr.isDraft)
|
||||||
|
|
||||||
|
console.log(`Found ${prs.length} open non-draft contributor PRs`)
|
||||||
|
|
||||||
|
console.log("Fetching latest dev branch...")
|
||||||
|
const fetchDev = await $`git fetch origin dev`.nothrow()
|
||||||
|
if (fetchDev.exitCode !== 0) {
|
||||||
|
throw new Error(`Failed to fetch dev branch: ${fetchDev.stderr}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Checking out beta branch...")
|
||||||
|
const checkoutBeta = await $`git checkout -B beta origin/dev`.nothrow()
|
||||||
|
if (checkoutBeta.exitCode !== 0) {
|
||||||
|
throw new Error(`Failed to checkout beta branch: ${checkoutBeta.stderr}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const applied: number[] = []
|
||||||
|
const skipped: Array<{ number: number; reason: string }> = []
|
||||||
|
|
||||||
|
for (const pr of prs) {
|
||||||
|
console.log(`\nProcessing PR #${pr.number}: ${pr.title}`)
|
||||||
|
|
||||||
|
// Fetch the PR
|
||||||
|
const fetchPR = await $`git fetch origin pull/${pr.number}/head:pr-${pr.number}`.nothrow()
|
||||||
|
if (fetchPR.exitCode !== 0) {
|
||||||
|
console.log(` Failed to fetch PR #${pr.number}, skipping`)
|
||||||
|
skipped.push({ number: pr.number, reason: "Failed to fetch" })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to rebase onto current beta branch
|
||||||
|
console.log(` Attempting to rebase PR #${pr.number}...`)
|
||||||
|
const rebase = await $`git rebase beta pr-${pr.number}`.nothrow()
|
||||||
|
if (rebase.exitCode !== 0) {
|
||||||
|
console.log(` Rebase failed for PR #${pr.number} (has conflicts)`)
|
||||||
|
await $`git rebase --abort`.nothrow()
|
||||||
|
await $`git checkout beta`.nothrow()
|
||||||
|
skipped.push({ number: pr.number, reason: "Rebase failed (conflicts)" })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move rebased commits to pr-${pr.number} branch and checkout back to beta
|
||||||
|
await $`git checkout -B pr-${pr.number}`.nothrow()
|
||||||
|
await $`git checkout beta`.nothrow()
|
||||||
|
|
||||||
|
console.log(` Successfully rebased PR #${pr.number}`)
|
||||||
|
|
||||||
|
// Now squash merge the rebased PR
|
||||||
|
const merge = await $`git merge --squash pr-${pr.number}`.nothrow()
|
||||||
|
if (merge.exitCode !== 0) {
|
||||||
|
console.log(` Squash merge failed for PR #${pr.number}`)
|
||||||
|
console.log(` Error: ${merge.stderr}`)
|
||||||
|
await $`git reset --hard HEAD`.nothrow()
|
||||||
|
skipped.push({ number: pr.number, reason: `Squash merge failed: ${merge.stderr}` })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = await $`git add -A`.nothrow()
|
||||||
|
if (add.exitCode !== 0) {
|
||||||
|
console.log(` Failed to stage changes for PR #${pr.number}`)
|
||||||
|
await $`git reset --hard HEAD`.nothrow()
|
||||||
|
skipped.push({ number: pr.number, reason: "Failed to stage" })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await $`git status --porcelain`.nothrow()
|
||||||
|
if (status.exitCode !== 0 || !status.stdout.trim()) {
|
||||||
|
console.log(` No changes to commit for PR #${pr.number}, skipping`)
|
||||||
|
await $`git reset --hard HEAD`.nothrow()
|
||||||
|
skipped.push({ number: pr.number, reason: "No changes to commit" })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitMsg = `Apply PR #${pr.number}: ${pr.title}`
|
||||||
|
const commit = await Bun.spawn(["git", "commit", "-m", commitMsg], { stdout: "pipe", stderr: "pipe" })
|
||||||
|
const commitExit = await commit.exited
|
||||||
|
const commitStderr = await Bun.readableStreamToText(commit.stderr)
|
||||||
|
|
||||||
|
if (commitExit !== 0) {
|
||||||
|
console.log(` Failed to commit PR #${pr.number}`)
|
||||||
|
console.log(` Error: ${commitStderr}`)
|
||||||
|
await $`git reset --hard HEAD`.nothrow()
|
||||||
|
skipped.push({ number: pr.number, reason: `Commit failed: ${commitStderr}` })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` Successfully applied PR #${pr.number}`)
|
||||||
|
applied.push(pr.number)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\n--- Summary ---")
|
||||||
|
console.log(`Applied: ${applied.length} PRs`)
|
||||||
|
applied.forEach((num) => console.log(` - PR #${num}`))
|
||||||
|
console.log(`Skipped: ${skipped.length} PRs`)
|
||||||
|
skipped.forEach((x) => console.log(` - PR #${x.number}: ${x.reason}`))
|
||||||
|
|
||||||
|
console.log("\nForce pushing beta branch...")
|
||||||
|
const push = await $`git push origin beta --force`.nothrow()
|
||||||
|
if (push.exitCode !== 0) {
|
||||||
|
throw new Error(`Failed to push beta branch: ${push.stderr}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Successfully synced beta branch")
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Error:", err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
function $(strings: TemplateStringsArray, ...values: unknown[]) {
|
||||||
|
const cmd = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ""), "")
|
||||||
|
return {
|
||||||
|
async nothrow() {
|
||||||
|
const proc = Bun.spawn(cmd.split(" "), {
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
})
|
||||||
|
const exitCode = await proc.exited
|
||||||
|
const stdout = await new Response(proc.stdout).text()
|
||||||
|
const stderr = await new Response(proc.stderr).text()
|
||||||
|
return { exitCode, stdout, stderr }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-11
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import { createOpencode } from "@opencode-ai/sdk"
|
import { createOpencode } from "@opencode-ai/sdk/v2"
|
||||||
import { parseArgs } from "util"
|
import { parseArgs } from "util"
|
||||||
|
|
||||||
export const team = [
|
export const team = [
|
||||||
@@ -18,13 +18,29 @@ export const team = [
|
|||||||
"R44VC0RP",
|
"R44VC0RP",
|
||||||
]
|
]
|
||||||
|
|
||||||
export async function getLatestRelease() {
|
type Release = {
|
||||||
return fetch("https://api.github.com/repos/anomalyco/opencode/releases/latest")
|
tag_name: string
|
||||||
.then((res) => {
|
draft: boolean
|
||||||
|
prerelease: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLatestRelease(skip?: string) {
|
||||||
|
const data = await fetch("https://api.github.com/repos/anomalyco/opencode/releases?per_page=100").then((res) => {
|
||||||
if (!res.ok) throw new Error(res.statusText)
|
if (!res.ok) throw new Error(res.statusText)
|
||||||
return res.json()
|
return res.json()
|
||||||
})
|
})
|
||||||
.then((data: any) => data.tag_name.replace(/^v/, ""))
|
|
||||||
|
const releases = data as Release[]
|
||||||
|
const target = skip?.replace(/^v/, "")
|
||||||
|
|
||||||
|
for (const release of releases) {
|
||||||
|
if (release.draft) continue
|
||||||
|
const tag = release.tag_name.replace(/^v/, "")
|
||||||
|
if (target && tag === target) continue
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("No releases found")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Commit = {
|
type Commit = {
|
||||||
@@ -137,9 +153,9 @@ async function summarizeCommit(opencode: Awaited<ReturnType<typeof createOpencod
|
|||||||
console.log("summarizing commit:", message)
|
console.log("summarizing commit:", message)
|
||||||
const session = await opencode.client.session.create()
|
const session = await opencode.client.session.create()
|
||||||
const result = await opencode.client.session
|
const result = await opencode.client.session
|
||||||
.prompt({
|
.prompt(
|
||||||
path: { id: session.data!.id },
|
{
|
||||||
body: {
|
sessionID: session.data!.id,
|
||||||
model: { providerID: "opencode", modelID: "claude-sonnet-4-5" },
|
model: { providerID: "opencode", modelID: "claude-sonnet-4-5" },
|
||||||
tools: {
|
tools: {
|
||||||
"*": false,
|
"*": false,
|
||||||
@@ -153,8 +169,10 @@ Commit: ${message}`,
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
signal: AbortSignal.timeout(120_000),
|
signal: AbortSignal.timeout(120_000),
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.then((x) => x.data?.parts?.find((y) => y.type === "text")?.text ?? message)
|
.then((x) => x.data?.parts?.find((y) => y.type === "text")?.text ?? message)
|
||||||
return result.trim()
|
return result.trim()
|
||||||
}
|
}
|
||||||
@@ -222,7 +240,7 @@ export async function buildNotes(from: string, to: string) {
|
|||||||
|
|
||||||
console.log("generating changelog since " + from)
|
console.log("generating changelog since " + from)
|
||||||
|
|
||||||
const opencode = await createOpencode({ port: 5044 })
|
const opencode = await createOpencode({ port: 0 })
|
||||||
const notes: string[] = []
|
const notes: string[] = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -242,8 +260,9 @@ export async function buildNotes(from: string, to: string) {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
opencode.server.close()
|
await opencode.server.close()
|
||||||
}
|
}
|
||||||
|
console.log("changelog generation complete")
|
||||||
|
|
||||||
const contributors = await getContributors(from, to)
|
const contributors = await getContributors(from, to)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import { Script } from "@opencode-ai/script"
|
import { Script } from "@opencode-ai/script"
|
||||||
import { buildNotes, getLatestRelease } from "./changelog"
|
|
||||||
|
|
||||||
const highlightsTemplate = `
|
const highlightsTemplate = `
|
||||||
<!--
|
<!--
|
||||||
@@ -58,16 +57,12 @@ await $`bun install`
|
|||||||
await import(`../packages/sdk/js/script/build.ts`)
|
await import(`../packages/sdk/js/script/build.ts`)
|
||||||
|
|
||||||
if (Script.release) {
|
if (Script.release) {
|
||||||
const previous = await getLatestRelease()
|
|
||||||
const notes = await buildNotes(previous, "HEAD")
|
|
||||||
// notes.unshift(highlightsTemplate)
|
|
||||||
await $`git commit -am "release: v${Script.version}"`
|
await $`git commit -am "release: v${Script.version}"`
|
||||||
await $`git tag v${Script.version}`
|
await $`git tag v${Script.version}`
|
||||||
await $`git fetch origin`
|
await $`git fetch origin`
|
||||||
await $`git cherry-pick HEAD..origin/dev`.nothrow()
|
await $`git cherry-pick HEAD..origin/dev`.nothrow()
|
||||||
await $`git push origin HEAD --tags --no-verify --force-with-lease`
|
await $`git push origin HEAD --tags --no-verify --force-with-lease`
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5_000))
|
await new Promise((resolve) => setTimeout(resolve, 5_000))
|
||||||
await $`gh release edit v${Script.version} --draft=false --title "v${Script.version}" --notes ${notes.join("\n") || "No notable changes"}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("\n=== cli ===\n")
|
console.log("\n=== cli ===\n")
|
||||||
|
|||||||
+12
-3
@@ -2,11 +2,18 @@
|
|||||||
|
|
||||||
import { Script } from "@opencode-ai/script"
|
import { Script } from "@opencode-ai/script"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
import { buildNotes, getLatestRelease } from "./changelog"
|
||||||
|
|
||||||
let output = [`version=${Script.version}`]
|
const output = [`version=${Script.version}`]
|
||||||
|
|
||||||
if (!Script.preview) {
|
if (!Script.preview || true) {
|
||||||
await $`gh release create v${Script.version} -d --title "v${Script.version}" ${Script.preview ? "--prerelease" : ""}`
|
const previous = await getLatestRelease()
|
||||||
|
const notes = await buildNotes(previous, "HEAD")
|
||||||
|
const body = notes.join("\n") || "No notable changes"
|
||||||
|
const dir = process.env.RUNNER_TEMP ?? "/tmp"
|
||||||
|
const file = `${dir}/opencode-release-notes.txt`
|
||||||
|
await Bun.write(file, body)
|
||||||
|
await $`gh release create v${Script.version} -d --title "v${Script.version}" --notes-file ${file} --prerelease=${Script.preview}`
|
||||||
const release = await $`gh release view v${Script.version} --json id,tagName`.json()
|
const release = await $`gh release view v${Script.version} --json id,tagName`.json()
|
||||||
output.push(`release=${release.id}`)
|
output.push(`release=${release.id}`)
|
||||||
output.push(`tag=${release.tagName}`)
|
output.push(`tag=${release.tagName}`)
|
||||||
@@ -15,3 +22,5 @@ if (!Script.preview) {
|
|||||||
if (process.env.GITHUB_OUTPUT) {
|
if (process.env.GITHUB_OUTPUT) {
|
||||||
await Bun.write(process.env.GITHUB_OUTPUT, output.join("\n"))
|
await Bun.write(process.env.GITHUB_OUTPUT, output.join("\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"displayName": "opencode",
|
"displayName": "opencode",
|
||||||
"description": "opencode for VS Code",
|
"description": "opencode for VS Code",
|
||||||
"version": "0.0.0-ci-202601291718",
|
"version": "1.1.45",
|
||||||
"publisher": "sst-dev",
|
"publisher": "sst-dev",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
Reference in New Issue
Block a user