Compare commits

..

37 Commits

Author SHA1 Message Date
opencode 33d8bfc937 release: v1.0.179 2025-12-20 15:38:38 +00:00
Dax Raad f2343a6794 ci 2025-12-20 10:35:01 -05:00
GitHub Action bab000eeb5 chore: generate 2025-12-20 15:29:23 +00:00
Dax Raad 8e674ae053 ci 2025-12-20 10:28:46 -05:00
opencode 6a4f4009d5 release: v1.0.178 2025-12-20 15:08:27 +00:00
Dax Raad 5e79b95927 ci 2025-12-20 10:05:03 -05:00
Tommy D. Rossi a7a2bbb497 feat: add endpoints to delete and update message parts (#5433) 2025-12-20 15:00:41 +00:00
GitHub Action 6e93d14bdb chore: generate 2025-12-20 15:00:41 +00:00
opencode f29f284b3e release: v1.0.177 2025-12-20 15:00:40 +00:00
Dax Raad b1b8f6cf71 ci 2025-12-20 09:57:19 -05:00
opencode 4c3336bbe7 release: v1.0.176 2025-12-20 12:28:27 +00:00
Adam 354ac0b493 fix(desktop): sidebar UX issues 2025-12-20 06:25:39 -06:00
Adam 1d159c6858 fix(desktop): task rendering perf 2025-12-20 06:15:59 -06:00
GitHub Action d70639b256 chore: generate 2025-12-20 12:13:52 +00:00
Adam e4a92f0084 fix(desktop): show last text part when summarized 2025-12-20 06:12:58 -06:00
Adam fdf5a70a27 fix(desktop): performance with lots of session changes 2025-12-20 06:12:58 -06:00
Adam f71da42520 fix(desktop): event reconnect gaps 2025-12-20 06:12:58 -06:00
GitHub Action f6bdeb9e3a ignore: update download stats 2025-12-20 2025-12-20 12:04:14 +00:00
opencode 2400354bab release: v1.0.175 2025-12-20 11:10:23 +00:00
Adam db348c46cc fix(desktop): perf tweaks 2025-12-20 05:06:57 -06:00
Adam 49567fe61a fix(desktop): add retries to init promises 2025-12-20 04:57:39 -06:00
Adam e5b3f796e4 fix: types 2025-12-20 04:33:42 -06:00
Adam a9700c8773 fix: shouldEncode 2025-12-20 04:25:17 -06:00
Adam 26cf5e003e fix(desktop): perf stuff 2025-12-20 04:25:17 -06:00
Adam 742cf10dee fix(desktop): removed projects 2025-12-20 04:25:16 -06:00
Frank 7664453f94 zen: add minimax m2.1 2025-12-20 01:55:10 -05:00
GitHub Action 460672aa93 chore: generate 2025-12-20 06:27:02 +00:00
Frank b4e4fd9807 zen: add minimax m2.1 2025-12-20 01:26:27 -05:00
opencode 34bdfd0937 release: v1.0.174 2025-12-20 04:06:28 +00:00
GitHub Action 84591ca8ad chore: generate 2025-12-20 03:58:36 +00:00
Aiden Cline fd4d0c5c0b fix: file permissions 2025-12-19 21:57:55 -06:00
opencode 9f5db46911 release: v1.0.173 2025-12-20 03:48:15 +00:00
Ariane Emory 755ddbb223 feat(tui): reinsert forked message text in prompt text input box when forking session (resolves #5495) (#5545)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-19 21:45:33 -06:00
GitHub Action 701d470d01 chore: generate 2025-12-20 03:42:00 +00:00
Aiden Cline 1d9058d26b ci: fix archive 2025-12-19 21:41:18 -06:00
opencode 39e2a5f595 release: v1.0.172 2025-12-20 03:29:07 +00:00
Aiden Cline f862ab6722 ci: fix undefined 2025-12-19 21:22:49 -06:00
55 changed files with 704 additions and 151 deletions
+11 -14
View File
@@ -80,8 +80,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: false
outputs:
releaseId: ${{ steps.publish.outputs.releaseId }}
tagName: ${{ steps.publish.outputs.tagName }}
release: ${{ steps.publish.outputs.release }}
tag: ${{ steps.publish.outputs.tag }}
version: ${{ steps.publish.outputs.version }}
publish-tauri:
needs: publish
@@ -103,7 +104,7 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ needs.publish.outputs.tagName }}
ref: ${{ needs.publish.outputs.tag }}
- uses: apple-actions/import-codesign-certs@v2
if: ${{ runner.os == 'macOS' }}
@@ -146,20 +147,18 @@ jobs:
shared-key: ${{ matrix.settings.target }}
- name: Prepare
if: inputs.bump || inputs.version
run: |
cd packages/tauri
bun ./scripts/prepare.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
OPENCODE_VERSION: ${{ inputs.version }}
OPENCODE_CHANNEL: latest
OPENCODE_VERSION: ${{ needs.publish.outputs.version }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
RUST_TARGET: ${{ matrix.settings.target }}
GH_TOKEN: ${{ github.token }}
OPENCODE_RELEASE_TAG: ${{ needs.publish.outputs.tagName }}
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
- name: Install tauri-cli from portable appimage branch
@@ -189,8 +188,8 @@ jobs:
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
args: --target ${{ matrix.settings.target }} --config src-tauri/tauri.prod.conf.json
updaterJsonPreferNsis: true
releaseId: ${{ needs.publish.outputs.releaseId }}
tagName: ${{ needs.publish.outputs.tagName }}
releaseId: ${{ needs.publish.outputs.release }}
tagName: ${{ needs.publish.outputs.tag }}
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
releaseDraft: true
@@ -198,13 +197,13 @@ jobs:
needs:
- publish
- publish-tauri
if: needs.publish.outputs.tagName
if: needs.publish.outputs.tag
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ needs.publish.outputs.tagName }}
ref: ${{ needs.publish.outputs.tag }}
- uses: ./.github/actions/setup-bun
@@ -221,8 +220,6 @@ jobs:
- run: ./script/publish-complete.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
OPENCODE_VERSION: ${{ inputs.version }}
OPENCODE_VERSION: ${{ needs.publish.outputs.version }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
OPENCODE_RELEASE_TAG: ${{ needs.publish.outputs.tagName }}
+1
View File
@@ -175,3 +175,4 @@
| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |
| 2025-12-19 | 1,203,485 (+24,827) | 1,129,698 (+16,280) | 2,333,183 (+41,107) |
| 2025-12-20 | 1,223,000 (+19,515) | 1,146,258 (+16,560) | 2,369,258 (+36,075) |
+15 -15
View File
@@ -20,7 +20,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -48,7 +48,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -75,7 +75,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -99,7 +99,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -123,7 +123,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -171,7 +171,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -200,7 +200,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -216,7 +216,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.170",
"version": "1.0.179",
"bin": {
"opencode": "./bin/opencode",
},
@@ -308,7 +308,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -328,7 +328,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.170",
"version": "1.0.179",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@tsconfig/node22": "catalog:",
@@ -339,7 +339,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -352,7 +352,7 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@opencode-ai/desktop": "workspace:*",
"@solid-primitives/storage": "catalog:",
@@ -379,7 +379,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -414,7 +414,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"zod": "catalog:",
},
@@ -425,7 +425,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.0.170",
"version": "1.0.179",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
@@ -202,6 +202,14 @@ export function IconZai(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
)
}
export function IconMiniMax(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" />
</svg>
)
}
export function IconGemini(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 50 50" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
@@ -9,6 +9,7 @@ import {
IconAlibaba,
IconAnthropic,
IconGemini,
IconMiniMax,
IconMoonshotAI,
IconOpenAI,
IconStealth,
@@ -23,6 +24,7 @@ const getModelLab = (modelId: string) => {
if (modelId.startsWith("kimi")) return "Moonshot AI"
if (modelId.startsWith("glm")) return "Z.ai"
if (modelId.startsWith("qwen")) return "Alibaba"
if (modelId.startsWith("minimax")) return "MiniMax"
if (modelId.startsWith("grok")) return "xAI"
return "Stealth"
}
@@ -35,7 +37,7 @@ const getModelsInfo = query(async (workspaceID: string) => {
.filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
.filter(([id, _model]) => !id.startsWith("alpha-"))
.sort(([idA, modelA], [idB, modelB]) => {
const priority = ["big-pickle", "grok", "claude", "gpt", "gemini"]
const priority = ["big-pickle", "minimax", "grok", "claude", "gpt", "gemini"]
const getPriority = (id: string) => {
const index = priority.findIndex((p) => id.startsWith(p))
return index === -1 ? Infinity : index
@@ -129,6 +131,8 @@ export function ModelSection() {
return <IconAlibaba width={16} height={16} />
case "xAI":
return <IconXai width={16} height={16} />
case "MiniMax":
return <IconMiniMax width={16} height={16} />
default:
return <IconStealth width={16} height={16} />
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.170",
"version": "1.0.179",
"private": true,
"type": "module",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.170",
"version": "1.0.179",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.170",
"version": "1.0.179",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.170",
"version": "1.0.179",
"description": "",
"type": "module",
"exports": {
+1 -2
View File
@@ -8,8 +8,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
init: (props: { url: string }) => {
const eventSdk = createOpencodeClient({
baseUrl: props.url,
signal: AbortSignal.timeout(1000 * 60 * 10),
throwOnError: true,
// signal: AbortSignal.timeout(1000 * 60 * 10),
})
const emitter = createGlobalEmitter<{
[key: string]: Event
+25 -16
View File
@@ -18,6 +18,7 @@ import {
} from "@opencode-ai/sdk/v2/client"
import { createStore, produce, reconcile } from "solid-js/store"
import { Binary } from "@opencode-ai/util/binary"
import { retry } from "@opencode-ai/util/retry"
import { useGlobalSDK } from "./global-sdk"
import { ErrorPage, type InitError } from "../pages/error"
import { createContext, useContext, onMount, type ParentProps, Switch, Match } from "solid-js"
@@ -145,7 +146,7 @@ function createGlobalSync() {
changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)),
node: () => sdk.file.list({ path: "/" }).then((x) => setStore("node", x.data!)),
}
await Promise.all(Object.values(load).map((p) => p().catch((e) => setGlobalStore("error", e))))
await Promise.all(Object.values(load).map((p) => retry(p).catch((e) => setGlobalStore("error", e))))
.then(() => setStore("ready", true))
.catch((e) => setGlobalStore("error", e))
}
@@ -295,21 +296,29 @@ function createGlobalSync() {
async function bootstrap() {
return Promise.all([
globalSDK.client.path.get().then((x) => {
setGlobalStore("path", x.data!)
}),
globalSDK.client.project.list().then(async (x) => {
setGlobalStore(
"project",
x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)),
)
}),
globalSDK.client.provider.list().then((x) => {
setGlobalStore("provider", x.data ?? {})
}),
globalSDK.client.provider.auth().then((x) => {
setGlobalStore("provider_auth", x.data ?? {})
}),
retry(() =>
globalSDK.client.path.get().then((x) => {
setGlobalStore("path", x.data!)
}),
),
retry(() =>
globalSDK.client.project.list().then(async (x) => {
setGlobalStore(
"project",
x.data!.filter((p) => !p.worktree.includes("opencode-test")).sort((a, b) => a.id.localeCompare(b.id)),
)
}),
),
retry(() =>
globalSDK.client.provider.list().then((x) => {
setGlobalStore("provider", x.data ?? {})
}),
),
retry(() =>
globalSDK.client.provider.auth().then((x) => {
setGlobalStore("provider_auth", x.data ?? {})
}),
),
])
.then(() => setGlobalStore("ready", true))
.catch((e) => setGlobalStore("error", e))
+10 -5
View File
@@ -27,6 +27,8 @@ type SessionTabs = {
all: string[]
}
export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
name: "Layout",
init: () => {
@@ -61,21 +63,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
function enrich(project: { worktree: string; expanded: boolean }) {
const metadata = globalSync.data.project.find((x) => x.worktree === project.worktree)
if (!metadata) return []
return [
{
...project,
...metadata,
...(metadata ?? {}),
},
]
}
function colorize(project: Project & { expanded: boolean }) {
function colorize(project: LocalProject) {
if (project.icon?.color) return project
const color = pickAvailableColor()
usedColors.add(color)
project.icon = { ...project.icon, color }
globalSdk.client.project.update({ projectID: project.id, icon: { color } })
if (project.id) {
globalSdk.client.project.update({ projectID: project.id, icon: { color } })
}
return project
}
@@ -95,7 +98,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
projects: {
list,
open(directory: string) {
if (store.projects.find((x) => x.worktree === directory)) return
if (store.projects.find((x) => x.worktree === directory)) {
return
}
globalSync.project.loadSessions(directory)
setStore("projects", (x) => [{ worktree: directory, expanded: true }, ...x])
},
+2 -1
View File
@@ -337,6 +337,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const load = async (path: string) => {
const relativePath = relative(path)
await sdk.client.file.read({ path: relativePath }).then((x) => {
if (!store.node[relativePath]) return
setStore(
"node",
relativePath,
@@ -425,7 +426,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
init,
expand(path: string) {
setStore("node", path, "expanded", true)
if (store.node[path].loaded) return
if (store.node[path]?.loaded) return
setStore("node", path, "loaded", true)
list(path)
},
+5 -4
View File
@@ -1,6 +1,7 @@
import { produce } from "solid-js/store"
import { createMemo } from "solid-js"
import { Binary } from "@opencode-ai/util/binary"
import { retry } from "@opencode-ai/util/retry"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSync } from "./global-sync"
import { useSDK } from "./sdk"
@@ -61,10 +62,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
},
async sync(sessionID: string, _isRetry = false) {
const [session, messages, todo, diff] = await Promise.all([
sdk.client.session.get({ sessionID }, { throwOnError: true }),
sdk.client.session.messages({ sessionID, limit: 100 }),
sdk.client.session.todo({ sessionID }),
sdk.client.session.diff({ sessionID }),
retry(() => sdk.client.session.get({ sessionID })),
retry(() => sdk.client.session.messages({ sessionID, limit: 100 })),
retry(() => sdk.client.session.todo({ sessionID })),
retry(() => sdk.client.session.diff({ sessionID })),
])
setStore(
produce((draft) => {
+18 -21
View File
@@ -1,18 +1,7 @@
import {
createEffect,
createMemo,
createSignal,
For,
Match,
onMount,
ParentProps,
Show,
Switch,
type JSX,
} from "solid-js"
import { createEffect, createMemo, For, Match, onMount, ParentProps, Show, Switch, type JSX } from "solid-js"
import { DateTime } from "luxon"
import { A, useNavigate, useParams } from "@solidjs/router"
import { useLayout, getAvatarColors } from "@/context/layout"
import { useLayout, getAvatarColors, LocalProject } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync"
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { Avatar } from "@opencode-ai/ui/avatar"
@@ -26,7 +15,7 @@ import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { Spinner } from "@opencode-ai/ui/spinner"
import { getFilename } from "@opencode-ai/util/path"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Session, Project } from "@opencode-ai/sdk/v2/client"
import { Session } from "@opencode-ai/sdk/v2/client"
import { usePlatform } from "@/context/platform"
import { createStore, produce } from "solid-js/store"
import {
@@ -348,7 +337,7 @@ export default function Layout(props: ParentProps) {
}
const ProjectAvatar = (props: {
project: Project
project: LocalProject
class?: string
expandable?: boolean
notify?: boolean
@@ -391,7 +380,7 @@ export default function Layout(props: ParentProps) {
)
}
const ProjectVisual = (props: { project: Project & { expanded: boolean }; class?: string }): JSX.Element => {
const ProjectVisual = (props: { project: LocalProject; class?: string }): JSX.Element => {
const name = createMemo(() => getFilename(props.project.worktree))
const current = createMemo(() => base64Decode(params.dir ?? ""))
return (
@@ -427,7 +416,7 @@ export default function Layout(props: ParentProps) {
const SessionItem = (props: {
session: Session
slug: string
project: Project
project: LocalProject
depth?: number
childrenMap: Map<string, Session[]>
}): JSX.Element => {
@@ -517,7 +506,7 @@ export default function Layout(props: ParentProps) {
)
}
const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => {
const SortableProject = (props: { project: LocalProject }): JSX.Element => {
const sortable = createSortable(props.project.worktree)
const slug = createMemo(() => base64Encode(props.project.worktree))
const name = createMemo(() => getFilename(props.project.worktree))
@@ -542,13 +531,21 @@ export default function Layout(props: ParentProps) {
setProjectStore("limit", (limit) => limit + 5)
await globalSync.project.loadSessions(props.project.worktree)
}
const [expanded, setExpanded] = createSignal(true)
const handleOpenChange = (open: boolean) => {
if (open) layout.projects.expand(props.project.worktree)
else layout.projects.collapse(props.project.worktree)
}
return (
// @ts-ignore
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
<Switch>
<Match when={layout.sidebar.opened()}>
<Collapsible variant="ghost" defaultOpen class="gap-2 shrink-0" onOpenChange={setExpanded}>
<Collapsible
variant="ghost"
open={props.project.expanded}
class="gap-2 shrink-0"
onOpenChange={handleOpenChange}
>
<Button
as={"div"}
variant="ghost"
@@ -559,7 +556,7 @@ export default function Layout(props: ParentProps) {
project={props.project}
class="group-hover/session:hidden"
expandable
notify={!expanded()}
notify={!props.project.expanded}
/>
<span class="truncate text-14-medium text-text-strong">{name()}</span>
</Collapsible.Trigger>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.0.170",
"version": "1.0.179",
"private": true,
"type": "module",
"scripts": {
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.0.170"
version = "1.0.179"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.179/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.179/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-linux-arm64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.179/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-linux-x64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.179/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.170/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.179/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.0.170",
"version": "1.0.179",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.170",
"version": "1.0.179",
"name": "opencode",
"type": "module",
"private": true,
+9
View File
@@ -53,6 +53,15 @@ for (const tag of tags) {
}
if (!Script.preview) {
// Create archives for GitHub release
for (const key of Object.keys(binaries)) {
if (key.includes("linux")) {
await $`tar -czf ../../${key}.tar.gz *`.cwd(`dist/${key}/bin`)
} else {
await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`)
}
}
const image = "ghcr.io/sst/opencode"
const platforms = "linux/amd64,linux/arm64"
const tags = [`${image}:${Script.version}`, `${image}:latest`]
@@ -10,6 +10,7 @@ export type HomeRoute = {
export type SessionRoute = {
type: "session"
sessionID: string
initialPrompt?: PromptInfo
}
export type Route = HomeRoute | SessionRoute
@@ -6,6 +6,7 @@ import { Locale } from "@/util/locale"
import { useSDK } from "@tui/context/sdk"
import { useRoute } from "@tui/context/route"
import { useDialog } from "../../ui/dialog"
import type { PromptInfo } from "@tui/component/prompt/history"
export function DialogForkFromTimeline(props: { sessionID: string; onMove: (messageID: string) => void }) {
const sync = useSync()
@@ -35,9 +36,21 @@ export function DialogForkFromTimeline(props: { sessionID: string; onMove: (mess
sessionID: props.sessionID,
messageID: message.id,
})
const parts = sync.data.part[message.id] ?? []
const initialPrompt = parts.reduce(
(agg, part) => {
if (part.type === "text") {
if (!part.synthetic) agg.input += part.text
}
if (part.type === "file") agg.parts.push(part)
return agg
},
{ input: "", parts: [] as PromptInfo["parts"] },
)
route.navigate({
sessionID: forked.data!.id,
type: "session",
initialPrompt,
})
dialog.clear()
},
@@ -80,9 +80,25 @@ export function DialogMessage(props: {
sessionID: props.sessionID,
messageID: props.messageID,
})
const initialPrompt = (() => {
const msg = message()
if (!msg) return undefined
const parts = sync.data.part[msg.id]
return parts.reduce(
(agg, part) => {
if (part.type === "text") {
if (!part.synthetic) agg.input += part.text
}
if (part.type === "file") agg.parts.push(part)
return agg
},
{ input: "", parts: [] as PromptInfo["parts"] },
)
})()
route.navigate({
sessionID: result.data!.id,
type: "session",
initialPrompt,
})
dialog.clear()
},
@@ -168,6 +168,13 @@ export function Session() {
const toast = useToast()
const sdk = useSDK()
// Handle initial prompt from fork
createEffect(() => {
if (route.initialPrompt && prompt) {
prompt.set(route.initialPrompt)
}
})
// Auto-navigate to whichever session currently needs permission input
createEffect(() => {
const currentSession = session()
+6 -5
View File
@@ -1,5 +1,4 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import z from "zod"
import { $ } from "bun"
import type { BunFile } from "bun"
@@ -74,6 +73,7 @@ export namespace File {
async function shouldEncode(file: BunFile): Promise<boolean> {
const type = file.type?.toLowerCase()
log.info("shouldEncode", { type })
if (!type) return false
if (type.startsWith("text/")) return false
@@ -93,7 +93,6 @@ export namespace File {
"bzip",
"compressed",
"binary",
"stream",
"pdf",
"msword",
"powerpoint",
@@ -290,9 +289,11 @@ export namespace File {
}
const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
const nodes: Node[] = []
for (const entry of await fs.promises.readdir(resolved, {
withFileTypes: true,
})) {
for (const entry of await fs.promises
.readdir(resolved, {
withFileTypes: true,
})
.catch(() => [])) {
if (exclude.includes(entry.name)) continue
const fullPath = path.join(resolved, entry.name)
const relativePath = path.relative(Instance.directory, fullPath)
+73
View File
@@ -1188,6 +1188,79 @@ export namespace Server {
return c.json(message)
},
)
.delete(
"/session/:sessionID/message/:messageID/part/:partID",
describeRoute({
description: "Delete a part from a message",
operationId: "part.delete",
responses: {
200: {
description: "Successfully deleted part",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
...errors(400, 404),
},
}),
validator(
"param",
z.object({
sessionID: z.string().meta({ description: "Session ID" }),
messageID: z.string().meta({ description: "Message ID" }),
partID: z.string().meta({ description: "Part ID" }),
}),
),
async (c) => {
const params = c.req.valid("param")
await Session.removePart({
sessionID: params.sessionID,
messageID: params.messageID,
partID: params.partID,
})
return c.json(true)
},
)
.patch(
"/session/:sessionID/message/:messageID/part/:partID",
describeRoute({
description: "Update a part in a message",
operationId: "part.update",
responses: {
200: {
description: "Successfully updated part",
content: {
"application/json": {
schema: resolver(MessageV2.Part),
},
},
},
...errors(400, 404),
},
}),
validator(
"param",
z.object({
sessionID: z.string().meta({ description: "Session ID" }),
messageID: z.string().meta({ description: "Message ID" }),
partID: z.string().meta({ description: "Part ID" }),
}),
),
validator("json", MessageV2.Part),
async (c) => {
const params = c.req.valid("param")
const body = c.req.valid("json")
if (body.id !== params.partID || body.messageID !== params.messageID || body.sessionID !== params.sessionID) {
throw new Error(
`Part mismatch: body.id='${body.id}' vs partID='${params.partID}', body.messageID='${body.messageID}' vs messageID='${params.messageID}', body.sessionID='${body.sessionID}' vs sessionID='${params.sessionID}'`,
)
}
const part = await Session.updatePart(body)
return c.json(part)
},
)
.post(
"/session/:sessionID/message",
describeRoute({
+17
View File
@@ -339,6 +339,23 @@ export namespace Session {
},
)
export const removePart = fn(
z.object({
sessionID: Identifier.schema("session"),
messageID: Identifier.schema("message"),
partID: Identifier.schema("part"),
}),
async (input) => {
await Storage.remove(["part", input.messageID, input.partID])
Bus.publish(MessageV2.Event.PartRemoved, {
sessionID: input.sessionID,
messageID: input.messageID,
partID: input.partID,
})
return input.partID
},
)
const UpdatePartInput = z.union([
MessageV2.Part,
z.object({
+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.0.170",
"version": "1.0.179",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
@@ -24,4 +24,4 @@
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.0.170",
"version": "1.0.179",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
@@ -29,4 +29,4 @@
"publishConfig": {
"directory": "dist"
}
}
}
+80
View File
@@ -47,6 +47,11 @@ import type {
McpLocalConfig,
McpRemoteConfig,
McpStatusResponses,
Part as Part2,
PartDeleteErrors,
PartDeleteResponses,
PartUpdateErrors,
PartUpdateResponses,
PathGetResponses,
PermissionRespondErrors,
PermissionRespondResponses,
@@ -1486,6 +1491,79 @@ export class Session extends HeyApiClient {
}
}
export class Part extends HeyApiClient {
/**
* Delete a part from a message
*/
public delete<ThrowOnError extends boolean = false>(
parameters: {
sessionID: string
messageID: string
partID: string
directory?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "path", key: "sessionID" },
{ in: "path", key: "messageID" },
{ in: "path", key: "partID" },
{ in: "query", key: "directory" },
],
},
],
)
return (options?.client ?? this.client).delete<PartDeleteResponses, PartDeleteErrors, ThrowOnError>({
url: "/session/{sessionID}/message/{messageID}/part/{partID}",
...options,
...params,
})
}
/**
* Update a part in a message
*/
public update<ThrowOnError extends boolean = false>(
parameters: {
sessionID: string
messageID: string
partID: string
directory?: string
part?: Part2
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "path", key: "sessionID" },
{ in: "path", key: "messageID" },
{ in: "path", key: "partID" },
{ in: "query", key: "directory" },
{ key: "part", map: "body" },
],
},
],
)
return (options?.client ?? this.client).patch<PartUpdateResponses, PartUpdateErrors, ThrowOnError>({
url: "/session/{sessionID}/message/{messageID}/part/{partID}",
...options,
...params,
headers: {
"Content-Type": "application/json",
...options?.headers,
...params.headers,
},
})
}
}
export class Permission extends HeyApiClient {
/**
* Respond to permission
@@ -2588,6 +2666,8 @@ export class OpencodeClient extends HeyApiClient {
session = new Session({ client: this.client })
part = new Part({ client: this.client })
permission = new Permission({ client: this.client })
command = new Command({ client: this.client })
+88
View File
@@ -2915,6 +2915,94 @@ export type SessionMessageResponses = {
export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses]
export type PartDeleteData = {
body?: never
path: {
/**
* Session ID
*/
sessionID: string
/**
* Message ID
*/
messageID: string
/**
* Part ID
*/
partID: string
}
query?: {
directory?: string
}
url: "/session/{sessionID}/message/{messageID}/part/{partID}"
}
export type PartDeleteErrors = {
/**
* Bad request
*/
400: BadRequestError
/**
* Not found
*/
404: NotFoundError
}
export type PartDeleteError = PartDeleteErrors[keyof PartDeleteErrors]
export type PartDeleteResponses = {
/**
* Successfully deleted part
*/
200: boolean
}
export type PartDeleteResponse = PartDeleteResponses[keyof PartDeleteResponses]
export type PartUpdateData = {
body?: Part
path: {
/**
* Session ID
*/
sessionID: string
/**
* Message ID
*/
messageID: string
/**
* Part ID
*/
partID: string
}
query?: {
directory?: string
}
url: "/session/{sessionID}/message/{messageID}/part/{partID}"
}
export type PartUpdateErrors = {
/**
* Bad request
*/
400: BadRequestError
/**
* Not found
*/
404: NotFoundError
}
export type PartUpdateError = PartUpdateErrors[keyof PartUpdateErrors]
export type PartUpdateResponses = {
/**
* Successfully updated part
*/
200: Part
}
export type PartUpdateResponse = PartUpdateResponses[keyof PartUpdateResponses]
export type SessionPromptAsyncData = {
body?: {
messageID?: string
+167
View File
@@ -2126,6 +2126,173 @@
]
}
},
"/session/{sessionID}/message/{messageID}/part/{partID}": {
"delete": {
"operationId": "part.delete",
"parameters": [
{
"in": "query",
"name": "directory",
"schema": {
"type": "string"
}
},
{
"in": "path",
"name": "sessionID",
"schema": {
"type": "string"
},
"required": true,
"description": "Session ID"
},
{
"in": "path",
"name": "messageID",
"schema": {
"type": "string"
},
"required": true,
"description": "Message ID"
},
{
"in": "path",
"name": "partID",
"schema": {
"type": "string"
},
"required": true,
"description": "Part ID"
}
],
"description": "Delete a part from a message",
"responses": {
"200": {
"description": "Successfully deleted part",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundError"
}
}
}
}
},
"x-codeSamples": [
{
"lang": "js",
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.delete({\n ...\n})"
}
]
},
"patch": {
"operationId": "part.update",
"parameters": [
{
"in": "query",
"name": "directory",
"schema": {
"type": "string"
}
},
{
"in": "path",
"name": "sessionID",
"schema": {
"type": "string"
},
"required": true,
"description": "Session ID"
},
{
"in": "path",
"name": "messageID",
"schema": {
"type": "string"
},
"required": true,
"description": "Message ID"
},
{
"in": "path",
"name": "partID",
"schema": {
"type": "string"
},
"required": true,
"description": "Part ID"
}
],
"description": "Update a part in a message",
"responses": {
"200": {
"description": "Successfully updated part",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Part"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundError"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Part"
}
}
}
},
"x-codeSamples": [
{
"lang": "js",
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.update({\n ...\n})"
}
]
}
},
"/session/{sessionID}/prompt_async": {
"post": {
"operationId": "session.prompt_async",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.0.170",
"version": "1.0.179",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/tauri",
"private": true,
"version": "1.0.170",
"version": "1.0.179",
"type": "module",
"scripts": {
"typecheck": "tsgo -b",
+2 -1
View File
@@ -3,13 +3,14 @@
import { $ } from "bun"
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
import { Script } from "@opencode-ai/script"
const sidecarConfig = getCurrentSidecar()
const dir = "src-tauri/target/opencode-binaries"
await $`mkdir -p ${dir}`
await $`gh release download ${Bun.env.OPENCODE_RELEASE_TAG} --pattern ${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} --repo sst/opencode --skip-existing --dir ${dir}`
await $`gh release download v${Script.version} --pattern ${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} --repo sst/opencode --skip-existing --dir ${dir}`
if (sidecarConfig.assetExt === "tar.gz") {
await $`tar -xvzf ${dir}/${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} -C ${dir}`
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.0.170",
"version": "1.0.179",
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",
@@ -1,4 +1,5 @@
[data-component="tool-trigger"] {
content-visibility: auto;
width: 100%;
display: flex;
align-items: center;
+4 -5
View File
@@ -1,4 +1,4 @@
import { children, For, Match, Show, Switch, type JSX } from "solid-js"
import { For, Match, Show, Switch, type JSX } from "solid-js"
import { Collapsible } from "./collapsible"
import { Icon, IconProps } from "./icon"
@@ -27,7 +27,6 @@ export interface BasicToolProps {
}
export function BasicTool(props: BasicToolProps) {
const resolved = children(() => props.children)
return (
<Collapsible defaultOpen={props.defaultOpen}>
<Collapsible.Trigger>
@@ -81,13 +80,13 @@ export function BasicTool(props: BasicToolProps) {
</Switch>
</div>
</div>
<Show when={resolved() && !props.hideDetails}>
<Show when={props.children && !props.hideDetails}>
<Collapsible.Arrow />
</Show>
</div>
</Collapsible.Trigger>
<Show when={resolved() && !props.hideDetails}>
<Collapsible.Content>{resolved()}</Collapsible.Content>
<Show when={props.children && !props.hideDetails}>
<Collapsible.Content>{props.children}</Collapsible.Content>
</Show>
</Collapsible>
)
+3 -4
View File
@@ -1,5 +1,5 @@
import { Checkbox as Kobalte } from "@kobalte/core/checkbox"
import { children, Show, splitProps } from "solid-js"
import { Show, splitProps } from "solid-js"
import type { ComponentProps, JSX, ParentProps } from "solid-js"
export interface CheckboxProps extends ParentProps<ComponentProps<typeof Kobalte>> {
@@ -10,7 +10,6 @@ export interface CheckboxProps extends ParentProps<ComponentProps<typeof Kobalte
export function Checkbox(props: CheckboxProps) {
const [local, others] = splitProps(props, ["children", "class", "label", "hideLabel", "description", "icon"])
const resolved = children(() => local.children)
return (
<Kobalte {...others} data-component="checkbox">
<Kobalte.Input data-slot="checkbox-checkbox-input" />
@@ -29,9 +28,9 @@ export function Checkbox(props: CheckboxProps) {
</Kobalte.Indicator>
</Kobalte.Control>
<div data-slot="checkbox-checkbox-content">
<Show when={resolved()}>
<Show when={props.children}>
<Kobalte.Label data-slot="checkbox-checkbox-label" classList={{ "sr-only": local.hideLabel }}>
{resolved()}
{props.children}
</Kobalte.Label>
</Show>
<Show when={local.description}>
+1
View File
@@ -1,3 +1,4 @@
[data-component="code"] {
content-visibility: auto;
overflow: hidden;
}
+1 -1
View File
@@ -1,5 +1,5 @@
[data-component="diff"] {
contain: content;
content-visibility: auto;
[data-slot="diff-hunk-separator-line-number"] {
position: sticky;
@@ -1,4 +1,5 @@
[data-component="assistant-message"] {
content-visibility: auto;
width: 100%;
display: flex;
flex-direction: column;
+7 -7
View File
@@ -463,13 +463,13 @@ ToolRegistry.register({
subtitle: props.input.description,
}}
>
<Show when={props.output}>
{(output) => (
<div data-component="tool-output" data-scrollable>
<Markdown text={output()} />
</div>
)}
</Show>
{/* <Show when={false && props.output}> */}
{/* {(output) => ( */}
{/* <div data-component="tool-output" data-scrollable> */}
{/* <Markdown text={output()} /> */}
{/* </div> */}
{/* )} */}
{/* </Show> */}
</BasicTool>
)
},
@@ -25,7 +25,7 @@ export interface SessionReviewProps {
export const SessionReview = (props: SessionReviewProps) => {
const diffComponent = useDiffComponent()
const [store, setStore] = createStore({
open: props.diffs.map((d) => d.file),
open: props.diffs.length > 10 ? [] : props.diffs.map((d) => d.file),
})
const handleChange = (open: string[]) => {
@@ -78,7 +78,7 @@ export const SessionReview = (props: SessionReviewProps) => {
<Accordion multiple value={store.open} onChange={handleChange}>
<For each={props.diffs}>
{(diff) => (
<Accordion.Item forceMount value={diff.file} data-slot="session-review-accordion-item">
<Accordion.Item value={diff.file} data-slot="session-review-accordion-item">
<StickyAccordionHeader>
<Accordion.Trigger>
<div data-slot="session-review-trigger-content">
+1 -1
View File
@@ -499,7 +499,7 @@ export function SessionTurn(
)
return (
<Switch>
<Match when={response() && lastTextPart()?.id === last()?.id}>
<Match when={!summary() && response() && lastTextPart()?.id === last()?.id}>
<Message
message={assistantMessage}
parts={parts().filter((p) => p?.id !== last()?.id)}
+3 -4
View File
@@ -1,5 +1,5 @@
import { Switch as Kobalte } from "@kobalte/core/switch"
import { children, Show, splitProps } from "solid-js"
import { Show, splitProps } from "solid-js"
import type { ComponentProps, ParentProps } from "solid-js"
export interface SwitchProps extends ParentProps<ComponentProps<typeof Kobalte>> {
@@ -9,13 +9,12 @@ export interface SwitchProps extends ParentProps<ComponentProps<typeof Kobalte>>
export function Switch(props: SwitchProps) {
const [local, others] = splitProps(props, ["children", "class", "hideLabel", "description"])
const resolved = children(() => local.children)
return (
<Kobalte {...others} data-component="switch">
<Kobalte.Input data-slot="switch-input" />
<Show when={resolved()}>
<Show when={local.children}>
<Kobalte.Label data-slot="switch-label" classList={{ "sr-only": local.hideLabel }}>
{resolved()}
{local.children}
</Kobalte.Label>
</Show>
<Show when={local.description}>
+8 -1
View File
@@ -24,6 +24,13 @@ type Data = {
export const { use: useData, provider: DataProvider } = createSimpleContext({
name: "Data",
init: (props: { data: Data; directory: string }) => {
return { store: props.data, directory: props.directory }
return {
get store() {
return props.data
},
get directory() {
return props.directory
},
}
},
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
"version": "1.0.170",
"version": "1.0.179",
"private": true,
"type": "module",
"exports": {
+41
View File
@@ -0,0 +1,41 @@
export interface RetryOptions {
attempts?: number
delay?: number
factor?: number
maxDelay?: number
retryIf?: (error: unknown) => boolean
}
const TRANSIENT_MESSAGES = [
"load failed",
"network connection was lost",
"network request failed",
"failed to fetch",
"econnreset",
"econnrefused",
"etimedout",
"socket hang up",
]
function isTransientError(error: unknown): boolean {
if (!error) return false
const message = String(error instanceof Error ? error.message : error).toLowerCase()
return TRANSIENT_MESSAGES.some((m) => message.includes(m))
}
export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = isTransientError } = options
let lastError: unknown
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await fn()
} catch (error) {
lastError = error
if (attempt === attempts - 1 || !retryIf(error)) throw error
const wait = Math.min(delay * Math.pow(factor, attempt), maxDelay)
await new Promise((resolve) => setTimeout(resolve, wait))
}
}
throw lastError
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
"version": "1.0.170",
"version": "1.0.179",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+4
View File
@@ -77,6 +77,7 @@ You can also access our models through the following API endpoints.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -109,6 +110,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| Model | Input | Output | Cached Read | Cached Write |
| --------------------------------- | ------ | ------ | ----------- | ------------ |
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.1 | Free | Free | Free | - |
| Grok Code Fast 1 | Free | Free | Free | - |
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
| Kimi K2 | $0.40 | $2.50 | - | - |
@@ -142,6 +144,7 @@ Credit card fees are passed along at cost; we don't charge anything beyond that.
The free models:
- Grok Code Fast 1 is currently free on OpenCode for a limited time. The xAI team is using this time to collect feedback and improve Grok Code.
- MiniMax M2.1 is currently free on OpenCode for a limited time. The MiniMax team is using this time to collect feedback and improve M2.1.
- Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
<a href={email}>Contact us</a> if you have any questions.
@@ -153,6 +156,7 @@ The free models:
All our models are hosted in the US. Our providers follow a zero-retention policy and do not use your data for model training, with the following exceptions:
- Grok Code Fast 1: During its free period, collected data may be used to improve Grok Code.
- MiniMax M2.1: During its free period, collected data may be used to improve M2.1.
- Big Pickle: During its free period, collected data may be used to improve the model.
- OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
Regular → Executable
+1 -1
View File
@@ -4,7 +4,7 @@ import { Script } from "@opencode-ai/script"
import { $ } from "bun"
if (!Script.preview) {
await $`gh release edit ${process.env.OPENCODE_RELEASE_TAG} --draft=false`
await $`gh release edit v${Script.version} --draft=false`
}
await $`bun install`
+22 -16
View File
@@ -27,6 +27,20 @@ if (!Script.preview) {
const opencode = await createOpencode()
const session = await opencode.client.session.create()
console.log("generating changelog since " + previous)
const team = [
"actions-user",
"opencode",
"rekram1-node",
"thdxr",
"kommander",
"jayair",
"fwang",
"adamdotdevin",
"iamdavidhill",
"opencode-agent[bot]",
]
const raw = await opencode.client.session
.prompt({
path: {
@@ -106,19 +120,6 @@ if (!Script.preview) {
console.log("-----------------------------")
opencode.server.close()
// Get contributors
const team = [
"actions-user",
"opencode",
"rekram1-node",
"thdxr",
"kommander",
"jayair",
"fwang",
"adamdotdevin",
"iamdavidhill",
"opencode-agent[bot]",
]
const compare =
await $`gh api "/repos/sst/opencode/compare/v${previous}...HEAD" --jq '.commits[] | {login: .author.login, message: .commit.message}'`.text()
const contributors = new Map<string, string[]>()
@@ -180,6 +181,8 @@ await import(`../packages/plugin/script/publish.ts`)
const dir = new URL("..", import.meta.url).pathname
process.chdir(dir)
let output = `version=${Script.version}\n`
if (!Script.preview) {
await $`git commit -am "release: v${Script.version}"`
await $`git tag v${Script.version}`
@@ -189,7 +192,10 @@ if (!Script.preview) {
await new Promise((resolve) => setTimeout(resolve, 5_000))
await $`gh release create v${Script.version} -d --title "v${Script.version}" --notes ${notes.join("\n") || "No notable changes"} ./packages/opencode/dist/*.zip ./packages/opencode/dist/*.tar.gz`
const release = await $`gh release view v${Script.version} --json id,tagName`.json()
if (process.env.GITHUB_OUTPUT) {
await Bun.write(process.env.GITHUB_OUTPUT, `releaseId=${release.id}\ntagName=${release.tagName}\n`)
}
output += `release=${release.id}`
output += `tag=${release.tagName}`
}
if (process.env.GITHUB_OUTPUT) {
await Bun.write(process.env.GITHUB_OUTPUT, output)
}
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.0.170",
"version": "1.0.179",
"publisher": "sst-dev",
"repository": {
"type": "git",