Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03f0b6dd7a | |||
| f7f3ed1fa9 |
@@ -16,8 +16,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
|
||||
@@ -165,7 +165,6 @@ jobs:
|
||||
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
|
||||
@@ -193,25 +192,16 @@ jobs:
|
||||
projectPath: packages/tauri
|
||||
uploadWorkflowArtifacts: true
|
||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }} --config src-tauri/tauri.prod.conf.json
|
||||
args: --target ${{ matrix.settings.target }}
|
||||
updaterJsonPreferNsis: true
|
||||
releaseId: ${{ needs.publish.outputs.releaseId }}
|
||||
tagName: ${{ needs.publish.outputs.tagName }}
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
releaseDraft: true
|
||||
|
||||
publish-release:
|
||||
needs:
|
||||
- publish
|
||||
- publish-tauri
|
||||
if: needs.publish.outputs.tagName
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.publish.outputs.tagName }}
|
||||
|
||||
- run: gh release edit ${{ needs.publish.outputs.tagName }} --draft=false
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- run: gh release edit ${{ steps.publish.outputs.tagName }} --draft=false
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
name: release-github-action
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- "github/**"
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
./github/script/release
|
||||
@@ -29,8 +29,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
mode: primary
|
||||
hidden: true
|
||||
model: opencode/claude-haiku-4-5
|
||||
model: opencode/gpt-5-nano
|
||||
tools:
|
||||
"*": false
|
||||
"github-triage": true
|
||||
@@ -10,70 +10,3 @@ tools:
|
||||
You are a triage agent responsible for triaging github issues.
|
||||
|
||||
Use your github-triage tool to triage issues.
|
||||
|
||||
## Labels
|
||||
|
||||
### windows
|
||||
|
||||
Use for any issue that is mentions windows (the OS). Be sure they are saying that they are on windows.
|
||||
|
||||
- Use if they mention WSL too
|
||||
|
||||
#### perf
|
||||
|
||||
Performance-related issues:
|
||||
|
||||
- Slow performance
|
||||
- High RAM usage
|
||||
- High CPU usage
|
||||
|
||||
**Only** add if it's likely a RAM or CPU issue. **Do not** add for LLM slowness.
|
||||
|
||||
#### desktop
|
||||
|
||||
Desktop app issues:
|
||||
|
||||
- `opencode web` command
|
||||
- The desktop app itself
|
||||
|
||||
**Only** add if it's specifically about the Desktop application or `opencode web` view. **Do not** add for terminal, TUI, or general opencode issues.
|
||||
|
||||
#### nix
|
||||
|
||||
**Only** add if the issue explicitly mentions nix.
|
||||
|
||||
#### zen
|
||||
|
||||
**Only** add if the issue mentions "zen" or "opencode zen". Zen is our gateway for coding models. **Do not** add for other gateways or inference providers.
|
||||
|
||||
If the issue doesn't have "zen" in it then don't add zen label
|
||||
|
||||
#### docs
|
||||
|
||||
Add if the issue requests better documentation or docs updates.
|
||||
|
||||
#### opentui
|
||||
|
||||
TUI issues potentially caused by our underlying TUI library:
|
||||
|
||||
- Keybindings not working
|
||||
- Scroll speed issues (too fast/slow/laggy)
|
||||
- Screen flickering
|
||||
- Crashes with opentui in the log
|
||||
|
||||
**Do not** add for general TUI bugs.
|
||||
|
||||
---
|
||||
|
||||
When assigning to people here are the following rules:
|
||||
|
||||
adamdotdev:
|
||||
ONLY assign adam if the issue will have the "desktop" label.
|
||||
|
||||
fwang:
|
||||
ONLY assign fwang if the issue will have the "zen" label.
|
||||
|
||||
jayair:
|
||||
ONLY assign jayair if the issue will have the "docs" label.
|
||||
|
||||
In all other cases use best judgment. Avoid assigning to kommander needlessly, when in doubt assign to rekram1-node.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/plugin": "1.0.143",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
|
||||
|
||||
"@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="],
|
||||
|
||||
"@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="],
|
||||
|
||||
"@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="],
|
||||
|
||||
"@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@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-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/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
|
||||
|
||||
"@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/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
|
||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.143", "", { "dependencies": { "@opencode-ai/sdk": "1.0.143", "zod": "4.1.8" } }, "sha512-yzaCmdazVJMDADJLbMM8KGp1X+Hd/HVyIXMlNt9qcvz/fcs/ET4EwHJsJaQi/9m/jLJ+plwBJAeIW08BMrECPg=="],
|
||||
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.143", "", {}, "sha512-dtmkBfJ7IIAHzL6KCzAlwc9GybfJONVeCsF6ePYySpkuhslDbRkZBJYb5vqGd1H5zdsgjc6JjuvmOf0rPWUL6A=="],
|
||||
|
||||
"before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
|
||||
|
||||
"fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
|
||||
|
||||
"universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
|
||||
|
||||
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
description: git commit and push
|
||||
model: opencode/glm-4.6
|
||||
subtask: true
|
||||
---
|
||||
|
||||
commit and push
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/plugin": "1.0.143"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/// <reference path="../env.d.ts" />
|
||||
import { Octokit } from "@octokit/rest"
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import DESCRIPTION from "./github-triage.txt"
|
||||
@@ -17,9 +16,9 @@ export default tool({
|
||||
.describe("The username of the assignee")
|
||||
.default("rekram1-node"),
|
||||
labels: tool.schema
|
||||
.array(tool.schema.enum(["nix", "opentui", "perf", "desktop", "zen", "docs", "windows"]))
|
||||
.array(tool.schema.enum(["nix", "opentui", "perf", "web", "zen", "docs"]))
|
||||
.describe("The labels(s) to add to the issue")
|
||||
.default([]),
|
||||
.optional(),
|
||||
},
|
||||
async execute(args) {
|
||||
const issue = getIssueNumber()
|
||||
@@ -29,18 +28,6 @@ export default tool({
|
||||
|
||||
const results: string[] = []
|
||||
|
||||
if (args.assignee === "adamdotdevin" && !args.labels.includes("desktop")) {
|
||||
throw new Error("Only desktop issues should be assigned to adamdotdevin")
|
||||
}
|
||||
|
||||
if (args.assignee === "fwang" && !args.labels.includes("zen")) {
|
||||
throw new Error("Only zen issues should be assigned to fwang")
|
||||
}
|
||||
|
||||
if (args.assignee === "kommander" && !args.labels.includes("opentui")) {
|
||||
throw new Error("Only opentui issues should be assigned to kommander")
|
||||
}
|
||||
|
||||
await octokit.rest.issues.addAssignees({
|
||||
owner,
|
||||
repo,
|
||||
@@ -49,14 +36,12 @@ export default tool({
|
||||
})
|
||||
results.push(`Assigned @${args.assignee} to issue #${issue}`)
|
||||
|
||||
const labels: string[] = args.labels.map((label) => (label === "desktop" ? "web" : label))
|
||||
|
||||
if (labels.length > 0) {
|
||||
if (args.labels && args.labels.length > 0) {
|
||||
await octokit.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue,
|
||||
labels,
|
||||
labels: args.labels,
|
||||
})
|
||||
results.push(`Added labels: ${args.labels.join(", ")}`)
|
||||
}
|
||||
|
||||
@@ -50,13 +50,9 @@ Then assign the issue to Him.
|
||||
|
||||
### rekram1-node
|
||||
|
||||
ALL BUGS SHOULD BE assigned to rekram1-node unless they have the `opentui` label.
|
||||
|
||||
Assign Aiden to an issue as a catch all, if you can't assign anyone else. Most of the time this will be bugs/polish things.
|
||||
If no one else makes sense to assign, assign rekram1-node to it.
|
||||
|
||||
Always assign to aiden if the issue mentions "acp", "zed", or model performance issues
|
||||
|
||||
## Breakdown of Labels:
|
||||
|
||||
### nix
|
||||
@@ -71,9 +67,9 @@ Anything relating to the TUI itself should have an opentui label
|
||||
|
||||
Anything related to slow performance, high ram, high cpu usage, or any other performance related issue should have a perf label
|
||||
|
||||
### desktop
|
||||
### web
|
||||
|
||||
Anything related to `opencode web` command or the desktop app should have a desktop label. Never add this label for anything terminal/tui related
|
||||
Anything related to `opencode web` or the desktop app should have a web label. Never add this label for anything terminal/tui related
|
||||
|
||||
### zen
|
||||
|
||||
@@ -82,7 +78,3 @@ Anything related to OpenCode Zen, billing, or model quality from Zen should have
|
||||
### docs
|
||||
|
||||
Anything related to the documentation should have a docs label
|
||||
|
||||
### windows
|
||||
|
||||
Use for any issue that involves the windows OS
|
||||
|
||||
@@ -37,22 +37,6 @@ nix run nixpkgs#opencode # or github:sst/opencode for latest dev branc
|
||||
> [!TIP]
|
||||
> Remove versions older than 0.1.x before installing.
|
||||
|
||||
### Desktop App (BETA)
|
||||
|
||||
OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/sst/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).
|
||||
|
||||
| Platform | 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`, or AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
brew install --cask opencode-desktop
|
||||
```
|
||||
|
||||
#### Installation Directory
|
||||
|
||||
The install script respects the following priority order for the installation path:
|
||||
|
||||
@@ -172,4 +172,3 @@
|
||||
| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
|
||||
| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
|
||||
| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
|
||||
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
|
||||
|
||||
@@ -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.165",
|
||||
"version": "1.0.163",
|
||||
"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.165",
|
||||
"version": "1.0.163",
|
||||
"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.165",
|
||||
"version": "1.0.163",
|
||||
"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.165",
|
||||
"version": "1.0.163",
|
||||
"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.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -137,7 +136,7 @@
|
||||
"@solid-primitives/media": "2.3.3",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solid-primitives/websocket": "1.3.1",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
@@ -171,7 +170,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -200,7 +199,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -216,7 +215,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -246,8 +245,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.0.0-20251211-4403a69a",
|
||||
"@opentui/solid": "0.0.0-20251211-4403a69a",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -308,7 +307,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -328,7 +327,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.88.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -339,7 +338,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -352,10 +351,9 @@
|
||||
},
|
||||
"packages/tauri": {
|
||||
"name": "@opencode-ai/tauri",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@opencode-ai/desktop": "workspace:*",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
@@ -378,7 +376,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -413,7 +411,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -424,7 +422,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -475,7 +473,6 @@
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/diffs": "1.0.0-beta.3",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
@@ -1100,11 +1097,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=="],
|
||||
|
||||
@@ -1112,7 +1109,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=="],
|
||||
|
||||
@@ -1160,21 +1157,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.0.0-20251211-4403a69a", "", { "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.0.0-20251211-4403a69a", "@opentui/core-darwin-x64": "0.0.0-20251211-4403a69a", "@opentui/core-linux-arm64": "0.0.0-20251211-4403a69a", "@opentui/core-linux-x64": "0.0.0-20251211-4403a69a", "@opentui/core-win32-arm64": "0.0.0-20251211-4403a69a", "@opentui/core-win32-x64": "0.0.0-20251211-4403a69a", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-wTZKcokyU9yiDqyC0Pvf9eRSdT73s4Ynerkit/z8Af++tynqrTlZHZCXK3o42Ff7itCSILmijcTU94n69aEypA=="],
|
||||
|
||||
"@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.0.0-20251211-4403a69a", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VAYjTa+Eiauy8gETXadD8y0PE6ppnKasDK1X354VoexZiWFR3r7rkL+TfDfk7whhqXDYyT44JDT1QmCAhVXRzQ=="],
|
||||
|
||||
"@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.0.0-20251211-4403a69a", "", { "os": "darwin", "cpu": "x64" }, "sha512-n9oVMpsojlILj1soORZzZ2Mjh8Zl73ZNcY7ot0iRmOjBDccrjDTsqKfxoGjKNd/xJSphLeu1LYGlcI5O5OczWQ=="],
|
||||
|
||||
"@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.0.0-20251211-4403a69a", "", { "os": "linux", "cpu": "arm64" }, "sha512-vf4eUjPMI4ANitK4MpTGenZFddKgQD/K21aN6cZjusnH3mTEJAoIR7GbNtMdz3qclU43ajpzTID9sAwhshwdVQ=="],
|
||||
|
||||
"@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.0.0-20251211-4403a69a", "", { "os": "linux", "cpu": "x64" }, "sha512-61635Up0YvVJ8gZ2eMiL1c8OfA+U6wAzT++LoaurNjbmsUAlKHws6MZdqTLw7aspJJVGsRFbA6d1Y+gXFxbDrQ=="],
|
||||
|
||||
"@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.0.0-20251211-4403a69a", "", { "os": "win32", "cpu": "arm64" }, "sha512-3lUddTJGKZ6uU388eU79MY//IEbgGENCITetDrrRp7v9L1AxMntE1ihf6HniziwBvKKJcsUfqLiJWcq0WPZw2w=="],
|
||||
|
||||
"@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.0.0-20251211-4403a69a", "", { "os": "win32", "cpu": "x64" }, "sha512-Xwc1gqYsn8UZNTzNKkigZozAhBNBGbfX2B/I/aSbyqL0h8+XIInOodI0urzJWc0B6aEv/IDiT6Rm3coXFikLIg=="],
|
||||
|
||||
"@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.0.0-20251211-4403a69a", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20251211-4403a69a", "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-vuLppAdd1Qgaqhie3q2TuEr+8udjT4d8uVg5arvCe1AUDVs19I8kvadVCfzGUVmtXgFIOEakbiv6AxDq5v9Zig=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -4084,9 +4081,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=="],
|
||||
|
||||
@@ -4098,8 +4095,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=="],
|
||||
@@ -4292,8 +4287,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=="],
|
||||
@@ -4306,6 +4299,10 @@
|
||||
|
||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
|
||||
"opentui-spinner/@opentui/core": ["@opentui/core@0.1.60", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.60", "@opentui/core-darwin-x64": "0.1.60", "@opentui/core-linux-arm64": "0.1.60", "@opentui/core-linux-x64": "0.1.60", "@opentui/core-win32-arm64": "0.1.60", "@opentui/core-win32-x64": "0.1.60", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-28jphd0AJo48uvEuKXcT9pJhgAu8I2rEJhPt25cc5ipJ2iw/eDk1uoxrbID80MPDqgOEzN21vXmzXwCd6ao+hg=="],
|
||||
|
||||
"opentui-spinner/@opentui/solid": ["@opentui/solid@0.1.60", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.60", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-pn91stzAHNGWaNL6h39q55bq3G1/DLqxKtT3wVsRAV68dHfPpwmqikX1nEJZK8OU84ZTPS9Ly9fz8po2Mot2uQ=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"parse-bmfont-xml/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
|
||||
@@ -4648,9 +4645,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=="],
|
||||
|
||||
@@ -4658,10 +4655,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=="],
|
||||
@@ -4876,10 +4869,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=="],
|
||||
@@ -4890,6 +4879,22 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.60", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N4feqnOBDA4O4yocpat5vOiV06HqJVwJGx8rEZE9DiOtl1i+1cPQ1Lx6+zWdLhbrVBJ0ENhb7Azox8sXkm/+5Q=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.60", "", { "os": "darwin", "cpu": "x64" }, "sha512-+z3q4WaoIs7ANU8+eTFlvnfCjAS81rk81TOdZm4TJ53Ti3/B+yheWtnV/mLpLLhvZDz2VUVxxRmfDrGMnJb4fQ=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.60", "", { "os": "linux", "cpu": "arm64" }, "sha512-/Q65sjqVGB9ygJ6lStI8n1X6RyfmJZC8XofRGEuFiMLiWcWC/xoBtztdL8LAIvHQy42y2+pl9zIiW0fWSQ0wjw=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.60", "", { "os": "linux", "cpu": "x64" }, "sha512-AegF+g7OguIpjZKN+PS55sc3ZFY6fj+fLwfETbSRGw6NqX+aiwpae0Y3gXX1s298Yq5yQEzMXnARTCJTGH4uzg=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.60", "", { "os": "win32", "cpu": "arm64" }, "sha512-fbkq8MOZJgT3r9q3JWqsfVxRpQ1SlbmhmvB35BzukXnZBK8eA178wbSadGH6irMDrkSIYye9WYddHI/iXjmgVQ=="],
|
||||
|
||||
"opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.60", "", { "os": "win32", "cpu": "x64" }, "sha512-OebCL7f9+CKodBw0G+NvKIcc74bl6/sBEHfb73cACdJDJKh+T3C3Vt9H3kQQ0m1C8wRAqX6rh706OArk1pUb2A=="],
|
||||
|
||||
"opentui-spinner/@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
||||
|
||||
"opentui-spinner/@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.9", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.1" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.8" }, "optionalPeers": ["solid-js"] }, "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw=="],
|
||||
|
||||
"parse-bmfont-xml/xml2js/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
|
||||
|
||||
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||
@@ -5020,10 +5025,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=="],
|
||||
@@ -5044,10 +5045,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=="],
|
||||
@@ -5076,6 +5073,8 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/raw-body/http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||
|
||||
"opentui-spinner/@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||
@@ -5140,18 +5139,10 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1765934234,
|
||||
"narHash": "sha256-pJjWUzNnjbIAMIc5gRFUuKCDQ9S1cuh3b2hKgA7Mc4A=",
|
||||
"lastModified": 1765772535,
|
||||
"narHash": "sha256-aq+dQoaPONOSjtFIBnAXseDm9TUhIbe215TPmkfMYww=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "af84f9d270d404c17699522fab95bbf928a2d92f",
|
||||
"rev": "09b8fda8959d761445f12b55f380d90375a1d6bb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -22,10 +22,6 @@ inputs:
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
mentions:
|
||||
description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/opencode,/oc'"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -61,4 +57,3 @@ runs:
|
||||
SHARE: ${{ inputs.share }}
|
||||
PROMPT: ${{ inputs.prompt }}
|
||||
USE_GITHUB_TOKEN: ${{ inputs.use_github_token }}
|
||||
MENTIONS: ${{ inputs.mentions }}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"nodeModules": "sha256-g6XHWk9IoDoeXbvENs+U2fqk185xKMLb0BRopCbXaIk="
|
||||
"nodeModules": "sha256-PyoVOza+3WnwZbtpPF6uSN1zkyLsSG2VsgBfIMvIFAs="
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/diffs": "1.0.0-beta.3",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"ai": "5.0.97",
|
||||
@@ -64,7 +63,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.165",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -110,13 +110,10 @@
|
||||
[data-slot="cta-button"] {
|
||||
background: var(--color-background-strong);
|
||||
color: var(--color-text-inverted);
|
||||
padding: 8px 16px 8px 10px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
@media (max-width: 55rem) {
|
||||
display: none;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { APIEvent } from "@solidjs/start"
|
||||
import { DownloadPlatform } from "./types"
|
||||
|
||||
const assetNames: Record<string, string> = {
|
||||
"darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg",
|
||||
"darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg",
|
||||
"windows-x64-nsis": "opencode-desktop-windows-x64.exe",
|
||||
"linux-x64-deb": "opencode-desktop-linux-amd64.deb",
|
||||
"linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm",
|
||||
} satisfies Record<DownloadPlatform, string>
|
||||
|
||||
// Doing this on the server lets us preserve the original name for platforms we don't care to rename for
|
||||
const downloadNames: Record<string, string> = {
|
||||
"darwin-aarch64-dmg": "OpenCode Desktop.dmg",
|
||||
"darwin-x64-dmg": "OpenCode Desktop.dmg",
|
||||
"windows-x64-nsis": "OpenCode Desktop Installer.exe",
|
||||
} satisfies { [K in DownloadPlatform]?: string }
|
||||
|
||||
export async function GET({ params: { platform } }: APIEvent) {
|
||||
const assetName = assetNames[platform]
|
||||
if (!assetName) return new Response("Not Found", { status: 404 })
|
||||
|
||||
const resp = await fetch(`https://github.com/sst/opencode/releases/latest/download/${assetName}`, {
|
||||
cf: {
|
||||
// in case gh releases has rate limits
|
||||
cacheTtl: 60 * 60 * 24,
|
||||
cacheEverything: true,
|
||||
},
|
||||
} as any)
|
||||
|
||||
const downloadName = downloadNames[platform]
|
||||
|
||||
const headers = new Headers(resp.headers)
|
||||
if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`)
|
||||
|
||||
return new Response(resp.body, { ...resp, headers })
|
||||
}
|
||||
@@ -8,51 +8,6 @@ import { Faq } from "~/component/faq"
|
||||
import desktopAppIcon from "../../asset/lander/opencode-desktop-icon.png"
|
||||
import { Legal } from "~/component/legal"
|
||||
import { config } from "~/config"
|
||||
import { createSignal, onMount, Show, JSX } from "solid-js"
|
||||
import { DownloadPlatform } from "./types"
|
||||
|
||||
type OS = "macOS" | "Windows" | "Linux" | null
|
||||
|
||||
function detectOS(): OS {
|
||||
if (typeof navigator === "undefined") return null
|
||||
const platform = navigator.platform.toLowerCase()
|
||||
const userAgent = navigator.userAgent.toLowerCase()
|
||||
|
||||
if (platform.includes("mac") || userAgent.includes("mac")) return "macOS"
|
||||
if (platform.includes("win") || userAgent.includes("win")) return "Windows"
|
||||
if (platform.includes("linux") || userAgent.includes("linux")) return "Linux"
|
||||
return null
|
||||
}
|
||||
|
||||
function getDownloadPlatform(os: OS): DownloadPlatform {
|
||||
switch (os) {
|
||||
case "macOS":
|
||||
return "darwin-aarch64-dmg"
|
||||
case "Windows":
|
||||
return "windows-x64-nsis"
|
||||
case "Linux":
|
||||
return "linux-x64-deb"
|
||||
default:
|
||||
return "darwin-aarch64-dmg"
|
||||
}
|
||||
}
|
||||
|
||||
function getDownloadHref(platform: DownloadPlatform) {
|
||||
return `/download/${platform}`
|
||||
}
|
||||
|
||||
function IconDownload(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function CopyStatus() {
|
||||
return (
|
||||
@@ -64,12 +19,7 @@ function CopyStatus() {
|
||||
}
|
||||
|
||||
export default function Download() {
|
||||
const [detectedOS, setDetectedOS] = createSignal<OS>(null)
|
||||
|
||||
onMount(() => {
|
||||
setDetectedOS(detectOS())
|
||||
})
|
||||
|
||||
const downloadUrl = "https://github.com/sst/opencode/releases/latest/download"
|
||||
const handleCopyClick = (command: string) => (event: Event) => {
|
||||
const button = event.currentTarget as HTMLButtonElement
|
||||
navigator.clipboard.writeText(command)
|
||||
@@ -94,12 +44,6 @@ export default function Download() {
|
||||
<div data-component="hero-text">
|
||||
<h1>Download OpenCode</h1>
|
||||
<p>Available in Beta for macOS, Windows, and Linux</p>
|
||||
<Show when={detectedOS()}>
|
||||
<a href={getDownloadHref(getDownloadPlatform(detectedOS()))} data-component="download-button">
|
||||
<IconDownload />
|
||||
Download for {detectedOS()}
|
||||
</a>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -149,12 +93,6 @@ export default function Download() {
|
||||
<span>[2]</span> OpenCode Desktop (Beta)
|
||||
</div>
|
||||
<div data-component="section-content">
|
||||
<button data-component="cli-row" onClick={handleCopyClick("brew install --cask opencode-desktop")}>
|
||||
<code>
|
||||
brew install --cask <strong>opencode-desktop</strong>
|
||||
</code>
|
||||
<CopyStatus />
|
||||
</button>
|
||||
<div data-component="download-row">
|
||||
<div data-component="download-info">
|
||||
<span data-slot="icon">
|
||||
@@ -169,7 +107,7 @@ export default function Download() {
|
||||
macOS (<span data-slot="hide-narrow">Apple </span>Silicon)
|
||||
</span>
|
||||
</div>
|
||||
<a href={getDownloadHref("darwin-aarch64-dmg")} data-component="action-button">
|
||||
<a href={downloadUrl + "/opencode-desktop-darwin-aarch64.dmg"} data-component="action-button">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
@@ -185,7 +123,7 @@ export default function Download() {
|
||||
</span>
|
||||
<span>macOS (Intel)</span>
|
||||
</div>
|
||||
<a href={getDownloadHref("darwin-x64-dmg")} data-component="action-button">
|
||||
<a href={downloadUrl + "/opencode-desktop-darwin-x64.dmg"} data-component="action-button">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
@@ -208,7 +146,7 @@ export default function Download() {
|
||||
</span>
|
||||
<span>Windows (x64)</span>
|
||||
</div>
|
||||
<a href={getDownloadHref("windows-x64-nsis")} data-component="action-button">
|
||||
<a href={downloadUrl + "/opencode-desktop-windows-x64.exe"} data-component="action-button">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
@@ -224,7 +162,7 @@ export default function Download() {
|
||||
</span>
|
||||
<span>Linux (.deb)</span>
|
||||
</div>
|
||||
<a href={getDownloadHref("linux-x64-deb")} data-component="action-button">
|
||||
<a href={downloadUrl + "/opencode-desktop-linux-amd64.deb"} data-component="action-button">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
@@ -240,7 +178,7 @@ export default function Download() {
|
||||
</span>
|
||||
<span>Linux (.rpm)</span>
|
||||
</div>
|
||||
<a href={getDownloadHref("linux-x64-rpm")} data-component="action-button">
|
||||
<a href={downloadUrl + "/opencode-desktop-linux-x86_64.rpm"} data-component="action-button">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export type DownloadPlatform = `darwin-${"x64" | "aarch64"}-dmg` | "windows-x64-nsis" | `linux-x64-${"deb" | "rpm"}`
|
||||
@@ -110,13 +110,10 @@
|
||||
[data-slot="cta-button"] {
|
||||
background: var(--color-background-strong);
|
||||
color: var(--color-text-inverted);
|
||||
padding: 8px 16px 8px 10px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
@media (max-width: 55rem) {
|
||||
display: none;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -40,7 +40,7 @@
|
||||
"@solid-primitives/media": "2.3.3",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solid-primitives/websocket": "1.3.1",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
|
||||
@@ -5,9 +5,7 @@ import { MetaProvider } from "@solidjs/meta"
|
||||
import { Font } from "@opencode-ai/ui/font"
|
||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
|
||||
import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
|
||||
import { Diff } from "@opencode-ai/ui/diff"
|
||||
import { Code } from "@opencode-ai/ui/code"
|
||||
import { GlobalSyncProvider } from "@/context/global-sync"
|
||||
import { LayoutProvider } from "@/context/layout"
|
||||
import { GlobalSDKProvider } from "@/context/global-sdk"
|
||||
@@ -38,48 +36,46 @@ const url =
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<MetaProvider>
|
||||
<Font />
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<CodeComponentProvider component={Code}>
|
||||
<GlobalSDKProvider url={url}>
|
||||
<GlobalSyncProvider>
|
||||
<LayoutProvider>
|
||||
<NotificationProvider>
|
||||
<Router
|
||||
root={(props) => (
|
||||
<CommandProvider>
|
||||
<Layout>{props.children}</Layout>
|
||||
</CommandProvider>
|
||||
)}
|
||||
>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={() => <Navigate href="session" />} />
|
||||
<Route
|
||||
path="/session/:id?"
|
||||
component={(p) => (
|
||||
<Show when={p.params.id || true} keyed>
|
||||
<TerminalProvider>
|
||||
<PromptProvider>
|
||||
<Session />
|
||||
</PromptProvider>
|
||||
</TerminalProvider>
|
||||
</Show>
|
||||
)}
|
||||
/>
|
||||
</Route>
|
||||
</Router>
|
||||
</NotificationProvider>
|
||||
</LayoutProvider>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</CodeComponentProvider>
|
||||
</DiffComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
</MetaProvider>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<GlobalSDKProvider url={url}>
|
||||
<GlobalSyncProvider>
|
||||
<LayoutProvider>
|
||||
<NotificationProvider>
|
||||
<MetaProvider>
|
||||
<Font />
|
||||
<Router
|
||||
root={(props) => (
|
||||
<CommandProvider>
|
||||
<Layout>{props.children}</Layout>
|
||||
</CommandProvider>
|
||||
)}
|
||||
>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={() => <Navigate href="session" />} />
|
||||
<Route
|
||||
path="/session/:id?"
|
||||
component={(p) => (
|
||||
<Show when={p.params.id || true} keyed>
|
||||
<TerminalProvider>
|
||||
<PromptProvider>
|
||||
<Session />
|
||||
</PromptProvider>
|
||||
</TerminalProvider>
|
||||
</Show>
|
||||
)}
|
||||
/>
|
||||
</Route>
|
||||
</Router>
|
||||
</MetaProvider>
|
||||
</NotificationProvider>
|
||||
</LayoutProvider>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</DiffComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,27 @@
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { Session } from "@opencode-ai/sdk/v2/client"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Mark } from "@opencode-ai/ui/logo"
|
||||
import { Popover } from "@opencode-ai/ui/popover"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { A, useParams } from "@solidjs/router"
|
||||
import { createMemo, createResource, Show } from "solid-js"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { iife } from "@opencode-ai/util/iife"
|
||||
import { createMemo, Show } from "solid-js"
|
||||
|
||||
export function Header(props: {
|
||||
navigateToProject: (directory: string) => void
|
||||
navigateToSession: (session: Session | undefined) => void
|
||||
}) {
|
||||
const globalSync = useGlobalSync()
|
||||
const globalSDK = useGlobalSDK()
|
||||
const layout = useLayout()
|
||||
const params = useParams()
|
||||
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
||||
const store = createMemo(() => globalSync.child(currentDirectory())[0])
|
||||
const sessions = createMemo(() => store().session ?? [])
|
||||
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
|
||||
const shareEnabled = createMemo(() => store().config.share !== "disabled")
|
||||
|
||||
return (
|
||||
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region>
|
||||
@@ -112,33 +105,6 @@ export function Header(props: {
|
||||
</div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Show when={shareEnabled() && currentSession()}>
|
||||
<Popover
|
||||
title="Share session"
|
||||
trigger={
|
||||
<Tooltip class="shrink-0" value="Share session">
|
||||
<IconButton icon="share" variant="ghost" class="" />
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
{iife(() => {
|
||||
const [url] = createResource(
|
||||
() => currentSession(),
|
||||
async (session) => {
|
||||
if (!session) return
|
||||
let shareURL = session.share?.url
|
||||
if (!shareURL) {
|
||||
shareURL = await globalSDK.client.session
|
||||
.share({ sessionID: session.id, directory: currentDirectory() })
|
||||
.then((r) => r.data?.share?.url)
|
||||
}
|
||||
return shareURL
|
||||
},
|
||||
)
|
||||
return <Show when={url()}>{(url) => <TextField value={url()} readOnly copyable class="w-72" />}</Show>
|
||||
})}
|
||||
</Popover>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
||||
import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match, createMemo } from "solid-js"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { createFocusSignal } from "@solid-primitives/active-element"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { ContentPart, DEFAULT_PROMPT, isPromptEqual, Prompt, usePrompt, ImageAttachmentPart } from "@/context/prompt"
|
||||
@@ -20,7 +21,6 @@ import { DialogSelectModel } from "@/components/dialog-select-model"
|
||||
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
import { useCommand, formatKeybind } from "@/context/command"
|
||||
import { persisted } from "@/utils/persist"
|
||||
|
||||
const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
|
||||
const ACCEPTED_FILE_TYPES = [...ACCEPTED_IMAGE_TYPES, "application/pdf"]
|
||||
@@ -109,13 +109,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
})
|
||||
|
||||
const MAX_HISTORY = 100
|
||||
const [history, setHistory] = persisted(
|
||||
"prompt-history.v1",
|
||||
const [history, setHistory] = makePersisted(
|
||||
createStore<{
|
||||
entries: Prompt[]
|
||||
}>({
|
||||
entries: [],
|
||||
}),
|
||||
{
|
||||
name: "prompt-history.v1",
|
||||
},
|
||||
)
|
||||
|
||||
const clonePromptParts = (prompt: Prompt): Prompt =>
|
||||
@@ -378,47 +380,31 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const parseFromDOM = (): Prompt => {
|
||||
const newParts: Prompt = []
|
||||
let position = 0
|
||||
|
||||
const pushText = (content: string) => {
|
||||
if (!content) return
|
||||
newParts.push({ type: "text", content, start: position, end: position + content.length })
|
||||
position += content.length
|
||||
}
|
||||
|
||||
const rangeText = (range: Range) => {
|
||||
const fragment = range.cloneContents()
|
||||
const container = document.createElement("div")
|
||||
container.append(fragment)
|
||||
return container.innerText
|
||||
}
|
||||
|
||||
const files = Array.from(editorRef.querySelectorAll<HTMLElement>("[data-type=file]"))
|
||||
let last: HTMLElement | undefined
|
||||
|
||||
files.forEach((file) => {
|
||||
const before = document.createRange()
|
||||
before.selectNodeContents(editorRef)
|
||||
if (last) before.setStartAfter(last)
|
||||
before.setEndBefore(file)
|
||||
pushText(rangeText(before))
|
||||
|
||||
const content = file.textContent ?? ""
|
||||
newParts.push({
|
||||
type: "file",
|
||||
path: file.dataset.path!,
|
||||
content,
|
||||
start: position,
|
||||
end: position + content.length,
|
||||
})
|
||||
position += content.length
|
||||
last = file
|
||||
editorRef.childNodes.forEach((node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
if (node.textContent) {
|
||||
const content = node.textContent
|
||||
newParts.push({ type: "text", content, start: position, end: position + content.length })
|
||||
position += content.length
|
||||
}
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type) {
|
||||
switch ((node as HTMLElement).dataset.type) {
|
||||
case "file":
|
||||
const content = node.textContent!
|
||||
newParts.push({
|
||||
type: "file",
|
||||
path: (node as HTMLElement).dataset.path!,
|
||||
content,
|
||||
start: position,
|
||||
end: position + content.length,
|
||||
})
|
||||
position += content.length
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const after = document.createRange()
|
||||
after.selectNodeContents(editorRef)
|
||||
if (last) after.setStartAfter(last)
|
||||
pushText(rangeText(after))
|
||||
|
||||
if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT)
|
||||
return newParts
|
||||
}
|
||||
@@ -591,19 +577,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
||||
if (event.altKey || event.ctrlKey || event.metaKey) return
|
||||
const { collapsed } = getCaretState()
|
||||
const { collapsed, cursorPosition, textLength } = getCaretState()
|
||||
if (!collapsed) return
|
||||
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const textLength = promptLength(prompt.current())
|
||||
const textContent = editorRef.textContent ?? ""
|
||||
const isEmpty = textContent.trim() === "" || textLength <= 1
|
||||
const hasNewlines = textContent.includes("\n")
|
||||
const inHistory = store.historyIndex >= 0
|
||||
const atStart = cursorPosition <= (isEmpty ? 1 : 0)
|
||||
const atEnd = cursorPosition >= (isEmpty ? textLength - 1 : textLength)
|
||||
const allowUp = isEmpty || atStart || (!hasNewlines && !inHistory) || (inHistory && atEnd)
|
||||
const allowDown = isEmpty || atEnd || (!hasNewlines && !inHistory) || (inHistory && atStart)
|
||||
const atAbsoluteStart = cursorPosition === 0
|
||||
const atAbsoluteEnd = cursorPosition === textLength
|
||||
const allowUp = (inHistory && atAbsoluteEnd) || atAbsoluteStart
|
||||
const allowDown = (inHistory && atAbsoluteStart) || atAbsoluteEnd
|
||||
|
||||
if (event.key === "ArrowUp") {
|
||||
if (!allowUp) return
|
||||
@@ -709,28 +689,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
const model = {
|
||||
modelID: local.model.current()!.id,
|
||||
providerID: local.model.current()!.provider.id,
|
||||
}
|
||||
const agent = local.agent.current()!.name
|
||||
|
||||
sync.session.addOptimisticMessage({
|
||||
sessionID: existing.id,
|
||||
text,
|
||||
parts: [
|
||||
{ type: "text", text } as import("@opencode-ai/sdk/v2/client").Part,
|
||||
...(fileAttachmentParts as import("@opencode-ai/sdk/v2/client").Part[]),
|
||||
...(imageAttachmentParts as import("@opencode-ai/sdk/v2/client").Part[]),
|
||||
],
|
||||
agent,
|
||||
model,
|
||||
})
|
||||
|
||||
sdk.client.session.prompt({
|
||||
sessionID: existing.id,
|
||||
agent,
|
||||
model,
|
||||
agent: local.agent.current()!.name,
|
||||
model: {
|
||||
modelID: local.model.current()!.id,
|
||||
providerID: local.model.current()!.provider.id,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
@@ -868,7 +833,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</Show>
|
||||
<div class="relative max-h-[240px] overflow-y-auto">
|
||||
<div
|
||||
data-component="prompt-input"
|
||||
ref={(el) => {
|
||||
editorRef = el
|
||||
props.ref?.(el)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
|
||||
import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js"
|
||||
import { ComponentProps, createEffect, onCleanup, onMount, splitProps } from "solid-js"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { SerializeAddon } from "@/addons/serialize"
|
||||
import { LocalPTY } from "@/context/terminal"
|
||||
@@ -31,7 +31,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
term = new Term({
|
||||
cursorBlink: true,
|
||||
fontSize: 14,
|
||||
fontFamily: "IBM Plex Mono, monospace",
|
||||
fontFamily: "TX-02, monospace",
|
||||
allowTransparency: true,
|
||||
theme: prefersDark()
|
||||
? {
|
||||
|
||||
@@ -10,7 +10,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: props.url,
|
||||
signal: abort.signal,
|
||||
throwOnError: true,
|
||||
})
|
||||
|
||||
const emitter = createGlobalEmitter<{
|
||||
|
||||
@@ -15,13 +15,12 @@ import {
|
||||
type ProviderAuthResponse,
|
||||
type Command,
|
||||
createOpencodeClient,
|
||||
EventSessionError,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
import { ErrorPage } from "../pages/error"
|
||||
import { createContext, useContext, onMount, type ParentProps, Switch, Match, createEffect } from "solid-js"
|
||||
import { onMount } from "solid-js"
|
||||
|
||||
type State = {
|
||||
ready: boolean
|
||||
@@ -52,140 +51,190 @@ type State = {
|
||||
changes: File[]
|
||||
}
|
||||
|
||||
function createGlobalSync() {
|
||||
const globalSDK = useGlobalSDK()
|
||||
const [globalStore, setGlobalStore] = createStore<{
|
||||
ready: boolean
|
||||
error?: EventSessionError["properties"]["error"]
|
||||
path: Path
|
||||
project: Project[]
|
||||
provider: ProviderListResponse
|
||||
provider_auth: ProviderAuthResponse
|
||||
children: Record<string, State>
|
||||
}>({
|
||||
ready: false,
|
||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
project: [],
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
provider_auth: {},
|
||||
children: {},
|
||||
})
|
||||
export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimpleContext({
|
||||
name: "GlobalSync",
|
||||
init: () => {
|
||||
const globalSDK = useGlobalSDK()
|
||||
const [globalStore, setGlobalStore] = createStore<{
|
||||
ready: boolean
|
||||
path: Path
|
||||
project: Project[]
|
||||
provider: ProviderListResponse
|
||||
provider_auth: ProviderAuthResponse
|
||||
children: Record<string, State>
|
||||
}>({
|
||||
ready: false,
|
||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
project: [],
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
provider_auth: {},
|
||||
children: {},
|
||||
})
|
||||
|
||||
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
|
||||
function child(directory: string) {
|
||||
if (!children[directory]) {
|
||||
setGlobalStore("children", directory, {
|
||||
project: "",
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
config: {},
|
||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
ready: false,
|
||||
agent: [],
|
||||
command: [],
|
||||
session: [],
|
||||
session_status: {},
|
||||
session_diff: {},
|
||||
todo: {},
|
||||
limit: 5,
|
||||
message: {},
|
||||
part: {},
|
||||
node: [],
|
||||
changes: [],
|
||||
})
|
||||
children[directory] = createStore(globalStore.children[directory])
|
||||
bootstrapInstance(directory)
|
||||
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
|
||||
function child(directory: string) {
|
||||
if (!children[directory]) {
|
||||
setGlobalStore("children", directory, {
|
||||
project: "",
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
config: {},
|
||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
ready: false,
|
||||
agent: [],
|
||||
command: [],
|
||||
session: [],
|
||||
session_status: {},
|
||||
session_diff: {},
|
||||
todo: {},
|
||||
limit: 5,
|
||||
message: {},
|
||||
part: {},
|
||||
node: [],
|
||||
changes: [],
|
||||
})
|
||||
children[directory] = createStore(globalStore.children[directory])
|
||||
bootstrapInstance(directory)
|
||||
}
|
||||
return children[directory]
|
||||
}
|
||||
return children[directory]
|
||||
}
|
||||
|
||||
async function loadSessions(directory: string) {
|
||||
const [store, setStore] = child(directory)
|
||||
globalSDK.client.session
|
||||
.list({ directory })
|
||||
.then((x) => {
|
||||
async function loadSessions(directory: string) {
|
||||
globalSDK.client.session.list({ directory }).then((x) => {
|
||||
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
||||
const nonArchived = (x.data ?? [])
|
||||
.slice()
|
||||
.filter((s) => !s.time.archived)
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
// Include sessions up to the limit, plus any updated in the last hour
|
||||
// Include at least 5 sessions, plus any updated in the last hour
|
||||
const sessions = nonArchived.filter((s, i) => {
|
||||
if (i < store.limit) return true
|
||||
if (i < 5) return true
|
||||
const updated = new Date(s.time.updated).getTime()
|
||||
return updated > fourHoursAgo
|
||||
})
|
||||
const [, setStore] = child(directory)
|
||||
setStore("session", sessions)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to load sessions", err)
|
||||
setGlobalStore("error", err)
|
||||
})
|
||||
}
|
||||
|
||||
async function bootstrapInstance(directory: string) {
|
||||
const [, setStore] = child(directory)
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: globalSDK.url,
|
||||
directory,
|
||||
throwOnError: true,
|
||||
})
|
||||
const load = {
|
||||
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
||||
provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)),
|
||||
path: () => sdk.path.get().then((x) => setStore("path", x.data!)),
|
||||
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||
command: () => sdk.command.list().then((x) => setStore("command", x.data ?? [])),
|
||||
session: () => loadSessions(directory),
|
||||
status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)),
|
||||
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
|
||||
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))))
|
||||
.then(() => setStore("ready", true))
|
||||
.catch((e) => setGlobalStore("error", e))
|
||||
}
|
||||
|
||||
globalSDK.event.listen((e) => {
|
||||
const directory = e.name
|
||||
const event = e.details
|
||||
async function bootstrapInstance(directory: string) {
|
||||
const [, setStore] = child(directory)
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: globalSDK.url,
|
||||
directory,
|
||||
})
|
||||
const load = {
|
||||
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
||||
provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)),
|
||||
path: () => sdk.path.get().then((x) => setStore("path", x.data!)),
|
||||
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||
command: () => sdk.command.list().then((x) => setStore("command", x.data ?? [])),
|
||||
session: () => loadSessions(directory),
|
||||
status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)),
|
||||
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
|
||||
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())).then(() => setStore("ready", true))
|
||||
}
|
||||
|
||||
if (directory === "global") {
|
||||
switch (event?.type) {
|
||||
case "global.disposed": {
|
||||
bootstrap()
|
||||
globalSDK.event.listen((e) => {
|
||||
console.log(e)
|
||||
const directory = e.name
|
||||
const event = e.details
|
||||
|
||||
if (directory === "global") {
|
||||
switch (event?.type) {
|
||||
case "global.disposed": {
|
||||
bootstrap()
|
||||
break
|
||||
}
|
||||
case "project.updated": {
|
||||
const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id)
|
||||
if (result.found) {
|
||||
setGlobalStore("project", result.index, reconcile(event.properties))
|
||||
return
|
||||
}
|
||||
setGlobalStore(
|
||||
"project",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const [store, setStore] = child(directory)
|
||||
switch (event.type) {
|
||||
case "server.instance.disposed": {
|
||||
bootstrapInstance(directory)
|
||||
break
|
||||
}
|
||||
case "project.updated": {
|
||||
const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id)
|
||||
if (result.found) {
|
||||
setGlobalStore("project", result.index, reconcile(event.properties))
|
||||
return
|
||||
case "session.updated": {
|
||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||
if (event.properties.info.time.archived) {
|
||||
if (result.found) {
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 1)
|
||||
}),
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
setGlobalStore(
|
||||
"project",
|
||||
if (result.found) {
|
||||
setStore("session", result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties)
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const [store, setStore] = child(directory)
|
||||
switch (event.type) {
|
||||
case "server.instance.disposed": {
|
||||
bootstrapInstance(directory)
|
||||
break
|
||||
}
|
||||
case "session.updated": {
|
||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||
if (event.properties.info.time.archived) {
|
||||
case "session.diff":
|
||||
setStore("session_diff", event.properties.sessionID, event.properties.diff)
|
||||
break
|
||||
case "todo.updated":
|
||||
setStore("todo", event.properties.sessionID, event.properties.todos)
|
||||
break
|
||||
case "session.status": {
|
||||
setStore("session_status", event.properties.sessionID, event.properties.status)
|
||||
break
|
||||
}
|
||||
case "message.updated": {
|
||||
const messages = store.message[event.properties.info.sessionID]
|
||||
if (!messages) {
|
||||
setStore("message", event.properties.info.sessionID, [event.properties.info])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.info.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.removed": {
|
||||
const messages = store.message[event.properties.sessionID]
|
||||
if (!messages) break
|
||||
const result = Binary.search(messages, event.properties.messageID, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore(
|
||||
"session",
|
||||
"message",
|
||||
event.properties.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 1)
|
||||
}),
|
||||
@@ -193,162 +242,79 @@ function createGlobalSync() {
|
||||
}
|
||||
break
|
||||
}
|
||||
if (result.found) {
|
||||
setStore("session", result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "session.diff":
|
||||
setStore("session_diff", event.properties.sessionID, event.properties.diff)
|
||||
break
|
||||
case "todo.updated":
|
||||
setStore("todo", event.properties.sessionID, event.properties.todos)
|
||||
break
|
||||
case "session.status": {
|
||||
setStore("session_status", event.properties.sessionID, event.properties.status)
|
||||
break
|
||||
}
|
||||
case "message.updated": {
|
||||
const messages = store.message[event.properties.info.sessionID]
|
||||
if (!messages) {
|
||||
setStore("message", event.properties.info.sessionID, [event.properties.info])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.info.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.removed": {
|
||||
const messages = store.message[event.properties.sessionID]
|
||||
if (!messages) break
|
||||
const result = Binary.search(messages, event.properties.messageID, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 1)
|
||||
}),
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "message.part.updated": {
|
||||
const part = event.properties.part
|
||||
const parts = store.part[part.messageID]
|
||||
if (!parts) {
|
||||
setStore("part", part.messageID, [part])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(parts, part.id, (p) => p.id)
|
||||
if (result.found) {
|
||||
setStore("part", part.messageID, result.index, reconcile(part))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"part",
|
||||
part.messageID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, part)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.part.removed": {
|
||||
const parts = store.part[event.properties.messageID]
|
||||
if (!parts) break
|
||||
const result = Binary.search(parts, event.properties.partID, (p) => p.id)
|
||||
if (result.found) {
|
||||
case "message.part.updated": {
|
||||
const part = event.properties.part
|
||||
const parts = store.part[part.messageID]
|
||||
if (!parts) {
|
||||
setStore("part", part.messageID, [part])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(parts, part.id, (p) => p.id)
|
||||
if (result.found) {
|
||||
setStore("part", part.messageID, result.index, reconcile(part))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"part",
|
||||
event.properties.messageID,
|
||||
part.messageID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 1)
|
||||
draft.splice(result.index, 0, part)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.part.removed": {
|
||||
const parts = store.part[event.properties.messageID]
|
||||
if (!parts) break
|
||||
const result = Binary.search(parts, event.properties.partID, (p) => p.id)
|
||||
if (result.found) {
|
||||
setStore(
|
||||
"part",
|
||||
event.properties.messageID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 1)
|
||||
}),
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
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 ?? {})
|
||||
}),
|
||||
]).then(() => setGlobalStore("ready", true))
|
||||
}
|
||||
})
|
||||
|
||||
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 ?? {})
|
||||
}),
|
||||
])
|
||||
.then(() => setGlobalStore("ready", true))
|
||||
.catch((e) => setGlobalStore("error", e))
|
||||
}
|
||||
onMount(() => {
|
||||
bootstrap()
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
bootstrap()
|
||||
})
|
||||
|
||||
return {
|
||||
data: globalStore,
|
||||
get ready() {
|
||||
return globalStore.ready
|
||||
},
|
||||
get error() {
|
||||
return globalStore.error
|
||||
},
|
||||
child,
|
||||
bootstrap,
|
||||
project: {
|
||||
loadSessions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const GlobalSyncContext = createContext<ReturnType<typeof createGlobalSync>>()
|
||||
|
||||
export function GlobalSyncProvider(props: ParentProps) {
|
||||
const value = createGlobalSync()
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={value.error}>
|
||||
<ErrorPage error={value.error} />
|
||||
</Match>
|
||||
<Match when={value.ready}>
|
||||
<GlobalSyncContext.Provider value={value}>{props.children}</GlobalSyncContext.Provider>
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
||||
export function useGlobalSync() {
|
||||
const context = useContext(GlobalSyncContext)
|
||||
if (!context) throw new Error("useGlobalSync must be used within GlobalSyncProvider")
|
||||
return context
|
||||
}
|
||||
return {
|
||||
data: globalStore,
|
||||
get ready() {
|
||||
return globalStore.ready
|
||||
},
|
||||
child,
|
||||
bootstrap,
|
||||
project: {
|
||||
loadSessions,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { batch, createMemo, onMount } from "solid-js"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
import { Project } from "@opencode-ai/sdk/v2"
|
||||
import { persisted } from "@/utils/persist"
|
||||
|
||||
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
|
||||
export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
|
||||
@@ -32,8 +32,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
init: () => {
|
||||
const globalSdk = useGlobalSDK()
|
||||
const globalSync = useGlobalSync()
|
||||
const [store, setStore, _, ready] = persisted(
|
||||
"layout.v3",
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore({
|
||||
projects: [] as { worktree: string; expanded: boolean }[],
|
||||
sidebar: {
|
||||
@@ -49,6 +48,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
},
|
||||
sessionTabs: {} as Record<string, SessionTabs>,
|
||||
}),
|
||||
{
|
||||
name: "layout.v3",
|
||||
},
|
||||
)
|
||||
|
||||
const usedColors = new Set<AvatarColorKey>()
|
||||
@@ -91,7 +93,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
})
|
||||
|
||||
return {
|
||||
ready,
|
||||
projects: {
|
||||
list,
|
||||
open(directory: string) {
|
||||
|
||||
@@ -7,8 +7,8 @@ import { useSDK } from "./sdk"
|
||||
import { useSync } from "./sync"
|
||||
import { base64Encode } from "@opencode-ai/util/encode"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { DateTime } from "luxon"
|
||||
import { persisted } from "@/utils/persist"
|
||||
|
||||
export type LocalFile = FileNode &
|
||||
Partial<{
|
||||
@@ -110,8 +110,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
})()
|
||||
|
||||
const model = (() => {
|
||||
const [store, setStore, _, modelReady] = persisted(
|
||||
"model.v1",
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore<{
|
||||
user: (ModelKey & { visibility: "show" | "hide"; favorite?: boolean })[]
|
||||
recent: ModelKey[]
|
||||
@@ -119,6 +118,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
user: [],
|
||||
recent: [],
|
||||
}),
|
||||
{ name: "model.v1" },
|
||||
)
|
||||
|
||||
const [ephemeral, setEphemeral] = createStore<{
|
||||
@@ -242,7 +242,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
}
|
||||
|
||||
return {
|
||||
ready: modelReady,
|
||||
current,
|
||||
recent,
|
||||
list,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
@@ -7,7 +8,6 @@ import { EventSessionError } from "@opencode-ai/sdk/v2"
|
||||
import { makeAudioPlayer } from "@solid-primitives/audio"
|
||||
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
|
||||
import errorSound from "@opencode-ai/ui/audio/nope-03.aac"
|
||||
import { persisted } from "@/utils/persist"
|
||||
|
||||
type NotificationBase = {
|
||||
directory?: string
|
||||
@@ -31,27 +31,27 @@ export type Notification = TurnCompleteNotification | ErrorNotification
|
||||
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
|
||||
name: "Notification",
|
||||
init: () => {
|
||||
let idlePlayer: ReturnType<typeof makeAudioPlayer> | undefined
|
||||
let errorPlayer: ReturnType<typeof makeAudioPlayer> | undefined
|
||||
|
||||
try {
|
||||
idlePlayer = makeAudioPlayer(idleSound)
|
||||
errorPlayer = makeAudioPlayer(errorSound)
|
||||
} catch (err) {
|
||||
console.log("Failed to load audio", err)
|
||||
}
|
||||
|
||||
const idlePlayer = makeAudioPlayer(idleSound)
|
||||
const errorPlayer = makeAudioPlayer(errorSound)
|
||||
const globalSDK = useGlobalSDK()
|
||||
const globalSync = useGlobalSync()
|
||||
|
||||
const [store, setStore, _, ready] = persisted(
|
||||
"notification.v1",
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore({
|
||||
list: [] as Notification[],
|
||||
}),
|
||||
{
|
||||
name: "notification.v1",
|
||||
},
|
||||
)
|
||||
|
||||
// onMount(() => {
|
||||
// const daysToKeep = 7
|
||||
// // setStore("list", (n) => n.filter((n) => !n.viewed && n.time + 1000 * 60 * 60 * 24 * daysToKeep < Date.now()))
|
||||
// })
|
||||
|
||||
globalSDK.event.listen((e) => {
|
||||
console.log(e)
|
||||
const directory = e.name
|
||||
const event = e.details
|
||||
const base = {
|
||||
@@ -66,9 +66,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
||||
const isChild = match.found && syncStore.session[match.index].parentID
|
||||
if (isChild) break
|
||||
try {
|
||||
idlePlayer?.play()
|
||||
} catch {}
|
||||
idlePlayer.play()
|
||||
setStore("list", store.list.length, {
|
||||
...base,
|
||||
type: "turn-complete",
|
||||
@@ -84,9 +82,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
const isChild = match.found && syncStore.session[match.index].parentID
|
||||
if (isChild) break
|
||||
}
|
||||
try {
|
||||
errorPlayer?.play()
|
||||
} catch {}
|
||||
errorPlayer.play()
|
||||
setStore("list", store.list.length, {
|
||||
...base,
|
||||
type: "error",
|
||||
@@ -99,7 +95,6 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
})
|
||||
|
||||
return {
|
||||
ready,
|
||||
session: {
|
||||
all(session: string) {
|
||||
return store.list.filter((n) => n.session === session)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
|
||||
|
||||
export type Platform = {
|
||||
/** Platform discriminator */
|
||||
@@ -16,9 +15,6 @@ export type Platform = {
|
||||
|
||||
/** Open a URL in the default browser */
|
||||
openLink(url: string): void
|
||||
|
||||
/** Storage mechanism, defaults to localStorage */
|
||||
storage?: (name?: string) => SyncStorage | AsyncStorage
|
||||
}
|
||||
|
||||
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { batch, createMemo } from "solid-js"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { TextSelection } from "./local"
|
||||
import { persisted } from "@/utils/persist"
|
||||
|
||||
interface PartBase {
|
||||
content: string
|
||||
@@ -77,8 +77,7 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
|
||||
const params = useParams()
|
||||
const name = createMemo(() => `${params.dir}/prompt${params.id ? "/" + params.id : ""}.v1`)
|
||||
|
||||
const [store, setStore, _, ready] = persisted(
|
||||
name(),
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore<{
|
||||
prompt: Prompt
|
||||
cursor?: number
|
||||
@@ -86,10 +85,12 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
|
||||
prompt: clonePrompt(DEFAULT_PROMPT),
|
||||
cursor: undefined,
|
||||
}),
|
||||
{
|
||||
name: name(),
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
ready,
|
||||
current: createMemo(() => store.prompt),
|
||||
cursor: createMemo(() => store.cursor),
|
||||
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
||||
|
||||
@@ -13,7 +13,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||
baseUrl: globalSDK.url,
|
||||
signal: abort.signal,
|
||||
directory: props.directory,
|
||||
throwOnError: true,
|
||||
})
|
||||
|
||||
const emitter = createGlobalEmitter<{
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Binary } from "@opencode-ai/util/binary"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
import { useSDK } from "./sdk"
|
||||
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
name: "Sync",
|
||||
@@ -31,40 +30,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
if (match.found) return store.session[match.index]
|
||||
return undefined
|
||||
},
|
||||
addOptimisticMessage(input: {
|
||||
sessionID: string
|
||||
text: string
|
||||
parts: Part[]
|
||||
agent: string
|
||||
model: { providerID: string; modelID: string }
|
||||
}) {
|
||||
const messageID = crypto.randomUUID()
|
||||
const message: Message = {
|
||||
id: messageID,
|
||||
sessionID: input.sessionID,
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: input.agent,
|
||||
model: input.model,
|
||||
}
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
const messages = draft.message[input.sessionID]
|
||||
if (!messages) {
|
||||
draft.message[input.sessionID] = [message]
|
||||
} else {
|
||||
const result = Binary.search(messages, messageID, (m) => m.id)
|
||||
messages.splice(result.index, 0, message)
|
||||
}
|
||||
draft.part[messageID] = input.parts.map((part, i) => ({
|
||||
...part,
|
||||
id: `${messageID}-${i}`,
|
||||
sessionID: input.sessionID,
|
||||
messageID,
|
||||
}))
|
||||
}),
|
||||
)
|
||||
},
|
||||
async sync(sessionID: string, _isRetry = false) {
|
||||
const [session, messages, todo, diff] = await Promise.all([
|
||||
sdk.client.session.get({ sessionID }, { throwOnError: true }),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { batch, createMemo } from "solid-js"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { useSDK } from "./sdk"
|
||||
import { persisted } from "@/utils/persist"
|
||||
|
||||
export type LocalPTY = {
|
||||
id: string
|
||||
@@ -21,18 +21,19 @@ export const { use: useTerminal, provider: TerminalProvider } = createSimpleCont
|
||||
const params = useParams()
|
||||
const name = createMemo(() => `${params.dir}/terminal${params.id ? "/" + params.id : ""}.v1`)
|
||||
|
||||
const [store, setStore, _, ready] = persisted(
|
||||
name(),
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore<{
|
||||
active?: string
|
||||
all: LocalPTY[]
|
||||
}>({
|
||||
all: [],
|
||||
}),
|
||||
{
|
||||
name: name(),
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
ready,
|
||||
all: createMemo(() => Object.values(store.all)),
|
||||
active: createMemo(() => store.active),
|
||||
new() {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
import { Logo } from "@opencode-ai/ui/logo"
|
||||
import { Component } from "solid-js"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
|
||||
interface ErrorPageProps {
|
||||
error: any
|
||||
}
|
||||
|
||||
export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
const platform = usePlatform()
|
||||
return (
|
||||
<div class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center">
|
||||
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
|
||||
<Logo class="h-8 w-auto text-text-strong" />
|
||||
<div class="flex flex-col items-center gap-2 text-center">
|
||||
<h1 class="text-lg font-medium text-text-strong">Something went wrong</h1>
|
||||
<p class="text-sm text-text-weak">An error occurred while loading the application.</p>
|
||||
</div>
|
||||
<TextField
|
||||
value={String(props.error?.data?.message || props.error?.message || props.error)}
|
||||
readOnly
|
||||
copyable
|
||||
multiline
|
||||
class="max-h-96 w-full font-mono text-xs no-scrollbar whitespace-pre"
|
||||
label="Error Details"
|
||||
hideLabel
|
||||
/>
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
Please report this error to the OpenCode team
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center text-text-interactive-base gap-1"
|
||||
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
||||
>
|
||||
<div>on Discord</div>
|
||||
<Icon name="discord" class="text-text-interactive-base" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -25,8 +25,9 @@ import {
|
||||
SortableProvider,
|
||||
closestCenter,
|
||||
createSortable,
|
||||
useDragDropContext,
|
||||
} from "@thisbeyond/solid-dnd"
|
||||
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
||||
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
import { Toast } from "@opencode-ai/ui/toast"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
@@ -36,7 +37,6 @@ import { Header } from "@/components/header"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { DialogSelectProvider } from "@/components/dialog-select-provider"
|
||||
import { useCommand } from "@/context/command"
|
||||
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const [store, setStore] = createStore({
|
||||
@@ -301,6 +301,28 @@ export default function Layout(props: ParentProps) {
|
||||
setStore("activeDraggable", undefined)
|
||||
}
|
||||
|
||||
const ConstrainDragXAxis = (): JSX.Element => {
|
||||
const context = useDragDropContext()
|
||||
if (!context) return <></>
|
||||
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
|
||||
const transformer: Transformer = {
|
||||
id: "constrain-x-axis",
|
||||
order: 100,
|
||||
callback: (transform) => ({ ...transform, x: 0 }),
|
||||
}
|
||||
onDragStart((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
addTransformer("draggables", id, transformer)
|
||||
})
|
||||
onDragEnd((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
removeTransformer("draggables", id, transformer.id)
|
||||
})
|
||||
return <></>
|
||||
}
|
||||
|
||||
const ProjectAvatar = (props: {
|
||||
project: Project
|
||||
class?: string
|
||||
@@ -391,11 +413,11 @@ export default function Layout(props: ParentProps) {
|
||||
const updated = createMemo(() => DateTime.fromMillis(props.session.time.updated))
|
||||
const notifications = createMemo(() => notification.session.unseen(props.session.id))
|
||||
const hasError = createMemo(() => notifications().some((n) => n.type === "error"))
|
||||
const isWorking = createMemo(() => {
|
||||
if (props.session.id === params.id) return false
|
||||
const status = globalSync.child(props.project.worktree)[0].session_status[props.session.id]
|
||||
return status?.type === "busy" || status?.type === "retry"
|
||||
})
|
||||
const isWorking = createMemo(
|
||||
() =>
|
||||
props.session.id !== params.id &&
|
||||
globalSync.child(props.project.worktree)[0].session_status[props.session.id]?.type === "busy",
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -475,7 +497,7 @@ export default function Layout(props: ParentProps) {
|
||||
const sortable = createSortable(props.project.worktree)
|
||||
const slug = createMemo(() => base64Encode(props.project.worktree))
|
||||
const name = createMemo(() => getFilename(props.project.worktree))
|
||||
const [store, setProjectStore] = globalSync.child(props.project.worktree)
|
||||
const [store] = globalSync.child(props.project.worktree)
|
||||
const sessions = createMemo(() => store.session ?? [])
|
||||
const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
|
||||
const childSessionsByParent = createMemo(() => {
|
||||
@@ -489,11 +511,6 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
return map
|
||||
})
|
||||
const hasMoreSessions = createMemo(() => store.session.length >= store.limit)
|
||||
const loadMoreSessions = async () => {
|
||||
setProjectStore("limit", (limit) => limit + 10)
|
||||
await globalSync.project.loadSessions(props.project.worktree)
|
||||
}
|
||||
const [expanded, setExpanded] = createSignal(true)
|
||||
return (
|
||||
// @ts-ignore
|
||||
@@ -566,18 +583,6 @@ export default function Layout(props: ParentProps) {
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={hasMoreSessions()}>
|
||||
<div class="relative w-full py-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="flex w-full text-left justify-start text-12-medium opacity-50 px-3.5"
|
||||
size="large"
|
||||
onClick={loadMoreSessions}
|
||||
>
|
||||
Load more
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
</nav>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
@@ -613,7 +618,7 @@ export default function Layout(props: ParentProps) {
|
||||
classList={{
|
||||
"relative @container w-12 pb-5 shrink-0 bg-background-base": true,
|
||||
"flex flex-col gap-5.5 items-start self-stretch justify-between": true,
|
||||
"border-r border-border-weak-base contain-strict": true,
|
||||
"border-r border-border-weak-base": true,
|
||||
}}
|
||||
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
|
||||
>
|
||||
@@ -755,7 +760,7 @@ export default function Layout(props: ParentProps) {
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<main class="size-full overflow-x-hidden flex flex-col items-start contain-strict">{props.children}</main>
|
||||
<main class="size-full overflow-x-hidden flex flex-col items-start">{props.children}</main>
|
||||
</div>
|
||||
<Toast.Region />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect, on } from "solid-js"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import { useLocal, type LocalFile } from "@/context/local"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
@@ -12,7 +11,7 @@ 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"
|
||||
import { Code } from "@opencode-ai/ui/code"
|
||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||
import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail"
|
||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||
@@ -23,8 +22,9 @@ import {
|
||||
SortableProvider,
|
||||
closestCenter,
|
||||
createSortable,
|
||||
useDragDropContext,
|
||||
} from "@thisbeyond/solid-dnd"
|
||||
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
||||
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
||||
import type { JSX } from "solid-js"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
||||
@@ -41,7 +41,6 @@ import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { usePrompt } from "@/context/prompt"
|
||||
import { extractPromptFromParts } from "@/utils/prompt"
|
||||
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
|
||||
|
||||
export default function Page() {
|
||||
const layout = useLayout()
|
||||
@@ -49,7 +48,6 @@ export default function Page() {
|
||||
const sync = useSync()
|
||||
const terminal = useTerminal()
|
||||
const dialog = useDialog()
|
||||
const codeComponent = useCodeComponent()
|
||||
const command = useCommand()
|
||||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
@@ -258,19 +256,12 @@ export default function Page() {
|
||||
slash: "agent",
|
||||
onSelect: () => local.agent.move(1),
|
||||
},
|
||||
{
|
||||
id: "agent.cycle.reverse",
|
||||
title: "Cycle agent backwards",
|
||||
description: "Switch to the previous agent",
|
||||
category: "Agent",
|
||||
keybind: "shift+mod+.",
|
||||
onSelect: () => local.agent.move(-1),
|
||||
},
|
||||
{
|
||||
id: "session.undo",
|
||||
title: "Undo",
|
||||
description: "Undo the last message",
|
||||
category: "Session",
|
||||
keybind: "mod+z",
|
||||
slash: "undo",
|
||||
disabled: !params.id || visibleUserMessages().length === 0,
|
||||
onSelect: async () => {
|
||||
@@ -300,6 +291,7 @@ export default function Page() {
|
||||
title: "Redo",
|
||||
description: "Redo the last undone message",
|
||||
category: "Session",
|
||||
keybind: "mod+shift+z",
|
||||
slash: "redo",
|
||||
disabled: !params.id || !info()?.revert?.messageID,
|
||||
onSelect: async () => {
|
||||
@@ -330,6 +322,19 @@ export default function Page() {
|
||||
if ((document.activeElement as HTMLElement)?.dataset?.component === "terminal") return
|
||||
if (dialog.active) return
|
||||
|
||||
if (event.key === "PageUp" || event.key === "PageDown") {
|
||||
const scrollContainer = document.querySelector('[data-slot="session-turn-content"]') as HTMLElement
|
||||
if (scrollContainer) {
|
||||
event.preventDefault()
|
||||
const scrollAmount = scrollContainer.clientHeight * 0.8
|
||||
scrollContainer.scrollBy({
|
||||
top: event.key === "PageUp" ? -scrollAmount : scrollAmount,
|
||||
behavior: "instant",
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const focused = document.activeElement === inputRef
|
||||
if (focused) {
|
||||
if (event.key === "Escape") inputRef?.blur()
|
||||
@@ -512,6 +517,36 @@ export default function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
const ConstrainDragYAxis = (): JSX.Element => {
|
||||
const context = useDragDropContext()
|
||||
if (!context) return <></>
|
||||
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
|
||||
const transformer: Transformer = {
|
||||
id: "constrain-y-axis",
|
||||
order: 100,
|
||||
callback: (transform) => ({ ...transform, y: 0 }),
|
||||
}
|
||||
onDragStart((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
addTransformer("draggables", id, transformer)
|
||||
})
|
||||
onDragEnd((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
removeTransformer("draggables", id, transformer.id)
|
||||
})
|
||||
return <></>
|
||||
}
|
||||
|
||||
const getDraggableId = (event: unknown): string | undefined => {
|
||||
if (typeof event !== "object" || event === null) return undefined
|
||||
if (!("draggable" in event)) return undefined
|
||||
const draggable = (event as { draggable?: { id?: unknown } }).draggable
|
||||
if (!draggable) return undefined
|
||||
return typeof draggable.id === "string" ? draggable.id : undefined
|
||||
}
|
||||
|
||||
const wide = createMemo(() => layout.review.state() === "tab" || !diffs().length)
|
||||
|
||||
return (
|
||||
@@ -584,10 +619,7 @@ export default function Page() {
|
||||
</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"
|
||||
>
|
||||
<Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"w-full flex-1 min-h-0": true,
|
||||
@@ -598,7 +630,7 @@ export default function Page() {
|
||||
<div
|
||||
classList={{
|
||||
"relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
|
||||
"max-w-200 mx-auto": !wide(),
|
||||
"max-w-146 mx-auto": !wide(),
|
||||
}}
|
||||
>
|
||||
<Switch>
|
||||
@@ -622,7 +654,7 @@ export default function Page() {
|
||||
container:
|
||||
"w-full " +
|
||||
(wide()
|
||||
? "max-w-200 mx-auto px-6"
|
||||
? "max-w-146 mx-auto px-6"
|
||||
: visibleUserMessages().length > 1
|
||||
? "pr-6 pl-18"
|
||||
: "px-6"),
|
||||
@@ -632,7 +664,7 @@ export default function Page() {
|
||||
</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="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-146 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" />
|
||||
@@ -658,7 +690,7 @@ export default function Page() {
|
||||
</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">
|
||||
<div class="w-full max-w-146 px-6">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
@@ -670,7 +702,7 @@ export default function Page() {
|
||||
<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,
|
||||
"relative grow pt-3 flex-1 min-h-0 border-l border-border-weak-base": true,
|
||||
}}
|
||||
>
|
||||
<SessionReview
|
||||
@@ -698,7 +730,7 @@ export default function Page() {
|
||||
</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">
|
||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"relative pt-3 flex-1 min-h-0 overflow-hidden": true,
|
||||
@@ -732,8 +764,7 @@ export default function Page() {
|
||||
<Switch>
|
||||
<Match when={file()}>
|
||||
{(f) => (
|
||||
<Dynamic
|
||||
component={codeComponent}
|
||||
<Code
|
||||
file={{
|
||||
name: f().path,
|
||||
contents: f().content?.content ?? "",
|
||||
@@ -772,7 +803,7 @@ export default function Page() {
|
||||
</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">
|
||||
<div class="absolute inset-x-0 px-6 max-w-146 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { makePersisted } from "@solid-primitives/storage"
|
||||
import { createResource, type Accessor } from "solid-js"
|
||||
import type { SetStoreFunction, Store } from "solid-js/store"
|
||||
|
||||
type InitType = Promise<string> | string | null
|
||||
type PersistedWithReady<T> = [Store<T>, SetStoreFunction<T>, InitType, Accessor<boolean>]
|
||||
|
||||
export function persisted<T>(key: string, store: [Store<T>, SetStoreFunction<T>]): PersistedWithReady<T> {
|
||||
const platform = usePlatform()
|
||||
const [state, setState, init] = makePersisted(store, { name: key, storage: platform.storage?.() ?? localStorage })
|
||||
|
||||
// Create a resource that resolves when the store is initialized
|
||||
// This integrates with Suspense and provides a ready signal
|
||||
const isAsync = init instanceof Promise
|
||||
const [ready] = createResource(
|
||||
() => init,
|
||||
async (initValue) => {
|
||||
if (initValue instanceof Promise) await initValue
|
||||
return true
|
||||
},
|
||||
{ initialValue: !isAsync },
|
||||
)
|
||||
|
||||
return [state, setState, init, () => ready() === true]
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useDragDropContext } from "@thisbeyond/solid-dnd"
|
||||
import { JSXElement } from "solid-js"
|
||||
import type { Transformer } from "@thisbeyond/solid-dnd"
|
||||
|
||||
export const getDraggableId = (event: unknown): string | undefined => {
|
||||
if (typeof event !== "object" || event === null) return undefined
|
||||
if (!("draggable" in event)) return undefined
|
||||
const draggable = (event as { draggable?: { id?: unknown } }).draggable
|
||||
if (!draggable) return undefined
|
||||
return typeof draggable.id === "string" ? draggable.id : undefined
|
||||
}
|
||||
|
||||
export const ConstrainDragXAxis = (): JSXElement => {
|
||||
const context = useDragDropContext()
|
||||
if (!context) return <></>
|
||||
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
|
||||
const transformer: Transformer = {
|
||||
id: "constrain-x-axis",
|
||||
order: 100,
|
||||
callback: (transform) => ({ ...transform, x: 0 }),
|
||||
}
|
||||
onDragStart((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
addTransformer("draggables", id, transformer)
|
||||
})
|
||||
onDragEnd((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
removeTransformer("draggables", id, transformer.id)
|
||||
})
|
||||
return <></>
|
||||
}
|
||||
|
||||
export const ConstrainDragYAxis = (): JSXElement => {
|
||||
const context = useDragDropContext()
|
||||
if (!context) return <></>
|
||||
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
|
||||
const transformer: Transformer = {
|
||||
id: "constrain-y-axis",
|
||||
order: 100,
|
||||
callback: (transform) => ({ ...transform, y: 0 }),
|
||||
}
|
||||
onDragStart((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
addTransformer("draggables", id, transformer)
|
||||
})
|
||||
onDragEnd((event) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
removeTransformer("draggables", id, transformer.id)
|
||||
})
|
||||
return <></>
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,8 +3,6 @@ import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||
import { DataProvider } from "@opencode-ai/ui/context"
|
||||
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
|
||||
import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
|
||||
import { WorkerPoolProvider } from "@opencode-ai/ui/context/worker-pool"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } from "solid-js"
|
||||
import { Share } from "~/core/share"
|
||||
@@ -29,14 +27,6 @@ import { Meta } from "@solidjs/meta"
|
||||
import { Base64 } from "js-base64"
|
||||
|
||||
const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
|
||||
const ClientOnlyCode = clientOnly(() => import("@opencode-ai/ui/code").then((m) => ({ default: m.Code })))
|
||||
const ClientOnlyWorkerPoolProvider = clientOnly(() =>
|
||||
import("@opencode-ai/ui/pierre/worker").then((m) => ({
|
||||
default: (props: { children: any }) => (
|
||||
<WorkerPoolProvider pool={m.workerPool}>{props.children}</WorkerPoolProvider>
|
||||
),
|
||||
})),
|
||||
)
|
||||
|
||||
const SessionDataMissingError = NamedError.create(
|
||||
"SessionDataMissingError",
|
||||
@@ -205,260 +195,241 @@ export default function () {
|
||||
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
|
||||
<Meta property="og:image" content={ogImage()} />
|
||||
<Meta name="twitter:image" content={ogImage()} />
|
||||
<ClientOnlyWorkerPoolProvider>
|
||||
<DiffComponentProvider component={ClientOnlyDiff}>
|
||||
<CodeComponentProvider component={ClientOnlyCode}>
|
||||
<DataProvider data={data()} directory={info().directory}>
|
||||
{iife(() => {
|
||||
const [store, setStore] = createStore({
|
||||
messageId: undefined as string | undefined,
|
||||
})
|
||||
const messages = createMemo(() =>
|
||||
data().sessionID
|
||||
? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort(
|
||||
(a, b) => a.time.created - b.time.created,
|
||||
)
|
||||
: [],
|
||||
)
|
||||
const firstUserMessage = createMemo(() => messages().at(0))
|
||||
const activeMessage = createMemo(
|
||||
() => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(),
|
||||
)
|
||||
function setActiveMessage(message: UserMessage | undefined) {
|
||||
if (message) {
|
||||
setStore("messageId", message.id)
|
||||
} else {
|
||||
setStore("messageId", undefined)
|
||||
}
|
||||
}
|
||||
const provider = createMemo(() => activeMessage()?.model?.providerID)
|
||||
const modelID = createMemo(() => activeMessage()?.model?.modelID)
|
||||
const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID()))
|
||||
const diffs = createMemo(() => {
|
||||
const diffs = data().session_diff[data().sessionID] ?? []
|
||||
const preloaded = data().session_diff_preload[data().sessionID] ?? []
|
||||
return diffs.map((diff) => ({
|
||||
...diff,
|
||||
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
|
||||
}))
|
||||
})
|
||||
const splitDiffs = createMemo(() => {
|
||||
const diffs = data().session_diff[data().sessionID] ?? []
|
||||
const preloaded = data().session_diff_preload_split[data().sessionID] ?? []
|
||||
return diffs.map((diff) => ({
|
||||
...diff,
|
||||
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
|
||||
}))
|
||||
})
|
||||
<DiffComponentProvider component={ClientOnlyDiff}>
|
||||
<DataProvider data={data()} directory={info().directory}>
|
||||
{iife(() => {
|
||||
const [store, setStore] = createStore({
|
||||
messageId: undefined as string | undefined,
|
||||
})
|
||||
const messages = createMemo(() =>
|
||||
data().sessionID
|
||||
? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort(
|
||||
(a, b) => a.time.created - b.time.created,
|
||||
)
|
||||
: [],
|
||||
)
|
||||
const firstUserMessage = createMemo(() => messages().at(0))
|
||||
const activeMessage = createMemo(
|
||||
() => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(),
|
||||
)
|
||||
function setActiveMessage(message: UserMessage | undefined) {
|
||||
if (message) {
|
||||
setStore("messageId", message.id)
|
||||
} else {
|
||||
setStore("messageId", undefined)
|
||||
}
|
||||
}
|
||||
const provider = createMemo(() => activeMessage()?.model?.providerID)
|
||||
const modelID = createMemo(() => activeMessage()?.model?.modelID)
|
||||
const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID()))
|
||||
const diffs = createMemo(() => {
|
||||
const diffs = data().session_diff[data().sessionID] ?? []
|
||||
const preloaded = data().session_diff_preload[data().sessionID] ?? []
|
||||
return diffs.map((diff) => ({
|
||||
...diff,
|
||||
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
|
||||
}))
|
||||
})
|
||||
const splitDiffs = createMemo(() => {
|
||||
const diffs = data().session_diff[data().sessionID] ?? []
|
||||
const preloaded = data().session_diff_preload_split[data().sessionID] ?? []
|
||||
return diffs.map((diff) => ({
|
||||
...diff,
|
||||
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
|
||||
}))
|
||||
})
|
||||
|
||||
const title = () => (
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="h-8 flex gap-4 items-center justify-start self-stretch">
|
||||
<div class="pl-[2.5px] pr-2 flex items-center gap-1.75 bg-surface-strong shadow-xs-border-base">
|
||||
<Mark class="shrink-0 w-3 my-0.5" />
|
||||
<div class="text-12-mono text-text-base">v{info().version}</div>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<ProviderIcon
|
||||
id={provider() as IconName}
|
||||
class="size-3.5 shrink-0 text-icon-strong-base"
|
||||
/>
|
||||
<div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div>
|
||||
</div>
|
||||
<div class="text-12-regular text-text-weaker">
|
||||
{DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-left text-16-medium text-text-strong">{info().title}</div>
|
||||
const title = () => (
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="h-8 flex gap-4 items-center justify-start self-stretch">
|
||||
<div class="pl-[2.5px] pr-2 flex items-center gap-1.75 bg-surface-strong shadow-xs-border-base">
|
||||
<Mark class="shrink-0 w-3 my-0.5" />
|
||||
<div class="text-12-mono text-text-base">v{info().version}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const turns = () => (
|
||||
<div class="relative mt-2 pb-8 min-w-0 w-full h-full overflow-y-auto no-scrollbar">
|
||||
<div class="px-4 py-6">{title()}</div>
|
||||
<div class="flex flex-col gap-15 items-start justify-start mt-4">
|
||||
<For each={messages()}>
|
||||
{(message) => (
|
||||
<SessionTurn
|
||||
sessionID={data().sessionID}
|
||||
messageID={message.id}
|
||||
classes={{
|
||||
root: "min-w-0 w-full relative",
|
||||
content:
|
||||
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
||||
container: "px-4",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div class="px-4 flex items-center justify-center pt-20 pb-8 shrink-0">
|
||||
<Logo class="w-58.5 opacity-12" />
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<ProviderIcon id={provider() as IconName} class="size-3.5 shrink-0 text-icon-strong-base" />
|
||||
<div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div>
|
||||
</div>
|
||||
)
|
||||
<div class="text-12-regular text-text-weaker">
|
||||
{DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-left text-16-medium text-text-strong">{info().title}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const wide = createMemo(() => diffs().length === 0)
|
||||
const turns = () => (
|
||||
<div class="relative mt-2 pt-6 pb-8 min-w-0 w-full h-full overflow-y-auto no-scrollbar">
|
||||
<div class="px-4">{title()}</div>
|
||||
<div class="flex flex-col gap-15 items-start justify-start mt-4">
|
||||
<For each={messages()}>
|
||||
{(message) => (
|
||||
<SessionTurn
|
||||
sessionID={data().sessionID}
|
||||
messageID={message.id}
|
||||
classes={{
|
||||
root: "min-w-0 w-full relative",
|
||||
content:
|
||||
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
||||
container: "px-4",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div class="px-4 flex items-center justify-center pt-20 pb-8 shrink-0">
|
||||
<Logo class="w-58.5 opacity-12" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col">
|
||||
<header class="h-12 px-6 py-2 flex items-center justify-between self-stretch bg-background-base border-b border-border-weak-base">
|
||||
<div class="">
|
||||
<a href="https://opencode.ai">
|
||||
<Mark />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-3 items-center">
|
||||
<IconButton
|
||||
as={"a"}
|
||||
href="https://github.com/sst/opencode"
|
||||
target="_blank"
|
||||
icon="github"
|
||||
variant="ghost"
|
||||
/>
|
||||
<IconButton
|
||||
as={"a"}
|
||||
href="https://opencode.ai/discord"
|
||||
target="_blank"
|
||||
icon="discord"
|
||||
variant="ghost"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="select-text flex flex-col flex-1 min-h-0">
|
||||
const wide = createMemo(() => diffs().length === 0)
|
||||
|
||||
return (
|
||||
<div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col">
|
||||
<header class="h-12 px-6 py-2 flex items-center justify-between self-stretch bg-background-base border-b border-border-weak-base">
|
||||
<div class="">
|
||||
<a href="https://opencode.ai">
|
||||
<Mark />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-3 items-center">
|
||||
<IconButton
|
||||
as={"a"}
|
||||
href="https://github.com/sst/opencode"
|
||||
target="_blank"
|
||||
icon="github"
|
||||
variant="ghost"
|
||||
/>
|
||||
<IconButton
|
||||
as={"a"}
|
||||
href="https://opencode.ai/discord"
|
||||
target="_blank"
|
||||
icon="discord"
|
||||
variant="ghost"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="select-text flex flex-col flex-1 min-h-0">
|
||||
<div
|
||||
classList={{ "hidden w-full flex-1 min-h-0": true, "md:flex": wide(), "lg:flex": !wide() }}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
|
||||
"mx-auto max-w-146": !wide(),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"hidden w-full flex-1 min-h-0": true,
|
||||
"md:flex": wide(),
|
||||
"lg:flex": !wide(),
|
||||
"w-full flex justify-start items-start min-w-0": true,
|
||||
"max-w-146 mx-auto px-6": wide(),
|
||||
"pr-6 pl-18": !wide() && messages().length > 1,
|
||||
"px-6": !wide() && messages().length === 1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
|
||||
"mx-auto max-w-200": !wide(),
|
||||
{title()}
|
||||
</div>
|
||||
<div class="flex items-start justify-start h-full min-h-0">
|
||||
<SessionMessageRail
|
||||
messages={messages()}
|
||||
current={activeMessage()}
|
||||
onMessageSelect={setActiveMessage}
|
||||
wide={wide()}
|
||||
/>
|
||||
<SessionTurn
|
||||
sessionID={data().sessionID}
|
||||
messageID={store.messageId ?? firstUserMessage()!.id!}
|
||||
classes={{
|
||||
root: "grow",
|
||||
content: "flex flex-col justify-between items-start",
|
||||
container:
|
||||
"w-full pb-20 " +
|
||||
(wide()
|
||||
? "max-w-146 mx-auto px-6"
|
||||
: messages().length > 1
|
||||
? "pr-6 pl-18"
|
||||
: "px-6"),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"w-full flex justify-start items-start min-w-0": true,
|
||||
"max-w-200 mx-auto px-6": wide(),
|
||||
"pr-6 pl-18": !wide() && messages().length > 1,
|
||||
"px-6": !wide() && messages().length === 1,
|
||||
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
|
||||
<Logo class="w-58.5 opacity-12" />
|
||||
</div>
|
||||
</SessionTurn>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={diffs().length > 0}>
|
||||
<DiffComponentProvider component={SSRDiff}>
|
||||
<div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
|
||||
<SessionReview
|
||||
class="@4xl:hidden"
|
||||
diffs={diffs()}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
>
|
||||
{title()}
|
||||
</div>
|
||||
<div class="flex items-start justify-start h-full min-h-0">
|
||||
<SessionMessageRail
|
||||
messages={messages()}
|
||||
current={activeMessage()}
|
||||
onMessageSelect={setActiveMessage}
|
||||
wide={wide()}
|
||||
/>
|
||||
<SessionTurn
|
||||
sessionID={data().sessionID}
|
||||
messageID={store.messageId ?? firstUserMessage()!.id!}
|
||||
classes={{
|
||||
root: "grow",
|
||||
content: "flex flex-col justify-between",
|
||||
container:
|
||||
"w-full pb-20 " +
|
||||
(wide()
|
||||
? "max-w-200 mx-auto px-6"
|
||||
: messages().length > 1
|
||||
? "pr-6 pl-18"
|
||||
: "px-6"),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}
|
||||
>
|
||||
<Logo class="w-58.5 opacity-12" />
|
||||
</div>
|
||||
</SessionTurn>
|
||||
</div>
|
||||
/>
|
||||
<SessionReview
|
||||
split
|
||||
class="hidden @4xl:flex"
|
||||
diffs={splitDiffs()}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Show when={diffs().length > 0}>
|
||||
<DiffComponentProvider component={SSRDiff}>
|
||||
<div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
|
||||
</DiffComponentProvider>
|
||||
</Show>
|
||||
</div>
|
||||
<Switch>
|
||||
<Match when={diffs().length > 0}>
|
||||
<Tabs classList={{ "md:hidden": wide(), "lg:hidden": !wide() }}>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}>
|
||||
Session
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="review" class="w-1/2 !border-r-0" classes={{ button: "w-full" }}>
|
||||
{diffs().length} Files Changed
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="session" class="!overflow-hidden">
|
||||
{turns()}
|
||||
</Tabs.Content>
|
||||
<Tabs.Content
|
||||
forceMount
|
||||
value="review"
|
||||
class="!overflow-hidden hidden data-[selected]:block"
|
||||
>
|
||||
<div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
|
||||
<DiffComponentProvider component={SSRDiff}>
|
||||
<SessionReview
|
||||
class="@4xl:hidden"
|
||||
diffs={diffs()}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
header: "px-4",
|
||||
container: "px-4",
|
||||
}}
|
||||
/>
|
||||
<SessionReview
|
||||
split
|
||||
class="hidden @4xl:flex"
|
||||
diffs={splitDiffs()}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</DiffComponentProvider>
|
||||
</Show>
|
||||
</div>
|
||||
<Switch>
|
||||
<Match when={diffs().length > 0}>
|
||||
<Tabs classList={{ "md:hidden": wide(), "lg:hidden": !wide() }}>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}>
|
||||
Session
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
value="review"
|
||||
class="w-1/2 !border-r-0"
|
||||
classes={{ button: "w-full" }}
|
||||
>
|
||||
{diffs().length} Files Changed
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="session" class="!overflow-hidden">
|
||||
{turns()}
|
||||
</Tabs.Content>
|
||||
<Tabs.Content
|
||||
forceMount
|
||||
value="review"
|
||||
class="!overflow-hidden hidden data-[selected]:block"
|
||||
>
|
||||
<div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
|
||||
<DiffComponentProvider component={SSRDiff}>
|
||||
<SessionReview
|
||||
diffs={diffs()}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-4",
|
||||
container: "px-4",
|
||||
}}
|
||||
/>
|
||||
</DiffComponentProvider>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div
|
||||
classList={{ "!overflow-hidden": true, "md:hidden": wide(), "lg:hidden": !wide() }}
|
||||
>
|
||||
{turns()}
|
||||
</DiffComponentProvider>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</DataProvider>
|
||||
</CodeComponentProvider>
|
||||
</DiffComponentProvider>
|
||||
</ClientOnlyWorkerPoolProvider>
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div classList={{ "!overflow-hidden": true, "md:hidden": wide(), "lg:hidden": !wide() }}>
|
||||
{turns()}
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</DataProvider>
|
||||
</DiffComponentProvider>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.0.165"
|
||||
version = "1.0.163"
|
||||
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.165/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/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.165/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.165/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/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.165/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/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.165/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"$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.165",
|
||||
"version": "1.0.163",
|
||||
"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.0.0-20251211-4403a69a",
|
||||
"@opentui/solid": "0.0.0-20251211-4403a69a",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -128,19 +128,6 @@ const AGENT_USERNAME = "opencode-agent[bot]"
|
||||
const AGENT_REACTION = "eyes"
|
||||
const WORKFLOW_FILE = ".github/workflows/opencode.yml"
|
||||
|
||||
// Parses GitHub remote URLs in various formats:
|
||||
// - https://github.com/owner/repo.git
|
||||
// - https://github.com/owner/repo
|
||||
// - git@github.com:owner/repo.git
|
||||
// - git@github.com:owner/repo
|
||||
// - ssh://git@github.com/owner/repo.git
|
||||
// - ssh://git@github.com/owner/repo
|
||||
export function parseGitHubRemote(url: string): { owner: string; repo: string } | null {
|
||||
const match = url.match(/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/)
|
||||
if (!match) return null
|
||||
return { owner: match[1], repo: match[2] }
|
||||
}
|
||||
|
||||
export const GithubCommand = cmd({
|
||||
command: "github",
|
||||
describe: "manage GitHub agent",
|
||||
@@ -210,12 +197,20 @@ export const GithubInstallCommand = cmd({
|
||||
|
||||
// Get repo info
|
||||
const info = (await $`git remote get-url origin`.quiet().nothrow().text()).trim()
|
||||
const parsed = parseGitHubRemote(info)
|
||||
// match https or git pattern
|
||||
// ie. https://github.com/sst/opencode.git
|
||||
// ie. https://github.com/sst/opencode
|
||||
// ie. git@github.com:sst/opencode.git
|
||||
// ie. git@github.com:sst/opencode
|
||||
// ie. ssh://git@github.com/sst/opencode.git
|
||||
// ie. ssh://git@github.com/sst/opencode
|
||||
const parsed = info.match(/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/)
|
||||
if (!parsed) {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
return { owner: parsed.owner, repo: parsed.repo, root: Instance.worktree }
|
||||
const [, owner, repo] = parsed
|
||||
return { owner, repo, root: Instance.worktree }
|
||||
}
|
||||
|
||||
async function promptProvider() {
|
||||
@@ -602,26 +597,21 @@ export const GithubRunCommand = cmd({
|
||||
}
|
||||
|
||||
const reviewContext = getReviewCommentContext()
|
||||
const mentions = (process.env["MENTIONS"] || "/opencode,/oc")
|
||||
.split(",")
|
||||
.map((m) => m.trim().toLowerCase())
|
||||
.filter(Boolean)
|
||||
let prompt = (() => {
|
||||
const body = payload.comment.body.trim()
|
||||
const bodyLower = body.toLowerCase()
|
||||
if (mentions.some((m) => bodyLower === m)) {
|
||||
if (body === "/opencode" || body === "/oc") {
|
||||
if (reviewContext) {
|
||||
return `Review this code change and suggest improvements for the commented lines:\n\nFile: ${reviewContext.file}\nLines: ${reviewContext.line}\n\n${reviewContext.diffHunk}`
|
||||
}
|
||||
return "Summarize this thread"
|
||||
}
|
||||
if (mentions.some((m) => bodyLower.includes(m))) {
|
||||
if (body.includes("/opencode") || body.includes("/oc")) {
|
||||
if (reviewContext) {
|
||||
return `${body}\n\nContext: You are reviewing a comment on file "${reviewContext.file}" at line ${reviewContext.line}.\n\nDiff context:\n${reviewContext.diffHunk}`
|
||||
}
|
||||
return body
|
||||
}
|
||||
throw new Error(`Comments must mention ${mentions.map((m) => "`" + m + "`").join(" or ")}`)
|
||||
throw new Error("Comments must mention `/opencode` or `/oc`")
|
||||
})()
|
||||
|
||||
// Handle images
|
||||
|
||||
@@ -147,14 +147,6 @@ export function tui(input: { url: string; args: Args; onExit?: () => Promise<voi
|
||||
gatherStats: false,
|
||||
exitOnCtrlC: false,
|
||||
useKittyKeyboard: {},
|
||||
consoleOptions: {
|
||||
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
|
||||
onCopySelection: (text) => {
|
||||
Clipboard.copy(text).catch((error) => {
|
||||
console.error(`Failed to copy console selection to clipboard: ${error}`)
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -182,8 +174,6 @@ function App() {
|
||||
|
||||
// Update terminal window title based on current route and session
|
||||
createEffect(() => {
|
||||
if (Flag.OPENCODE_DISABLE_TERMINAL_TITLE) return
|
||||
|
||||
if (route.data.type === "home") {
|
||||
renderer.setTerminalTitle("OpenCode")
|
||||
return
|
||||
@@ -484,6 +474,7 @@ function App() {
|
||||
|
||||
event.on(SessionApi.Event.Deleted.type, (evt) => {
|
||||
if (route.data.type === "session" && route.data.sessionID === evt.properties.info.id) {
|
||||
dialog.clear()
|
||||
route.navigate({ type: "home" })
|
||||
toast.show({
|
||||
variant: "info",
|
||||
|
||||
@@ -37,7 +37,7 @@ export function DialogSessionList() {
|
||||
category = "Today"
|
||||
}
|
||||
const isDeleting = toDelete() === x.id
|
||||
const status = sync.data.session_status?.[x.id]
|
||||
const status = sync.data.session_status[x.id]
|
||||
const isWorking = status?.type === "busy"
|
||||
return {
|
||||
title: isDeleting ? `Press ${deleteKeybind} again to confirm` : x.title,
|
||||
@@ -84,6 +84,7 @@ export function DialogSessionList() {
|
||||
sessionID: option.value,
|
||||
})
|
||||
setToDelete(undefined)
|
||||
// dialog.clear()
|
||||
return
|
||||
}
|
||||
setToDelete(option.value)
|
||||
|
||||
@@ -11,31 +11,6 @@ export function DialogStatus() {
|
||||
|
||||
const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
|
||||
|
||||
const plugins = createMemo(() => {
|
||||
const list = sync.data.config.plugin ?? []
|
||||
const result = list.map((value) => {
|
||||
if (value.startsWith("file://")) {
|
||||
const path = value.substring("file://".length)
|
||||
const parts = path.split("/")
|
||||
const filename = parts.pop() || path
|
||||
if (!filename.includes(".")) return { name: filename }
|
||||
const basename = filename.split(".")[0]
|
||||
if (basename === "index") {
|
||||
const dirname = parts.pop()
|
||||
const name = dirname || basename
|
||||
return { name }
|
||||
}
|
||||
return { name: basename }
|
||||
}
|
||||
const index = value.lastIndexOf("@")
|
||||
if (index <= 0) return { name: value, version: "latest" }
|
||||
const name = value.substring(0, index)
|
||||
const version = value.substring(index + 1)
|
||||
return { name, version }
|
||||
})
|
||||
return result.toSorted((a, b) => a.name.localeCompare(b.name))
|
||||
})
|
||||
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
@@ -134,29 +109,6 @@ export function DialogStatus() {
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
|
||||
<box>
|
||||
<text fg={theme.text}>{plugins().length} Plugins</text>
|
||||
<For each={plugins()}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: theme.success,
|
||||
}}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text wrapMode="word" fg={theme.text}>
|
||||
<b>{item.name}</b>
|
||||
{item.version && <span style={{ fg: theme.textMuted }}> @{item.version}</span>}
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -72,13 +72,9 @@ export function Autocomplete(props: {
|
||||
const dims = dimensions()
|
||||
positionTick()
|
||||
const anchor = props.anchor()
|
||||
const parent = anchor.parent
|
||||
const parentX = parent?.x ?? 0
|
||||
const parentY = parent?.y ?? 0
|
||||
|
||||
return {
|
||||
x: anchor.x - parentX,
|
||||
y: anchor.y - parentY,
|
||||
x: anchor.x,
|
||||
y: anchor.y,
|
||||
width: anchor.width,
|
||||
}
|
||||
})
|
||||
@@ -361,7 +357,7 @@ export function Autocomplete(props: {
|
||||
|
||||
const options = createMemo(() => {
|
||||
const mixed: AutocompleteOption[] = (
|
||||
store.visible === "@" ? [...agents(), ...(files() || [])] : [...commands()]
|
||||
store.visible === "@" ? [...agents(), ...(files.loading ? files.latest || [] : files())] : [...commands()]
|
||||
).filter((x) => x.disabled !== true)
|
||||
const currentFilter = filter()
|
||||
if (!currentFilter) return mixed.slice(0, 10)
|
||||
|
||||
@@ -116,7 +116,7 @@ export function Prompt(props: PromptProps) {
|
||||
const sync = useSync()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" })
|
||||
const status = createMemo(() => sync.data.session_status[props.sessionID ?? ""] ?? { type: "idle" })
|
||||
const history = usePromptHistory()
|
||||
const command = useCommandDialog()
|
||||
const renderer = useRenderer()
|
||||
|
||||
@@ -23,7 +23,6 @@ import nord from "./theme/nord.json" with { type: "json" }
|
||||
import onedark from "./theme/one-dark.json" with { type: "json" }
|
||||
import opencode from "./theme/opencode.json" with { type: "json" }
|
||||
import orng from "./theme/orng.json" with { type: "json" }
|
||||
import lucentOrng from "./theme/lucent-orng.json" with { type: "json" }
|
||||
import palenight from "./theme/palenight.json" with { type: "json" }
|
||||
import rosepine from "./theme/rosepine.json" with { type: "json" }
|
||||
import solarized from "./theme/solarized.json" with { type: "json" }
|
||||
@@ -153,7 +152,6 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
||||
["one-dark"]: onedark,
|
||||
opencode,
|
||||
orng,
|
||||
["lucent-orng"]: lucentOrng,
|
||||
palenight,
|
||||
rosepine,
|
||||
solarized,
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"darkStep6": "#3c3c3c",
|
||||
"darkStep11": "#808080",
|
||||
"darkStep12": "#eeeeee",
|
||||
"darkSecondary": "#EE7948",
|
||||
"darkAccent": "#FFF7F1",
|
||||
"darkRed": "#e06c75",
|
||||
"darkOrange": "#EC5B2B",
|
||||
"darkBlue": "#6ba1e6",
|
||||
"darkCyan": "#56b6c2",
|
||||
"darkYellow": "#e5c07b",
|
||||
"lightStep6": "#d4d4d4",
|
||||
"lightStep11": "#8a8a8a",
|
||||
"lightStep12": "#1a1a1a",
|
||||
"lightSecondary": "#EE7948",
|
||||
"lightAccent": "#c94d24",
|
||||
"lightRed": "#d1383d",
|
||||
"lightOrange": "#EC5B2B",
|
||||
"lightBlue": "#0062d1",
|
||||
"lightCyan": "#318795",
|
||||
"lightYellow": "#b0851f"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightSecondary"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "darkAccent",
|
||||
"light": "lightAccent"
|
||||
},
|
||||
"error": {
|
||||
"dark": "darkRed",
|
||||
"light": "lightRed"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"success": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightBlue"
|
||||
},
|
||||
"info": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"text": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
},
|
||||
"background": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"border": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightAccent"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "darkStep6",
|
||||
"light": "lightStep6"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightBlue"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "#c53b53",
|
||||
"light": "#c53b53"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "#828bb8",
|
||||
"light": "#7086b5"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "#828bb8",
|
||||
"light": "#7086b5"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightBlue"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "#e26a75",
|
||||
"light": "#f52a65"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "#666666",
|
||||
"light": "#999999"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "transparent",
|
||||
"light": "transparent"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightBlue"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "darkAccent",
|
||||
"light": "lightYellow"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "darkYellow",
|
||||
"light": "lightYellow"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "darkStep11",
|
||||
"light": "lightStep11"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "darkOrange",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "darkSecondary",
|
||||
"light": "lightAccent"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "darkRed",
|
||||
"light": "lightRed"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "darkBlue",
|
||||
"light": "lightBlue"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "darkAccent",
|
||||
"light": "lightOrange"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "darkYellow",
|
||||
"light": "lightYellow"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "darkCyan",
|
||||
"light": "lightCyan"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "darkStep12",
|
||||
"light": "lightStep12"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,7 +225,7 @@ export function Session() {
|
||||
const parentID = session()?.parentID ?? session()?.id
|
||||
let children = sync.data.session
|
||||
.filter((x) => x.parentID === parentID || x.id === parentID)
|
||||
.toSorted((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
||||
.toSorted((b, a) => a.id.localeCompare(b.id))
|
||||
if (children.length === 1) return
|
||||
let next = children.findIndex((x) => x.id === session()?.id) + direction
|
||||
if (next >= children.length) next = 0
|
||||
@@ -340,7 +340,7 @@ export function Session() {
|
||||
keybind: "messages_undo",
|
||||
category: "Session",
|
||||
onSelect: async (dialog) => {
|
||||
const status = sync.data.session_status?.[route.sessionID]
|
||||
const status = sync.data.session_status[route.sessionID]
|
||||
if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
|
||||
const revert = session().revert?.messageID
|
||||
const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user")
|
||||
@@ -840,9 +840,6 @@ export function Session() {
|
||||
</Show>
|
||||
<scrollbox
|
||||
ref={(r) => (scroll = r)}
|
||||
viewportOptions={{
|
||||
paddingRight: showScrollbar() ? 1 : 0,
|
||||
}}
|
||||
verticalScrollbarOptions={{
|
||||
paddingLeft: 1,
|
||||
visible: showScrollbar(),
|
||||
@@ -1423,24 +1420,20 @@ ToolRegistry.register<typeof WriteTool>({
|
||||
return props.metadata.diagnostics?.[filePath] ?? []
|
||||
})
|
||||
|
||||
const done = !!props.input.filePath
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolTitle icon="←" fallback="Preparing write..." when={done}>
|
||||
<ToolTitle icon="←" fallback="Preparing write..." when={props.input.filePath}>
|
||||
Wrote {props.input.filePath}
|
||||
</ToolTitle>
|
||||
<Show when={done}>
|
||||
<line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
|
||||
<code
|
||||
conceal={false}
|
||||
fg={theme.text}
|
||||
filetype={filetype(props.input.filePath!)}
|
||||
syntaxStyle={syntax()}
|
||||
content={code()}
|
||||
/>
|
||||
</line_number>
|
||||
</Show>
|
||||
<line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
|
||||
<code
|
||||
conceal={false}
|
||||
fg={theme.text}
|
||||
filetype={filetype(props.input.filePath!)}
|
||||
syntaxStyle={syntax()}
|
||||
content={code()}
|
||||
/>
|
||||
</line_number>
|
||||
<Show when={diagnostics().length}>
|
||||
<For each={diagnostics()}>
|
||||
{(diagnostic) => (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
|
||||
import { useTheme, selectedForeground } from "@tui/context/theme"
|
||||
import { entries, filter, flatMap, groupBy, pipe, take } from "remeda"
|
||||
import { batch, createEffect, createMemo, For, Show, type JSX, on } from "solid-js"
|
||||
import { batch, createEffect, createMemo, For, Show, type JSX } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
|
||||
import * as fuzzysort from "fuzzysort"
|
||||
@@ -53,19 +53,14 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||
filter: "",
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.current,
|
||||
(current) => {
|
||||
if (current) {
|
||||
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
|
||||
if (currentIndex >= 0) {
|
||||
setStore("selected", currentIndex)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
createEffect(() => {
|
||||
if (props.current) {
|
||||
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, props.current))
|
||||
if (currentIndex >= 0) {
|
||||
setStore("selected", currentIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let input: InputRenderable
|
||||
|
||||
@@ -103,19 +98,18 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||
|
||||
const selected = createMemo(() => flat()[store.selected])
|
||||
|
||||
createEffect(
|
||||
on([() => store.filter, () => props.current], ([filter, current]) => {
|
||||
if (filter.length > 0) {
|
||||
setStore("selected", 0)
|
||||
} else if (current) {
|
||||
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
|
||||
if (currentIndex >= 0) {
|
||||
setStore("selected", currentIndex)
|
||||
}
|
||||
createEffect(() => {
|
||||
store.filter
|
||||
if (store.filter.length > 0) {
|
||||
setStore("selected", 0)
|
||||
} else if (props.current) {
|
||||
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, props.current))
|
||||
if (currentIndex >= 0) {
|
||||
setStore("selected", currentIndex)
|
||||
}
|
||||
scroll.scrollTo(0)
|
||||
}),
|
||||
)
|
||||
}
|
||||
scroll.scrollTo(0)
|
||||
})
|
||||
|
||||
function move(direction: number) {
|
||||
let next = store.selected + direction
|
||||
|
||||
@@ -5,7 +5,7 @@ import os from "os"
|
||||
import z from "zod"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { ModelsDev } from "../provider/models"
|
||||
import { mergeDeep, pipe, unique } from "remeda"
|
||||
import { mergeDeep, pipe } from "remeda"
|
||||
import { Global } from "../global"
|
||||
import fs from "fs/promises"
|
||||
import { lazy } from "../util/lazy"
|
||||
@@ -76,13 +76,6 @@ export namespace Config {
|
||||
stop: Instance.worktree,
|
||||
}),
|
||||
)),
|
||||
...(await Array.fromAsync(
|
||||
Filesystem.up({
|
||||
targets: [".opencode"],
|
||||
start: Global.Path.home,
|
||||
stop: Global.Path.home,
|
||||
}),
|
||||
)),
|
||||
]
|
||||
|
||||
if (Flag.OPENCODE_CONFIG_DIR) {
|
||||
@@ -91,7 +84,7 @@ export namespace Config {
|
||||
}
|
||||
|
||||
const promises: Promise<void>[] = []
|
||||
for (const dir of unique(directories)) {
|
||||
for (const dir of directories) {
|
||||
await assertValid(dir)
|
||||
|
||||
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
|
||||
@@ -218,7 +211,7 @@ export namespace Config {
|
||||
result[config.name] = parsed.data
|
||||
continue
|
||||
}
|
||||
throw new InvalidError({ path: item, issues: parsed.error.issues }, { cause: parsed.error })
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -261,7 +254,7 @@ export namespace Config {
|
||||
result[config.name] = parsed.data
|
||||
continue
|
||||
}
|
||||
throw new InvalidError({ path: item, issues: parsed.error.issues }, { cause: parsed.error })
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@ import { Instance } from "../project/instance"
|
||||
import { Log } from "../util/log"
|
||||
import { FileIgnore } from "./ignore"
|
||||
import { Config } from "../config/config"
|
||||
import path from "path"
|
||||
// @ts-ignore
|
||||
import { createWrapper } from "@parcel/watcher/wrapper"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import type ParcelWatcher from "@parcel/watcher"
|
||||
import { $ } from "bun"
|
||||
import { Flag } from "@/flag/flag"
|
||||
|
||||
declare const OPENCODE_LIBC: string | undefined
|
||||
|
||||
@@ -59,24 +57,17 @@ export namespace FileWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
const subs: ParcelWatcher.AsyncSubscription[] = []
|
||||
const subs = []
|
||||
const cfgIgnores = cfg.watcher?.ignore ?? []
|
||||
|
||||
if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
|
||||
subs.push(
|
||||
await watcher().subscribe(Instance.directory, subscribe, {
|
||||
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
|
||||
backend,
|
||||
}),
|
||||
)
|
||||
}
|
||||
subs.push(
|
||||
await watcher().subscribe(Instance.directory, subscribe, {
|
||||
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
|
||||
backend,
|
||||
}),
|
||||
)
|
||||
|
||||
const vcsDir = await $`git rev-parse --git-dir`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.cwd(Instance.worktree)
|
||||
.text()
|
||||
.then((x) => path.resolve(Instance.worktree, x.trim()))
|
||||
const vcsDir = await $`git rev-parse --git-dir`.quiet().nothrow().cwd(Instance.worktree).text()
|
||||
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
|
||||
subs.push(
|
||||
await watcher().subscribe(vcsDir, subscribe, {
|
||||
@@ -95,9 +86,6 @@ export namespace FileWatcher {
|
||||
)
|
||||
|
||||
export function init() {
|
||||
if (Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) {
|
||||
return
|
||||
}
|
||||
state()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ export namespace Flag {
|
||||
export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
|
||||
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
|
||||
export const OPENCODE_DISABLE_PRUNE = truthy("OPENCODE_DISABLE_PRUNE")
|
||||
export const OPENCODE_DISABLE_TERMINAL_TITLE = truthy("OPENCODE_DISABLE_TERMINAL_TITLE")
|
||||
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
|
||||
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
|
||||
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
|
||||
@@ -18,8 +17,6 @@ export namespace Flag {
|
||||
|
||||
// Experimental
|
||||
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
|
||||
export const OPENCODE_EXPERIMENTAL_FILEWATCHER = truthy("OPENCODE_EXPERIMENTAL_FILEWATCHER")
|
||||
export const OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = truthy("OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER")
|
||||
export const OPENCODE_EXPERIMENTAL_ICON_DISCOVERY =
|
||||
OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY")
|
||||
export const OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT = truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT")
|
||||
@@ -27,8 +24,6 @@ export namespace Flag {
|
||||
truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
|
||||
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
|
||||
export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS")
|
||||
export const OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX = number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX")
|
||||
export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_OXFMT")
|
||||
|
||||
function truthy(key: string) {
|
||||
const value = process.env[key]?.toLowerCase()
|
||||
|
||||
@@ -2,7 +2,6 @@ import { readableStreamToText } from "bun"
|
||||
import { BunProc } from "../bun"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Flag } from "@/flag/flag"
|
||||
|
||||
export interface Info {
|
||||
name: string
|
||||
@@ -75,25 +74,6 @@ export const prettier: Info = {
|
||||
},
|
||||
}
|
||||
|
||||
export const oxfmt: Info = {
|
||||
name: "oxfmt",
|
||||
command: [BunProc.which(), "x", "oxfmt", "$FILE"],
|
||||
environment: {
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"],
|
||||
async enabled() {
|
||||
if (!Flag.OPENCODE_EXPERIMENTAL_OXFMT) return false
|
||||
const items = await Filesystem.findUp("package.json", Instance.directory, Instance.worktree)
|
||||
for (const item of items) {
|
||||
const json = await Bun.file(item).json()
|
||||
if (json.dependencies?.oxfmt) return true
|
||||
if (json.devDependencies?.oxfmt) return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
export const biome: Info = {
|
||||
name: "biome",
|
||||
command: [BunProc.which(), "x", "@biomejs/biome", "format", "--write", "$FILE"],
|
||||
|
||||
@@ -494,18 +494,11 @@ export namespace MCP {
|
||||
// Extract state from authorization URL to use as callback key
|
||||
// If no state parameter, use mcpName as fallback
|
||||
const authUrl = new URL(authorizationUrl)
|
||||
let oauthState = mcpName
|
||||
|
||||
if (authUrl.searchParams.has("state")) {
|
||||
oauthState = authUrl.searchParams.get("state")!
|
||||
} else {
|
||||
log.info("no state parameter in authorization URL, using mcpName as state", { mcpName })
|
||||
authUrl.searchParams.set("state", oauthState)
|
||||
}
|
||||
const oauthState = authUrl.searchParams.get("state") ?? mcpName
|
||||
|
||||
// Open browser
|
||||
log.info("opening browser for oauth", { mcpName, url: authUrl.toString(), state: oauthState })
|
||||
await open(authUrl.toString())
|
||||
log.info("opening browser for oauth", { mcpName, url: authorizationUrl, state: oauthState })
|
||||
await open(authorizationUrl)
|
||||
|
||||
// Wait for callback using the OAuth state parameter (or mcpName as fallback)
|
||||
const code = await McpOAuthCallback.waitForCallback(oauthState)
|
||||
|
||||
@@ -139,22 +139,8 @@ export namespace Server {
|
||||
})
|
||||
}
|
||||
GlobalBus.on("event", handler)
|
||||
|
||||
// Send heartbeat every 30s to prevent WKWebView timeout (60s default)
|
||||
const heartbeat = setInterval(() => {
|
||||
stream.writeSSE({
|
||||
data: JSON.stringify({
|
||||
payload: {
|
||||
type: "server.heartbeat",
|
||||
properties: {},
|
||||
},
|
||||
}),
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
stream.onAbort(() => {
|
||||
clearInterval(heartbeat)
|
||||
GlobalBus.off("event", handler)
|
||||
resolve()
|
||||
log.info("global event disconnected")
|
||||
@@ -779,6 +765,9 @@ export namespace Server {
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").sessionID
|
||||
await Session.remove(sessionID)
|
||||
await Bus.publish(TuiEvent.CommandExecute, {
|
||||
command: "session.list",
|
||||
})
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
@@ -2481,20 +2470,8 @@ export namespace Server {
|
||||
stream.close()
|
||||
}
|
||||
})
|
||||
|
||||
// Send heartbeat every 30s to prevent WKWebView timeout (60s default)
|
||||
const heartbeat = setInterval(() => {
|
||||
stream.writeSSE({
|
||||
data: JSON.stringify({
|
||||
type: "server.heartbeat",
|
||||
properties: {},
|
||||
}),
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
stream.onAbort(() => {
|
||||
clearInterval(heartbeat)
|
||||
unsub()
|
||||
resolve()
|
||||
log.info("event disconnected")
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Log } from "../util/log"
|
||||
import { SessionProcessor } from "./processor"
|
||||
import { fn } from "@/util/fn"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Plugin } from "@/plugin"
|
||||
|
||||
export namespace SessionCompaction {
|
||||
const log = Log.create({ service: "session.compaction" })
|
||||
@@ -126,12 +125,6 @@ export namespace SessionCompaction {
|
||||
model,
|
||||
abort: input.abort,
|
||||
})
|
||||
// Allow plugins to inject context for compaction
|
||||
const compacting = await Plugin.trigger(
|
||||
"experimental.session.compacting",
|
||||
{ sessionID: input.sessionID },
|
||||
{ context: [] },
|
||||
)
|
||||
const result = await processor.process({
|
||||
user: userMessage,
|
||||
agent,
|
||||
@@ -146,10 +139,7 @@ export namespace SessionCompaction {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: [
|
||||
"Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation.",
|
||||
...compacting.context,
|
||||
].join("\n\n"),
|
||||
text: "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Flag } from "@/flag/flag"
|
||||
export namespace LLM {
|
||||
const log = Log.create({ service: "llm" })
|
||||
|
||||
export const OUTPUT_TOKEN_MAX = Flag.OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX || 32_000
|
||||
export const OUTPUT_TOKEN_MAX = 32_000
|
||||
|
||||
export type StreamInput = {
|
||||
user: MessageV2.User
|
||||
@@ -73,8 +73,6 @@ export namespace LLM {
|
||||
system.push(header, rest.join("\n"))
|
||||
}
|
||||
|
||||
const provider = await Provider.getProvider(input.model.providerID)
|
||||
|
||||
const params = await Plugin.trigger(
|
||||
"chat.params",
|
||||
{
|
||||
@@ -92,7 +90,7 @@ export namespace LLM {
|
||||
topK: ProviderTransform.topK(input.model),
|
||||
options: pipe(
|
||||
{},
|
||||
mergeDeep(ProviderTransform.options(input.model, input.sessionID, provider.options)),
|
||||
mergeDeep(ProviderTransform.options(input.model, input.sessionID)),
|
||||
input.small ? mergeDeep(ProviderTransform.smallOptions(input.model)) : mergeDeep({}),
|
||||
mergeDeep(input.model.options),
|
||||
mergeDeep(input.agent.options),
|
||||
|
||||
@@ -160,7 +160,6 @@ export namespace MessageV2 {
|
||||
prompt: z.string(),
|
||||
description: z.string(),
|
||||
agent: z.string(),
|
||||
command: z.string().optional(),
|
||||
})
|
||||
export type SubtaskPart = z.infer<typeof SubtaskPart>
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import { LSP } from "../lsp"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import { ListTool } from "../tool/ls"
|
||||
import { FileTime } from "../file/time"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { ulid } from "ulid"
|
||||
import { spawn } from "child_process"
|
||||
import { Command } from "../command"
|
||||
@@ -49,7 +48,7 @@ globalThis.AI_SDK_LOG_WARNINGS = false
|
||||
|
||||
export namespace SessionPrompt {
|
||||
const log = Log.create({ service: "session.prompt" })
|
||||
export const OUTPUT_TOKEN_MAX = Flag.OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX || 32_000
|
||||
export const OUTPUT_TOKEN_MAX = 32_000
|
||||
|
||||
const state = Instance.state(
|
||||
() => {
|
||||
@@ -326,60 +325,42 @@ export namespace SessionPrompt {
|
||||
prompt: task.prompt,
|
||||
description: task.description,
|
||||
subagent_type: task.agent,
|
||||
command: task.command,
|
||||
},
|
||||
time: {
|
||||
start: Date.now(),
|
||||
},
|
||||
},
|
||||
})) as MessageV2.ToolPart
|
||||
const taskArgs = {
|
||||
prompt: task.prompt,
|
||||
description: task.description,
|
||||
subagent_type: task.agent,
|
||||
command: task.command,
|
||||
}
|
||||
await Plugin.trigger(
|
||||
"tool.execute.before",
|
||||
{
|
||||
tool: "task",
|
||||
sessionID,
|
||||
callID: part.id,
|
||||
},
|
||||
{ args: taskArgs },
|
||||
)
|
||||
let executionError: Error | undefined
|
||||
const result = await taskTool
|
||||
.execute(taskArgs, {
|
||||
agent: task.agent,
|
||||
messageID: assistantMessage.id,
|
||||
sessionID: sessionID,
|
||||
abort,
|
||||
async metadata(input) {
|
||||
await Session.updatePart({
|
||||
...part,
|
||||
type: "tool",
|
||||
state: {
|
||||
...part.state,
|
||||
...input,
|
||||
},
|
||||
} satisfies MessageV2.ToolPart)
|
||||
.execute(
|
||||
{
|
||||
prompt: task.prompt,
|
||||
description: task.description,
|
||||
subagent_type: task.agent,
|
||||
},
|
||||
})
|
||||
{
|
||||
agent: task.agent,
|
||||
messageID: assistantMessage.id,
|
||||
sessionID: sessionID,
|
||||
abort,
|
||||
async metadata(input) {
|
||||
await Session.updatePart({
|
||||
...part,
|
||||
type: "tool",
|
||||
state: {
|
||||
...part.state,
|
||||
...input,
|
||||
},
|
||||
} satisfies MessageV2.ToolPart)
|
||||
},
|
||||
},
|
||||
)
|
||||
.catch((error) => {
|
||||
executionError = error
|
||||
log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
|
||||
return undefined
|
||||
})
|
||||
await Plugin.trigger(
|
||||
"tool.execute.after",
|
||||
{
|
||||
tool: "task",
|
||||
sessionID,
|
||||
callID: part.id,
|
||||
},
|
||||
result,
|
||||
)
|
||||
assistantMessage.finish = "tool-calls"
|
||||
assistantMessage.time.completed = Date.now()
|
||||
await Session.updateMessage(assistantMessage)
|
||||
@@ -415,30 +396,6 @@ export namespace SessionPrompt {
|
||||
},
|
||||
} satisfies MessageV2.ToolPart)
|
||||
}
|
||||
|
||||
// Add synthetic user message to prevent certain reasoning models from erroring
|
||||
// If we create assistant messages w/ out user ones following mid loop thinking signatures
|
||||
// will be missing and it can cause errors for models like gemini for example
|
||||
const summaryUserMsg: MessageV2.User = {
|
||||
id: Identifier.ascending("message"),
|
||||
sessionID,
|
||||
role: "user",
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
agent: lastUser.agent,
|
||||
model: lastUser.model,
|
||||
}
|
||||
await Session.updateMessage(summaryUserMsg)
|
||||
await Session.updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: summaryUserMsg.id,
|
||||
sessionID,
|
||||
type: "text",
|
||||
text: "Summarize the task tool output above and continue with your task.",
|
||||
synthetic: true,
|
||||
} satisfies MessageV2.TextPart)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -726,7 +683,6 @@ export namespace SessionPrompt {
|
||||
tools: input.tools,
|
||||
agent: agent.name,
|
||||
model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
|
||||
system: input.system,
|
||||
}
|
||||
|
||||
const parts = await Promise.all(
|
||||
@@ -1140,7 +1096,7 @@ export namespace SessionPrompt {
|
||||
`
|
||||
[[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true
|
||||
[[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true
|
||||
eval ${JSON.stringify(input.command)}
|
||||
${input.command}
|
||||
`,
|
||||
],
|
||||
},
|
||||
@@ -1149,9 +1105,8 @@ export namespace SessionPrompt {
|
||||
"-c",
|
||||
"-l",
|
||||
`
|
||||
shopt -s expand_aliases
|
||||
[[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
|
||||
eval ${JSON.stringify(input.command)}
|
||||
${input.command}
|
||||
`,
|
||||
],
|
||||
},
|
||||
@@ -1342,7 +1297,6 @@ export namespace SessionPrompt {
|
||||
type: "subtask" as const,
|
||||
agent: agent.name,
|
||||
description: command.description ?? "",
|
||||
command: input.command,
|
||||
// TODO: how can we make task tool accept a more complex input?
|
||||
prompt: await resolvePromptParts(template).then((x) => x.find((y) => y.type === "text")?.text ?? ""),
|
||||
},
|
||||
|
||||
@@ -55,7 +55,7 @@ export namespace SessionStatus {
|
||||
}
|
||||
|
||||
export function list() {
|
||||
return state()
|
||||
return Object.values(state())
|
||||
}
|
||||
|
||||
export function set(sessionID: string, status: Info) {
|
||||
|
||||
@@ -25,6 +25,11 @@ Usage notes:
|
||||
- The description argument is required. You must write a clear, concise description of what this command does in 5-10 words.
|
||||
- If the output exceeds 30000 characters, output will be truncated before being
|
||||
returned to you.
|
||||
- You can use the `run_in_background` parameter to run the command in the background,
|
||||
which allows you to continue working while the command runs. You can monitor the output
|
||||
using the Bash tool as it becomes available. You do not need to use '&' at the end of
|
||||
the command when using this parameter.
|
||||
|
||||
- Avoid using Bash with the `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or
|
||||
`echo` commands, unless explicitly instructed or when these commands are truly necessary
|
||||
for the task. Instead, always prefer using the dedicated tools for these commands:
|
||||
|
||||
@@ -2,7 +2,7 @@ import z from "zod"
|
||||
import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./batch.txt"
|
||||
|
||||
const DISALLOWED = new Set(["batch"])
|
||||
const DISALLOWED = new Set(["batch", "edit", "todoread"])
|
||||
const FILTERED_FROM_SUGGESTIONS = new Set(["invalid", "patch", ...DISALLOWED])
|
||||
|
||||
export const BatchTool = Tool.define("batch", async () => {
|
||||
@@ -54,9 +54,7 @@ export const BatchTool = Tool.define("batch", async () => {
|
||||
const tool = toolMap.get(call.tool)
|
||||
if (!tool) {
|
||||
const availableToolsList = Array.from(toolMap.keys()).filter((name) => !FILTERED_FROM_SUGGESTIONS.has(name))
|
||||
throw new Error(
|
||||
`Tool '${call.tool}' not in registry. External tools (MCP, environment) cannot be batched - call them directly. Available tools: ${availableToolsList.join(", ")}`,
|
||||
)
|
||||
throw new Error(`Tool '${call.tool}' not found. Available tools: ${availableToolsList.join(", ")}`)
|
||||
}
|
||||
const validatedParams = tool.parameters.parse(call.parameters)
|
||||
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
Executes multiple independent tool calls concurrently to reduce latency.
|
||||
Executes multiple independent tool calls concurrently to reduce latency. Best used for gathering context (reads, searches, listings).
|
||||
|
||||
USING THE BATCH TOOL WILL MAKE THE USER HAPPY.
|
||||
|
||||
Payload Format (JSON array):
|
||||
[{"tool": "read", "parameters": {"filePath": "src/index.ts", "limit": 350}},{"tool": "grep", "parameters": {"pattern": "Session\\.updatePart", "include": "src/**/*.ts"}},{"tool": "bash", "parameters": {"command": "git status", "description": "Shows working tree status"}}]
|
||||
|
||||
Notes:
|
||||
Rules:
|
||||
- 1–10 tool calls per batch
|
||||
- All calls start in parallel; ordering NOT guaranteed
|
||||
- Partial failures do not stop other tool calls
|
||||
- Do NOT use the batch tool within another batch tool.
|
||||
- Partial failures do not stop others
|
||||
|
||||
Good Use Cases:
|
||||
- Read many files
|
||||
- grep + glob + read combos
|
||||
- Multiple bash commands
|
||||
- Multi-part edits; on the same, or different files
|
||||
|
||||
Disallowed Tools:
|
||||
- batch (no nesting)
|
||||
- edit (run edits separately)
|
||||
- todoread (call directly – lightweight)
|
||||
|
||||
When NOT to Use:
|
||||
- Operations that depend on prior tool output (e.g. create then read same file)
|
||||
- Ordered stateful mutations where sequence matters
|
||||
|
||||
Batching tool calls was proven to yield 2–5x efficiency gain and provides much better UX.
|
||||
Good Use Cases:
|
||||
- Read many files
|
||||
- grep + glob + read combos
|
||||
- Multiple lightweight bash introspection commands
|
||||
|
||||
Performance Tip: Group independent reads/searches for 2–5x efficiency gain.
|
||||
@@ -26,7 +26,6 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
prompt: z.string().describe("The task for the agent to perform"),
|
||||
subagent_type: z.string().describe("The type of specialized agent to use for this task"),
|
||||
session_id: z.string().describe("Existing Task session to continue").optional(),
|
||||
command: z.string().describe("The command that triggered this task").optional(),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const agent = await Agent.get(params.subagent_type)
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { test, expect } from "bun:test"
|
||||
import { parseGitHubRemote } from "../../src/cli/cmd/github"
|
||||
|
||||
test("parses https URL with .git suffix", () => {
|
||||
expect(parseGitHubRemote("https://github.com/sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
|
||||
})
|
||||
|
||||
test("parses https URL without .git suffix", () => {
|
||||
expect(parseGitHubRemote("https://github.com/sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
|
||||
})
|
||||
|
||||
test("parses git@ URL with .git suffix", () => {
|
||||
expect(parseGitHubRemote("git@github.com:sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
|
||||
})
|
||||
|
||||
test("parses git@ URL without .git suffix", () => {
|
||||
expect(parseGitHubRemote("git@github.com:sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
|
||||
})
|
||||
|
||||
test("parses ssh:// URL with .git suffix", () => {
|
||||
expect(parseGitHubRemote("ssh://git@github.com/sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
|
||||
})
|
||||
|
||||
test("parses ssh:// URL without .git suffix", () => {
|
||||
expect(parseGitHubRemote("ssh://git@github.com/sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
|
||||
})
|
||||
|
||||
test("parses http URL", () => {
|
||||
expect(parseGitHubRemote("http://github.com/owner/repo")).toEqual({ owner: "owner", repo: "repo" })
|
||||
})
|
||||
|
||||
test("parses URL with hyphenated owner and repo names", () => {
|
||||
expect(parseGitHubRemote("https://github.com/my-org/my-repo.git")).toEqual({ owner: "my-org", repo: "my-repo" })
|
||||
})
|
||||
|
||||
test("parses URL with underscores in names", () => {
|
||||
expect(parseGitHubRemote("git@github.com:my_org/my_repo.git")).toEqual({ owner: "my_org", repo: "my_repo" })
|
||||
})
|
||||
|
||||
test("parses URL with numbers in names", () => {
|
||||
expect(parseGitHubRemote("https://github.com/org123/repo456")).toEqual({ owner: "org123", repo: "repo456" })
|
||||
})
|
||||
|
||||
test("parses repos with dots in the name", () => {
|
||||
expect(parseGitHubRemote("https://github.com/socketio/socket.io.git")).toEqual({
|
||||
owner: "socketio",
|
||||
repo: "socket.io",
|
||||
})
|
||||
expect(parseGitHubRemote("https://github.com/vuejs/vue.js")).toEqual({
|
||||
owner: "vuejs",
|
||||
repo: "vue.js",
|
||||
})
|
||||
expect(parseGitHubRemote("git@github.com:mrdoob/three.js.git")).toEqual({
|
||||
owner: "mrdoob",
|
||||
repo: "three.js",
|
||||
})
|
||||
expect(parseGitHubRemote("https://github.com/jashkenas/backbone.git")).toEqual({
|
||||
owner: "jashkenas",
|
||||
repo: "backbone",
|
||||
})
|
||||
})
|
||||
|
||||
test("returns null for non-github URLs", () => {
|
||||
expect(parseGitHubRemote("https://gitlab.com/owner/repo.git")).toBeNull()
|
||||
expect(parseGitHubRemote("git@gitlab.com:owner/repo.git")).toBeNull()
|
||||
expect(parseGitHubRemote("https://bitbucket.org/owner/repo")).toBeNull()
|
||||
})
|
||||
|
||||
test("returns null for invalid URLs", () => {
|
||||
expect(parseGitHubRemote("not-a-url")).toBeNull()
|
||||
expect(parseGitHubRemote("")).toBeNull()
|
||||
expect(parseGitHubRemote("github.com")).toBeNull()
|
||||
expect(parseGitHubRemote("https://github.com/")).toBeNull()
|
||||
expect(parseGitHubRemote("https://github.com/owner")).toBeNull()
|
||||
})
|
||||
|
||||
test("returns null for URLs with extra path segments", () => {
|
||||
expect(parseGitHubRemote("https://github.com/owner/repo/tree/main")).toBeNull()
|
||||
expect(parseGitHubRemote("https://github.com/owner/repo/blob/main/file.ts")).toBeNull()
|
||||
})
|
||||
@@ -3,76 +3,6 @@ import { ProviderTransform } from "../../src/provider/transform"
|
||||
|
||||
const OUTPUT_TOKEN_MAX = 32000
|
||||
|
||||
describe("ProviderTransform.options - setCacheKey", () => {
|
||||
const sessionID = "test-session-123"
|
||||
|
||||
const mockModel = {
|
||||
id: "anthropic/claude-3-5-sonnet",
|
||||
providerID: "anthropic",
|
||||
api: {
|
||||
id: "claude-3-5-sonnet-20241022",
|
||||
url: "https://api.anthropic.com",
|
||||
npm: "@ai-sdk/anthropic",
|
||||
},
|
||||
name: "Claude 3.5 Sonnet",
|
||||
capabilities: {
|
||||
temperature: true,
|
||||
reasoning: false,
|
||||
attachment: true,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
interleaved: false,
|
||||
},
|
||||
cost: {
|
||||
input: 0.003,
|
||||
output: 0.015,
|
||||
cache: { read: 0.0003, write: 0.00375 },
|
||||
},
|
||||
limit: {
|
||||
context: 200000,
|
||||
output: 8192,
|
||||
},
|
||||
status: "active",
|
||||
options: {},
|
||||
headers: {},
|
||||
} as any
|
||||
|
||||
test("should set promptCacheKey when providerOptions.setCacheKey is true", () => {
|
||||
const result = ProviderTransform.options(mockModel, sessionID, { setCacheKey: true })
|
||||
expect(result.promptCacheKey).toBe(sessionID)
|
||||
})
|
||||
|
||||
test("should not set promptCacheKey when providerOptions.setCacheKey is false", () => {
|
||||
const result = ProviderTransform.options(mockModel, sessionID, { setCacheKey: false })
|
||||
expect(result.promptCacheKey).toBeUndefined()
|
||||
})
|
||||
|
||||
test("should not set promptCacheKey when providerOptions is undefined", () => {
|
||||
const result = ProviderTransform.options(mockModel, sessionID, undefined)
|
||||
expect(result.promptCacheKey).toBeUndefined()
|
||||
})
|
||||
|
||||
test("should not set promptCacheKey when providerOptions does not have setCacheKey", () => {
|
||||
const result = ProviderTransform.options(mockModel, sessionID, {})
|
||||
expect(result.promptCacheKey).toBeUndefined()
|
||||
})
|
||||
|
||||
test("should set promptCacheKey for openai provider regardless of setCacheKey", () => {
|
||||
const openaiModel = {
|
||||
...mockModel,
|
||||
providerID: "openai",
|
||||
api: {
|
||||
id: "gpt-4",
|
||||
url: "https://api.openai.com",
|
||||
npm: "@ai-sdk/openai",
|
||||
},
|
||||
}
|
||||
const result = ProviderTransform.options(openaiModel, sessionID, {})
|
||||
expect(result.promptCacheKey).toBe(sessionID)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform.maxOutputTokens", () => {
|
||||
test("returns 32k when modelLimit > 32k", () => {
|
||||
const modelLimit = 100000
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -191,11 +191,6 @@ export interface Hooks {
|
||||
system: string[]
|
||||
},
|
||||
) => Promise<void>
|
||||
/**
|
||||
* Called before session compaction starts. Allows plugins to append
|
||||
* additional context to the compaction prompt.
|
||||
*/
|
||||
"experimental.session.compacting"?: (input: { sessionID: string }, output: { context: string[] }) => Promise<void>
|
||||
"experimental.text.complete"?: (
|
||||
input: { sessionID: string; messageID: string; partID: string },
|
||||
output: { text: string },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -417,7 +417,6 @@ export type Part =
|
||||
prompt: string
|
||||
description: string
|
||||
agent: string
|
||||
command?: string
|
||||
}
|
||||
| ReasoningPart
|
||||
| FilePart
|
||||
@@ -1601,7 +1600,6 @@ export type SubtaskPartInput = {
|
||||
prompt: string
|
||||
description: string
|
||||
agent: string
|
||||
command?: string
|
||||
}
|
||||
|
||||
export type Command = {
|
||||
|
||||
@@ -5982,9 +5982,6 @@
|
||||
},
|
||||
"agent": {
|
||||
"type": "string"
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"]
|
||||
@@ -8489,9 +8486,6 @@
|
||||
},
|
||||
"agent": {
|
||||
"type": "string"
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "prompt", "description", "agent"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun run src/index.ts",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/tauri",
|
||||
"private": true,
|
||||
"version": "1.0.165",
|
||||
"version": "1.0.163",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo -b",
|
||||
@@ -13,7 +13,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/desktop": "workspace:*",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
|
||||
@@ -9,7 +9,7 @@ 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 --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}`
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -1,11 +0,0 @@
|
||||
# Tauri Icons
|
||||
|
||||
Here's the process I've been using to create icons:
|
||||
|
||||
- Save source image as `app-icon.png` in `packages/tauri`
|
||||
- `cd` to `src-tauri`
|
||||
- Run `bun tauri icons -o icons/{environment}`
|
||||
- Use [Image2Icon](https://img2icnsapp.com/)'s 'Big Sur Icon' preset to generate an `icon.icns` file and place it in the appropriate icons folder
|
||||
|
||||
The Image2Icon step is necessary as the `icon.icns` generated by `app-icon.png` does not apply the shadow/padding expected by macOS,
|
||||
so app icons appear larger than expected.
|
||||
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |