mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-17 12:56:41 +02:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9cef22a53 | |||
| b5d7d3dec1 | |||
| 182630e0d7 | |||
| c81506b28d | |||
| 6c40bfe043 | |||
| 9caaae6a18 | |||
| ad6a5e6157 | |||
| 7dd8ea58c2 | |||
| 3b261e0125 | |||
| 426791f68a | |||
| c7cade2494 | |||
| 8f6c8844d7 | |||
| da6e0e60c0 | |||
| d89b567b47 | |||
| 34eb03f5b8 | |||
| 2f6d15a51e | |||
| 8ffea80980 | |||
| c87d61b561 | |||
| 35c12e2053 | |||
| 33d8bfc937 | |||
| f2343a6794 | |||
| bab000eeb5 | |||
| 8e674ae053 | |||
| 6a4f4009d5 | |||
| 5e79b95927 | |||
| a7a2bbb497 | |||
| 6e93d14bdb | |||
| f29f284b3e | |||
| b1b8f6cf71 | |||
| 4c3336bbe7 | |||
| 354ac0b493 | |||
| 1d159c6858 | |||
| d70639b256 | |||
| e4a92f0084 | |||
| fdf5a70a27 | |||
| f71da42520 | |||
| f6bdeb9e3a | |||
| 2400354bab | |||
| db348c46cc | |||
| 49567fe61a | |||
| e5b3f796e4 | |||
| a9700c8773 | |||
| 26cf5e003e | |||
| 742cf10dee | |||
| 7664453f94 | |||
| 460672aa93 | |||
| b4e4fd9807 | |||
| 34bdfd0937 | |||
| 84591ca8ad | |||
| fd4d0c5c0b | |||
| 9f5db46911 | |||
| 755ddbb223 | |||
| 701d470d01 | |||
| 1d9058d26b | |||
| 39e2a5f595 | |||
| f862ab6722 | |||
| 129d4f0b1b | |||
| 3a1e50d1f8 | |||
| e2fb690d8e | |||
| 0a7f58a811 | |||
| dae0168ed8 | |||
| edfe2e4f1c | |||
| 1bc1ea8b47 | |||
| dacbbe3184 | |||
| 89285d8f5f | |||
| 2e853911c3 | |||
| 695fdecf23 | |||
| 054d22791d | |||
| 4a57cc69d8 | |||
| 7e0c8db029 | |||
| ba4cc3bf86 | |||
| b19a424c85 | |||
| 1689281c35 | |||
| cdbb59fae8 | |||
| 4eb311e98f | |||
| 80eac96258 | |||
| 4bad6f9f1b | |||
| d7db57e8e1 | |||
| 943fbf39a3 | |||
| d8a34c2fcc | |||
| 5720ed1f44 | |||
| bb20a359e4 | |||
| 0d472a49a0 | |||
| 203581e82f | |||
| 677631916c | |||
| 1aa1e8c904 |
@@ -41,18 +41,6 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup SSH for AUR
|
||||
if: inputs.bump || inputs.version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pacman-package-manager
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
||||
|
||||
- name: Install OpenCode
|
||||
if: inputs.bump || inputs.version
|
||||
run: bun i -g opencode-ai@1.0.169
|
||||
@@ -75,9 +63,15 @@ jobs:
|
||||
node-version: "24"
|
||||
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 }}
|
||||
|
||||
- name: Publish
|
||||
id: publish
|
||||
run: ./script/publish.ts
|
||||
run: ./script/publish-start.ts
|
||||
env:
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_VERSION: ${{ inputs.version }}
|
||||
@@ -86,8 +80,9 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: false
|
||||
outputs:
|
||||
releaseId: ${{ steps.publish.outputs.releaseId }}
|
||||
tagName: ${{ steps.publish.outputs.tagName }}
|
||||
release: ${{ steps.publish.outputs.release }}
|
||||
tag: ${{ steps.publish.outputs.tag }}
|
||||
version: ${{ steps.publish.outputs.version }}
|
||||
|
||||
publish-tauri:
|
||||
needs: publish
|
||||
@@ -109,7 +104,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.publish.outputs.tagName }}
|
||||
ref: ${{ needs.publish.outputs.tag }}
|
||||
|
||||
- uses: apple-actions/import-codesign-certs@v2
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
@@ -152,20 +147,18 @@ jobs:
|
||||
shared-key: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Prepare
|
||||
if: inputs.bump || inputs.version
|
||||
run: |
|
||||
cd packages/tauri
|
||||
bun ./scripts/prepare.ts
|
||||
env:
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_VERSION: ${{ inputs.version }}
|
||||
OPENCODE_CHANNEL: latest
|
||||
OPENCODE_VERSION: ${{ needs.publish.outputs.version }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
RUST_TARGET: ${{ matrix.settings.target }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
OPENCODE_RELEASE_TAG: ${{ needs.publish.outputs.tagName }}
|
||||
|
||||
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
|
||||
- name: Install tauri-cli from portable appimage branch
|
||||
@@ -195,8 +188,8 @@ jobs:
|
||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }} --config src-tauri/tauri.prod.conf.json
|
||||
updaterJsonPreferNsis: true
|
||||
releaseId: ${{ needs.publish.outputs.releaseId }}
|
||||
tagName: ${{ needs.publish.outputs.tagName }}
|
||||
releaseId: ${{ needs.publish.outputs.release }}
|
||||
tagName: ${{ needs.publish.outputs.tag }}
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
releaseDraft: true
|
||||
|
||||
@@ -204,14 +197,29 @@ jobs:
|
||||
needs:
|
||||
- publish
|
||||
- publish-tauri
|
||||
if: needs.publish.outputs.tagName
|
||||
if: needs.publish.outputs.tag
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.publish.outputs.tagName }}
|
||||
ref: ${{ needs.publish.outputs.tag }}
|
||||
|
||||
- run: gh release edit ${{ needs.publish.outputs.tagName }} --draft=false
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup SSH for AUR
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pacman-package-manager
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
||||
|
||||
- run: ./script/publish-complete.ts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
OPENCODE_VERSION: ${{ needs.publish.outputs.version }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
|
||||
@@ -67,6 +67,8 @@ jobs:
|
||||
When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simpliest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts)
|
||||
|
||||
Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
|
||||
If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors.
|
||||
Generally, write a comment instead of writing suggested change if you can help it.
|
||||
|
||||
Command MUST be like this.
|
||||
\`\`\`
|
||||
|
||||
@@ -63,8 +63,6 @@ TUI issues potentially caused by our underlying TUI library:
|
||||
|
||||
**Do not** add for general TUI bugs.
|
||||
|
||||
---
|
||||
|
||||
When assigning to people here are the following rules:
|
||||
|
||||
adamdotdev:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/// <reference path="../env.d.ts" />
|
||||
import { Octokit } from "@octokit/rest"
|
||||
// import { Octokit } from "@octokit/rest"
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import DESCRIPTION from "./github-triage.txt"
|
||||
|
||||
@@ -9,6 +9,22 @@ function getIssueNumber(): number {
|
||||
return issue
|
||||
}
|
||||
|
||||
async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
||||
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||
...options,
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export default tool({
|
||||
description: DESCRIPTION,
|
||||
args: {
|
||||
@@ -23,7 +39,7 @@ export default tool({
|
||||
},
|
||||
async execute(args) {
|
||||
const issue = getIssueNumber()
|
||||
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
|
||||
// const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
|
||||
const owner = "sst"
|
||||
const repo = "opencode"
|
||||
|
||||
@@ -41,22 +57,30 @@ export default tool({
|
||||
throw new Error("Only opentui issues should be assigned to kommander")
|
||||
}
|
||||
|
||||
await octokit.rest.issues.addAssignees({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue,
|
||||
assignees: [args.assignee],
|
||||
// await octokit.rest.issues.addAssignees({
|
||||
// owner,
|
||||
// repo,
|
||||
// issue_number: issue,
|
||||
// assignees: [args.assignee],
|
||||
// })
|
||||
await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ assignees: [args.assignee] }),
|
||||
})
|
||||
results.push(`Assigned @${args.assignee} to issue #${issue}`)
|
||||
|
||||
const labels: string[] = args.labels.map((label) => (label === "desktop" ? "web" : label))
|
||||
|
||||
if (labels.length > 0) {
|
||||
await octokit.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue,
|
||||
labels,
|
||||
// await octokit.rest.issues.addLabels({
|
||||
// owner,
|
||||
// repo,
|
||||
// issue_number: issue,
|
||||
// labels,
|
||||
// })
|
||||
await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/labels`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ labels }),
|
||||
})
|
||||
results.push(`Added labels: ${args.labels.join(", ")}`)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ scoop bucket add extras; scoop install extras/opencode # Windows
|
||||
choco install opencode # Windows
|
||||
brew install opencode # macOS and Linux
|
||||
paru -S opencode-bin # Arch Linux
|
||||
mise use -g ubi:sst/opencode # Any OS
|
||||
mise use -g github:sst/opencode # Any OS
|
||||
nix run nixpkgs#opencode # or github:sst/opencode for latest dev branch
|
||||
```
|
||||
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai">
|
||||
<picture>
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">開源的 AI Coding Agent。</p>
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
---
|
||||
|
||||
### 安裝
|
||||
|
||||
```bash
|
||||
# 直接安裝 (YOLO)
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
# 套件管理員
|
||||
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
|
||||
scoop bucket add extras; scoop install extras/opencode # Windows
|
||||
choco install opencode # Windows
|
||||
brew install opencode # macOS 與 Linux
|
||||
paru -S opencode-bin # Arch Linux
|
||||
mise use -g github:sst/opencode # 任何作業系統
|
||||
nix run nixpkgs#opencode # 或使用 github:sst/opencode 以取得最新開發分支
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> 安裝前請先移除 0.1.x 以前的舊版本。
|
||||
|
||||
### 桌面應用程式 (BETA)
|
||||
|
||||
OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (releases page)](https://github.com/sst/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下載。
|
||||
|
||||
| 平台 | 下載連結 |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, 或 AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew Cask)
|
||||
brew install --cask opencode-desktop
|
||||
```
|
||||
|
||||
#### 安裝目錄
|
||||
|
||||
安裝腳本會依據以下優先順序決定安裝路徑:
|
||||
|
||||
1. `$OPENCODE_INSTALL_DIR` - 自定義安裝目錄
|
||||
2. `$XDG_BIN_DIR` - 符合 XDG 基礎目錄規範的路徑
|
||||
3. `$HOME/bin` - 標準使用者執行檔目錄 (若存在或可建立)
|
||||
4. `$HOME/.opencode/bin` - 預設備用路徑
|
||||
|
||||
```bash
|
||||
# 範例
|
||||
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
|
||||
### Agents
|
||||
|
||||
OpenCode 內建了兩種 Agent,您可以使用 `Tab` 鍵快速切換。
|
||||
|
||||
- **build** - 預設模式,具備完整權限的 Agent,適用於開發工作。
|
||||
- **plan** - 唯讀模式,適用於程式碼分析與探索。
|
||||
- 預設禁止修改檔案。
|
||||
- 執行 bash 指令前會詢問權限。
|
||||
- 非常適合用來探索陌生的程式碼庫或規劃變更。
|
||||
|
||||
此外,OpenCode 還包含一個 **general** 子 Agent,用於處理複雜搜尋與多步驟任務。此 Agent 供系統內部使用,亦可透過在訊息中輸入 `@general` 來呼叫。
|
||||
|
||||
了解更多關於 [Agents](https://opencode.ai/docs/agents) 的資訊。
|
||||
|
||||
### 線上文件
|
||||
|
||||
關於如何設定 OpenCode 的詳細資訊,請參閱我們的 [**官方文件**](https://opencode.ai/docs)。
|
||||
|
||||
### 參與貢獻
|
||||
|
||||
如果您有興趣參與 OpenCode 的開發,請在提交 Pull Request 前先閱讀我們的 [貢獻指南 (Contributing Docs)](./CONTRIBUTING.md)。
|
||||
|
||||
### 基於 OpenCode 進行開發
|
||||
|
||||
如果您正在開發與 OpenCode 相關的專案,並在名稱中使用了 "opencode"(例如 "opencode-dashboard" 或 "opencode-mobile"),請在您的 README 中加入聲明,說明該專案並非由 OpenCode 團隊開發,且與我們沒有任何隸屬關係。
|
||||
|
||||
### 常見問題 (FAQ)
|
||||
|
||||
#### 這跟 Claude Code 有什麼不同?
|
||||
|
||||
在功能面上與 Claude Code 非常相似。以下是關鍵差異:
|
||||
|
||||
- 100% 開源。
|
||||
- 不綁定特定的服務提供商。雖然我們推薦使用透過 [OpenCode Zen](https://opencode.ai/zen) 提供的模型,但 OpenCode 也可搭配 Claude, OpenAI, Google 甚至本地模型使用。隨著模型不斷演進,彼此間的差距會縮小且價格會下降,因此具備「不限廠商 (provider-agnostic)」的特性至關重要。
|
||||
- 內建 LSP (語言伺服器協定) 支援。
|
||||
- 專注於終端機介面 (TUI)。OpenCode 由 Neovim 愛好者與 [terminal.shop](https://terminal.shop) 的創作者打造;我們將不斷挑戰終端機介面的極限。
|
||||
- 客戶端/伺服器架構 (Client/Server Architecture)。這讓 OpenCode 能夠在您的電腦上運行的同時,由行動裝置進行遠端操控。這意味著 TUI 前端只是眾多可能的客戶端之一。
|
||||
|
||||
#### 另一個同名的 Repo 是什麼?
|
||||
|
||||
另一個名稱相近的儲存庫與本專案無關。您可以點此[閱讀背後的故事](https://x.com/thdxr/status/1933561254481666466)。
|
||||
|
||||
---
|
||||
|
||||
**加入我們的社群** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
|
||||
@@ -175,3 +175,4 @@
|
||||
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
|
||||
| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |
|
||||
| 2025-12-19 | 1,203,485 (+24,827) | 1,129,698 (+16,280) | 2,333,183 (+41,107) |
|
||||
| 2025-12-20 | 1,223,000 (+19,515) | 1,146,258 (+16,560) | 2,369,258 (+36,075) |
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"name": "opencode",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.933.0",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"typescript": "catalog:",
|
||||
@@ -21,7 +20,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -49,7 +48,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -76,7 +75,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -100,7 +99,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -124,7 +123,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -172,7 +171,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -201,7 +200,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -217,7 +216,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -247,8 +246,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||
"@opentui/core": "0.1.61",
|
||||
"@opentui/solid": "0.1.61",
|
||||
"@opentui/core": "0.1.62",
|
||||
"@opentui/solid": "0.1.62",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -309,7 +308,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -329,7 +328,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.88.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -340,7 +339,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -353,7 +352,7 @@
|
||||
},
|
||||
"packages/tauri": {
|
||||
"name": "@opencode-ai/tauri",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@opencode-ai/desktop": "workspace:*",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
@@ -380,7 +379,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -415,7 +414,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -426,7 +425,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -480,7 +479,7 @@
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@57aeb22",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
@@ -1102,11 +1101,11 @@
|
||||
|
||||
"@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
|
||||
|
||||
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@14.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw=="],
|
||||
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="],
|
||||
|
||||
"@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@17.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw=="],
|
||||
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],
|
||||
|
||||
"@octokit/plugin-retry": ["@octokit/plugin-retry@3.0.9", "", { "dependencies": { "@octokit/types": "^6.0.3", "bottleneck": "^2.15.3" } }, "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ=="],
|
||||
|
||||
@@ -1114,7 +1113,7 @@
|
||||
|
||||
"@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
||||
|
||||
"@octokit/rest": ["@octokit/rest@22.0.1", "", { "dependencies": { "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0" } }, "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw=="],
|
||||
"@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
|
||||
|
||||
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
|
||||
|
||||
@@ -1162,21 +1161,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.61", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.61", "@opentui/core-darwin-x64": "0.1.61", "@opentui/core-linux-arm64": "0.1.61", "@opentui/core-linux-x64": "0.1.61", "@opentui/core-win32-arm64": "0.1.61", "@opentui/core-win32-x64": "0.1.61", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-WrVbdki0tnsgmWCB3Iix6n8eXGXUheTqr/tcnBN7gLA/TqT9udcX+DW3/qRdgtTNJS1sVBVeuwSTYU3eqDSUJQ=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.62", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.62", "@opentui/core-darwin-x64": "0.1.62", "@opentui/core-linux-arm64": "0.1.62", "@opentui/core-linux-x64": "0.1.62", "@opentui/core-win32-arm64": "0.1.62", "@opentui/core-win32-x64": "0.1.62", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-T9wsXaS4rFoZF2loaEFqAeuGj5DV3pJzrk18z1um3UfUS2NNH4jyDh5rDdHPb2/YrvO1lU9hd0VoAS/7zUAq/w=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.61", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zX7EK8PwBJFwsZ2tDnScLFD0GbBfHE7sqpzGDXP2luMnBZJ0OOO95a4Hzu9dQWqxEr4RgfGDT8uIRhgimKNQEg=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.62", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IohPhCkD/DbZEH4M5ft1/o1pI6Vvw2pdxdyoouW/TO1g21W5G8usaWTSRDXO+16BT115Nfb9/DT69H5pzAc2Eg=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.61", "", { "os": "darwin", "cpu": "x64" }, "sha512-xfvl8EnyN0XwlYpyTskVhHOpbMdgt++ntcuTh7M7IEFYQGzJux19NBwJl17mOxB1McG+KTa7kNx5/zu0VB9eVQ=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.62", "", { "os": "darwin", "cpu": "x64" }, "sha512-BqbjQl2sLYrJ1Pq1b3H1I2CFedRiMz0QtZX08IMbyZ5kok+J0A8eQS5tmlbfqoS/VH0de9XiEbuHjG09/nSj1A=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.61", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ghg7j4H6bz7CLxhgDcWx3Ann3AblDIjKFUu4vFrVysuiwfmDHwdKm8awLj8tnmC/0y8juG4ODUQbR0BXBIkE+Q=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.62", "", { "os": "linux", "cpu": "arm64" }, "sha512-P5FleF+W8O4uGubqBvV8DB1AK0+fJhJS8HvfmTZQ2DhSSJJH9Af/WXqitD7ILQY9ltlaUP7l38BC5cVdxnWzCQ=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.61", "", { "os": "linux", "cpu": "x64" }, "sha512-Xs9czMEOuHtnX4tigC4fNb1MU7+Gaohbk+k4teraulIgYZf19nRHIKNvXissDjOfqvOGygCkxMQIG0zeUFsPEA=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.62", "", { "os": "linux", "cpu": "x64" }, "sha512-l9ab5tgOGcdf8k3NU4TzK/3C8UC0+QuMxgLA/j60BhB1e9bwJleFeYJc+wLIktTUu9QwqCsU4YcuGHL+C2lCzA=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.61", "", { "os": "win32", "cpu": "arm64" }, "sha512-2CYAEPqArJqE36LkSRAs0csRzWwVJY99S/7EuY7abBm58BIL6RUw5kSw1r75oDo4I3W6v6WwW0u8B5Ik98m0Kg=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.62", "", { "os": "win32", "cpu": "arm64" }, "sha512-U1zsOpQl3EGhs8BwoehKAwwVONe+XOXRnXTxMhXw8huF0WWXDWOUL5psjBvfSWPm1rLmagxkQsH84jTSWA/vLA=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.61", "", { "os": "win32", "cpu": "x64" }, "sha512-c0OK5YwcKH51Qj6wPmwTZP3X8LHA0I0dKz4fO4mOh4f+OqgU9WOG4hpbf7lv0bVlHoTvgP4zDUsjmtIVA8l6Lg=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.62", "", { "os": "win32", "cpu": "x64" }, "sha512-JgLZXSaE4q7gUIQb9x6fLWFF3BYlMod2VBhOT1qGBdeveZxsM6ZAno/g+CL9IDUydWfLFadOIBjdYFDVWV2Z2w=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.61", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.61", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-CiZHduIoeABoS0ev+eGeHA/LiRl/SpdL6io4jrwiwFi/rToKtc7YgJ8MWxIgeHScHUbpQnIr1v7jzsGI3DAYvw=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.62", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.62", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-3th4oZROv3cZvcoL+IwNCEMTKLZaT1BBWKVHxH29wUD0/EPxtowLQCibnjKDqqdTuEUuFA/QtSX52WqQEioR8g=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -1598,7 +1597,7 @@
|
||||
|
||||
"@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="],
|
||||
|
||||
"@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@57aeb22", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite-plugin-solid": "^2.11.9" }, "peerDependencies": { "vite": "^7" } }],
|
||||
"@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@dfb2020", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite": "7.1.10", "vite-plugin-solid": "^2.11.9", "vitest": "^4.0.10" } }],
|
||||
|
||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.12", "", {}, "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA=="],
|
||||
|
||||
@@ -1714,10 +1713,14 @@
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
|
||||
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
@@ -1820,6 +1823,20 @@
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@4.0.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@4.0.16", "", { "dependencies": { "@vitest/spy": "4.0.16", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@4.0.16", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@4.0.16", "", { "dependencies": { "@vitest/utils": "4.0.16", "pathe": "^2.0.3" } }, "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@4.0.16", "", {}, "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
|
||||
|
||||
"@webgpu/types": ["@webgpu/types@0.1.66", "", {}, "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA=="],
|
||||
|
||||
"@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
|
||||
@@ -1886,6 +1903,8 @@
|
||||
|
||||
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
|
||||
|
||||
"astro": ["astro@5.7.13", "", { "dependencies": { "@astrojs/compiler": "^2.11.0", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.1", "@astrojs/telemetry": "3.2.1", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w=="],
|
||||
@@ -2032,6 +2051,8 @@
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
"chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
|
||||
|
||||
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
@@ -2336,6 +2357,8 @@
|
||||
|
||||
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
|
||||
@@ -3064,6 +3087,8 @@
|
||||
|
||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
||||
|
||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||
|
||||
"ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="],
|
||||
|
||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
@@ -3438,6 +3463,8 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
||||
@@ -3500,6 +3527,8 @@
|
||||
|
||||
"sst-win32-x86": ["sst-win32-x86@3.17.23", "", { "os": "win32", "cpu": "none" }, "sha512-DIp3s54IpNAfdYjSRt6McvkbEPQDMxUu6RUeRAd2C+FcTJgTloon/ghAPQBaDgu2VoVgymjcJARO/XyfKcCLOQ=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
|
||||
|
||||
"stage-js": ["stage-js@1.0.0-alpha.17", "", {}, "sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw=="],
|
||||
@@ -3508,6 +3537,8 @@
|
||||
|
||||
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||
|
||||
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
|
||||
@@ -3586,12 +3617,16 @@
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
|
||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
|
||||
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
|
||||
|
||||
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
|
||||
|
||||
"titleize": ["titleize@4.0.0", "", {}, "sha512-ZgUJ1K83rhdu7uh7EHAC2BgY5DzoX8V5rTvoWI4vFysggi6YjLe5gUXABPWAU7VkvGP7P/0YiWq+dcPeYDsf1g=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
@@ -3754,6 +3789,8 @@
|
||||
|
||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||
|
||||
"vitest": ["vitest@4.0.16", "", { "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", "@vitest/pretty-format": "4.0.16", "@vitest/runner": "4.0.16", "@vitest/snapshot": "4.0.16", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.16", "@vitest/browser-preview": "4.0.16", "@vitest/browser-webdriverio": "4.0.16", "@vitest/ui": "4.0.16", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q=="],
|
||||
|
||||
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="],
|
||||
|
||||
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
|
||||
@@ -4052,9 +4089,9 @@
|
||||
|
||||
"@octokit/oauth-methods/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
|
||||
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
|
||||
|
||||
"@octokit/plugin-retry/@octokit/types": ["@octokit/types@6.41.0", "", { "dependencies": { "@octokit/openapi-types": "^12.11.0" } }, "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg=="],
|
||||
|
||||
@@ -4066,8 +4103,6 @@
|
||||
|
||||
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
||||
|
||||
"@opencode-ai/function/@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
|
||||
|
||||
"@opencode-ai/tauri/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="],
|
||||
@@ -4122,6 +4157,8 @@
|
||||
|
||||
"@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
||||
|
||||
"@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
@@ -4258,8 +4295,6 @@
|
||||
|
||||
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bpYruxVLhrTbVH6CCq48zMJNeHu6FmHtEedl9FXckEgcIEAi036idFhJlcRwC1jNCwlacbzb8dPD7OAH1EKJaQ=="],
|
||||
|
||||
"opencode/@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
||||
|
||||
"opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
|
||||
@@ -4350,6 +4385,12 @@
|
||||
|
||||
"vite-plugin-icons-spritesheet/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
|
||||
"vitest/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
|
||||
|
||||
"vitest/why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
@@ -4610,9 +4651,9 @@
|
||||
|
||||
"@octokit/oauth-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
|
||||
"@octokit/plugin-retry/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@12.11.0", "", {}, "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="],
|
||||
|
||||
@@ -4620,10 +4661,6 @@
|
||||
|
||||
"@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@opencode-ai/function/@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="],
|
||||
|
||||
"@opencode-ai/function/@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.4.2", "", { "dependencies": { "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ=="],
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg=="],
|
||||
@@ -4838,10 +4875,6 @@
|
||||
|
||||
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
|
||||
|
||||
"opencode/@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="],
|
||||
|
||||
"opencode/@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||
@@ -4878,6 +4911,8 @@
|
||||
|
||||
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"vitest/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -4980,10 +5015,6 @@
|
||||
|
||||
"@modelcontextprotocol/sdk/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"@opencode-ai/function/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
|
||||
|
||||
"@opencode-ai/function/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||
@@ -5004,10 +5035,6 @@
|
||||
|
||||
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"opencode/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
|
||||
|
||||
"opencode/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
|
||||
@@ -5046,22 +5073,64 @@
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
||||
|
||||
"vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"@opencode-ai/function/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
|
||||
"@opencode-ai/function/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
|
||||
|
||||
"opencode/@octokit/rest/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
|
||||
"opencode/@octokit/rest/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
@@ -9,6 +9,10 @@ inputs:
|
||||
description: "Model to use"
|
||||
required: true
|
||||
|
||||
agent:
|
||||
description: "Agent to use. Must be a primary agent. Falls back to default_agent from config or 'build' if not found."
|
||||
required: false
|
||||
|
||||
share:
|
||||
description: "Share the opencode session (defaults to true for public repos)"
|
||||
required: false
|
||||
@@ -62,6 +66,7 @@ runs:
|
||||
run: opencode github run
|
||||
env:
|
||||
MODEL: ${{ inputs.model }}
|
||||
AGENT: ${{ inputs.agent }}
|
||||
SHARE: ${{ inputs.share }}
|
||||
PROMPT: ${{ inputs.prompt }}
|
||||
USE_GITHUB_TOKEN: ${{ inputs.use_github_token }}
|
||||
|
||||
+27
-1
@@ -318,6 +318,10 @@ function useEnvRunUrl() {
|
||||
return `/${repo.owner}/${repo.repo}/actions/runs/${runId}`
|
||||
}
|
||||
|
||||
function useEnvAgent() {
|
||||
return process.env["AGENT"] || undefined
|
||||
}
|
||||
|
||||
function useEnvShare() {
|
||||
const value = process.env["SHARE"]
|
||||
if (!value) return undefined
|
||||
@@ -578,16 +582,38 @@ async function summarize(response: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveAgent(): Promise<string | undefined> {
|
||||
const envAgent = useEnvAgent()
|
||||
if (!envAgent) return undefined
|
||||
|
||||
// Validate the agent exists and is a primary agent
|
||||
const agents = await client.agent.list<true>()
|
||||
const agent = agents.data?.find((a) => a.name === envAgent)
|
||||
|
||||
if (!agent) {
|
||||
console.warn(`agent "${envAgent}" not found. Falling back to default agent`)
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (agent.mode === "subagent") {
|
||||
console.warn(`agent "${envAgent}" is a subagent, not a primary agent. Falling back to default agent`)
|
||||
return undefined
|
||||
}
|
||||
|
||||
return envAgent
|
||||
}
|
||||
|
||||
async function chat(text: string, files: PromptFiles = []) {
|
||||
console.log("Sending message to opencode...")
|
||||
const { providerID, modelID } = useEnvModel()
|
||||
const agent = await resolveAgent()
|
||||
|
||||
const chat = await client.session.chat<true>({
|
||||
path: session,
|
||||
body: {
|
||||
providerID,
|
||||
modelID,
|
||||
agent: "build",
|
||||
agent,
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"nodeModules": "sha256-oT1WPPR1sHBhQcJaFL+mod5l3+V8O3uPKJUdrcfTst0="
|
||||
"nodeModules": "sha256-cpXmqJQJeFj3eED/aOb4YLUdkZFV//7u4f0STBxzUhk="
|
||||
}
|
||||
|
||||
+1
-2
@@ -50,7 +50,7 @@
|
||||
"vite": "7.1.4",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@57aeb22",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
"solid-js": "1.9.10",
|
||||
"vite-plugin-solid": "2.11.10"
|
||||
}
|
||||
@@ -64,7 +64,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.933.0",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"typescript": "catalog:"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -202,6 +202,14 @@ export function IconZai(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function IconMiniMax(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconGemini(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 50 50" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "./index.css"
|
||||
import { Title, Meta, Link } from "@solidjs/meta"
|
||||
import { HttpHeader } from "@solidjs/start"
|
||||
//import { HttpHeader } from "@solidjs/start"
|
||||
import video from "../asset/lander/opencode-min.mp4"
|
||||
import videoPoster from "../asset/lander/opencode-poster.png"
|
||||
import { IconCopy, IconCheck } from "../component/icon"
|
||||
@@ -42,7 +42,7 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<main data-page="opencode">
|
||||
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
|
||||
{/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
|
||||
<Title>OpenCode | The open source AI coding agent</Title>
|
||||
<Link rel="canonical" href={config.baseUrl} />
|
||||
<Meta property="og:image" content="/social-share.png" />
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
IconAlibaba,
|
||||
IconAnthropic,
|
||||
IconGemini,
|
||||
IconMiniMax,
|
||||
IconMoonshotAI,
|
||||
IconOpenAI,
|
||||
IconStealth,
|
||||
@@ -23,6 +24,7 @@ const getModelLab = (modelId: string) => {
|
||||
if (modelId.startsWith("kimi")) return "Moonshot AI"
|
||||
if (modelId.startsWith("glm")) return "Z.ai"
|
||||
if (modelId.startsWith("qwen")) return "Alibaba"
|
||||
if (modelId.startsWith("minimax")) return "MiniMax"
|
||||
if (modelId.startsWith("grok")) return "xAI"
|
||||
return "Stealth"
|
||||
}
|
||||
@@ -35,7 +37,7 @@ const getModelsInfo = query(async (workspaceID: string) => {
|
||||
.filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
|
||||
.filter(([id, _model]) => !id.startsWith("alpha-"))
|
||||
.sort(([idA, modelA], [idB, modelB]) => {
|
||||
const priority = ["big-pickle", "grok", "claude", "gpt", "gemini"]
|
||||
const priority = ["big-pickle", "minimax", "grok", "claude", "gpt", "gemini"]
|
||||
const getPriority = (id: string) => {
|
||||
const index = priority.findIndex((p) => id.startsWith(p))
|
||||
return index === -1 ? Infinity : index
|
||||
@@ -129,6 +131,8 @@ export function ModelSection() {
|
||||
return <IconAlibaba width={16} height={16} />
|
||||
case "xAI":
|
||||
return <IconXai width={16} height={16} />
|
||||
case "MiniMax":
|
||||
return <IconMiniMax width={16} height={16} />
|
||||
default:
|
||||
return <IconStealth width={16} height={16} />
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./index.css"
|
||||
import { createAsync, query, redirect } from "@solidjs/router"
|
||||
import { Title, Meta, Link } from "@solidjs/meta"
|
||||
import { HttpHeader } from "@solidjs/start"
|
||||
//import { HttpHeader } from "@solidjs/start"
|
||||
import zenLogoLight from "../../asset/zen-ornate-light.svg"
|
||||
import { config } from "~/config"
|
||||
import zenLogoDark from "../../asset/zen-ornate-dark.svg"
|
||||
@@ -30,7 +30,7 @@ export default function Home() {
|
||||
const loggedin = createAsync(() => checkLoggedIn())
|
||||
return (
|
||||
<main data-page="zen">
|
||||
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
|
||||
{/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
|
||||
<Title>OpenCode Zen | A curated set of reliable optimized models for coding agents</Title>
|
||||
<Link rel="canonical" href={`${config.baseUrl}/zen`} />
|
||||
<Meta property="og:image" content="/social-share-zen.png" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -6,8 +6,22 @@ import { usePlatform } from "./platform"
|
||||
export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({
|
||||
name: "GlobalSDK",
|
||||
init: (props: { url: string }) => {
|
||||
const platform = usePlatform()
|
||||
const eventSdk = createOpencodeClient({
|
||||
baseUrl: props.url,
|
||||
// signal: AbortSignal.timeout(1000 * 60 * 10),
|
||||
})
|
||||
const emitter = createGlobalEmitter<{
|
||||
[key: string]: Event
|
||||
}>()
|
||||
|
||||
eventSdk.global.event().then(async (events) => {
|
||||
for await (const event of events.stream) {
|
||||
// console.log("event", event)
|
||||
emitter.emit(event.directory ?? "global", event.payload)
|
||||
}
|
||||
})
|
||||
|
||||
const platform = usePlatform()
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: props.url,
|
||||
signal: AbortSignal.timeout(1000 * 60 * 10),
|
||||
@@ -15,17 +29,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
||||
throwOnError: true,
|
||||
})
|
||||
|
||||
const emitter = createGlobalEmitter<{
|
||||
[key: string]: Event
|
||||
}>()
|
||||
|
||||
sdk.global.event().then(async (events) => {
|
||||
for await (const event of events.stream) {
|
||||
// console.log("event", event)
|
||||
emitter.emit(event.directory ?? "global", event.payload)
|
||||
}
|
||||
})
|
||||
|
||||
return { url: props.url, client: sdk, event: emitter }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
import { retry } from "@opencode-ai/util/retry"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
import { ErrorPage, type InitError } from "../pages/error"
|
||||
import { createContext, useContext, onMount, type ParentProps, Switch, Match } from "solid-js"
|
||||
@@ -145,7 +146,7 @@ function createGlobalSync() {
|
||||
changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)),
|
||||
node: () => sdk.file.list({ path: "/" }).then((x) => setStore("node", x.data!)),
|
||||
}
|
||||
await Promise.all(Object.values(load).map((p) => p().catch((e) => setGlobalStore("error", e))))
|
||||
await Promise.all(Object.values(load).map((p) => retry(p).catch((e) => setGlobalStore("error", e))))
|
||||
.then(() => setStore("ready", true))
|
||||
.catch((e) => setGlobalStore("error", e))
|
||||
}
|
||||
@@ -295,21 +296,29 @@ function createGlobalSync() {
|
||||
|
||||
async function bootstrap() {
|
||||
return Promise.all([
|
||||
globalSDK.client.path.get().then((x) => {
|
||||
setGlobalStore("path", x.data!)
|
||||
}),
|
||||
globalSDK.client.project.list().then(async (x) => {
|
||||
setGlobalStore(
|
||||
"project",
|
||||
x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)),
|
||||
)
|
||||
}),
|
||||
globalSDK.client.provider.list().then((x) => {
|
||||
setGlobalStore("provider", x.data ?? {})
|
||||
}),
|
||||
globalSDK.client.provider.auth().then((x) => {
|
||||
setGlobalStore("provider_auth", x.data ?? {})
|
||||
}),
|
||||
retry(() =>
|
||||
globalSDK.client.path.get().then((x) => {
|
||||
setGlobalStore("path", x.data!)
|
||||
}),
|
||||
),
|
||||
retry(() =>
|
||||
globalSDK.client.project.list().then(async (x) => {
|
||||
setGlobalStore(
|
||||
"project",
|
||||
x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)),
|
||||
)
|
||||
}),
|
||||
),
|
||||
retry(() =>
|
||||
globalSDK.client.provider.list().then((x) => {
|
||||
setGlobalStore("provider", x.data ?? {})
|
||||
}),
|
||||
),
|
||||
retry(() =>
|
||||
globalSDK.client.provider.auth().then((x) => {
|
||||
setGlobalStore("provider_auth", x.data ?? {})
|
||||
}),
|
||||
),
|
||||
])
|
||||
.then(() => setGlobalStore("ready", true))
|
||||
.catch((e) => setGlobalStore("error", e))
|
||||
|
||||
@@ -27,6 +27,8 @@ type SessionTabs = {
|
||||
all: string[]
|
||||
}
|
||||
|
||||
export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
|
||||
|
||||
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
|
||||
name: "Layout",
|
||||
init: () => {
|
||||
@@ -44,8 +46,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
opened: false,
|
||||
height: 280,
|
||||
},
|
||||
review: {
|
||||
state: "pane" as "pane" | "tab",
|
||||
session: {
|
||||
width: 600,
|
||||
},
|
||||
sessionTabs: {} as Record<string, SessionTabs>,
|
||||
}),
|
||||
@@ -61,21 +63,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
|
||||
function enrich(project: { worktree: string; expanded: boolean }) {
|
||||
const metadata = globalSync.data.project.find((x) => x.worktree === project.worktree)
|
||||
if (!metadata) return []
|
||||
return [
|
||||
{
|
||||
...project,
|
||||
...metadata,
|
||||
...(metadata ?? {}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function colorize(project: Project & { expanded: boolean }) {
|
||||
function colorize(project: LocalProject) {
|
||||
if (project.icon?.color) return project
|
||||
const color = pickAvailableColor()
|
||||
usedColors.add(color)
|
||||
project.icon = { ...project.icon, color }
|
||||
globalSdk.client.project.update({ projectID: project.id, icon: { color } })
|
||||
if (project.id) {
|
||||
globalSdk.client.project.update({ projectID: project.id, icon: { color } })
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
||||
@@ -95,7 +98,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
projects: {
|
||||
list,
|
||||
open(directory: string) {
|
||||
if (store.projects.find((x) => x.worktree === directory)) return
|
||||
if (store.projects.find((x) => x.worktree === directory)) {
|
||||
return
|
||||
}
|
||||
globalSync.project.loadSessions(directory)
|
||||
setStore("projects", (x) => [{ worktree: directory, expanded: true }, ...x])
|
||||
},
|
||||
@@ -151,13 +156,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
setStore("terminal", "height", height)
|
||||
},
|
||||
},
|
||||
review: {
|
||||
state: createMemo(() => store.review?.state ?? "closed"),
|
||||
pane() {
|
||||
setStore("review", "state", "pane")
|
||||
},
|
||||
tab() {
|
||||
setStore("review", "state", "tab")
|
||||
session: {
|
||||
width: createMemo(() => store.session?.width ?? 600),
|
||||
resize(width: number) {
|
||||
if (!store.session) {
|
||||
setStore("session", { width })
|
||||
} else {
|
||||
setStore("session", "width", width)
|
||||
}
|
||||
},
|
||||
},
|
||||
tabs(sessionKey: string) {
|
||||
@@ -181,14 +187,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
}
|
||||
},
|
||||
async open(tab: string) {
|
||||
if (tab === "chat") {
|
||||
if (!store.sessionTabs[sessionKey]) {
|
||||
setStore("sessionTabs", sessionKey, { all: [], active: undefined })
|
||||
} else {
|
||||
setStore("sessionTabs", sessionKey, "active", undefined)
|
||||
}
|
||||
return
|
||||
}
|
||||
const current = store.sessionTabs[sessionKey] ?? { all: [] }
|
||||
if (tab !== "review") {
|
||||
if (!current.all.includes(tab)) {
|
||||
|
||||
@@ -337,6 +337,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
const load = async (path: string) => {
|
||||
const relativePath = relative(path)
|
||||
await sdk.client.file.read({ path: relativePath }).then((x) => {
|
||||
if (!store.node[relativePath]) return
|
||||
setStore(
|
||||
"node",
|
||||
relativePath,
|
||||
@@ -425,7 +426,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
init,
|
||||
expand(path: string) {
|
||||
setStore("node", path, "expanded", true)
|
||||
if (store.node[path].loaded) return
|
||||
if (store.node[path]?.loaded) return
|
||||
setStore("node", path, "loaded", true)
|
||||
list(path)
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { produce } from "solid-js/store"
|
||||
import { createMemo } from "solid-js"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
import { retry } from "@opencode-ai/util/retry"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
import { useSDK } from "./sdk"
|
||||
@@ -61,10 +62,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
},
|
||||
async sync(sessionID: string, _isRetry = false) {
|
||||
const [session, messages, todo, diff] = await Promise.all([
|
||||
sdk.client.session.get({ sessionID }, { throwOnError: true }),
|
||||
sdk.client.session.messages({ sessionID, limit: 100 }),
|
||||
sdk.client.session.todo({ sessionID }),
|
||||
sdk.client.session.diff({ sessionID }),
|
||||
retry(() => sdk.client.session.get({ sessionID })),
|
||||
retry(() => sdk.client.session.messages({ sessionID, limit: 100 })),
|
||||
retry(() => sdk.client.session.todo({ sessionID })),
|
||||
retry(() => sdk.client.session.diff({ sessionID })),
|
||||
])
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import {
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
For,
|
||||
Match,
|
||||
onMount,
|
||||
ParentProps,
|
||||
Show,
|
||||
Switch,
|
||||
type JSX,
|
||||
} from "solid-js"
|
||||
import { createEffect, createMemo, For, Match, onMount, ParentProps, Show, Switch, type JSX } from "solid-js"
|
||||
import { DateTime } from "luxon"
|
||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||
import { useLayout, getAvatarColors } from "@/context/layout"
|
||||
import { useLayout, getAvatarColors, LocalProject } from "@/context/layout"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
|
||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||
@@ -26,7 +15,7 @@ import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Session, Project } from "@opencode-ai/sdk/v2/client"
|
||||
import { Session } from "@opencode-ai/sdk/v2/client"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import {
|
||||
@@ -348,7 +337,7 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
|
||||
const ProjectAvatar = (props: {
|
||||
project: Project
|
||||
project: LocalProject
|
||||
class?: string
|
||||
expandable?: boolean
|
||||
notify?: boolean
|
||||
@@ -391,7 +380,7 @@ export default function Layout(props: ParentProps) {
|
||||
)
|
||||
}
|
||||
|
||||
const ProjectVisual = (props: { project: Project & { expanded: boolean }; class?: string }): JSX.Element => {
|
||||
const ProjectVisual = (props: { project: LocalProject; class?: string }): JSX.Element => {
|
||||
const name = createMemo(() => getFilename(props.project.worktree))
|
||||
const current = createMemo(() => base64Decode(params.dir ?? ""))
|
||||
return (
|
||||
@@ -427,7 +416,7 @@ export default function Layout(props: ParentProps) {
|
||||
const SessionItem = (props: {
|
||||
session: Session
|
||||
slug: string
|
||||
project: Project
|
||||
project: LocalProject
|
||||
depth?: number
|
||||
childrenMap: Map<string, Session[]>
|
||||
}): JSX.Element => {
|
||||
@@ -517,7 +506,7 @@ export default function Layout(props: ParentProps) {
|
||||
)
|
||||
}
|
||||
|
||||
const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => {
|
||||
const SortableProject = (props: { project: LocalProject }): JSX.Element => {
|
||||
const sortable = createSortable(props.project.worktree)
|
||||
const slug = createMemo(() => base64Encode(props.project.worktree))
|
||||
const name = createMemo(() => getFilename(props.project.worktree))
|
||||
@@ -542,13 +531,21 @@ export default function Layout(props: ParentProps) {
|
||||
setProjectStore("limit", (limit) => limit + 5)
|
||||
await globalSync.project.loadSessions(props.project.worktree)
|
||||
}
|
||||
const [expanded, setExpanded] = createSignal(true)
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (open) layout.projects.expand(props.project.worktree)
|
||||
else layout.projects.collapse(props.project.worktree)
|
||||
}
|
||||
return (
|
||||
// @ts-ignore
|
||||
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
||||
<Switch>
|
||||
<Match when={layout.sidebar.opened()}>
|
||||
<Collapsible variant="ghost" defaultOpen class="gap-2 shrink-0" onOpenChange={setExpanded}>
|
||||
<Collapsible
|
||||
variant="ghost"
|
||||
open={props.project.expanded}
|
||||
class="gap-2 shrink-0"
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<Button
|
||||
as={"div"}
|
||||
variant="ghost"
|
||||
@@ -559,7 +556,7 @@ export default function Layout(props: ParentProps) {
|
||||
project={props.project}
|
||||
class="group-hover/session:hidden"
|
||||
expandable
|
||||
notify={!expanded()}
|
||||
notify={!props.project.expanded}
|
||||
/>
|
||||
<span class="truncate text-14-medium text-text-strong">{name()}</span>
|
||||
</Collapsible.Trigger>
|
||||
|
||||
@@ -22,7 +22,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
||||
@@ -50,7 +49,7 @@ import { DialogSelectFile } from "@/components/dialog-select-file"
|
||||
import { DialogSelectModel } from "@/components/dialog-select-model"
|
||||
import { useCommand } from "@/context/command"
|
||||
import { useNavigate, useParams } from "@solidjs/router"
|
||||
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { usePrompt } from "@/context/prompt"
|
||||
import { extractPromptFromParts } from "@/utils/prompt"
|
||||
@@ -118,27 +117,8 @@ export default function Page() {
|
||||
setActiveMessage(msgs[targetIndex])
|
||||
}
|
||||
|
||||
const last = createMemo(
|
||||
() => messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage,
|
||||
)
|
||||
const model = createMemo(() =>
|
||||
last() ? sync.data.provider.all.find((x) => x.id === last().providerID)?.models[last().modelID] : undefined,
|
||||
)
|
||||
const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
|
||||
|
||||
const tokens = createMemo(() => {
|
||||
if (!last()) return
|
||||
const t = last().tokens
|
||||
return t.input + t.output + t.reasoning + t.cache.read + t.cache.write
|
||||
})
|
||||
|
||||
const context = createMemo(() => {
|
||||
const total = tokens()
|
||||
const limit = model()?.limit.context
|
||||
if (!total || !limit) return 0
|
||||
return Math.round((total / limit) * 100)
|
||||
})
|
||||
|
||||
const [store, setStore] = createStore({
|
||||
clickTimer: undefined as number | undefined,
|
||||
activeDraggable: undefined as string | undefined,
|
||||
@@ -551,273 +531,218 @@ export default function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
const wide = createMemo(() => layout.review.state() === "tab" || !diffs().length)
|
||||
const showTabs = createMemo(() => diffs().length > 0 || tabs().all().length > 0)
|
||||
|
||||
return (
|
||||
<div class="relative bg-background-base size-full overflow-x-hidden flex flex-col">
|
||||
<div class="min-h-0 grow w-full">
|
||||
<DragDropProvider
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
collisionDetector={closestCenter}
|
||||
<div class="min-h-0 grow w-full flex">
|
||||
{/* Session pane - always visible */}
|
||||
<div
|
||||
class="@container relative shrink-0 py-3 flex flex-col gap-6 min-h-0 h-full bg-background-stronger"
|
||||
style={{ width: showTabs() ? `${layout.session.width()}px` : "100%" }}
|
||||
>
|
||||
<DragDropSensors />
|
||||
<ConstrainDragYAxis />
|
||||
<Tabs value={tabs().active() ?? "chat"} onChange={tabs().open}>
|
||||
<div class="sticky top-0 shrink-0 flex">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="chat">
|
||||
<div class="flex gap-x-[17px] items-center">
|
||||
<div>Session</div>
|
||||
<Tooltip
|
||||
value={`${new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
}).format(tokens() ?? 0)} Tokens`}
|
||||
class="flex items-center gap-1.5"
|
||||
>
|
||||
<ProgressCircle percentage={context() ?? 0} />
|
||||
<div class="text-14-regular text-text-weak text-left w-7">{context() ?? 0}%</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tabs.Trigger>
|
||||
<Show when={layout.review.state() === "tab" && diffs().length}>
|
||||
<Tabs.Trigger
|
||||
value="review"
|
||||
closeButton={
|
||||
<Tooltip value="Close tab" placement="bottom">
|
||||
<IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Show when={diffs()}>
|
||||
<DiffChanges changes={diffs()} variant="bars" />
|
||||
</Show>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div>Review</div>
|
||||
<Show when={info()?.summary?.files}>
|
||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||
{info()?.summary?.files ?? 0}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Trigger>
|
||||
</Show>
|
||||
<SortableProvider ids={tabs().all() ?? []}>
|
||||
<For each={tabs().all() ?? []}>
|
||||
{(tab) => <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={tabs().close} />}
|
||||
</For>
|
||||
</SortableProvider>
|
||||
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
|
||||
<Tooltip value="Open file" class="flex items-center">
|
||||
<IconButton
|
||||
icon="plus-small"
|
||||
variant="ghost"
|
||||
iconSize="large"
|
||||
onClick={() => dialog.show(() => <DialogSelectFile />)}
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<Switch>
|
||||
<Match when={params.id}>
|
||||
<div class="flex items-start justify-start h-full min-h-0">
|
||||
<SessionMessageRail
|
||||
messages={visibleUserMessages()}
|
||||
current={activeMessage()}
|
||||
onMessageSelect={setActiveMessage}
|
||||
wide={!showTabs()}
|
||||
/>
|
||||
<Show when={activeMessage()}>
|
||||
<SessionTurn
|
||||
sessionID={params.id!}
|
||||
messageID={activeMessage()!.id}
|
||||
stepsExpanded={store.stepsExpanded}
|
||||
onStepsExpandedToggle={() => setStore("stepsExpanded", (x) => !x)}
|
||||
onUserInteracted={() => setStore("userInteracted", true)}
|
||||
classes={{
|
||||
root: "pb-20 flex-1 min-w-0",
|
||||
content: "pb-20",
|
||||
container:
|
||||
"w-full " +
|
||||
(!showTabs()
|
||||
? "max-w-200 mx-auto px-6"
|
||||
: visibleUserMessages().length > 1
|
||||
? "pr-6 pl-18"
|
||||
: "px-6"),
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</div>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
<Tabs.Content
|
||||
value="chat"
|
||||
class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden contain-strict"
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"w-full flex-1 min-h-0": true,
|
||||
grid: layout.review.state() === "tab",
|
||||
flex: layout.review.state() === "pane",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
|
||||
"max-w-146 mx-auto": !wide(),
|
||||
}}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={params.id}>
|
||||
<div class="flex items-start justify-start h-full min-h-0">
|
||||
<SessionMessageRail
|
||||
messages={visibleUserMessages()}
|
||||
current={activeMessage()}
|
||||
onMessageSelect={setActiveMessage}
|
||||
wide={wide()}
|
||||
/>
|
||||
<Show when={activeMessage()}>
|
||||
<SessionTurn
|
||||
sessionID={params.id!}
|
||||
messageID={activeMessage()!.id}
|
||||
stepsExpanded={store.stepsExpanded}
|
||||
onStepsExpandedToggle={() => setStore("stepsExpanded", (x) => !x)}
|
||||
onUserInteracted={() => setStore("userInteracted", true)}
|
||||
classes={{
|
||||
root: "pb-20 flex-1 min-w-0",
|
||||
content: "pb-20",
|
||||
container:
|
||||
"w-full " +
|
||||
(wide()
|
||||
? "max-w-200 mx-auto px-6"
|
||||
: visibleUserMessages().length > 1
|
||||
? "pr-6 pl-18"
|
||||
: "px-6"),
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6">
|
||||
<div class="text-20-medium text-text-weaker">New session</div>
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="folder" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
{getDirectory(sync.data.path.directory)}
|
||||
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={sync.project}>
|
||||
{(project) => (
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="pencil-line" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
Last modified
|
||||
<span class="text-text-strong">
|
||||
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6">
|
||||
<div class="text-20-medium text-text-weaker">New session</div>
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="folder" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
{getDirectory(sync.data.path.directory)}
|
||||
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
|
||||
<div
|
||||
classList={{
|
||||
"w-full px-6": true,
|
||||
"max-w-200": !showTabs(),
|
||||
}}
|
||||
>
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={showTabs()}>
|
||||
<ResizeHandle
|
||||
direction="horizontal"
|
||||
size={layout.session.width()}
|
||||
min={450}
|
||||
max={window.innerWidth * 0.45}
|
||||
onResize={layout.session.resize}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
{/* Tabs pane - visible when there are diffs or file tabs */}
|
||||
<Show when={showTabs()}>
|
||||
<div class="relative flex-1 min-w-0 h-full border-l border-border-weak-base">
|
||||
<DragDropProvider
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
collisionDetector={closestCenter}
|
||||
>
|
||||
<DragDropSensors />
|
||||
<ConstrainDragYAxis />
|
||||
<Tabs value={tabs().active() ?? "review"} onChange={tabs().open}>
|
||||
<div class="sticky top-0 shrink-0 flex">
|
||||
<Tabs.List>
|
||||
<Show when={diffs().length}>
|
||||
<Tabs.Trigger value="review">
|
||||
<div class="flex items-center gap-3">
|
||||
<Show when={diffs()}>
|
||||
<DiffChanges changes={diffs()} variant="bars" />
|
||||
</Show>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div>Review</div>
|
||||
<Show when={info()?.summary?.files}>
|
||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||
{info()?.summary?.files ?? 0}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={sync.project}>
|
||||
{(project) => (
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="pencil-line" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
Last modified
|
||||
<span class="text-text-strong">
|
||||
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
<div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
|
||||
<div class="w-full max-w-200 px-6">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
</Tabs.Trigger>
|
||||
</Show>
|
||||
<SortableProvider ids={tabs().all() ?? []}>
|
||||
<For each={tabs().all() ?? []}>
|
||||
{(tab) => <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={tabs().close} />}
|
||||
</For>
|
||||
</SortableProvider>
|
||||
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
|
||||
<Tooltip value="Open file" class="flex items-center">
|
||||
<IconButton
|
||||
icon="plus-small"
|
||||
variant="ghost"
|
||||
iconSize="large"
|
||||
onClick={() => dialog.show(() => <DialogSelectFile />)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
<Show when={diffs().length}>
|
||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden contain-strict">
|
||||
<div class="relative pt-3 flex-1 min-h-0 overflow-hidden">
|
||||
<SessionReview
|
||||
classes={{
|
||||
root: "pb-40",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={diffs()}
|
||||
split
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={layout.review.state() === "pane" && diffs().length}>
|
||||
<div
|
||||
classList={{
|
||||
"relative grow pt-3 flex-1 min-h-0 border-l border-border-weak-base contain-strict": true,
|
||||
}}
|
||||
>
|
||||
<SessionReview
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={diffs()}
|
||||
actions={
|
||||
<Tooltip value="Open in tab">
|
||||
<IconButton
|
||||
icon="expand"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
layout.review.tab()
|
||||
tabs().setActive("review")
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Show when={layout.review.state() === "tab" && diffs().length}>
|
||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden contain-strict">
|
||||
<div
|
||||
classList={{
|
||||
"relative pt-3 flex-1 min-h-0 overflow-hidden": true,
|
||||
}}
|
||||
>
|
||||
<SessionReview
|
||||
classes={{
|
||||
root: "pb-40",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={diffs()}
|
||||
split
|
||||
/>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Show>
|
||||
<For each={tabs().all()}>
|
||||
{(tab) => {
|
||||
const [file] = createResource(
|
||||
() => tab,
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<Tabs.Content value={tab} class="select-text mt-3">
|
||||
<Switch>
|
||||
<Match when={file()}>
|
||||
{(f) => (
|
||||
<Dynamic
|
||||
component={codeComponent}
|
||||
file={{
|
||||
name: f().path,
|
||||
contents: f().content?.content ?? "",
|
||||
cacheKey: checksum(f().content?.content ?? ""),
|
||||
}}
|
||||
overflow="scroll"
|
||||
class="pb-40"
|
||||
/>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</Tabs.Content>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Tabs>
|
||||
<DragOverlay>
|
||||
<Show when={store.activeDraggable}>
|
||||
{(draggedFile) => {
|
||||
const [file] = createResource(
|
||||
() => draggedFile(),
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
|
||||
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
</DragOverlay>
|
||||
</DragDropProvider>
|
||||
<Show when={tabs().active()}>
|
||||
<div class="absolute inset-x-0 px-6 max-w-200 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
<For each={tabs().all()}>
|
||||
{(tab) => {
|
||||
const [file] = createResource(
|
||||
() => tab,
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<Tabs.Content value={tab} class="select-text mt-3">
|
||||
<Switch>
|
||||
<Match when={file()}>
|
||||
{(f) => (
|
||||
<Dynamic
|
||||
component={codeComponent}
|
||||
file={{
|
||||
name: f().path,
|
||||
contents: f().content?.content ?? "",
|
||||
cacheKey: checksum(f().content?.content ?? ""),
|
||||
}}
|
||||
overflow="scroll"
|
||||
class="pb-40"
|
||||
/>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</Tabs.Content>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Tabs>
|
||||
<DragOverlay>
|
||||
<Show when={store.activeDraggable}>
|
||||
{(draggedFile) => {
|
||||
const [file] = createResource(
|
||||
() => draggedFile(),
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
|
||||
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
</DragOverlay>
|
||||
</DragDropProvider>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.0.170"
|
||||
version = "1.0.181"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/sst/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.181/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.181/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.181/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.181/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.181/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -71,8 +71,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||
"@opentui/core": "0.1.61",
|
||||
"@opentui/solid": "0.1.61",
|
||||
"@opentui/core": "0.1.62",
|
||||
"@opentui/solid": "0.1.62",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bun
|
||||
import { $ } from "bun"
|
||||
import { Script } from "@opencode-ai/script"
|
||||
|
||||
if (!Script.preview) {
|
||||
// Calculate SHA values
|
||||
const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
|
||||
const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2)
|
||||
|
||||
// arch
|
||||
const binaryPkgbuild = [
|
||||
"# Maintainer: dax",
|
||||
"# Maintainer: adam",
|
||||
"",
|
||||
"pkgname='opencode-bin'",
|
||||
`pkgver=${pkgver}`,
|
||||
`_subver=${_subver}`,
|
||||
"options=('!debug' '!strip')",
|
||||
"pkgrel=1",
|
||||
"pkgdesc='The AI coding agent built for the terminal.'",
|
||||
"url='https://github.com/sst/opencode'",
|
||||
"arch=('aarch64' 'x86_64')",
|
||||
"license=('MIT')",
|
||||
"provides=('opencode')",
|
||||
"conflicts=('opencode')",
|
||||
"depends=('ripgrep')",
|
||||
"",
|
||||
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
|
||||
`sha256sums_aarch64=('${arm64Sha}')`,
|
||||
|
||||
`source_x86_64=("\${pkgname}_\${pkgver}_x86_64.tar.gz::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-x64.tar.gz")`,
|
||||
`sha256sums_x86_64=('${x64Sha}')`,
|
||||
"",
|
||||
"package() {",
|
||||
' install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
|
||||
"}",
|
||||
"",
|
||||
].join("\n")
|
||||
|
||||
// Source-based PKGBUILD for opencode
|
||||
const sourcePkgbuild = [
|
||||
"# Maintainer: dax",
|
||||
"# Maintainer: adam",
|
||||
"",
|
||||
"pkgname='opencode'",
|
||||
`pkgver=${pkgver}`,
|
||||
`_subver=${_subver}`,
|
||||
"options=('!debug' '!strip')",
|
||||
"pkgrel=1",
|
||||
"pkgdesc='The AI coding agent built for the terminal.'",
|
||||
"url='https://github.com/sst/opencode'",
|
||||
"arch=('aarch64' 'x86_64')",
|
||||
"license=('MIT')",
|
||||
"provides=('opencode')",
|
||||
"conflicts=('opencode-bin')",
|
||||
"depends=('ripgrep')",
|
||||
"makedepends=('git' 'bun-bin' 'go')",
|
||||
"",
|
||||
`source=("opencode-\${pkgver}.tar.gz::https://github.com/sst/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
|
||||
`sha256sums=('SKIP')`,
|
||||
"",
|
||||
"build() {",
|
||||
` cd "opencode-\${pkgver}"`,
|
||||
` bun install`,
|
||||
" cd ./packages/opencode",
|
||||
` OPENCODE_CHANNEL=latest OPENCODE_VERSION=${pkgver} bun run ./script/build.ts --single`,
|
||||
"}",
|
||||
"",
|
||||
"package() {",
|
||||
` cd "opencode-\${pkgver}/packages/opencode"`,
|
||||
' mkdir -p "${pkgdir}/usr/bin"',
|
||||
' target_arch="x64"',
|
||||
' case "$CARCH" in',
|
||||
' x86_64) target_arch="x64" ;;',
|
||||
' aarch64) target_arch="arm64" ;;',
|
||||
' *) printf "unsupported architecture: %s\\n" "$CARCH" >&2 ; return 1 ;;',
|
||||
" esac",
|
||||
' libc=""',
|
||||
" if command -v ldd >/dev/null 2>&1; then",
|
||||
" if ldd --version 2>&1 | grep -qi musl; then",
|
||||
' libc="-musl"',
|
||||
" fi",
|
||||
" fi",
|
||||
' if [ -z "$libc" ] && ls /lib/ld-musl-* >/dev/null 2>&1; then',
|
||||
' libc="-musl"',
|
||||
" fi",
|
||||
' base=""',
|
||||
' if [ "$target_arch" = "x64" ]; then',
|
||||
" if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then",
|
||||
' base="-baseline"',
|
||||
" fi",
|
||||
" fi",
|
||||
' bin="dist/opencode-linux-${target_arch}${base}${libc}/bin/opencode"',
|
||||
' if [ ! -f "$bin" ]; then',
|
||||
' printf "unable to find binary for %s%s%s\\n" "$target_arch" "$base" "$libc" >&2',
|
||||
" return 1",
|
||||
" fi",
|
||||
' install -Dm755 "$bin" "${pkgdir}/usr/bin/opencode"',
|
||||
"}",
|
||||
"",
|
||||
].join("\n")
|
||||
|
||||
for (const [pkg, pkgbuild] of [
|
||||
["opencode-bin", binaryPkgbuild],
|
||||
["opencode", sourcePkgbuild],
|
||||
]) {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
await $`rm -rf ./dist/aur-${pkg}`
|
||||
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
||||
await $`cd ./dist/aur-${pkg} && git checkout master`
|
||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
|
||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${Script.version}"`
|
||||
await $`cd ./dist/aur-${pkg} && git push`
|
||||
break
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Homebrew formula
|
||||
const homebrewFormula = [
|
||||
"# typed: false",
|
||||
"# frozen_string_literal: true",
|
||||
"",
|
||||
"# This file was generated by GoReleaser. DO NOT EDIT.",
|
||||
"class Opencode < Formula",
|
||||
` desc "The AI coding agent built for the terminal."`,
|
||||
` homepage "https://github.com/sst/opencode"`,
|
||||
` version "${Script.version.split("-")[0]}"`,
|
||||
"",
|
||||
` depends_on "ripgrep"`,
|
||||
"",
|
||||
" on_macos do",
|
||||
" if Hardware::CPU.intel?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-darwin-x64.zip"`,
|
||||
` sha256 "${macX64Sha}"`,
|
||||
"",
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" if Hardware::CPU.arm?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-darwin-arm64.zip"`,
|
||||
` sha256 "${macArm64Sha}"`,
|
||||
"",
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" end",
|
||||
"",
|
||||
" on_linux do",
|
||||
" if Hardware::CPU.intel? and Hardware::CPU.is_64_bit?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-linux-x64.tar.gz"`,
|
||||
` sha256 "${x64Sha}"`,
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" if Hardware::CPU.arm? and Hardware::CPU.is_64_bit?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-linux-arm64.tar.gz"`,
|
||||
` sha256 "${arm64Sha}"`,
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" end",
|
||||
"end",
|
||||
"",
|
||||
"",
|
||||
].join("\n")
|
||||
|
||||
await $`rm -rf ./dist/homebrew-tap`
|
||||
await $`git clone https://${process.env["GITHUB_TOKEN"]}@github.com/sst/homebrew-tap.git ./dist/homebrew-tap`
|
||||
await Bun.file("./dist/homebrew-tap/opencode.rb").write(homebrewFormula)
|
||||
await $`cd ./dist/homebrew-tap && git add opencode.rb`
|
||||
await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
|
||||
await $`cd ./dist/homebrew-tap && git push`
|
||||
}
|
||||
@@ -53,196 +53,15 @@ for (const tag of tags) {
|
||||
}
|
||||
|
||||
if (!Script.preview) {
|
||||
// Create archives for GitHub release
|
||||
for (const key of Object.keys(binaries)) {
|
||||
if (key.includes("linux")) {
|
||||
await $`cd dist/${key}/bin && tar -czf ../../${key}.tar.gz *`
|
||||
await $`tar -czf ../../${key}.tar.gz *`.cwd(`dist/${key}/bin`)
|
||||
} else {
|
||||
await $`cd dist/${key}/bin && zip -r ../../${key}.zip *`
|
||||
await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate SHA values
|
||||
const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
|
||||
const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2)
|
||||
|
||||
// arch
|
||||
const binaryPkgbuild = [
|
||||
"# Maintainer: dax",
|
||||
"# Maintainer: adam",
|
||||
"",
|
||||
"pkgname='opencode-bin'",
|
||||
`pkgver=${pkgver}`,
|
||||
`_subver=${_subver}`,
|
||||
"options=('!debug' '!strip')",
|
||||
"pkgrel=1",
|
||||
"pkgdesc='The AI coding agent built for the terminal.'",
|
||||
"url='https://github.com/sst/opencode'",
|
||||
"arch=('aarch64' 'x86_64')",
|
||||
"license=('MIT')",
|
||||
"provides=('opencode')",
|
||||
"conflicts=('opencode')",
|
||||
"depends=('ripgrep')",
|
||||
"",
|
||||
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
|
||||
`sha256sums_aarch64=('${arm64Sha}')`,
|
||||
|
||||
`source_x86_64=("\${pkgname}_\${pkgver}_x86_64.tar.gz::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-x64.tar.gz")`,
|
||||
`sha256sums_x86_64=('${x64Sha}')`,
|
||||
"",
|
||||
"package() {",
|
||||
' install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
|
||||
"}",
|
||||
"",
|
||||
].join("\n")
|
||||
|
||||
// Source-based PKGBUILD for opencode
|
||||
const sourcePkgbuild = [
|
||||
"# Maintainer: dax",
|
||||
"# Maintainer: adam",
|
||||
"",
|
||||
"pkgname='opencode'",
|
||||
`pkgver=${pkgver}`,
|
||||
`_subver=${_subver}`,
|
||||
"options=('!debug' '!strip')",
|
||||
"pkgrel=1",
|
||||
"pkgdesc='The AI coding agent built for the terminal.'",
|
||||
"url='https://github.com/sst/opencode'",
|
||||
"arch=('aarch64' 'x86_64')",
|
||||
"license=('MIT')",
|
||||
"provides=('opencode')",
|
||||
"conflicts=('opencode-bin')",
|
||||
"depends=('ripgrep')",
|
||||
"makedepends=('git' 'bun-bin' 'go')",
|
||||
"",
|
||||
`source=("opencode-\${pkgver}.tar.gz::https://github.com/sst/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
|
||||
`sha256sums=('SKIP')`,
|
||||
"",
|
||||
"build() {",
|
||||
` cd "opencode-\${pkgver}"`,
|
||||
` bun install`,
|
||||
" cd ./packages/opencode",
|
||||
` OPENCODE_CHANNEL=latest OPENCODE_VERSION=${pkgver} bun run ./script/build.ts --single`,
|
||||
"}",
|
||||
"",
|
||||
"package() {",
|
||||
` cd "opencode-\${pkgver}/packages/opencode"`,
|
||||
' mkdir -p "${pkgdir}/usr/bin"',
|
||||
' target_arch="x64"',
|
||||
' case "$CARCH" in',
|
||||
' x86_64) target_arch="x64" ;;',
|
||||
' aarch64) target_arch="arm64" ;;',
|
||||
' *) printf "unsupported architecture: %s\\n" "$CARCH" >&2 ; return 1 ;;',
|
||||
" esac",
|
||||
' libc=""',
|
||||
" if command -v ldd >/dev/null 2>&1; then",
|
||||
" if ldd --version 2>&1 | grep -qi musl; then",
|
||||
' libc="-musl"',
|
||||
" fi",
|
||||
" fi",
|
||||
' if [ -z "$libc" ] && ls /lib/ld-musl-* >/dev/null 2>&1; then',
|
||||
' libc="-musl"',
|
||||
" fi",
|
||||
' base=""',
|
||||
' if [ "$target_arch" = "x64" ]; then',
|
||||
" if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then",
|
||||
' base="-baseline"',
|
||||
" fi",
|
||||
" fi",
|
||||
' bin="dist/opencode-linux-${target_arch}${base}${libc}/bin/opencode"',
|
||||
' if [ ! -f "$bin" ]; then',
|
||||
' printf "unable to find binary for %s%s%s\\n" "$target_arch" "$base" "$libc" >&2',
|
||||
" return 1",
|
||||
" fi",
|
||||
' install -Dm755 "$bin" "${pkgdir}/usr/bin/opencode"',
|
||||
"}",
|
||||
"",
|
||||
].join("\n")
|
||||
|
||||
for (const [pkg, pkgbuild] of [
|
||||
["opencode-bin", binaryPkgbuild],
|
||||
["opencode", sourcePkgbuild],
|
||||
]) {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
await $`rm -rf ./dist/aur-${pkg}`
|
||||
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
||||
await $`cd ./dist/aur-${pkg} && git checkout master`
|
||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
|
||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${Script.version}"`
|
||||
await $`cd ./dist/aur-${pkg} && git push`
|
||||
break
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Homebrew formula
|
||||
const homebrewFormula = [
|
||||
"# typed: false",
|
||||
"# frozen_string_literal: true",
|
||||
"",
|
||||
"# This file was generated by GoReleaser. DO NOT EDIT.",
|
||||
"class Opencode < Formula",
|
||||
` desc "The AI coding agent built for the terminal."`,
|
||||
` homepage "https://github.com/sst/opencode"`,
|
||||
` version "${Script.version.split("-")[0]}"`,
|
||||
"",
|
||||
` depends_on "ripgrep"`,
|
||||
"",
|
||||
" on_macos do",
|
||||
" if Hardware::CPU.intel?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-darwin-x64.zip"`,
|
||||
` sha256 "${macX64Sha}"`,
|
||||
"",
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" if Hardware::CPU.arm?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-darwin-arm64.zip"`,
|
||||
` sha256 "${macArm64Sha}"`,
|
||||
"",
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" end",
|
||||
"",
|
||||
" on_linux do",
|
||||
" if Hardware::CPU.intel? and Hardware::CPU.is_64_bit?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-linux-x64.tar.gz"`,
|
||||
` sha256 "${x64Sha}"`,
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" if Hardware::CPU.arm? and Hardware::CPU.is_64_bit?",
|
||||
` url "https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-linux-arm64.tar.gz"`,
|
||||
` sha256 "${arm64Sha}"`,
|
||||
" def install",
|
||||
' bin.install "opencode"',
|
||||
" end",
|
||||
" end",
|
||||
" end",
|
||||
"end",
|
||||
"",
|
||||
"",
|
||||
].join("\n")
|
||||
|
||||
await $`rm -rf ./dist/homebrew-tap`
|
||||
await $`git clone https://${process.env["GITHUB_TOKEN"]}@github.com/sst/homebrew-tap.git ./dist/homebrew-tap`
|
||||
await Bun.file("./dist/homebrew-tap/opencode.rb").write(homebrewFormula)
|
||||
await $`cd ./dist/homebrew-tap && git add opencode.rb`
|
||||
await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
|
||||
await $`cd ./dist/homebrew-tap && git push`
|
||||
|
||||
const image = "ghcr.io/sst/opencode"
|
||||
const platforms = "linux/amd64,linux/arm64"
|
||||
const tags = [`${image}:${Script.version}`, `${image}:latest`]
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Log } from "../util/log"
|
||||
import { ACPSessionManager } from "./session"
|
||||
import type { ACPConfig, ACPSessionState } from "./types"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { Agent as AgentModule } from "../agent/agent"
|
||||
import { Installation } from "@/installation"
|
||||
import { MessageV2 } from "@/session/message-v2"
|
||||
import { Config } from "@/config/config"
|
||||
@@ -698,14 +699,15 @@ export namespace ACP {
|
||||
})
|
||||
|
||||
const availableModes = agents
|
||||
.filter((agent) => agent.mode !== "subagent")
|
||||
.filter((agent) => agent.mode !== "subagent" && !agent.hidden)
|
||||
.map((agent) => ({
|
||||
id: agent.name,
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
}))
|
||||
|
||||
const currentModeId = availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
|
||||
const defaultAgentName = await AgentModule.defaultAgent()
|
||||
const currentModeId = availableModes.find((m) => m.name === defaultAgentName)?.id ?? availableModes[0].id
|
||||
|
||||
const mcpServers: Record<string, Config.Mcp> = {}
|
||||
for (const server of params.mcpServers) {
|
||||
@@ -807,7 +809,7 @@ export namespace ACP {
|
||||
if (!current) {
|
||||
this.sessionManager.setModel(session.id, model)
|
||||
}
|
||||
const agent = session.modeId ?? "build"
|
||||
const agent = session.modeId ?? (await AgentModule.defaultAgent())
|
||||
|
||||
const parts: Array<
|
||||
{ type: "text"; text: string } | { type: "file"; url: string; filename: string; mime: string }
|
||||
|
||||
@@ -5,6 +5,9 @@ import { generateObject, type ModelMessage } from "ai"
|
||||
import { SystemPrompt } from "../session/system"
|
||||
import { Instance } from "../project/instance"
|
||||
import { mergeDeep } from "remeda"
|
||||
import { Log } from "../util/log"
|
||||
|
||||
const log = Log.create({ service: "agent" })
|
||||
|
||||
import PROMPT_GENERATE from "./generate.txt"
|
||||
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
||||
@@ -20,6 +23,7 @@ export namespace Agent {
|
||||
mode: z.enum(["subagent", "primary", "all"]),
|
||||
native: z.boolean().optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
default: z.boolean().optional(),
|
||||
topP: z.number().optional(),
|
||||
temperature: z.number().optional(),
|
||||
color: z.string().optional(),
|
||||
@@ -245,6 +249,19 @@ export namespace Agent {
|
||||
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the default agent
|
||||
const defaultName = cfg.default_agent ?? "build"
|
||||
const defaultCandidate = result[defaultName]
|
||||
if (defaultCandidate && defaultCandidate.mode !== "subagent") {
|
||||
defaultCandidate.default = true
|
||||
} else {
|
||||
// Fall back to "build" if configured default is invalid
|
||||
if (result["build"]) {
|
||||
result["build"].default = true
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
@@ -256,6 +273,12 @@ export namespace Agent {
|
||||
return state().then((x) => Object.values(x))
|
||||
}
|
||||
|
||||
export async function defaultAgent(): Promise<string> {
|
||||
const agents = await state()
|
||||
const defaultCandidate = Object.values(agents).find((a) => a.default)
|
||||
return defaultCandidate?.name ?? "build"
|
||||
}
|
||||
|
||||
export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) {
|
||||
const cfg = await Config.get()
|
||||
const defaultModel = input.model ?? (await Provider.defaultModel())
|
||||
|
||||
@@ -762,7 +762,7 @@ export const GithubRunCommand = cmd({
|
||||
providerID,
|
||||
modelID,
|
||||
},
|
||||
agent: "build",
|
||||
// agent is omitted - server will use default_agent from config or fall back to "build"
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -10,6 +10,7 @@ import { select } from "@clack/prompts"
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import { Server } from "../../server/server"
|
||||
import { Provider } from "../../provider/provider"
|
||||
import { Agent } from "../../agent/agent"
|
||||
|
||||
const TOOL: Record<string, [string, string]> = {
|
||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||
@@ -223,10 +224,33 @@ export const RunCommand = cmd({
|
||||
}
|
||||
})()
|
||||
|
||||
// Validate agent if specified
|
||||
const resolvedAgent = await (async () => {
|
||||
if (!args.agent) return undefined
|
||||
const agent = await Agent.get(args.agent)
|
||||
if (!agent) {
|
||||
UI.println(
|
||||
UI.Style.TEXT_WARNING_BOLD + "!",
|
||||
UI.Style.TEXT_NORMAL,
|
||||
`agent "${args.agent}" not found. Falling back to default agent`,
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
if (agent.mode === "subagent") {
|
||||
UI.println(
|
||||
UI.Style.TEXT_WARNING_BOLD + "!",
|
||||
UI.Style.TEXT_NORMAL,
|
||||
`agent "${args.agent}" is a subagent, not a primary agent. Falling back to default agent`,
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
return args.agent
|
||||
})()
|
||||
|
||||
if (args.command) {
|
||||
await sdk.session.command({
|
||||
sessionID,
|
||||
agent: args.agent || "build",
|
||||
agent: resolvedAgent,
|
||||
model: args.model,
|
||||
command: args.command,
|
||||
arguments: message,
|
||||
@@ -235,7 +259,7 @@ export const RunCommand = cmd({
|
||||
const modelParam = args.model ? Provider.parseModel(args.model) : undefined
|
||||
await sdk.session.prompt({
|
||||
sessionID,
|
||||
agent: args.agent || "build",
|
||||
agent: resolvedAgent,
|
||||
model: modelParam,
|
||||
parts: [...fileParts, { type: "text", text: message }],
|
||||
})
|
||||
|
||||
@@ -229,7 +229,8 @@ function App() {
|
||||
|
||||
let continued = false
|
||||
createEffect(() => {
|
||||
if (continued || sync.status !== "complete" || !args.continue) return
|
||||
// When using -c, session list is loaded in blocking phase, so we can navigate at "partial"
|
||||
if (continued || sync.status === "loading" || !args.continue) return
|
||||
const match = sync.data.session
|
||||
.toSorted((a, b) => b.time.updated - a.time.updated)
|
||||
.find((x) => x.parentID === undefined)?.id
|
||||
|
||||
@@ -56,7 +56,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
const [agentStore, setAgentStore] = createStore<{
|
||||
current: string
|
||||
}>({
|
||||
current: agents()[0].name,
|
||||
current: agents().find((x) => x.default)?.name ?? agents()[0].name,
|
||||
})
|
||||
const { theme } = useTheme()
|
||||
const colors = createMemo(() => [
|
||||
|
||||
@@ -10,6 +10,7 @@ export type HomeRoute = {
|
||||
export type SessionRoute = {
|
||||
type: "session"
|
||||
sessionID: string
|
||||
initialPrompt?: PromptInfo
|
||||
}
|
||||
|
||||
export type Route = HomeRoute | SessionRoute
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Binary } from "@opencode-ai/util/binary"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import type { Snapshot } from "@/snapshot"
|
||||
import { useExit } from "./exit"
|
||||
import { useArgs } from "./args"
|
||||
import { batch, onMount } from "solid-js"
|
||||
import { Log } from "@/util/log"
|
||||
import type { Path } from "@opencode-ai/sdk"
|
||||
@@ -254,10 +255,18 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
})
|
||||
|
||||
const exit = useExit()
|
||||
const args = useArgs()
|
||||
|
||||
async function bootstrap() {
|
||||
// blocking
|
||||
await Promise.all([
|
||||
const sessionListPromise = sdk.client.session.list().then((x) =>
|
||||
setStore(
|
||||
"session",
|
||||
(x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)),
|
||||
),
|
||||
)
|
||||
|
||||
// blocking - include session.list when continuing a session
|
||||
const blockingRequests: Promise<unknown>[] = [
|
||||
sdk.client.config.providers({}, { throwOnError: true }).then((x) => {
|
||||
batch(() => {
|
||||
setStore("provider", x.data!.providers)
|
||||
@@ -271,17 +280,15 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
}),
|
||||
sdk.client.app.agents({}, { throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
|
||||
sdk.client.config.get({}, { throwOnError: true }).then((x) => setStore("config", x.data!)),
|
||||
])
|
||||
...(args.continue ? [sessionListPromise] : []),
|
||||
]
|
||||
|
||||
await Promise.all(blockingRequests)
|
||||
.then(() => {
|
||||
if (store.status !== "complete") setStore("status", "partial")
|
||||
// non-blocking
|
||||
Promise.all([
|
||||
sdk.client.session.list().then((x) =>
|
||||
setStore(
|
||||
"session",
|
||||
(x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)),
|
||||
),
|
||||
),
|
||||
...(args.continue ? [] : [sessionListPromise]),
|
||||
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
|
||||
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
|
||||
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
|
||||
|
||||
@@ -6,8 +6,10 @@ import { createSimpleContext } from "./helper"
|
||||
import aura from "./theme/aura.json" with { type: "json" }
|
||||
import ayu from "./theme/ayu.json" with { type: "json" }
|
||||
import catppuccin from "./theme/catppuccin.json" with { type: "json" }
|
||||
import catppuccinFrappe from "./theme/catppuccin-frappe.json" with { type: "json" }
|
||||
import catppuccinMacchiato from "./theme/catppuccin-macchiato.json" with { type: "json" }
|
||||
import cobalt2 from "./theme/cobalt2.json" with { type: "json" }
|
||||
import cursor from "./theme/cursor.json" with { type: "json" }
|
||||
import dracula from "./theme/dracula.json" with { type: "json" }
|
||||
import everforest from "./theme/everforest.json" with { type: "json" }
|
||||
import flexoki from "./theme/flexoki.json" with { type: "json" }
|
||||
@@ -136,8 +138,10 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
||||
aura,
|
||||
ayu,
|
||||
catppuccin,
|
||||
["catppuccin-frappe"]: catppuccinFrappe,
|
||||
["catppuccin-macchiato"]: catppuccinMacchiato,
|
||||
cobalt2,
|
||||
cursor,
|
||||
dracula,
|
||||
everforest,
|
||||
flexoki,
|
||||
@@ -279,14 +283,23 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
ready: false,
|
||||
})
|
||||
|
||||
createEffect(async () => {
|
||||
const custom = await getCustomThemes()
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
Object.assign(draft.themes, custom)
|
||||
draft.ready = true
|
||||
}),
|
||||
)
|
||||
createEffect(() => {
|
||||
getCustomThemes()
|
||||
.then((custom) => {
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
Object.assign(draft.themes, custom)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
setStore("active", "opencode")
|
||||
})
|
||||
.finally(() => {
|
||||
if (store.active !== "system") {
|
||||
setStore("ready", true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const renderer = useRenderer()
|
||||
@@ -295,8 +308,25 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
size: 16,
|
||||
})
|
||||
.then((colors) => {
|
||||
if (!colors.palette[0]) return
|
||||
setStore("themes", "system", generateSystem(colors, store.mode))
|
||||
if (!colors.palette[0]) {
|
||||
if (store.active === "system") {
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
draft.active = "opencode"
|
||||
draft.ready = true
|
||||
}),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
draft.themes.system = generateSystem(colors, store.mode)
|
||||
if (store.active === "system") {
|
||||
draft.ready = true
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
const values = createMemo(() => {
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"frappeRosewater": "#f2d5cf",
|
||||
"frappeFlamingo": "#eebebe",
|
||||
"frappePink": "#f4b8e4",
|
||||
"frappeMauve": "#ca9ee6",
|
||||
"frappeRed": "#e78284",
|
||||
"frappeMaroon": "#ea999c",
|
||||
"frappePeach": "#ef9f76",
|
||||
"frappeYellow": "#e5c890",
|
||||
"frappeGreen": "#a6d189",
|
||||
"frappeTeal": "#81c8be",
|
||||
"frappeSky": "#99d1db",
|
||||
"frappeSapphire": "#85c1dc",
|
||||
"frappeBlue": "#8da4e2",
|
||||
"frappeLavender": "#babbf1",
|
||||
"frappeText": "#c6d0f5",
|
||||
"frappeSubtext1": "#b5bfe2",
|
||||
"frappeSubtext0": "#a5adce",
|
||||
"frappeOverlay2": "#949cb8",
|
||||
"frappeOverlay1": "#838ba7",
|
||||
"frappeOverlay0": "#737994",
|
||||
"frappeSurface2": "#626880",
|
||||
"frappeSurface1": "#51576d",
|
||||
"frappeSurface0": "#414559",
|
||||
"frappeBase": "#303446",
|
||||
"frappeMantle": "#292c3c",
|
||||
"frappeCrust": "#232634"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "frappeBlue",
|
||||
"light": "frappeBlue"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "frappeMauve",
|
||||
"light": "frappeMauve"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "frappePink",
|
||||
"light": "frappePink"
|
||||
},
|
||||
"error": {
|
||||
"dark": "frappeRed",
|
||||
"light": "frappeRed"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "frappeYellow",
|
||||
"light": "frappeYellow"
|
||||
},
|
||||
"success": {
|
||||
"dark": "frappeGreen",
|
||||
"light": "frappeGreen"
|
||||
},
|
||||
"info": {
|
||||
"dark": "frappeTeal",
|
||||
"light": "frappeTeal"
|
||||
},
|
||||
"text": {
|
||||
"dark": "frappeText",
|
||||
"light": "frappeText"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "frappeSubtext1",
|
||||
"light": "frappeSubtext1"
|
||||
},
|
||||
"background": {
|
||||
"dark": "frappeBase",
|
||||
"light": "frappeBase"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "frappeMantle",
|
||||
"light": "frappeMantle"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "frappeCrust",
|
||||
"light": "frappeCrust"
|
||||
},
|
||||
"border": {
|
||||
"dark": "frappeSurface0",
|
||||
"light": "frappeSurface0"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "frappeSurface1",
|
||||
"light": "frappeSurface1"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "frappeSurface2",
|
||||
"light": "frappeSurface2"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "frappeGreen",
|
||||
"light": "frappeGreen"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "frappeRed",
|
||||
"light": "frappeRed"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "frappeOverlay2",
|
||||
"light": "frappeOverlay2"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "frappePeach",
|
||||
"light": "frappePeach"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "frappeGreen",
|
||||
"light": "frappeGreen"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "frappeRed",
|
||||
"light": "frappeRed"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "#29342b",
|
||||
"light": "#29342b"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "#3a2a31",
|
||||
"light": "#3a2a31"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "frappeMantle",
|
||||
"light": "frappeMantle"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "frappeSurface1",
|
||||
"light": "frappeSurface1"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "#223025",
|
||||
"light": "#223025"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "#2f242b",
|
||||
"light": "#2f242b"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "frappeText",
|
||||
"light": "frappeText"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "frappeMauve",
|
||||
"light": "frappeMauve"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "frappeBlue",
|
||||
"light": "frappeBlue"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "frappeSky",
|
||||
"light": "frappeSky"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "frappeGreen",
|
||||
"light": "frappeGreen"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "frappeYellow",
|
||||
"light": "frappeYellow"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "frappeYellow",
|
||||
"light": "frappeYellow"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "frappePeach",
|
||||
"light": "frappePeach"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "frappeSubtext0",
|
||||
"light": "frappeSubtext0"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "frappeBlue",
|
||||
"light": "frappeBlue"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "frappeSky",
|
||||
"light": "frappeSky"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "frappeBlue",
|
||||
"light": "frappeBlue"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "frappeSky",
|
||||
"light": "frappeSky"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "frappeText",
|
||||
"light": "frappeText"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "frappeOverlay2",
|
||||
"light": "frappeOverlay2"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "frappeMauve",
|
||||
"light": "frappeMauve"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "frappeBlue",
|
||||
"light": "frappeBlue"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "frappeRed",
|
||||
"light": "frappeRed"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "frappeGreen",
|
||||
"light": "frappeGreen"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "frappePeach",
|
||||
"light": "frappePeach"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "frappeYellow",
|
||||
"light": "frappeYellow"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "frappeSky",
|
||||
"light": "frappeSky"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "frappeText",
|
||||
"light": "frappeText"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"darkBg": "#181818",
|
||||
"darkPanel": "#141414",
|
||||
"darkElement": "#262626",
|
||||
"darkFg": "#e4e4e4",
|
||||
"darkMuted": "#e4e4e45e",
|
||||
"darkBorder": "#e4e4e413",
|
||||
"darkBorderActive": "#e4e4e426",
|
||||
"darkCyan": "#88c0d0",
|
||||
"darkBlue": "#81a1c1",
|
||||
"darkGreen": "#3fa266",
|
||||
"darkGreenBright": "#70b489",
|
||||
"darkRed": "#e34671",
|
||||
"darkRedBright": "#fc6b83",
|
||||
"darkYellow": "#f1b467",
|
||||
"darkOrange": "#d2943e",
|
||||
"darkPink": "#E394DC",
|
||||
"darkPurple": "#AAA0FA",
|
||||
"darkTeal": "#82D2CE",
|
||||
"darkSyntaxYellow": "#F8C762",
|
||||
"darkSyntaxOrange": "#EFB080",
|
||||
"darkSyntaxGreen": "#A8CC7C",
|
||||
"darkSyntaxBlue": "#87C3FF",
|
||||
"lightBg": "#fcfcfc",
|
||||
"lightPanel": "#f3f3f3",
|
||||
"lightElement": "#ededed",
|
||||
"lightFg": "#141414",
|
||||
"lightMuted": "#141414ad",
|
||||
"lightBorder": "#14141413",
|
||||
"lightBorderActive": "#14141426",
|
||||
"lightTeal": "#6f9ba6",
|
||||
"lightBlue": "#3c7cab",
|
||||
"lightBlueDark": "#206595",
|
||||
"lightGreen": "#1f8a65",
|
||||
"lightGreenBright": "#55a583",
|
||||
"lightRed": "#cf2d56",
|
||||
"lightRedBright": "#e75e78",
|
||||
"lightOrange": "#db704b",
|
||||
"lightYellow": "#c08532",
|
||||
"lightPurple": "#9e94d5",
|
||||
"lightPurpleDark": "#6049b3",
|
||||
"lightPink": "#b8448b",
|
||||
"lightMagenta": "#b3003f"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightTeal"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightBlue"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightTeal"
|
||||
},
|
||||
"error": {
|
||||
"dark": "darkRed",
|
||||
"light": "lightRed"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "darkYellow",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"success": {
|
||||
"dark": "darkGreen",
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"info": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightBlue"
|
||||
},
|
||||
"text": {
|
||||
"dark": "darkFg",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "darkMuted",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"background": {
|
||||
"dark": "darkBg",
|
||||
"light": "lightBg"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "darkPanel",
|
||||
"light": "lightPanel"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "darkElement",
|
||||
"light": "lightElement"
|
||||
},
|
||||
"border": {
|
||||
"dark": "darkBorder",
|
||||
"light": "lightBorder"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightTeal"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "#0f0f0f",
|
||||
"light": "#e0e0e0"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "darkGreen",
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "darkRed",
|
||||
"light": "lightRed"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "darkMuted",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "darkMuted",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "darkGreenBright",
|
||||
"light": "lightGreenBright"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "darkRedBright",
|
||||
"light": "lightRedBright"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "#3fa26633",
|
||||
"light": "#1f8a651f"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "#b8004933",
|
||||
"light": "#cf2d5614"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "darkPanel",
|
||||
"light": "lightPanel"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "#e4e4e442",
|
||||
"light": "#1414147a"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "#3fa26633",
|
||||
"light": "#1f8a651f"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "#b8004933",
|
||||
"light": "#cf2d5614"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "darkFg",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "darkPurple",
|
||||
"light": "lightBlueDark"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "darkTeal",
|
||||
"light": "lightBlueDark"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "darkPink",
|
||||
"light": "lightGreen"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "darkMuted",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "darkTeal",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "darkSyntaxYellow",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "darkMuted",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "darkFg",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightBlueDark"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "darkFg",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "darkMuted",
|
||||
"light": "lightMuted"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "darkTeal",
|
||||
"light": "lightMagenta"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "darkSyntaxOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "darkFg",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "darkPink",
|
||||
"light": "lightPurple"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "darkSyntaxYellow",
|
||||
"light": "lightPink"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "darkSyntaxOrange",
|
||||
"light": "lightBlueDark"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "darkFg",
|
||||
"light": "lightFg"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "darkFg",
|
||||
"light": "lightFg"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { Locale } from "@/util/locale"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import { useDialog } from "../../ui/dialog"
|
||||
import type { PromptInfo } from "@tui/component/prompt/history"
|
||||
|
||||
export function DialogForkFromTimeline(props: { sessionID: string; onMove: (messageID: string) => void }) {
|
||||
const sync = useSync()
|
||||
@@ -35,9 +36,21 @@ export function DialogForkFromTimeline(props: { sessionID: string; onMove: (mess
|
||||
sessionID: props.sessionID,
|
||||
messageID: message.id,
|
||||
})
|
||||
const parts = sync.data.part[message.id] ?? []
|
||||
const initialPrompt = parts.reduce(
|
||||
(agg, part) => {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
)
|
||||
route.navigate({
|
||||
sessionID: forked.data!.id,
|
||||
type: "session",
|
||||
initialPrompt,
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
|
||||
@@ -80,9 +80,25 @@ export function DialogMessage(props: {
|
||||
sessionID: props.sessionID,
|
||||
messageID: props.messageID,
|
||||
})
|
||||
const initialPrompt = (() => {
|
||||
const msg = message()
|
||||
if (!msg) return undefined
|
||||
const parts = sync.data.part[msg.id]
|
||||
return parts.reduce(
|
||||
(agg, part) => {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
)
|
||||
})()
|
||||
route.navigate({
|
||||
sessionID: result.data!.id,
|
||||
type: "session",
|
||||
initialPrompt,
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
|
||||
@@ -168,6 +168,13 @@ export function Session() {
|
||||
const toast = useToast()
|
||||
const sdk = useSDK()
|
||||
|
||||
// Handle initial prompt from fork
|
||||
createEffect(() => {
|
||||
if (route.initialPrompt && prompt) {
|
||||
prompt.set(route.initialPrompt)
|
||||
}
|
||||
})
|
||||
|
||||
// Auto-navigate to whichever session currently needs permission input
|
||||
createEffect(() => {
|
||||
const currentSession = session()
|
||||
|
||||
@@ -154,7 +154,11 @@ export function Sidebar(props: { sessionID: string }) {
|
||||
</box>
|
||||
<Show when={sync.data.lsp.length <= 2 || expanded.lsp}>
|
||||
<Show when={sync.data.lsp.length === 0}>
|
||||
<text fg={theme.textMuted}>LSPs will activate as files are read</text>
|
||||
<text fg={theme.textMuted}>
|
||||
{sync.data.config.lsp === false
|
||||
? "LSPs have been disabled in settings"
|
||||
: "LSPs will activate as files are read"}
|
||||
</text>
|
||||
</Show>
|
||||
<For each={sync.data.lsp}>
|
||||
{(item) => (
|
||||
|
||||
@@ -32,7 +32,8 @@ export function FormatError(input: unknown) {
|
||||
}
|
||||
if (Config.InvalidError.isInstance(input))
|
||||
return [
|
||||
`Config file at ${input.data.path} is invalid` + (input.data.message ? `: ${input.data.message}` : ""),
|
||||
`Configuration is invalid${input.data.path && input.data.path !== "config" ? ` at ${input.data.path}` : ""}` +
|
||||
(input.data.message ? `: ${input.data.message}` : ""),
|
||||
...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
|
||||
].join("\n")
|
||||
|
||||
|
||||
@@ -141,6 +141,17 @@ export namespace Config {
|
||||
|
||||
if (!result.keybinds) result.keybinds = Info.shape.keybinds.parse({})
|
||||
|
||||
// Only validate if user has configured agents - if none configured, built-in agents will be used
|
||||
if (Object.keys(result.agent).length > 0) {
|
||||
const primaryAgents = Object.values(result.agent).filter((a) => a.mode !== "subagent" && !a.hidden && !a.disable)
|
||||
if (primaryAgents.length === 0) {
|
||||
throw new InvalidError({
|
||||
path: "config",
|
||||
message: "No primary agents are available. Please configure at least one agent with mode 'primary' or 'all'.",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
config: result,
|
||||
directories,
|
||||
@@ -666,6 +677,12 @@ export namespace Config {
|
||||
.string()
|
||||
.describe("Small model to use for tasks like title generation in the format of provider/model")
|
||||
.optional(),
|
||||
default_agent: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.",
|
||||
),
|
||||
username: z
|
||||
.string()
|
||||
.optional()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { Bus } from "@/bus"
|
||||
import z from "zod"
|
||||
import { $ } from "bun"
|
||||
import type { BunFile } from "bun"
|
||||
@@ -74,6 +73,7 @@ export namespace File {
|
||||
|
||||
async function shouldEncode(file: BunFile): Promise<boolean> {
|
||||
const type = file.type?.toLowerCase()
|
||||
log.info("shouldEncode", { type })
|
||||
if (!type) return false
|
||||
|
||||
if (type.startsWith("text/")) return false
|
||||
@@ -87,15 +87,12 @@ export namespace File {
|
||||
const tops = ["image", "audio", "video", "font", "model", "multipart"]
|
||||
if (tops.includes(top)) return true
|
||||
|
||||
if (type === "application/octet-stream") return true
|
||||
|
||||
const bins = [
|
||||
"zip",
|
||||
"gzip",
|
||||
"bzip",
|
||||
"compressed",
|
||||
"binary",
|
||||
"stream",
|
||||
"pdf",
|
||||
"msword",
|
||||
"powerpoint",
|
||||
@@ -125,6 +122,8 @@ export namespace File {
|
||||
let cache: Entry = { files: [], dirs: [] }
|
||||
let fetching = false
|
||||
const fn = async (result: Entry) => {
|
||||
// Disable scanning if in root of file system
|
||||
if (Instance.directory === path.parse(Instance.directory).root) return
|
||||
fetching = true
|
||||
const set = new Set<string>()
|
||||
for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
|
||||
@@ -290,9 +289,11 @@ export namespace File {
|
||||
}
|
||||
const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
|
||||
const nodes: Node[] = []
|
||||
for (const entry of await fs.promises.readdir(resolved, {
|
||||
withFileTypes: true,
|
||||
})) {
|
||||
for (const entry of await fs.promises
|
||||
.readdir(resolved, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
.catch(() => [])) {
|
||||
if (exclude.includes(entry.name)) continue
|
||||
const fullPath = path.join(resolved, entry.name)
|
||||
const relativePath = path.relative(Instance.directory, fullPath)
|
||||
|
||||
@@ -75,11 +75,9 @@ export namespace ProviderTransform {
|
||||
}
|
||||
|
||||
if (
|
||||
model.providerID === "deepseek" ||
|
||||
model.api.id.toLowerCase().includes("deepseek") ||
|
||||
(model.capabilities.interleaved &&
|
||||
typeof model.capabilities.interleaved === "object" &&
|
||||
model.capabilities.interleaved.field === "reasoning_content")
|
||||
model.capabilities.interleaved &&
|
||||
typeof model.capabilities.interleaved === "object" &&
|
||||
model.capabilities.interleaved.field === "reasoning_content"
|
||||
) {
|
||||
return msgs.map((msg) => {
|
||||
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
||||
@@ -426,6 +424,10 @@ export namespace ProviderTransform {
|
||||
result.required = result.required.filter((field: any) => field in result.properties)
|
||||
}
|
||||
|
||||
if (result.type === "array" && result.items == null) {
|
||||
result.items = {}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -1060,11 +1060,11 @@ export namespace Server {
|
||||
const sessionID = c.req.valid("param").sessionID
|
||||
const body = c.req.valid("json")
|
||||
const msgs = await Session.messages({ sessionID })
|
||||
let currentAgent = "build"
|
||||
let currentAgent = await Agent.defaultAgent()
|
||||
for (let i = msgs.length - 1; i >= 0; i--) {
|
||||
const info = msgs[i].info
|
||||
if (info.role === "user") {
|
||||
currentAgent = info.agent || "build"
|
||||
currentAgent = info.agent || (await Agent.defaultAgent())
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1188,6 +1188,79 @@ export namespace Server {
|
||||
return c.json(message)
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/session/:sessionID/message/:messageID/part/:partID",
|
||||
describeRoute({
|
||||
description: "Delete a part from a message",
|
||||
operationId: "part.delete",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully deleted part",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400, 404),
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
sessionID: z.string().meta({ description: "Session ID" }),
|
||||
messageID: z.string().meta({ description: "Message ID" }),
|
||||
partID: z.string().meta({ description: "Part ID" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
await Session.removePart({
|
||||
sessionID: params.sessionID,
|
||||
messageID: params.messageID,
|
||||
partID: params.partID,
|
||||
})
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.patch(
|
||||
"/session/:sessionID/message/:messageID/part/:partID",
|
||||
describeRoute({
|
||||
description: "Update a part in a message",
|
||||
operationId: "part.update",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully updated part",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(MessageV2.Part),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400, 404),
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
sessionID: z.string().meta({ description: "Session ID" }),
|
||||
messageID: z.string().meta({ description: "Message ID" }),
|
||||
partID: z.string().meta({ description: "Part ID" }),
|
||||
}),
|
||||
),
|
||||
validator("json", MessageV2.Part),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const body = c.req.valid("json")
|
||||
if (body.id !== params.partID || body.messageID !== params.messageID || body.sessionID !== params.sessionID) {
|
||||
throw new Error(
|
||||
`Part mismatch: body.id='${body.id}' vs partID='${params.partID}', body.messageID='${body.messageID}' vs messageID='${params.messageID}', body.sessionID='${body.sessionID}' vs sessionID='${params.sessionID}'`,
|
||||
)
|
||||
}
|
||||
const part = await Session.updatePart(body)
|
||||
return c.json(part)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:sessionID/message",
|
||||
describeRoute({
|
||||
|
||||
@@ -339,6 +339,23 @@ export namespace Session {
|
||||
},
|
||||
)
|
||||
|
||||
export const removePart = fn(
|
||||
z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
messageID: Identifier.schema("message"),
|
||||
partID: Identifier.schema("part"),
|
||||
}),
|
||||
async (input) => {
|
||||
await Storage.remove(["part", input.messageID, input.partID])
|
||||
Bus.publish(MessageV2.Event.PartRemoved, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
partID: input.partID,
|
||||
})
|
||||
return input.partID
|
||||
},
|
||||
)
|
||||
|
||||
const UpdatePartInput = z.union([
|
||||
MessageV2.Part,
|
||||
z.object({
|
||||
|
||||
@@ -715,7 +715,7 @@ export namespace SessionPrompt {
|
||||
}
|
||||
|
||||
async function createUserMessage(input: PromptInput) {
|
||||
const agent = await Agent.get(input.agent ?? "build")
|
||||
const agent = await Agent.get(input.agent ?? (await Agent.defaultAgent()))
|
||||
const info: MessageV2.Info = {
|
||||
id: input.messageID ?? Identifier.ascending("message"),
|
||||
role: "user",
|
||||
@@ -1282,7 +1282,7 @@ export namespace SessionPrompt {
|
||||
export async function command(input: CommandInput) {
|
||||
log.info("command", input)
|
||||
const command = await Command.get(input.command)
|
||||
const agentName = command.agent ?? input.agent ?? "build"
|
||||
const agentName = command.agent ?? input.agent ?? (await Agent.defaultAgent())
|
||||
|
||||
const raw = input.arguments.match(argsRegex) ?? []
|
||||
const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
|
||||
@@ -1425,7 +1425,7 @@ export namespace SessionPrompt {
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
agent: input.message.info.role === "user" ? input.message.info.agent : "build",
|
||||
agent: input.message.info.role === "user" ? input.message.info.agent : await Agent.defaultAgent(),
|
||||
model: {
|
||||
providerID: input.providerID,
|
||||
modelID: input.modelID,
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import fsSync from "fs"
|
||||
import { afterAll } from "bun:test"
|
||||
|
||||
const dir = path.join(os.tmpdir(), "opencode-test-data-" + process.pid)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
afterAll(() => {
|
||||
fsSync.rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
process.env["XDG_DATA_HOME"] = path.join(dir, "share")
|
||||
process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
|
||||
process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
|
||||
|
||||
@@ -167,6 +167,30 @@ describe("ProviderTransform.maxOutputTokens", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform.schema - gemini array items", () => {
|
||||
test("adds missing items for array properties", () => {
|
||||
const geminiModel = {
|
||||
providerID: "google",
|
||||
api: {
|
||||
id: "gemini-3-pro",
|
||||
},
|
||||
} as any
|
||||
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
nodes: { type: "array" },
|
||||
edges: { type: "array", items: { type: "string" } },
|
||||
},
|
||||
} as any
|
||||
|
||||
const result = ProviderTransform.schema(geminiModel, schema) as any
|
||||
|
||||
expect(result.properties.nodes.items).toBeDefined()
|
||||
expect(result.properties.edges.items.type).toBe("string")
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||
test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => {
|
||||
const msgs = [
|
||||
@@ -200,7 +224,9 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
interleaved: false,
|
||||
interleaved: {
|
||||
field: "reasoning_content",
|
||||
},
|
||||
},
|
||||
cost: {
|
||||
input: 0.001,
|
||||
@@ -229,58 +255,6 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBe("Let me think about this...")
|
||||
})
|
||||
|
||||
test("DeepSeek model ID containing 'deepseek' matches (case insensitive)", () => {
|
||||
const msgs = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "reasoning", text: "Thinking..." },
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "test",
|
||||
toolName: "get_weather",
|
||||
input: { location: "Hangzhou" },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as any[]
|
||||
|
||||
const result = ProviderTransform.message(msgs, {
|
||||
id: "someprovider/deepseek-reasoner",
|
||||
providerID: "someprovider",
|
||||
api: {
|
||||
id: "deepseek-reasoner",
|
||||
url: "https://api.someprovider.com",
|
||||
npm: "@ai-sdk/openai-compatible",
|
||||
},
|
||||
name: "SomeProvider DeepSeek Reasoner",
|
||||
capabilities: {
|
||||
temperature: true,
|
||||
reasoning: true,
|
||||
attachment: false,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
interleaved: false,
|
||||
},
|
||||
cost: {
|
||||
input: 0.001,
|
||||
output: 0.002,
|
||||
cache: { read: 0.0001, write: 0.0002 },
|
||||
},
|
||||
limit: {
|
||||
context: 128000,
|
||||
output: 8192,
|
||||
},
|
||||
status: "active",
|
||||
options: {},
|
||||
headers: {},
|
||||
release_date: "2023-04-01",
|
||||
})
|
||||
|
||||
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBe("Thinking...")
|
||||
})
|
||||
|
||||
test("Non-DeepSeek providers leave reasoning content unchanged", () => {
|
||||
const msgs = [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -47,6 +47,11 @@ import type {
|
||||
McpLocalConfig,
|
||||
McpRemoteConfig,
|
||||
McpStatusResponses,
|
||||
Part as Part2,
|
||||
PartDeleteErrors,
|
||||
PartDeleteResponses,
|
||||
PartUpdateErrors,
|
||||
PartUpdateResponses,
|
||||
PathGetResponses,
|
||||
PermissionRespondErrors,
|
||||
PermissionRespondResponses,
|
||||
@@ -1486,6 +1491,79 @@ export class Session extends HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
export class Part extends HeyApiClient {
|
||||
/**
|
||||
* Delete a part from a message
|
||||
*/
|
||||
public delete<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
partID: string
|
||||
directory?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "sessionID" },
|
||||
{ in: "path", key: "messageID" },
|
||||
{ in: "path", key: "partID" },
|
||||
{ in: "query", key: "directory" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).delete<PartDeleteResponses, PartDeleteErrors, ThrowOnError>({
|
||||
url: "/session/{sessionID}/message/{messageID}/part/{partID}",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a part in a message
|
||||
*/
|
||||
public update<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
partID: string
|
||||
directory?: string
|
||||
part?: Part2
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "sessionID" },
|
||||
{ in: "path", key: "messageID" },
|
||||
{ in: "path", key: "partID" },
|
||||
{ in: "query", key: "directory" },
|
||||
{ key: "part", map: "body" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).patch<PartUpdateResponses, PartUpdateErrors, ThrowOnError>({
|
||||
url: "/session/{sessionID}/message/{messageID}/part/{partID}",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class Permission extends HeyApiClient {
|
||||
/**
|
||||
* Respond to permission
|
||||
@@ -2588,6 +2666,8 @@ export class OpencodeClient extends HeyApiClient {
|
||||
|
||||
session = new Session({ client: this.client })
|
||||
|
||||
part = new Part({ client: this.client })
|
||||
|
||||
permission = new Permission({ client: this.client })
|
||||
|
||||
command = new Command({ client: this.client })
|
||||
|
||||
@@ -1414,6 +1414,10 @@ export type Config = {
|
||||
* Small model to use for tasks like title generation in the format of provider/model
|
||||
*/
|
||||
small_model?: string
|
||||
/**
|
||||
* Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.
|
||||
*/
|
||||
default_agent?: string
|
||||
/**
|
||||
* Custom username to display in conversations instead of system username
|
||||
*/
|
||||
@@ -1767,6 +1771,7 @@ export type Agent = {
|
||||
mode: "subagent" | "primary" | "all"
|
||||
native?: boolean
|
||||
hidden?: boolean
|
||||
default?: boolean
|
||||
topP?: number
|
||||
temperature?: number
|
||||
color?: string
|
||||
@@ -2915,6 +2920,94 @@ export type SessionMessageResponses = {
|
||||
|
||||
export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses]
|
||||
|
||||
export type PartDeleteData = {
|
||||
body?: never
|
||||
path: {
|
||||
/**
|
||||
* Session ID
|
||||
*/
|
||||
sessionID: string
|
||||
/**
|
||||
* Message ID
|
||||
*/
|
||||
messageID: string
|
||||
/**
|
||||
* Part ID
|
||||
*/
|
||||
partID: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/session/{sessionID}/message/{messageID}/part/{partID}"
|
||||
}
|
||||
|
||||
export type PartDeleteErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type PartDeleteError = PartDeleteErrors[keyof PartDeleteErrors]
|
||||
|
||||
export type PartDeleteResponses = {
|
||||
/**
|
||||
* Successfully deleted part
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type PartDeleteResponse = PartDeleteResponses[keyof PartDeleteResponses]
|
||||
|
||||
export type PartUpdateData = {
|
||||
body?: Part
|
||||
path: {
|
||||
/**
|
||||
* Session ID
|
||||
*/
|
||||
sessionID: string
|
||||
/**
|
||||
* Message ID
|
||||
*/
|
||||
messageID: string
|
||||
/**
|
||||
* Part ID
|
||||
*/
|
||||
partID: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/session/{sessionID}/message/{messageID}/part/{partID}"
|
||||
}
|
||||
|
||||
export type PartUpdateErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type PartUpdateError = PartUpdateErrors[keyof PartUpdateErrors]
|
||||
|
||||
export type PartUpdateResponses = {
|
||||
/**
|
||||
* Successfully updated part
|
||||
*/
|
||||
200: Part
|
||||
}
|
||||
|
||||
export type PartUpdateResponse = PartUpdateResponses[keyof PartUpdateResponses]
|
||||
|
||||
export type SessionPromptAsyncData = {
|
||||
body?: {
|
||||
messageID?: string
|
||||
|
||||
@@ -2126,6 +2126,173 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/session/{sessionID}/message/{messageID}/part/{partID}": {
|
||||
"delete": {
|
||||
"operationId": "part.delete",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "directory",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "sessionID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"description": "Session ID"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "messageID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"description": "Message ID"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "partID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"description": "Part ID"
|
||||
}
|
||||
],
|
||||
"description": "Delete a part from a message",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successfully deleted part",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NotFoundError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.delete({\n ...\n})"
|
||||
}
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "part.update",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "directory",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "sessionID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"description": "Session ID"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "messageID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"description": "Message ID"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "partID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"description": "Part ID"
|
||||
}
|
||||
],
|
||||
"description": "Update a part in a message",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successfully updated part",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Part"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NotFoundError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Part"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.update({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/session/{sessionID}/prompt_async": {
|
||||
"post": {
|
||||
"operationId": "session.prompt_async",
|
||||
@@ -7986,6 +8153,10 @@
|
||||
"description": "Small model to use for tasks like title generation in the format of provider/model",
|
||||
"type": "string"
|
||||
},
|
||||
"default_agent": {
|
||||
"description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Custom username to display in conversations instead of system username",
|
||||
"type": "string"
|
||||
@@ -8985,6 +9156,9 @@
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"topP": {
|
||||
"type": "number"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun run src/index.ts",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/tauri",
|
||||
"private": true,
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo -b",
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
import { $ } from "bun"
|
||||
|
||||
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
||||
import { Script } from "@opencode-ai/script"
|
||||
|
||||
const sidecarConfig = getCurrentSidecar()
|
||||
|
||||
const dir = "src-tauri/target/opencode-binaries"
|
||||
|
||||
await $`mkdir -p ${dir}`
|
||||
await $`gh release download ${Bun.env.OPENCODE_RELEASE_TAG} --pattern ${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} --repo sst/opencode --skip-existing --dir ${dir}`
|
||||
await $`gh release download v${Script.version} --pattern ${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} --repo sst/opencode --skip-existing --dir ${dir}`
|
||||
|
||||
if (sidecarConfig.assetExt === "tar.gz") {
|
||||
await $`tar -xvzf ${dir}/${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} -C ${dir}`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./*": "./src/components/*.tsx",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[data-component="tool-trigger"] {
|
||||
content-visibility: auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { children, For, Match, Show, Switch, type JSX } from "solid-js"
|
||||
import { For, Match, Show, Switch, type JSX } from "solid-js"
|
||||
import { Collapsible } from "./collapsible"
|
||||
import { Icon, IconProps } from "./icon"
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface BasicToolProps {
|
||||
}
|
||||
|
||||
export function BasicTool(props: BasicToolProps) {
|
||||
const resolved = children(() => props.children)
|
||||
return (
|
||||
<Collapsible defaultOpen={props.defaultOpen}>
|
||||
<Collapsible.Trigger>
|
||||
@@ -81,13 +80,13 @@ export function BasicTool(props: BasicToolProps) {
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={resolved() && !props.hideDetails}>
|
||||
<Show when={props.children && !props.hideDetails}>
|
||||
<Collapsible.Arrow />
|
||||
</Show>
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Show when={resolved() && !props.hideDetails}>
|
||||
<Collapsible.Content>{resolved()}</Collapsible.Content>
|
||||
<Show when={props.children && !props.hideDetails}>
|
||||
<Collapsible.Content>{props.children}</Collapsible.Content>
|
||||
</Show>
|
||||
</Collapsible>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Checkbox as Kobalte } from "@kobalte/core/checkbox"
|
||||
import { children, Show, splitProps } from "solid-js"
|
||||
import { Show, splitProps } from "solid-js"
|
||||
import type { ComponentProps, JSX, ParentProps } from "solid-js"
|
||||
|
||||
export interface CheckboxProps extends ParentProps<ComponentProps<typeof Kobalte>> {
|
||||
@@ -10,7 +10,6 @@ export interface CheckboxProps extends ParentProps<ComponentProps<typeof Kobalte
|
||||
|
||||
export function Checkbox(props: CheckboxProps) {
|
||||
const [local, others] = splitProps(props, ["children", "class", "label", "hideLabel", "description", "icon"])
|
||||
const resolved = children(() => local.children)
|
||||
return (
|
||||
<Kobalte {...others} data-component="checkbox">
|
||||
<Kobalte.Input data-slot="checkbox-checkbox-input" />
|
||||
@@ -29,9 +28,9 @@ export function Checkbox(props: CheckboxProps) {
|
||||
</Kobalte.Indicator>
|
||||
</Kobalte.Control>
|
||||
<div data-slot="checkbox-checkbox-content">
|
||||
<Show when={resolved()}>
|
||||
<Show when={props.children}>
|
||||
<Kobalte.Label data-slot="checkbox-checkbox-label" classList={{ "sr-only": local.hideLabel }}>
|
||||
{resolved()}
|
||||
{props.children}
|
||||
</Kobalte.Label>
|
||||
</Show>
|
||||
<Show when={local.description}>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[data-component="code"] {
|
||||
content-visibility: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[data-component="diff"] {
|
||||
contain: content;
|
||||
content-visibility: auto;
|
||||
|
||||
[data-slot="diff-hunk-separator-line-number"] {
|
||||
position: sticky;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[data-component="assistant-message"] {
|
||||
content-visibility: auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -319,5 +320,10 @@
|
||||
[data-slot="diagnostic-message"] {
|
||||
color: var(--text-on-critical-base);
|
||||
word-break: break-word;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,13 +463,13 @@ ToolRegistry.register({
|
||||
subtitle: props.input.description,
|
||||
}}
|
||||
>
|
||||
<Show when={props.output}>
|
||||
{(output) => (
|
||||
<div data-component="tool-output" data-scrollable>
|
||||
<Markdown text={output()} />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
{/* <Show when={false && props.output}> */}
|
||||
{/* {(output) => ( */}
|
||||
{/* <div data-component="tool-output" data-scrollable> */}
|
||||
{/* <Markdown text={output()} /> */}
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
{/* </Show> */}
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
@@ -531,12 +531,14 @@ ToolRegistry.register({
|
||||
<Dynamic
|
||||
component={diffComponent}
|
||||
before={{
|
||||
name: getFilename(props.metadata.filediff.path),
|
||||
name: props.metadata.filediff.path,
|
||||
contents: props.metadata.filediff.before,
|
||||
cacheKey: checksum(props.metadata.filediff.before),
|
||||
}}
|
||||
after={{
|
||||
name: getFilename(props.metadata.filediff.path),
|
||||
name: props.metadata.filediff.path,
|
||||
contents: props.metadata.filediff.after,
|
||||
cacheKey: checksum(props.metadata.filediff.after),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface SessionReviewProps {
|
||||
export const SessionReview = (props: SessionReviewProps) => {
|
||||
const diffComponent = useDiffComponent()
|
||||
const [store, setStore] = createStore({
|
||||
open: props.diffs.map((d) => d.file),
|
||||
open: props.diffs.length > 10 ? [] : props.diffs.map((d) => d.file),
|
||||
})
|
||||
|
||||
const handleChange = (open: string[]) => {
|
||||
@@ -78,7 +78,7 @@ export const SessionReview = (props: SessionReviewProps) => {
|
||||
<Accordion multiple value={store.open} onChange={handleChange}>
|
||||
<For each={props.diffs}>
|
||||
{(diff) => (
|
||||
<Accordion.Item forceMount value={diff.file} data-slot="session-review-accordion-item">
|
||||
<Accordion.Item value={diff.file} data-slot="session-review-accordion-item">
|
||||
<StickyAccordionHeader>
|
||||
<Accordion.Trigger>
|
||||
<div data-slot="session-review-trigger-content">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AssistantMessage, ToolPart } from "@opencode-ai/sdk/v2/client"
|
||||
import { AssistantMessage, Part as PartType, TextPart, ToolPart } from "@opencode-ai/sdk/v2/client"
|
||||
import { useData } from "../context"
|
||||
import { useDiffComponent } from "../context/diff"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
@@ -20,6 +20,45 @@ import { Spinner } from "./spinner"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { DateTime, DurationUnit, Interval } from "luxon"
|
||||
|
||||
function computeStatusFromPart(part: PartType | undefined): string | undefined {
|
||||
if (!part) return undefined
|
||||
|
||||
if (part.type === "tool") {
|
||||
switch (part.tool) {
|
||||
case "task":
|
||||
return "Delegating work"
|
||||
case "todowrite":
|
||||
case "todoread":
|
||||
return "Planning next steps"
|
||||
case "read":
|
||||
return "Gathering context"
|
||||
case "list":
|
||||
case "grep":
|
||||
case "glob":
|
||||
return "Searching the codebase"
|
||||
case "webfetch":
|
||||
return "Searching the web"
|
||||
case "edit":
|
||||
case "write":
|
||||
return "Making edits"
|
||||
case "bash":
|
||||
return "Running commands"
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
if (part.type === "reasoning") {
|
||||
const text = part.text ?? ""
|
||||
const match = text.trimStart().match(/^\*\*(.+?)\*\*/)
|
||||
if (match) return `Thinking · ${match[1].trim()}`
|
||||
return "Thinking"
|
||||
}
|
||||
if (part.type === "text") {
|
||||
return "Gathering thoughts"
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function SessionTurn(
|
||||
props: ParentProps<{
|
||||
sessionID: string
|
||||
@@ -36,119 +75,152 @@ export function SessionTurn(
|
||||
) {
|
||||
const data = useData()
|
||||
const diffComponent = useDiffComponent()
|
||||
const messages = createMemo(() => data.store.message[props.sessionID] ?? [])
|
||||
const userMessages = createMemo(() =>
|
||||
messages()
|
||||
.filter((m) => m.role === "user")
|
||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||
)
|
||||
const lastUserMessage = createMemo(() => userMessages().at(-1)!)
|
||||
const message = createMemo(() => userMessages().find((m) => m.id === props.messageID)!)
|
||||
|
||||
const derived = createMemo(() => {
|
||||
const allMessages = data.store.message[props.sessionID] ?? []
|
||||
const userMessages = allMessages.filter((m) => m.role === "user").sort((a, b) => a.id.localeCompare(b.id))
|
||||
const lastUserMessage = userMessages.at(-1)
|
||||
const message = userMessages.find((m) => m.id === props.messageID)
|
||||
|
||||
if (!message) {
|
||||
return {
|
||||
message: undefined,
|
||||
parts: [] as PartType[],
|
||||
assistantMessages: [] as AssistantMessage[],
|
||||
assistantParts: [] as PartType[],
|
||||
lastAssistantMessage: undefined as AssistantMessage | undefined,
|
||||
lastTextPart: undefined as PartType | undefined,
|
||||
error: undefined,
|
||||
hasSteps: false,
|
||||
isShellMode: false,
|
||||
rawStatus: undefined as string | undefined,
|
||||
isLastUserMessage: false,
|
||||
}
|
||||
}
|
||||
|
||||
const parts = data.store.part[message.id] ?? []
|
||||
const assistantMessages = allMessages.filter(
|
||||
(m) => m.role === "assistant" && m.parentID === message.id,
|
||||
) as AssistantMessage[]
|
||||
|
||||
const assistantParts: PartType[] = []
|
||||
for (const m of assistantMessages) {
|
||||
const msgParts = data.store.part[m.id]
|
||||
if (msgParts) {
|
||||
for (const p of msgParts) {
|
||||
if (p) assistantParts.push(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lastAssistantMessage = assistantMessages.at(-1)
|
||||
const error = assistantMessages.find((m) => m.error)?.error
|
||||
|
||||
let lastTextPart: PartType | undefined
|
||||
for (let i = assistantParts.length - 1; i >= 0; i--) {
|
||||
if (assistantParts[i]?.type === "text") {
|
||||
lastTextPart = assistantParts[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const hasSteps = assistantParts.some((p) => p?.type === "tool")
|
||||
|
||||
let isShellMode = false
|
||||
if (parts.every((p) => p?.type === "text" && p?.synthetic) && assistantParts.length === 1) {
|
||||
const assistantPart = assistantParts[0]
|
||||
if (assistantPart?.type === "tool" && assistantPart?.tool === "bash") {
|
||||
isShellMode = true
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedParts = assistantParts
|
||||
const currentTask = assistantParts.findLast(
|
||||
(p) =>
|
||||
p &&
|
||||
p.type === "tool" &&
|
||||
p.tool === "task" &&
|
||||
p.state &&
|
||||
"metadata" in p.state &&
|
||||
p.state.metadata &&
|
||||
p.state.metadata.sessionId &&
|
||||
p.state.status === "running",
|
||||
) as ToolPart | undefined
|
||||
|
||||
if (currentTask?.state && "metadata" in currentTask.state && currentTask.state.metadata?.sessionId) {
|
||||
const taskMessages = data.store.message[currentTask.state.metadata.sessionId as string]?.filter(
|
||||
(m) => m.role === "assistant",
|
||||
)
|
||||
if (taskMessages) {
|
||||
const taskParts: PartType[] = []
|
||||
for (const m of taskMessages) {
|
||||
const msgParts = data.store.part[m.id]
|
||||
if (msgParts) {
|
||||
for (const p of msgParts) {
|
||||
if (p) taskParts.push(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (taskParts.length > 0) {
|
||||
resolvedParts = taskParts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lastPart = resolvedParts.at(-1)
|
||||
const rawStatus = computeStatusFromPart(lastPart)
|
||||
|
||||
return {
|
||||
message,
|
||||
parts,
|
||||
assistantMessages,
|
||||
assistantParts,
|
||||
lastAssistantMessage,
|
||||
lastTextPart,
|
||||
error,
|
||||
hasSteps,
|
||||
isShellMode,
|
||||
rawStatus,
|
||||
isLastUserMessage: message.id === lastUserMessage?.id,
|
||||
}
|
||||
})
|
||||
|
||||
const message = () => derived().message
|
||||
const parts = () => derived().parts
|
||||
const assistantMessages = () => derived().assistantMessages
|
||||
const assistantParts = () => derived().assistantParts
|
||||
const lastAssistantMessage = () => derived().lastAssistantMessage
|
||||
const lastTextPart = () => derived().lastTextPart
|
||||
const error = () => derived().error
|
||||
const hasSteps = () => derived().hasSteps
|
||||
const isShellMode = () => derived().isShellMode
|
||||
const rawStatus = () => derived().rawStatus
|
||||
|
||||
const status = createMemo(
|
||||
() =>
|
||||
data.store.session_status[props.sessionID] ?? {
|
||||
type: "idle",
|
||||
},
|
||||
)
|
||||
const working = createMemo(() => status().type !== "idle" && message().id === lastUserMessage().id)
|
||||
const working = createMemo(() => status().type !== "idle" && derived().isLastUserMessage)
|
||||
const retry = createMemo(() => {
|
||||
const s = status()
|
||||
if (s.type !== "retry") return
|
||||
return s
|
||||
})
|
||||
|
||||
const assistantMessages = createMemo(() => {
|
||||
return messages().filter((m) => m.role === "assistant" && m.parentID == message().id) as AssistantMessage[]
|
||||
})
|
||||
const assistantParts = createMemo(() => assistantMessages().flatMap((m) => data.store.part[m.id]) ?? [])
|
||||
const lastAssistantMessage = createMemo(() => assistantMessages().at(-1))
|
||||
const error = createMemo(() => assistantMessages().find((m) => m.error)?.error)
|
||||
const parts = createMemo(() => data.store.part[message().id] ?? [])
|
||||
const lastTextPart = createMemo(() =>
|
||||
assistantParts()
|
||||
.filter((p) => p?.type === "text")
|
||||
.at(-1),
|
||||
)
|
||||
const summary = createMemo(() => message().summary?.body)
|
||||
const response = createMemo(() => lastTextPart()?.text)
|
||||
const hasSteps = createMemo(() => assistantParts().some((p) => p?.type === "tool"))
|
||||
|
||||
const currentTask = createMemo(
|
||||
() =>
|
||||
assistantParts().findLast(
|
||||
(p) =>
|
||||
p &&
|
||||
p.type === "tool" &&
|
||||
p.tool === "task" &&
|
||||
p.state &&
|
||||
"metadata" in p.state &&
|
||||
p.state.metadata &&
|
||||
p.state.metadata.sessionId &&
|
||||
p.state.status === "running",
|
||||
) as ToolPart,
|
||||
)
|
||||
const resolvedParts = createMemo(() => {
|
||||
let resolved = assistantParts()
|
||||
const task = currentTask()
|
||||
if (task && task.state && "metadata" in task.state && task.state.metadata?.sessionId) {
|
||||
const messages = data.store.message[task.state.metadata.sessionId as string]?.filter(
|
||||
(m) => m.role === "assistant",
|
||||
)
|
||||
resolved = messages?.flatMap((m) => data.store.part[m.id]) ?? assistantParts()
|
||||
}
|
||||
return resolved
|
||||
})
|
||||
const lastPart = createMemo(() => resolvedParts().slice(-1)?.at(0))
|
||||
const rawStatus = createMemo(() => {
|
||||
const last = lastPart()
|
||||
if (!last) return undefined
|
||||
|
||||
if (last.type === "tool") {
|
||||
switch (last.tool) {
|
||||
case "task":
|
||||
return "Delegating work"
|
||||
case "todowrite":
|
||||
case "todoread":
|
||||
return "Planning next steps"
|
||||
case "read":
|
||||
return "Gathering context"
|
||||
case "list":
|
||||
case "grep":
|
||||
case "glob":
|
||||
return "Searching the codebase"
|
||||
case "webfetch":
|
||||
return "Searching the web"
|
||||
case "edit":
|
||||
case "write":
|
||||
return "Making edits"
|
||||
case "bash":
|
||||
return "Running commands"
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if (last.type === "reasoning") {
|
||||
const text = last.text ?? ""
|
||||
const match = text.trimStart().match(/^\*\*(.+?)\*\*/)
|
||||
if (match) return `Thinking · ${match[1].trim()}`
|
||||
return "Thinking"
|
||||
} else if (last.type === "text") {
|
||||
return "Gathering thoughts"
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
const hasDiffs = createMemo(() => message().summary?.diffs?.length)
|
||||
const isShellMode = createMemo(() => {
|
||||
if (parts().some((p) => p?.type !== "text" || !p?.synthetic)) return false
|
||||
if (assistantParts().length !== 1) return false
|
||||
const assistantPart = assistantParts()[0]
|
||||
if (assistantPart?.type !== "tool") return false
|
||||
if (assistantPart?.tool !== "bash") return false
|
||||
return true
|
||||
})
|
||||
const summary = () => message()?.summary?.body
|
||||
const response = () => {
|
||||
const part = lastTextPart()
|
||||
return part?.type === "text" ? (part as TextPart).text : undefined
|
||||
}
|
||||
const hasDiffs = () => message()?.summary?.diffs?.length
|
||||
|
||||
function duration() {
|
||||
const msg = message()
|
||||
if (!msg) return ""
|
||||
const completed = lastAssistantMessage()?.time.completed
|
||||
const from = DateTime.fromMillis(message().time.created)
|
||||
const from = DateTime.fromMillis(msg.time.created)
|
||||
const to = completed ? DateTime.fromMillis(completed) : DateTime.now()
|
||||
const interval = Interval.fromDateTimes(from, to)
|
||||
const unit: DurationUnit[] = interval.length("seconds") > 60 ? ["minutes", "seconds"] : ["seconds"]
|
||||
@@ -167,8 +239,11 @@ export function SessionTurn(
|
||||
stickyTitleRef: undefined as HTMLDivElement | undefined,
|
||||
stickyTriggerRef: undefined as HTMLDivElement | undefined,
|
||||
lastScrollTop: 0,
|
||||
lastScrollHeight: 0,
|
||||
lastContainerWidth: 0,
|
||||
autoScrolled: false,
|
||||
userScrolled: false,
|
||||
reflowing: false,
|
||||
stickyHeaderHeight: 0,
|
||||
retrySeconds: 0,
|
||||
status: rawStatus(),
|
||||
@@ -192,22 +267,53 @@ export function SessionTurn(
|
||||
|
||||
function handleScroll() {
|
||||
if (!scrollRef || store.autoScrolled) return
|
||||
|
||||
const scrollTop = scrollRef.scrollTop
|
||||
console.log("scrollTop", scrollTop)
|
||||
console.log("clientHeight", store.contentRef?.clientHeight)
|
||||
const scrollHeight = scrollRef.scrollHeight
|
||||
|
||||
if (store.reflowing) {
|
||||
batch(() => {
|
||||
setStore("lastScrollTop", scrollTop)
|
||||
setStore("lastScrollHeight", scrollHeight)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const scrollHeightChanged = Math.abs(scrollHeight - store.lastScrollHeight) > 10
|
||||
const scrollTopDelta = scrollTop - store.lastScrollTop
|
||||
|
||||
if (scrollHeightChanged && scrollTopDelta < 0) {
|
||||
const heightRatio = store.lastScrollHeight > 0 ? scrollHeight / store.lastScrollHeight : 1
|
||||
const expectedScrollTop = store.lastScrollTop * heightRatio
|
||||
if (Math.abs(scrollTop - expectedScrollTop) < 100) {
|
||||
batch(() => {
|
||||
setStore("lastScrollTop", scrollTop)
|
||||
setStore("lastScrollHeight", scrollHeight)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const reset = scrollTop <= 0 && store.lastScrollTop > 0 && working() && !store.userScrolled
|
||||
if (reset) {
|
||||
setStore("lastScrollTop", scrollTop)
|
||||
batch(() => {
|
||||
setStore("lastScrollTop", scrollTop)
|
||||
setStore("lastScrollHeight", scrollHeight)
|
||||
})
|
||||
requestAnimationFrame(scrollToBottom)
|
||||
return
|
||||
}
|
||||
const scrolledUp = scrollTop < store.lastScrollTop - 50
|
||||
|
||||
const scrolledUp = scrollTop < store.lastScrollTop - 50 && !scrollHeightChanged
|
||||
if (scrolledUp && working()) {
|
||||
console.log("scrolled up")
|
||||
setStore("userScrolled", true)
|
||||
props.onUserInteracted?.()
|
||||
}
|
||||
setStore("lastScrollTop", scrollTop)
|
||||
|
||||
batch(() => {
|
||||
setStore("lastScrollTop", scrollTop)
|
||||
setStore("lastScrollHeight", scrollHeight)
|
||||
})
|
||||
}
|
||||
|
||||
function handleInteraction() {
|
||||
@@ -225,13 +331,33 @@ export function SessionTurn(
|
||||
requestAnimationFrame(() => {
|
||||
batch(() => {
|
||||
setStore("lastScrollTop", scrollRef?.scrollTop ?? 0)
|
||||
setStore("lastScrollHeight", scrollRef?.scrollHeight ?? 0)
|
||||
setStore("autoScrolled", false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
createResizeObserver(() => store.contentRef, scrollToBottom)
|
||||
createResizeObserver(
|
||||
() => store.contentRef,
|
||||
({ width }) => {
|
||||
const widthChanged = Math.abs(width - store.lastContainerWidth) > 5
|
||||
if (widthChanged && store.lastContainerWidth > 0) {
|
||||
setStore("reflowing", true)
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setStore("reflowing", false)
|
||||
if (working() && !store.userScrolled) {
|
||||
scrollToBottom()
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (!store.reflowing) {
|
||||
scrollToBottom()
|
||||
}
|
||||
setStore("lastContainerWidth", width)
|
||||
},
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (!working()) setStore("userScrolled", false)
|
||||
@@ -288,184 +414,201 @@ export function SessionTurn(
|
||||
<div data-component="session-turn" class={props.classes?.root}>
|
||||
<div ref={scrollRef} onScroll={handleScroll} data-slot="session-turn-content" class={props.classes?.content}>
|
||||
<div onClick={handleInteraction}>
|
||||
<div
|
||||
ref={(el) => setStore("contentRef", el)}
|
||||
data-message={message().id}
|
||||
data-slot="session-turn-message-container"
|
||||
class={props.classes?.container}
|
||||
style={{ "--sticky-header-height": `${store.stickyHeaderHeight}px` }}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={isShellMode()}>
|
||||
<Part part={assistantParts()[0]} message={message()} defaultOpen />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
{/* Title (sticky) */}
|
||||
<div ref={(el) => setStore("stickyTitleRef", el)} data-slot="session-turn-sticky-title">
|
||||
<div data-slot="session-turn-message-header">
|
||||
<div data-slot="session-turn-message-title">
|
||||
<Switch>
|
||||
<Match when={working()}>
|
||||
<Typewriter as="h1" text={message().summary?.title} data-slot="session-turn-typewriter" />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<h1>{message().summary?.title}</h1>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* User Message */}
|
||||
<div data-slot="session-turn-message-content">
|
||||
<Message message={message()} parts={parts()} />
|
||||
</div>
|
||||
{/* Trigger (sticky) */}
|
||||
<Show when={working() || hasSteps()}>
|
||||
<div ref={(el) => setStore("stickyTriggerRef", el)} data-slot="session-turn-response-trigger">
|
||||
<Button
|
||||
data-expandable={assistantMessages().length > 0}
|
||||
data-slot="session-turn-collapsible-trigger-content"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
onClick={props.onStepsExpandedToggle ?? (() => {})}
|
||||
>
|
||||
<Show when={working()}>
|
||||
<Spinner />
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={retry()}>
|
||||
<span data-slot="session-turn-retry-message">
|
||||
{(() => {
|
||||
const r = retry()
|
||||
if (!r) return ""
|
||||
return r.message.length > 60 ? r.message.slice(0, 60) + "..." : r.message
|
||||
})()}
|
||||
</span>
|
||||
<span data-slot="session-turn-retry-seconds">
|
||||
· retrying {store.retrySeconds > 0 ? `in ${store.retrySeconds}s ` : ""}
|
||||
</span>
|
||||
<span data-slot="session-turn-retry-attempt">(#{retry()?.attempt})</span>
|
||||
</Match>
|
||||
<Match when={working()}>{store.status ?? "Considering next steps"}</Match>
|
||||
<Match when={props.stepsExpanded}>Hide steps</Match>
|
||||
<Match when={!props.stepsExpanded}>Show steps</Match>
|
||||
</Switch>
|
||||
<span>·</span>
|
||||
<span>{store.duration}</span>
|
||||
<Show when={assistantMessages().length > 0}>
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</Show>
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
{/* Response */}
|
||||
<Show when={props.stepsExpanded && assistantMessages().length > 0}>
|
||||
<div data-slot="session-turn-collapsible-content-inner">
|
||||
<For each={assistantMessages()}>
|
||||
{(assistantMessage) => {
|
||||
const parts = createMemo(() => data.store.part[assistantMessage.id] ?? [])
|
||||
const last = createMemo(() =>
|
||||
parts()
|
||||
.filter((p) => p?.type === "text")
|
||||
.at(-1),
|
||||
)
|
||||
return (
|
||||
<Show when={message()}>
|
||||
{(msg) => (
|
||||
<div
|
||||
ref={(el) => setStore("contentRef", el)}
|
||||
data-message={msg().id}
|
||||
data-slot="session-turn-message-container"
|
||||
class={props.classes?.container}
|
||||
style={{ "--sticky-header-height": `${store.stickyHeaderHeight}px` }}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={isShellMode()}>
|
||||
<Part part={assistantParts()[0]} message={msg()} defaultOpen />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
{/* Title (sticky) */}
|
||||
<div ref={(el) => setStore("stickyTitleRef", el)} data-slot="session-turn-sticky-title">
|
||||
<div data-slot="session-turn-message-header">
|
||||
<div data-slot="session-turn-message-title">
|
||||
<Switch>
|
||||
<Match when={response() && lastTextPart()?.id === last()?.id}>
|
||||
<Message message={assistantMessage} parts={parts().filter((p) => p?.id !== last()?.id)} />
|
||||
<Match when={working()}>
|
||||
<Typewriter as="h1" text={msg().summary?.title} data-slot="session-turn-typewriter" />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<Message message={assistantMessage} parts={parts()} />
|
||||
<h1>{msg().summary?.title}</h1>
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
<Show when={error()}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* User Message */}
|
||||
<div data-slot="session-turn-message-content">
|
||||
<Message message={msg()} parts={parts()} />
|
||||
</div>
|
||||
{/* Trigger (sticky) */}
|
||||
<Show when={working() || hasSteps()}>
|
||||
<div ref={(el) => setStore("stickyTriggerRef", el)} data-slot="session-turn-response-trigger">
|
||||
<Button
|
||||
data-expandable={assistantMessages().length > 0}
|
||||
data-slot="session-turn-collapsible-trigger-content"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
onClick={props.onStepsExpandedToggle ?? (() => {})}
|
||||
>
|
||||
<Show when={working()}>
|
||||
<Spinner />
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={retry()}>
|
||||
<span data-slot="session-turn-retry-message">
|
||||
{(() => {
|
||||
const r = retry()
|
||||
if (!r) return ""
|
||||
return r.message.length > 60 ? r.message.slice(0, 60) + "..." : r.message
|
||||
})()}
|
||||
</span>
|
||||
<span data-slot="session-turn-retry-seconds">
|
||||
· retrying {store.retrySeconds > 0 ? `in ${store.retrySeconds}s ` : ""}
|
||||
</span>
|
||||
<span data-slot="session-turn-retry-attempt">(#{retry()?.attempt})</span>
|
||||
</Match>
|
||||
<Match when={working()}>{store.status ?? "Considering next steps"}</Match>
|
||||
<Match when={props.stepsExpanded}>Hide steps</Match>
|
||||
<Match when={!props.stepsExpanded}>Show steps</Match>
|
||||
</Switch>
|
||||
<span>·</span>
|
||||
<span>{store.duration}</span>
|
||||
<Show when={assistantMessages().length > 0}>
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</Show>
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
{/* Response */}
|
||||
<Show when={props.stepsExpanded && assistantMessages().length > 0}>
|
||||
<div data-slot="session-turn-collapsible-content-inner">
|
||||
<For each={assistantMessages()}>
|
||||
{(assistantMessage) => {
|
||||
const parts = createMemo(() => data.store.part[assistantMessage.id] ?? [])
|
||||
const last = createMemo(() =>
|
||||
parts()
|
||||
.filter((p) => p?.type === "text")
|
||||
.at(-1),
|
||||
)
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={!summary() && response() && lastTextPart()?.id === last()?.id}>
|
||||
<Message
|
||||
message={assistantMessage}
|
||||
parts={parts().filter((p) => p?.id !== last()?.id)}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<Message message={assistantMessage} parts={parts()} />
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
<Show when={error()}>
|
||||
<Card variant="error" class="error-card">
|
||||
{error()?.data?.message as string}
|
||||
</Card>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
{/* Summary */}
|
||||
<Show when={!working()}>
|
||||
<div data-slot="session-turn-summary-section">
|
||||
<div data-slot="session-turn-summary-header">
|
||||
<Switch>
|
||||
<Match when={summary()}>
|
||||
{(summary) => (
|
||||
<>
|
||||
<h2 data-slot="session-turn-summary-title">Summary</h2>
|
||||
<Markdown
|
||||
data-slot="session-turn-markdown"
|
||||
data-diffs={hasDiffs()}
|
||||
text={summary()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={response()}>
|
||||
{(response) => (
|
||||
<>
|
||||
<h2 data-slot="session-turn-summary-title">Response</h2>
|
||||
<Markdown
|
||||
data-slot="session-turn-markdown"
|
||||
data-diffs={hasDiffs()}
|
||||
text={response()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<Accordion data-slot="session-turn-accordion" multiple>
|
||||
<For each={msg().summary?.diffs ?? []}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<StickyAccordionHeader>
|
||||
<Accordion.Trigger>
|
||||
<div data-slot="session-turn-accordion-trigger-content">
|
||||
<div data-slot="session-turn-file-info">
|
||||
<FileIcon
|
||||
node={{ path: diff.file, type: "file" }}
|
||||
data-slot="session-turn-file-icon"
|
||||
/>
|
||||
<div data-slot="session-turn-file-path">
|
||||
<Show when={diff.file.includes("/")}>
|
||||
<span data-slot="session-turn-directory">
|
||||
{getDirectory(diff.file)}‎
|
||||
</span>
|
||||
</Show>
|
||||
<span data-slot="session-turn-filename">{getFilename(diff.file)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-slot="session-turn-accordion-actions">
|
||||
<DiffChanges changes={diff} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content data-slot="session-turn-accordion-content">
|
||||
<Dynamic
|
||||
component={diffComponent}
|
||||
before={{
|
||||
name: diff.file!,
|
||||
contents: diff.before!,
|
||||
cacheKey: checksum(diff.before!),
|
||||
}}
|
||||
after={{
|
||||
name: diff.file!,
|
||||
contents: diff.after!,
|
||||
cacheKey: checksum(diff.after!),
|
||||
}}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={error() && !props.stepsExpanded}>
|
||||
<Card variant="error" class="error-card">
|
||||
{error()?.data?.message as string}
|
||||
</Card>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
{/* Summary */}
|
||||
<Show when={!working()}>
|
||||
<div data-slot="session-turn-summary-section">
|
||||
<div data-slot="session-turn-summary-header">
|
||||
<Switch>
|
||||
<Match when={summary()}>
|
||||
{(summary) => (
|
||||
<>
|
||||
<h2 data-slot="session-turn-summary-title">Summary</h2>
|
||||
<Markdown data-slot="session-turn-markdown" data-diffs={hasDiffs()} text={summary()} />
|
||||
</>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={response()}>
|
||||
{(response) => (
|
||||
<>
|
||||
<h2 data-slot="session-turn-summary-title">Response</h2>
|
||||
<Markdown data-slot="session-turn-markdown" data-diffs={hasDiffs()} text={response()} />
|
||||
</>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<Accordion data-slot="session-turn-accordion" multiple>
|
||||
<For each={message().summary?.diffs ?? []}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<StickyAccordionHeader>
|
||||
<Accordion.Trigger>
|
||||
<div data-slot="session-turn-accordion-trigger-content">
|
||||
<div data-slot="session-turn-file-info">
|
||||
<FileIcon
|
||||
node={{ path: diff.file, type: "file" }}
|
||||
data-slot="session-turn-file-icon"
|
||||
/>
|
||||
<div data-slot="session-turn-file-path">
|
||||
<Show when={diff.file.includes("/")}>
|
||||
<span data-slot="session-turn-directory">{getDirectory(diff.file)}‎</span>
|
||||
</Show>
|
||||
<span data-slot="session-turn-filename">{getFilename(diff.file)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-slot="session-turn-accordion-actions">
|
||||
<DiffChanges changes={diff} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content data-slot="session-turn-accordion-content">
|
||||
<Dynamic
|
||||
component={diffComponent}
|
||||
before={{
|
||||
name: diff.file!,
|
||||
contents: diff.before!,
|
||||
cacheKey: checksum(diff.before!),
|
||||
}}
|
||||
after={{
|
||||
name: diff.file!,
|
||||
contents: diff.after!,
|
||||
cacheKey: checksum(diff.after!),
|
||||
}}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={error() && !props.stepsExpanded}>
|
||||
<Card variant="error" class="error-card">
|
||||
{error()?.data?.message as string}
|
||||
</Card>
|
||||
</Show>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Switch as Kobalte } from "@kobalte/core/switch"
|
||||
import { children, Show, splitProps } from "solid-js"
|
||||
import { Show, splitProps } from "solid-js"
|
||||
import type { ComponentProps, ParentProps } from "solid-js"
|
||||
|
||||
export interface SwitchProps extends ParentProps<ComponentProps<typeof Kobalte>> {
|
||||
@@ -9,13 +9,12 @@ export interface SwitchProps extends ParentProps<ComponentProps<typeof Kobalte>>
|
||||
|
||||
export function Switch(props: SwitchProps) {
|
||||
const [local, others] = splitProps(props, ["children", "class", "hideLabel", "description"])
|
||||
const resolved = children(() => local.children)
|
||||
return (
|
||||
<Kobalte {...others} data-component="switch">
|
||||
<Kobalte.Input data-slot="switch-input" />
|
||||
<Show when={resolved()}>
|
||||
<Show when={local.children}>
|
||||
<Kobalte.Label data-slot="switch-label" classList={{ "sr-only": local.hideLabel }}>
|
||||
{resolved()}
|
||||
{local.children}
|
||||
</Kobalte.Label>
|
||||
</Show>
|
||||
<Show when={local.description}>
|
||||
|
||||
@@ -24,6 +24,13 @@ type Data = {
|
||||
export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
name: "Data",
|
||||
init: (props: { data: Data; directory: string }) => {
|
||||
return { store: props.data, directory: props.directory }
|
||||
return {
|
||||
get store() {
|
||||
return props.data
|
||||
},
|
||||
get directory() {
|
||||
return props.directory
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
export interface RetryOptions {
|
||||
attempts?: number
|
||||
delay?: number
|
||||
factor?: number
|
||||
maxDelay?: number
|
||||
retryIf?: (error: unknown) => boolean
|
||||
}
|
||||
|
||||
const TRANSIENT_MESSAGES = [
|
||||
"load failed",
|
||||
"network connection was lost",
|
||||
"network request failed",
|
||||
"failed to fetch",
|
||||
"econnreset",
|
||||
"econnrefused",
|
||||
"etimedout",
|
||||
"socket hang up",
|
||||
]
|
||||
|
||||
function isTransientError(error: unknown): boolean {
|
||||
if (!error) return false
|
||||
const message = String(error instanceof Error ? error.message : error).toLowerCase()
|
||||
return TRANSIENT_MESSAGES.some((m) => message.includes(m))
|
||||
}
|
||||
|
||||
export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
|
||||
const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = isTransientError } = options
|
||||
|
||||
let lastError: unknown
|
||||
for (let attempt = 0; attempt < attempts; attempt++) {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
if (attempt === attempts - 1 || !retryIf(error)) throw error
|
||||
const wait = Math.min(delay * Math.pow(factor, attempt), maxDelay)
|
||||
await new Promise((resolve) => setTimeout(resolve, wait))
|
||||
}
|
||||
}
|
||||
throw lastError
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -133,9 +133,9 @@ if (image) {
|
||||
</div>
|
||||
<div class="col4">
|
||||
<h3>Mise</h3>
|
||||
<button class="command" data-command="mise use -g ubi:sst/opencode">
|
||||
<button class="command" data-command="mise use -g github:sst/opencode">
|
||||
<code>
|
||||
<span>mise use -g</span> <span class="highlight">ubi:sst/opencode</span>
|
||||
<span>mise use -g</span> <span class="highlight">github:sst/opencode</span>
|
||||
</code>
|
||||
<span class="copy">
|
||||
<CopyIcon />
|
||||
|
||||
@@ -331,6 +331,8 @@ If you don’t specify a model, primary agents use the [model globally configure
|
||||
}
|
||||
```
|
||||
|
||||
The model ID in your OpenCode config uses the format `provider/model-id`. For example, if you're using [OpenCode Zen](/docs/zen), you would use `opencode/gpt-5.1-codex` for GPT 5.1 Codex.
|
||||
|
||||
---
|
||||
|
||||
### Tools
|
||||
|
||||
@@ -194,6 +194,23 @@ You can also define agents using markdown files in `~/.config/opencode/agent/` o
|
||||
|
||||
---
|
||||
|
||||
### Default agent
|
||||
|
||||
You can set the default agent using the `default_agent` option. This determines which agent is used when none is explicitly specified.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"default_agent": "plan"
|
||||
}
|
||||
```
|
||||
|
||||
The default agent must be a primary agent (not a subagent). This can be a built-in agent like `"build"` or `"plan"`, or a [custom agent](/docs/agents) you've defined. If the specified agent doesn't exist or is a subagent, OpenCode will fall back to `"build"` with a warning.
|
||||
|
||||
This setting applies across all interfaces: TUI, CLI (`opencode run`), desktop app, and GitHub Action.
|
||||
|
||||
---
|
||||
|
||||
### Sharing
|
||||
|
||||
You can configure the [share](/docs/share) feature through the `share` option.
|
||||
|
||||
@@ -81,6 +81,7 @@ Or you can set it up manually.
|
||||
## Configuration
|
||||
|
||||
- `model`: The model to use with OpenCode. Takes the format of `provider/model`. This is **required**.
|
||||
- `agent`: The agent to use. Must be a primary agent. Falls back to `default_agent` from config or `"build"` if not found.
|
||||
- `share`: Whether to share the OpenCode session. Defaults to **true** for public repositories.
|
||||
- `prompt`: Optional custom prompt to override the default behavior. Use this to customize how OpenCode processes requests.
|
||||
- `token`: Optional GitHub access token for performing operations such as creating comments, committing changes, and opening pull requests. By default, OpenCode uses the installation access token from the OpenCode GitHub App, so commits, comments, and pull requests appear as coming from the app.
|
||||
|
||||
@@ -109,7 +109,7 @@ You can also install it with the following commands:
|
||||
- **Using Mise**
|
||||
|
||||
```bash
|
||||
mise use -g ubi:sst/opencode
|
||||
mise use -g github:sst/opencode
|
||||
```
|
||||
|
||||
- **Using Docker**
|
||||
|
||||
@@ -112,3 +112,44 @@ You can disable a keybind by adding the key to your config with a value of "none
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shift+Enter
|
||||
|
||||
Some terminals don't send modifier keys with Enter by default. You may need to configure your terminal to send `Shift+Enter` as an escape sequence.
|
||||
|
||||
### Windows Terminal
|
||||
|
||||
Open your `settings.json` at:
|
||||
|
||||
```
|
||||
%LOCALAPPDATA%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json
|
||||
```
|
||||
|
||||
Add this to the root-level `actions` array:
|
||||
|
||||
```json
|
||||
"actions": [
|
||||
{
|
||||
"command": {
|
||||
"action": "sendInput",
|
||||
"input": "\u001b[13;2u"
|
||||
},
|
||||
"id": "User.sendInput.ShiftEnterCustom"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Add this to the root-level `keybindings` array:
|
||||
|
||||
```json
|
||||
"keybindings": [
|
||||
{
|
||||
"keys": "shift+enter",
|
||||
"id": "User.sendInput.ShiftEnterCustom"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Save the file and restart Windows Terminal or open a new tab.
|
||||
|
||||
@@ -60,7 +60,7 @@ OpenCode config.
|
||||
}
|
||||
```
|
||||
|
||||
Here the full ID is `provider_id/model_id`.
|
||||
Here the full ID is `provider_id/model_id`. For example, if you're using [OpenCode Zen](/docs/zen), you would use `opencode/gpt-5.1-codex` for GPT 5.1 Codex.
|
||||
|
||||
If you've configured a [custom provider](/docs/providers#custom), the `provider_id` is key from the `provider` part of your config, and the `model_id` is the key from `provider.models`.
|
||||
|
||||
@@ -117,6 +117,7 @@ You can also define custom models that extend built-in ones and can optionally u
|
||||
"models": {
|
||||
"gpt-5-high": {
|
||||
"id": "gpt-5",
|
||||
"name": "MyGPT5 (High Reasoning)",
|
||||
"options": {
|
||||
"reasoningEffort": "high",
|
||||
"textVerbosity": "low",
|
||||
@@ -125,6 +126,7 @@ You can also define custom models that extend built-in ones and can optionally u
|
||||
},
|
||||
"gpt-5-low": {
|
||||
"id": "gpt-5",
|
||||
"name": "MyGPT5 (Low Reasoning)",
|
||||
"options": {
|
||||
"reasoningEffort": "low",
|
||||
"textVerbosity": "low",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { APIRoute } from "astro"
|
||||
import { getCollection } from "astro:content"
|
||||
|
||||
export const GET: APIRoute = async ({ params }) => {
|
||||
const slug = params.slug || "index"
|
||||
const docs = await getCollection("docs")
|
||||
const doc = docs.find((d) => d.id === slug)
|
||||
|
||||
if (!doc) {
|
||||
return new Response("Not found", { status: 404 })
|
||||
}
|
||||
|
||||
return new Response(doc.body, {
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import { createOpencode } from "@opencode-ai/sdk"
|
||||
|
||||
const TEAM = [
|
||||
"actions-user",
|
||||
"opencode",
|
||||
"rekram1-node",
|
||||
"thdxr",
|
||||
"kommander",
|
||||
"jayair",
|
||||
"fwang",
|
||||
"adamdotdevin",
|
||||
"iamdavidhill",
|
||||
"opencode-agent[bot]",
|
||||
]
|
||||
|
||||
const MODEL = "gemini-3-flash"
|
||||
|
||||
function getAreaFromPath(file: string): string {
|
||||
if (file.startsWith("packages/")) {
|
||||
const parts = file.replace("packages/", "").split("/")
|
||||
if (parts[0] === "extensions" && parts[1]) return `extensions/${parts[1]}`
|
||||
return parts[0] || "other"
|
||||
}
|
||||
if (file.startsWith("sdks/")) {
|
||||
const name = file.replace("sdks/", "").split("/")[0] || "other"
|
||||
return `extensions/${name}`
|
||||
}
|
||||
const rootDir = file.split("/")[0]
|
||||
if (rootDir && !rootDir.includes(".")) return rootDir
|
||||
return "other"
|
||||
}
|
||||
|
||||
function buildPrompt(previous: string, commits: string): string {
|
||||
return `
|
||||
Analyze these commits and generate a changelog of all notable user facing changes, grouped by area.
|
||||
|
||||
Each commit below includes:
|
||||
- [author: username] showing the GitHub username of the commit author
|
||||
- [areas: ...] showing which areas of the codebase were modified
|
||||
|
||||
Commits between ${previous} and HEAD:
|
||||
${commits}
|
||||
|
||||
Group the changes into these categories based on the [areas: ...] tags (omit any category with no changes):
|
||||
- **TUI**: Changes to "opencode" area (the terminal/CLI interface)
|
||||
- **Desktop**: Changes to "desktop" or "tauri" areas (the desktop application)
|
||||
- **SDK**: Changes to "sdk" or "plugin" areas (the SDK and plugin system)
|
||||
- **Extensions**: Changes to "extensions/zed", "extensions/vscode", or "github" areas (editor extensions and GitHub Action)
|
||||
- **Other**: Any user-facing changes that don't fit the above categories
|
||||
|
||||
Excluded areas (omit these entirely unless they contain user-facing changes like refactors that may affect behavior):
|
||||
- "nix", "infra", "script" - CI/build infrastructure
|
||||
- "ui", "docs", "web", "console", "enterprise", "function", "util", "identity", "slack" - internal packages
|
||||
|
||||
Rules:
|
||||
- Use the [areas: ...] tags to determine the correct category. If a commit touches multiple areas, put it in the most relevant user-facing category.
|
||||
- ONLY include commits that have user-facing impact. Omit purely internal changes (CI, build scripts, internal tooling).
|
||||
- However, DO include refactors that touch user-facing code - refactors can introduce bugs or change behavior.
|
||||
- Do NOT make general statements about "improvements", be very specific about what was changed.
|
||||
- For commits that are already well-written and descriptive, avoid rewording them. Simply capitalize the first letter, fix any misspellings, and ensure proper English grammar.
|
||||
- DO NOT read any other commits than the ones listed above (THIS IS IMPORTANT TO AVOID DUPLICATING THINGS IN OUR CHANGELOG).
|
||||
- If a commit was made and then reverted do not include it in the changelog. If the commits only include a revert but not the original commit, then include the revert in the changelog.
|
||||
- Omit categories that have no changes.
|
||||
- For community contributors: if the [author: username] is NOT in the team list, add (@username) at the end of the changelog entry. This is REQUIRED for all non-team contributors.
|
||||
- The team members are: ${TEAM.join(", ")}. Do NOT add @ mentions for team members.
|
||||
|
||||
IMPORTANT: ONLY return the grouped changelog, do not include any other information. Do not include a preamble like "Based on my analysis..." or "Here is the changelog..."
|
||||
|
||||
<example>
|
||||
## TUI
|
||||
- Added experimental support for the Ty language server (@OpeOginni)
|
||||
- Added /fork slash command for keyboard-friendly session forking (@ariane-emory)
|
||||
- Increased retry attempts for failed requests
|
||||
- Fixed model validation before executing slash commands (@devxoul)
|
||||
|
||||
## Desktop
|
||||
- Added shell mode support
|
||||
- Fixed prompt history navigation and optimistic prompt duplication
|
||||
- Disabled pinch-to-zoom on Linux (@Brendonovich)
|
||||
|
||||
## Extensions
|
||||
- Added OIDC_BASE_URL support for custom GitHub App installations (@elithrar)
|
||||
</example>
|
||||
`
|
||||
}
|
||||
|
||||
function parseChangelog(raw: string): string[] {
|
||||
const lines: string[] = []
|
||||
for (const line of raw.split("\n")) {
|
||||
if (line.startsWith("## ")) {
|
||||
if (lines.length > 0) lines.push("")
|
||||
lines.push(line)
|
||||
} else if (line.startsWith("- ")) {
|
||||
lines.push(line)
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
function formatContributors(contributors: Map<string, string[]>): string[] {
|
||||
if (contributors.size === 0) return []
|
||||
const lines: string[] = []
|
||||
lines.push("")
|
||||
lines.push(`**Thank you to ${contributors.size} community contributor${contributors.size > 1 ? "s" : ""}:**`)
|
||||
for (const username of contributors.keys()) {
|
||||
lines.push(`- @${username}`)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a changelog for a release.
|
||||
*
|
||||
* Uses GitHub API for commit authors, git for file changes,
|
||||
* and Gemini Flash via opencode SDK for changelog generation.
|
||||
*
|
||||
* @param previous - The previous version tag (e.g. "v1.0.167")
|
||||
* @param current - The current ref (e.g. "HEAD" or "v1.0.168")
|
||||
* @returns Formatted changelog string ready for GitHub release notes
|
||||
*/
|
||||
export async function generateChangelog(previous: string, current: string): Promise<string> {
|
||||
// Fetch commit authors from GitHub API (hash -> login)
|
||||
const compare =
|
||||
await $`gh api "/repos/sst/opencode/compare/${previous}...${current}" --jq '.commits[] | {sha: .sha, login: .author.login, message: .commit.message}'`
|
||||
.text()
|
||||
.catch(() => "")
|
||||
|
||||
const authorByHash = new Map<string, string>()
|
||||
const contributors = new Map<string, string[]>()
|
||||
|
||||
for (const line of compare.split("\n").filter(Boolean)) {
|
||||
const { sha, login, message } = JSON.parse(line) as { sha: string; login: string | null; message: string }
|
||||
if (login) authorByHash.set(sha, login)
|
||||
|
||||
const title = message.split("\n")[0] || ""
|
||||
if (title.match(/^(ignore:|test:|chore:|ci:|release:)/i)) continue
|
||||
if (login && !TEAM.includes(login)) {
|
||||
if (!contributors.has(login)) contributors.set(login, [])
|
||||
contributors.get(login)?.push(title)
|
||||
}
|
||||
}
|
||||
|
||||
function findAuthor(shortHash: string): string | undefined {
|
||||
for (const [sha, login] of authorByHash) {
|
||||
if (sha.startsWith(shortHash)) return login
|
||||
}
|
||||
}
|
||||
|
||||
// Batch-fetch files for all commits (hash -> areas)
|
||||
const diffLog = await $`git log ${previous}..${current} --name-only --format="%h"`.text()
|
||||
const areasByHash = new Map<string, Set<string>>()
|
||||
let currentHash: string | null = null
|
||||
|
||||
for (const rawLine of diffLog.split("\n")) {
|
||||
const line = rawLine.trim()
|
||||
if (!line) continue
|
||||
if (/^[0-9a-f]{7,}$/i.test(line) && !line.includes("/")) {
|
||||
currentHash = line
|
||||
if (!areasByHash.has(currentHash)) areasByHash.set(currentHash, new Set())
|
||||
continue
|
||||
}
|
||||
if (currentHash) {
|
||||
areasByHash.get(currentHash)!.add(getAreaFromPath(line))
|
||||
}
|
||||
}
|
||||
|
||||
// Build commit lines with author and areas
|
||||
const log = await $`git log ${previous}..${current} --oneline --format="%h %s"`.text()
|
||||
const commitLines = log.split("\n").filter((line) => line && !line.match(/^\w+ (ignore:|test:|chore:|ci:|release:)/i))
|
||||
|
||||
const commitsWithMeta = commitLines
|
||||
.map((line) => {
|
||||
const hash = line.split(" ")[0]
|
||||
if (!hash) return null
|
||||
const author = findAuthor(hash)
|
||||
const authorStr = author ? ` [author: ${author}]` : ""
|
||||
const areas = areasByHash.get(hash)
|
||||
const areaStr = areas && areas.size > 0 ? ` [areas: ${[...areas].join(", ")}]` : " [areas: other]"
|
||||
return `${line}${authorStr}${areaStr}`
|
||||
})
|
||||
.filter(Boolean) as string[]
|
||||
|
||||
const commits = commitsWithMeta.join("\n")
|
||||
|
||||
if (!commits.trim()) {
|
||||
console.error("No commits found to generate changelog")
|
||||
}
|
||||
|
||||
// Generate changelog via LLM
|
||||
// different port to not conflict with dev running opencode
|
||||
let raw: string | undefined
|
||||
try {
|
||||
const opencode = await createOpencode({ port: 8192 })
|
||||
try {
|
||||
const session = await opencode.client.session.create()
|
||||
if (!session.data?.id) {
|
||||
console.error("Failed to create session:", session)
|
||||
throw new Error("Failed to create session")
|
||||
}
|
||||
const response = await opencode.client.session.prompt({
|
||||
path: { id: session.data.id },
|
||||
body: {
|
||||
model: { providerID: "opencode", modelID: MODEL },
|
||||
parts: [{ type: "text", text: buildPrompt(previous, commits) }],
|
||||
},
|
||||
})
|
||||
if (!response.data?.parts) {
|
||||
console.error("Empty response from LLM:", response)
|
||||
}
|
||||
raw = response.data?.parts?.find((y) => y.type === "text")?.text
|
||||
} finally {
|
||||
opencode.server.close()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to generate changelog via LLM:", err)
|
||||
}
|
||||
|
||||
const notes = parseChangelog(raw ?? "")
|
||||
notes.push(...formatContributors(contributors))
|
||||
|
||||
return notes.join("\n")
|
||||
}
|
||||
|
||||
// Standalone runner for local testing
|
||||
if (import.meta.main) {
|
||||
const [previous, current] = process.argv.slice(2)
|
||||
if (!previous || !current) {
|
||||
console.error("Usage: bun script/changelog.ts <previous> <current>")
|
||||
console.error("Example: bun script/changelog.ts v1.0.167 HEAD")
|
||||
process.exit(1)
|
||||
}
|
||||
const changelog = await generateChangelog(previous, current)
|
||||
console.log(changelog)
|
||||
process.exit(0)
|
||||
}
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { Script } from "@opencode-ai/script"
|
||||
import { $ } from "bun"
|
||||
|
||||
if (!Script.preview) {
|
||||
await $`gh release edit v${Script.version} --draft=false`
|
||||
}
|
||||
|
||||
await $`bun install`
|
||||
|
||||
await $`gh release download --pattern "opencode-linux-*64.tar.gz" --pattern "opencode-darwin-*64.zip" -D dist`
|
||||
|
||||
await import(`../packages/opencode/script/publish-registries.ts`)
|
||||
Executable
+201
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import { createOpencode } from "@opencode-ai/sdk"
|
||||
import { Script } from "@opencode-ai/script"
|
||||
|
||||
const notes = [] as string[]
|
||||
|
||||
console.log("=== publishing ===\n")
|
||||
|
||||
if (!Script.preview) {
|
||||
const previous = await fetch("https://registry.npmjs.org/opencode-ai/latest")
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
return res.json()
|
||||
})
|
||||
.then((data: any) => data.version)
|
||||
|
||||
const log =
|
||||
await $`git log v${previous}..HEAD --oneline --format="%h %s" -- packages/opencode packages/sdk packages/plugin packages/tauri packages/desktop`.text()
|
||||
|
||||
const commits = log
|
||||
.split("\n")
|
||||
.filter((line) => line && !line.match(/^\w+ (ignore:|test:|chore:|ci:)/i))
|
||||
.join("\n")
|
||||
|
||||
const opencode = await createOpencode()
|
||||
const session = await opencode.client.session.create()
|
||||
console.log("generating changelog since " + previous)
|
||||
|
||||
const team = [
|
||||
"actions-user",
|
||||
"opencode",
|
||||
"rekram1-node",
|
||||
"thdxr",
|
||||
"kommander",
|
||||
"jayair",
|
||||
"fwang",
|
||||
"adamdotdevin",
|
||||
"iamdavidhill",
|
||||
"opencode-agent[bot]",
|
||||
]
|
||||
|
||||
const raw = await opencode.client.session
|
||||
.prompt({
|
||||
path: {
|
||||
id: session.data!.id,
|
||||
},
|
||||
body: {
|
||||
model: {
|
||||
providerID: "opencode",
|
||||
modelID: "gemini-3-flash",
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
text: `
|
||||
Analyze these commits and generate a changelog of all notable user facing changes, grouped by area.
|
||||
|
||||
Each commit below includes:
|
||||
- [author: username] showing the GitHub username of the commit author
|
||||
- [areas: ...] showing which areas of the codebase were modified
|
||||
|
||||
Commits between ${previous} and HEAD:
|
||||
${commits}
|
||||
|
||||
Group the changes into these categories based on the [areas: ...] tags (omit any category with no changes):
|
||||
- **TUI**: Changes to "opencode" area (the terminal/CLI interface)
|
||||
- **Desktop**: Changes to "desktop" or "tauri" areas (the desktop application)
|
||||
- **SDK**: Changes to "sdk" or "plugin" areas (the SDK and plugin system)
|
||||
- **Extensions**: Changes to "extensions/zed", "extensions/vscode", or "github" areas (editor extensions and GitHub Action)
|
||||
- **Other**: Any user-facing changes that don't fit the above categories
|
||||
|
||||
Excluded areas (omit these entirely unless they contain user-facing changes like refactors that may affect behavior):
|
||||
- "nix", "infra", "script" - CI/build infrastructure
|
||||
- "ui", "docs", "web", "console", "enterprise", "function", "util", "identity", "slack" - internal packages
|
||||
|
||||
Rules:
|
||||
- Use the [areas: ...] tags to determine the correct category. If a commit touches multiple areas, put it in the most relevant user-facing category.
|
||||
- ONLY include commits that have user-facing impact. Omit purely internal changes (CI, build scripts, internal tooling).
|
||||
- However, DO include refactors that touch user-facing code - refactors can introduce bugs or change behavior.
|
||||
- Do NOT make general statements about "improvements", be very specific about what was changed.
|
||||
- For commits that are already well-written and descriptive, avoid rewording them. Simply capitalize the first letter, fix any misspellings, and ensure proper English grammar.
|
||||
- DO NOT read any other commits than the ones listed above (THIS IS IMPORTANT TO AVOID DUPLICATING THINGS IN OUR CHANGELOG).
|
||||
- If a commit was made and then reverted do not include it in the changelog. If the commits only include a revert but not the original commit, then include the revert in the changelog.
|
||||
- Omit categories that have no changes.
|
||||
- For community contributors: if the [author: username] is NOT in the team list, add (@username) at the end of the changelog entry. This is REQUIRED for all non-team contributors.
|
||||
- The team members are: ${team.join(", ")}. Do NOT add @ mentions for team members.
|
||||
|
||||
IMPORTANT: ONLY return the grouped changelog, do not include any other information. Do not include a preamble like "Based on my analysis..." or "Here is the changelog..."
|
||||
|
||||
<example>
|
||||
## TUI
|
||||
- Added experimental support for the Ty language server (@OpeOginni)
|
||||
- Added /fork slash command for keyboard-friendly session forking (@ariane-emory)
|
||||
- Increased retry attempts for failed requests
|
||||
- Fixed model validation before executing slash commands (@devxoul)
|
||||
|
||||
## Desktop
|
||||
- Added shell mode support
|
||||
- Fixed prompt history navigation and optimistic prompt duplication
|
||||
- Disabled pinch-to-zoom on Linux (@Brendonovich)
|
||||
|
||||
## Extensions
|
||||
- Added OIDC_BASE_URL support for custom GitHub App installations (@elithrar)
|
||||
</example>
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.then((x) => x.data?.parts?.find((y) => y.type === "text")?.text)
|
||||
for (const line of raw?.split("\n") ?? []) {
|
||||
if (line.startsWith("- ")) {
|
||||
notes.push(line)
|
||||
}
|
||||
}
|
||||
console.log("---- Generated Changelog ----")
|
||||
console.log(notes.join("\n"))
|
||||
console.log("-----------------------------")
|
||||
opencode.server.close()
|
||||
|
||||
const compare =
|
||||
await $`gh api "/repos/sst/opencode/compare/v${previous}...HEAD" --jq '.commits[] | {login: .author.login, message: .commit.message}'`.text()
|
||||
const contributors = new Map<string, string[]>()
|
||||
|
||||
for (const line of compare.split("\n").filter(Boolean)) {
|
||||
const { login, message } = JSON.parse(line) as { login: string | null; message: string }
|
||||
const title = message.split("\n")[0] ?? ""
|
||||
if (title.match(/^(ignore:|test:|chore:|ci:|release:)/i)) continue
|
||||
|
||||
if (login && !team.includes(login)) {
|
||||
if (!contributors.has(login)) contributors.set(login, [])
|
||||
contributors.get(login)?.push(title)
|
||||
}
|
||||
}
|
||||
|
||||
if (contributors.size > 0) {
|
||||
notes.push("")
|
||||
notes.push(`**Thank you to ${contributors.size} community contributor${contributors.size > 1 ? "s" : ""}:**`)
|
||||
for (const [username, userCommits] of contributors) {
|
||||
notes.push(`- @${username}:`)
|
||||
for (const commit of userCommits) {
|
||||
notes.push(` - ${commit}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pkgjsons = await Array.fromAsync(
|
||||
new Bun.Glob("**/package.json").scan({
|
||||
absolute: true,
|
||||
}),
|
||||
).then((arr) => arr.filter((x) => !x.includes("node_modules") && !x.includes("dist")))
|
||||
|
||||
for (const file of pkgjsons) {
|
||||
let pkg = await Bun.file(file).text()
|
||||
pkg = pkg.replaceAll(/"version": "[^"]+"/g, `"version": "${Script.version}"`)
|
||||
console.log("updated:", file)
|
||||
await Bun.file(file).write(pkg)
|
||||
}
|
||||
|
||||
const extensionToml = new URL("../packages/extensions/zed/extension.toml", import.meta.url).pathname
|
||||
let toml = await Bun.file(extensionToml).text()
|
||||
toml = toml.replace(/^version = "[^"]+"/m, `version = "${Script.version}"`)
|
||||
toml = toml.replaceAll(/releases\/download\/v[^/]+\//g, `releases/download/v${Script.version}/`)
|
||||
console.log("updated:", extensionToml)
|
||||
await Bun.file(extensionToml).write(toml)
|
||||
|
||||
await $`bun install`
|
||||
|
||||
console.log("\n=== opencode ===\n")
|
||||
await import(`../packages/opencode/script/publish.ts`)
|
||||
|
||||
console.log("\n=== sdk ===\n")
|
||||
await import(`../packages/sdk/js/script/publish.ts`)
|
||||
|
||||
console.log("\n=== plugin ===\n")
|
||||
await import(`../packages/plugin/script/publish.ts`)
|
||||
|
||||
const dir = new URL("..", import.meta.url).pathname
|
||||
process.chdir(dir)
|
||||
|
||||
let output = `version=${Script.version}\n`
|
||||
|
||||
if (!Script.preview) {
|
||||
await $`git commit -am "release: v${Script.version}"`
|
||||
await $`git tag v${Script.version}`
|
||||
await $`git fetch origin`
|
||||
await $`git cherry-pick HEAD..origin/dev`.nothrow()
|
||||
await $`git push origin HEAD --tags --no-verify --force-with-lease`
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000))
|
||||
await $`gh release create v${Script.version} -d --title "v${Script.version}" --notes ${notes.join("\n") || "No notable changes"} ./packages/opencode/dist/*.zip ./packages/opencode/dist/*.tar.gz`
|
||||
const release = await $`gh release view v${Script.version} --json id,tagName`.json()
|
||||
output += `release=${release.id}\n`
|
||||
output += `tag=${release.tagName}\n`
|
||||
}
|
||||
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
await Bun.write(process.env.GITHUB_OUTPUT, output)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import { Script } from "@opencode-ai/script"
|
||||
import { generateChangelog } from "./changelog"
|
||||
|
||||
let notes = ""
|
||||
|
||||
console.log("=== publishing ===\n")
|
||||
|
||||
if (!Script.preview) {
|
||||
const previous = await fetch("https://registry.npmjs.org/opencode-ai/latest")
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
return res.json()
|
||||
})
|
||||
.then((data: any) => data.version)
|
||||
|
||||
console.log("generating changelog since " + previous)
|
||||
notes = await generateChangelog(`v${previous}`, "HEAD")
|
||||
console.log("---- Generated Changelog ----")
|
||||
console.log(notes)
|
||||
console.log("-----------------------------")
|
||||
}
|
||||
|
||||
const pkgjsons = await Array.fromAsync(
|
||||
new Bun.Glob("**/package.json").scan({
|
||||
absolute: true,
|
||||
}),
|
||||
).then((arr) => arr.filter((x) => !x.includes("node_modules") && !x.includes("dist")))
|
||||
|
||||
for (const file of pkgjsons) {
|
||||
let pkg = await Bun.file(file).text()
|
||||
pkg = pkg.replaceAll(/"version": "[^"]+"/g, `"version": "${Script.version}"`)
|
||||
console.log("updated:", file)
|
||||
await Bun.file(file).write(pkg)
|
||||
}
|
||||
|
||||
const extensionToml = new URL("../packages/extensions/zed/extension.toml", import.meta.url).pathname
|
||||
let toml = await Bun.file(extensionToml).text()
|
||||
toml = toml.replace(/^version = "[^"]+"/m, `version = "${Script.version}"`)
|
||||
toml = toml.replaceAll(/releases\/download\/v[^/]+\//g, `releases/download/v${Script.version}/`)
|
||||
console.log("updated:", extensionToml)
|
||||
await Bun.file(extensionToml).write(toml)
|
||||
|
||||
await $`bun install`
|
||||
|
||||
console.log("\n=== opencode ===\n")
|
||||
await import(`../packages/opencode/script/publish.ts`)
|
||||
|
||||
console.log("\n=== sdk ===\n")
|
||||
await import(`../packages/sdk/js/script/publish.ts`)
|
||||
|
||||
console.log("\n=== plugin ===\n")
|
||||
await import(`../packages/plugin/script/publish.ts`)
|
||||
|
||||
const dir = new URL("..", import.meta.url).pathname
|
||||
process.chdir(dir)
|
||||
|
||||
if (!Script.preview) {
|
||||
await $`git commit -am "release: v${Script.version}"`
|
||||
await $`git tag v${Script.version}`
|
||||
await $`git fetch origin`
|
||||
await $`git cherry-pick HEAD..origin/dev`.nothrow()
|
||||
await $`git push origin HEAD --tags --no-verify --force-with-lease`
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000))
|
||||
await $`gh release create v${Script.version} -d --title "v${Script.version}" --notes ${notes || "No notable changes"} ./packages/opencode/dist/*.zip ./packages/opencode/dist/*.tar.gz`
|
||||
const release = await $`gh release view v${Script.version} --json id,tagName`.json()
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
await Bun.write(process.env.GITHUB_OUTPUT, `releaseId=${release.id}\ntagName=${release.tagName}\n`)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.0.170",
|
||||
"version": "1.0.181",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user