Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b45a01ac71 | ||
|
|
306fc05c00 | ||
|
|
9d8d0e97ec | ||
|
|
f4086ac459 | ||
|
|
b9b5d42bd8 | ||
|
|
83ed1adcbd | ||
|
|
9b57db30d1 | ||
|
|
df8e6e6014 | ||
|
|
472a6cc83e | ||
|
|
47d43aaf2d | ||
|
|
da3dea0429 | ||
|
|
9862303eed | ||
|
|
b14622352e | ||
|
|
ea643f1e3f |
@@ -17,11 +17,11 @@ on:
|
||||
- "packages/*/package.json"
|
||||
|
||||
jobs:
|
||||
update-linux:
|
||||
update-flake:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SYSTEM: x86_64-linux
|
||||
TITLE: flake.lock
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -33,39 +33,32 @@ jobs:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
- name: Setup Nix
|
||||
uses: DeterminateSystems/nix-installer-action@v20
|
||||
uses: nixbuild/nix-quick-install-action@v34
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.email "action@github.com"
|
||||
git config --global user.name "Github Action"
|
||||
|
||||
- name: Update flake.lock
|
||||
- name: Update ${{ env.TITLE }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "📦 Updating flake.lock..."
|
||||
echo "📦 Updating $TITLE..."
|
||||
nix flake update
|
||||
echo "✅ flake.lock updated successfully"
|
||||
echo "✅ $TITLE updated successfully"
|
||||
|
||||
- name: Update node_modules hash for x86_64-linux
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "🔄 Updating node_modules hash for x86_64-linux..."
|
||||
nix/scripts/update-hashes.sh
|
||||
echo "✅ node_modules hash for x86_64-linux updated successfully"
|
||||
|
||||
- name: Commit Linux hash changes
|
||||
- name: Commit ${{ env.TITLE }} changes
|
||||
env:
|
||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "🔍 Checking for changes in tracked Nix files..."
|
||||
echo "🔍 Checking for changes in tracked files..."
|
||||
|
||||
summarize() {
|
||||
local status="$1"
|
||||
{
|
||||
echo "### Nix Hash Update (x86_64-linux)"
|
||||
echo "### Nix $TITLE"
|
||||
echo ""
|
||||
echo "- ref: ${GITHUB_REF_NAME}"
|
||||
echo "- status: ${status}"
|
||||
@@ -75,11 +68,10 @@ jobs:
|
||||
fi
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
}
|
||||
|
||||
FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json)
|
||||
FILES=(flake.lock flake.nix)
|
||||
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
||||
if [ -z "$STATUS" ]; then
|
||||
echo "✅ No changes detected. Hashes are already up to date."
|
||||
echo "✅ No changes detected."
|
||||
summarize "no changes"
|
||||
exit 0
|
||||
fi
|
||||
@@ -89,7 +81,7 @@ jobs:
|
||||
echo "🔗 Staging files..."
|
||||
git add "${FILES[@]}"
|
||||
echo "💾 Committing changes..."
|
||||
git commit -m "Update Nix flake.lock and x86_64-linux hash"
|
||||
git commit -m "Update $TITLE"
|
||||
echo "✅ Changes committed"
|
||||
|
||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||
@@ -101,12 +93,25 @@ jobs:
|
||||
|
||||
summarize "committed $(git rev-parse --short HEAD)"
|
||||
|
||||
update-macos:
|
||||
needs: update-linux
|
||||
update-node-modules-hash:
|
||||
needs: update-flake
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- system: x86_64-linux
|
||||
host: ubuntu-latest
|
||||
- system: aarch64-linux
|
||||
host: ubuntu-22.04-arm
|
||||
- system: x86_64-darwin
|
||||
host: macos-15-intel
|
||||
- system: aarch64-darwin
|
||||
host: macos-latest
|
||||
runs-on: ${{ matrix.host }}
|
||||
env:
|
||||
SYSTEM: aarch64-darwin
|
||||
SYSTEM: ${{ matrix.system }}
|
||||
TITLE: node_modules hash (${{ matrix.system }})
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -118,7 +123,7 @@ jobs:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
- name: Setup Nix
|
||||
uses: DeterminateSystems/nix-installer-action@v20
|
||||
uses: nixbuild/nix-quick-install-action@v34
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
@@ -132,25 +137,25 @@ jobs:
|
||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||
git pull origin "$BRANCH"
|
||||
|
||||
- name: Update node_modules hash for aarch64-darwin
|
||||
- name: Update ${{ env.TITLE }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "🔄 Updating node_modules hash for aarch64-darwin..."
|
||||
echo "🔄 Updating $TITLE..."
|
||||
nix/scripts/update-hashes.sh
|
||||
echo "✅ node_modules hash for aarch64-darwin updated successfully"
|
||||
echo "✅ $TITLE updated successfully"
|
||||
|
||||
- name: Commit macOS hash changes
|
||||
- name: Commit ${{ env.TITLE }} changes
|
||||
env:
|
||||
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "🔍 Checking for changes in tracked Nix files..."
|
||||
echo "🔍 Checking for changes in tracked files..."
|
||||
|
||||
summarize() {
|
||||
local status="$1"
|
||||
{
|
||||
echo "### Nix Hash Update (aarch64-darwin)"
|
||||
echo "### Nix $TITLE"
|
||||
echo ""
|
||||
echo "- ref: ${GITHUB_REF_NAME}"
|
||||
echo "- status: ${status}"
|
||||
@@ -164,7 +169,7 @@ jobs:
|
||||
FILES=(nix/hashes.json)
|
||||
STATUS="$(git status --short -- "${FILES[@]}" || true)"
|
||||
if [ -z "$STATUS" ]; then
|
||||
echo "✅ No changes detected. Hash is already up to date."
|
||||
echo "✅ No changes detected."
|
||||
summarize "no changes"
|
||||
exit 0
|
||||
fi
|
||||
@@ -174,7 +179,7 @@ jobs:
|
||||
echo "🔗 Staging files..."
|
||||
git add "${FILES[@]}"
|
||||
echo "💾 Committing changes..."
|
||||
git commit -m "Update aarch64-darwin hash"
|
||||
git commit -m "Update $TITLE"
|
||||
echo "✅ Changes committed"
|
||||
|
||||
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -102,7 +102,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -129,7 +129,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -153,7 +153,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -177,7 +177,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -206,7 +206,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -235,7 +235,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -251,7 +251,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -355,7 +355,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -375,7 +375,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.88.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -386,7 +386,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -399,7 +399,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -439,7 +439,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -450,7 +450,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-Fl1BdjNSg19LJVSgDMiBX8JuTaGlL2I5T+rqLfjSeO4=",
|
||||
"aarch64-darwin": "sha256-7UajHu40n7JKqurU/+CGlitErsVFA2qDneUytI8+/zQ="
|
||||
"x86_64-linux": "sha256-4ndHIlS9t1ynRdFszJ1nvcu3YhunhuOc7jcuHI1FbnM=",
|
||||
"aarch64-linux": "sha256-H9eUk/yVrQqVrAYONlb6As7mjkPXtOauBVfMBeVAmRo=",
|
||||
"aarch64-darwin": "sha256-C0E9KAEj3GI83HwirIL2zlXYIe92T+7Iv6F51BB6slY=",
|
||||
"x86_64-darwin": "sha256-wj5fZnyfu6Sf1HcqvsQM3M7dl5BKRAHmoqm1Ai1cL2M="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -17,6 +17,7 @@ export function Titlebar() {
|
||||
const reserve = createMemo(
|
||||
() => platform.platform === "desktop" && (platform.os === "windows" || platform.os === "linux"),
|
||||
)
|
||||
const web = createMemo(() => platform.platform === "web")
|
||||
|
||||
const getWin = () => {
|
||||
if (platform.platform !== "desktop") return
|
||||
@@ -88,7 +89,7 @@ export function Titlebar() {
|
||||
onClick={layout.mobileSidebar.toggle}
|
||||
/>
|
||||
<TooltipKeybind
|
||||
class="hidden xl:flex shrink-0 ml-14"
|
||||
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0"}
|
||||
placement="bottom"
|
||||
title="Toggle sidebar"
|
||||
keybind={command.keybind("sidebar.toggle")}
|
||||
|
||||
@@ -19,15 +19,27 @@ import {
|
||||
type QuestionRequest,
|
||||
createOpencodeClient,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { createStore, produce, reconcile, type SetStoreFunction, type Store } 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 { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js"
|
||||
import {
|
||||
batch,
|
||||
createContext,
|
||||
createEffect,
|
||||
useContext,
|
||||
onCleanup,
|
||||
onMount,
|
||||
type Accessor,
|
||||
type ParentProps,
|
||||
Switch,
|
||||
Match,
|
||||
} from "solid-js"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { usePlatform } from "./platform"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
|
||||
type State = {
|
||||
status: "loading" | "partial" | "complete"
|
||||
@@ -68,9 +80,16 @@ type State = {
|
||||
}
|
||||
}
|
||||
|
||||
type VcsCache = {
|
||||
store: Store<{ value: VcsInfo | undefined }>
|
||||
setStore: SetStoreFunction<{ value: VcsInfo | undefined }>
|
||||
ready: Accessor<boolean>
|
||||
}
|
||||
|
||||
function createGlobalSync() {
|
||||
const globalSDK = useGlobalSDK()
|
||||
const platform = usePlatform()
|
||||
const vcsCache = new Map<string, VcsCache>()
|
||||
const [globalStore, setGlobalStore] = createStore<{
|
||||
ready: boolean
|
||||
error?: InitError
|
||||
@@ -86,10 +105,16 @@ function createGlobalSync() {
|
||||
provider_auth: {},
|
||||
})
|
||||
|
||||
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
|
||||
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||
function child(directory: string) {
|
||||
if (!directory) console.error("No directory provided")
|
||||
if (!children[directory]) {
|
||||
const cache = persisted(
|
||||
Persist.workspace(directory, "vcs", ["vcs.v1"]),
|
||||
createStore({ value: undefined as VcsInfo | undefined }),
|
||||
)
|
||||
vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] })
|
||||
|
||||
children[directory] = createStore<State>({
|
||||
project: "",
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
@@ -107,14 +132,16 @@ function createGlobalSync() {
|
||||
question: {},
|
||||
mcp: {},
|
||||
lsp: [],
|
||||
vcs: undefined,
|
||||
vcs: cache[0].value,
|
||||
limit: 5,
|
||||
message: {},
|
||||
part: {},
|
||||
})
|
||||
bootstrapInstance(directory)
|
||||
}
|
||||
return children[directory]
|
||||
const childStore = children[directory]
|
||||
if (!childStore) throw new Error("Failed to create store")
|
||||
return childStore
|
||||
}
|
||||
|
||||
async function loadSessions(directory: string) {
|
||||
@@ -157,6 +184,8 @@ function createGlobalSync() {
|
||||
async function bootstrapInstance(directory: string) {
|
||||
if (!directory) return
|
||||
const [store, setStore] = child(directory)
|
||||
const cache = vcsCache.get(directory)
|
||||
if (!cache) return
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: globalSDK.url,
|
||||
fetch: platform.fetch,
|
||||
@@ -164,6 +193,13 @@ function createGlobalSync() {
|
||||
throwOnError: true,
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!cache.ready()) return
|
||||
const cached = cache.store.value
|
||||
if (!cached?.branch) return
|
||||
setStore("vcs", (value) => value ?? cached)
|
||||
})
|
||||
|
||||
const blockingRequests = {
|
||||
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
||||
provider: () =>
|
||||
@@ -193,7 +229,11 @@ function createGlobalSync() {
|
||||
loadSessions(directory),
|
||||
sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
|
||||
sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
|
||||
sdk.vcs.get().then((x) => setStore("vcs", x.data)),
|
||||
sdk.vcs.get().then((x) => {
|
||||
const next = x.data ?? store.vcs
|
||||
setStore("vcs", next)
|
||||
if (next?.branch) cache.setStore("value", next)
|
||||
}),
|
||||
sdk.permission.list().then((x) => {
|
||||
const grouped: Record<string, PermissionRequest[]> = {}
|
||||
for (const perm of x.data ?? []) {
|
||||
@@ -406,7 +446,10 @@ function createGlobalSync() {
|
||||
break
|
||||
}
|
||||
case "vcs.branch.updated": {
|
||||
setStore("vcs", { branch: event.properties.branch })
|
||||
const next = { branch: event.properties.branch }
|
||||
setStore("vcs", next)
|
||||
const cache = vcsCache.get(directory)
|
||||
if (cache) cache.setStore("value", next)
|
||||
break
|
||||
}
|
||||
case "permission.asked": {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||
import { useLayout, getAvatarColors, LocalProject } from "@/context/layout"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
|
||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||
@@ -62,13 +63,16 @@ import { Titlebar } from "@/components/titlebar"
|
||||
import { useServer } from "@/context/server"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const [store, setStore] = createStore({
|
||||
lastSession: {} as { [directory: string]: string },
|
||||
activeProject: undefined as string | undefined,
|
||||
activeWorkspace: undefined as string | undefined,
|
||||
workspaceOrder: {} as Record<string, string[]>,
|
||||
workspaceExpanded: {} as Record<string, boolean>,
|
||||
})
|
||||
const [store, setStore, , ready] = persisted(
|
||||
Persist.global("layout.page", ["layout.page.v1"]),
|
||||
createStore({
|
||||
lastSession: {} as { [directory: string]: string },
|
||||
activeProject: undefined as string | undefined,
|
||||
activeWorkspace: undefined as string | undefined,
|
||||
workspaceOrder: {} as Record<string, string[]>,
|
||||
workspaceExpanded: {} as Record<string, boolean>,
|
||||
}),
|
||||
)
|
||||
|
||||
let scrollContainerRef: HTMLDivElement | undefined
|
||||
const xlQuery = window.matchMedia("(min-width: 1280px)")
|
||||
@@ -289,6 +293,7 @@ export default function Layout(props: ParentProps) {
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!ready()) return
|
||||
const project = currentProject()
|
||||
if (!project) return
|
||||
|
||||
@@ -708,7 +713,7 @@ export default function Layout(props: ParentProps) {
|
||||
const id = params.id
|
||||
setStore("lastSession", directory, id)
|
||||
notification.session.markViewed(id)
|
||||
untrack(() => setStore("workspaceExpanded", directory, true))
|
||||
untrack(() => setStore("workspaceExpanded", directory, (value) => value ?? true))
|
||||
requestAnimationFrame(() => scrollToSession(id))
|
||||
})
|
||||
|
||||
@@ -816,13 +821,13 @@ export default function Layout(props: ParentProps) {
|
||||
const opencode = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
|
||||
|
||||
return (
|
||||
<div class={`relative size-8 shrink-0 rounded-sm ${props.class ?? ""}`}>
|
||||
<div class="size-full rounded-sm overflow-clip">
|
||||
<div class={`relative size-8 shrink-0 rounded ${props.class ?? ""}`}>
|
||||
<div class="size-full rounded overflow-clip">
|
||||
<Avatar
|
||||
fallback={name()}
|
||||
src={props.project.id === opencode ? "https://opencode.ai/favicon.svg" : props.project.icon?.url}
|
||||
{...getAvatarColors(props.project.icon?.color)}
|
||||
class="size-full rounded-sm"
|
||||
class="size-full rounded"
|
||||
style={
|
||||
notifications().length > 0 && props.notify
|
||||
? { "-webkit-mask-image": mask, "mask-image": mask }
|
||||
@@ -977,7 +982,7 @@ export default function Layout(props: ParentProps) {
|
||||
<button
|
||||
type="button"
|
||||
classList={{
|
||||
"flex items-center justify-center size-10 p-1 rounded-md border transition-colors cursor-default": true,
|
||||
"flex items-center justify-center size-10 p-1 rounded-lg border transition-colors cursor-default": true,
|
||||
"bg-transparent border-icon-strong-base hover:bg-surface-base-hover": selected(),
|
||||
"bg-transparent border-transparent hover:bg-surface-base-hover hover:border-border-weak-base": !selected(),
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -34,14 +34,14 @@ export const defaultConfig: LightRaysConfig = {
|
||||
raysOrigin: "top-center",
|
||||
raysColor: "#ffffff",
|
||||
raysSpeed: 1.0,
|
||||
lightSpread: 1.15,
|
||||
rayLength: 4.0,
|
||||
lightSpread: 1.2,
|
||||
rayLength: 4.5,
|
||||
sourceWidth: 0.1,
|
||||
pulsating: true,
|
||||
pulsatingMin: 0.9,
|
||||
pulsatingMax: 1.0,
|
||||
fadeDistance: 1.15,
|
||||
saturation: 0.325,
|
||||
pulsatingMax: 1.05,
|
||||
fadeDistance: 1.25,
|
||||
saturation: 0.35,
|
||||
followMouse: false,
|
||||
mouseInfluence: 0.05,
|
||||
noiseAmount: 0.5,
|
||||
|
||||
@@ -89,47 +89,26 @@
|
||||
animation: reveal-terms 300ms cubic-bezier(0.25, 0, 0.5, 1) 50ms forwards;
|
||||
}
|
||||
|
||||
::view-transition-old(action-20),
|
||||
::view-transition-old(action-100),
|
||||
::view-transition-old(action-200) {
|
||||
animation: fade-out 100ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
::view-transition-old(actions-20),
|
||||
::view-transition-old(actions-100),
|
||||
::view-transition-old(actions-200) {
|
||||
animation: fade-out 80ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
::view-transition-new(action-20),
|
||||
::view-transition-new(action-100),
|
||||
::view-transition-new(action-200) {
|
||||
animation: fade-in-up 200ms cubic-bezier(0.16, 1, 0.3, 1) 250ms forwards;
|
||||
::view-transition-new(actions-20),
|
||||
::view-transition-new(actions-100),
|
||||
::view-transition-new(actions-200) {
|
||||
animation: fade-in-up 300ms cubic-bezier(0.16, 1, 0.3, 1) 300ms forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
::view-transition-group(plan-card-20),
|
||||
::view-transition-group(plan-card-100),
|
||||
::view-transition-group(plan-card-200) {
|
||||
animation-duration: 200ms;
|
||||
::view-transition-group(card-20),
|
||||
::view-transition-group(card-100),
|
||||
::view-transition-group(card-200) {
|
||||
animation-duration: 250ms;
|
||||
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
::view-transition-image-pair(plan-card-20),
|
||||
::view-transition-image-pair(plan-card-100),
|
||||
::view-transition-image-pair(plan-card-200) {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
::view-transition-old(plan-card-20),
|
||||
::view-transition-old(plan-card-100),
|
||||
::view-transition-old(plan-card-200) {
|
||||
animation: fade-out 120ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
::view-transition-new(plan-card-20),
|
||||
::view-transition-new(plan-card-100),
|
||||
::view-transition-new(plan-card-200) {
|
||||
animation: fade-in 150ms cubic-bezier(0.4, 0, 0.2, 1) 50ms forwards;
|
||||
opacity: 0;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
[data-page="black"] {
|
||||
background: #000;
|
||||
min-height: 100vh;
|
||||
@@ -362,8 +341,7 @@
|
||||
gap: 12px;
|
||||
padding: 24px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.17);
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
backdrop-filter: blur(4px);
|
||||
background: black;
|
||||
background-clip: padding-box;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
@@ -421,7 +399,6 @@
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
backdrop-filter: blur(4px);
|
||||
z-index: 1;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -604,6 +581,8 @@
|
||||
|
||||
[data-slot="icon"] {
|
||||
color: rgba(255, 255, 255, 0.59);
|
||||
isolation: isolate;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
[data-slot="price"] {
|
||||
|
||||
@@ -49,10 +49,10 @@ export default function Black() {
|
||||
data-slot="pricing-card"
|
||||
style={{ "view-transition-name": `card-${plan.id}` }}
|
||||
>
|
||||
<div data-slot="icon" style={{ "view-transition-name": `icon-${plan.id}` }}>
|
||||
<div data-slot="icon">
|
||||
<PlanIcon plan={plan.id} />
|
||||
</div>
|
||||
<p data-slot="price" style={{ "view-transition-name": `price-${plan.id}` }}>
|
||||
<p data-slot="price">
|
||||
<span data-slot="amount">${plan.id}</span> <span data-slot="period">per month</span>
|
||||
<Show when={plan.multiplier}>
|
||||
<span data-slot="multiplier">{plan.multiplier}</span>
|
||||
@@ -67,76 +67,6 @@ export default function Black() {
|
||||
{(plan) => (
|
||||
<div data-slot="selected-plan">
|
||||
<div data-slot="selected-card" style={{ "view-transition-name": `card-${plan().id}` }}>
|
||||
<div data-slot="icon" style={{ "view-transition-name": `icon-${plan().id}` }}>
|
||||
<PlanIcon plan={plan().id} />
|
||||
</div>
|
||||
<p data-slot="price" style={{ "view-transition-name": `price-${plan().id}` }}>
|
||||
<span data-slot="amount">${plan().id}</span>{" "}
|
||||
<span data-slot="period">per person billed monthly</span>
|
||||
<Show when={plan().multiplier}>
|
||||
<span data-slot="multiplier">{plan().multiplier}</span>
|
||||
</Show>
|
||||
</p>
|
||||
<ul data-slot="terms" style={{ "view-transition-name": `terms-${plan().id}` }}>
|
||||
<li>Your subscription will not start immediately</li>
|
||||
<li>You will be added to the waitlist and activated soon</li>
|
||||
<li>Your card will be only charged when your subscription is activated</li>
|
||||
<li>Usage limits apply, heavily automated use may reach limits sooner</li>
|
||||
<li>Subscriptions for individuals, contact Enterprise for teams</li>
|
||||
<li>Limits may be adjusted and plans may be discontinued in the future</li>
|
||||
<li>Cancel your subscription at anytime</li>
|
||||
</ul>
|
||||
<div data-slot="actions" style={{ "view-transition-name": `actions-${plan().id}` }}>
|
||||
<button type="button" onClick={() => cancel()} data-slot="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<a href={`/black/subscribe/${plan().id}`} data-slot="continue">
|
||||
Continue
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={selectedPlan()}>
|
||||
{(plan) => (
|
||||
<div data-slot="selected-plan">
|
||||
<div data-slot="selected-card">
|
||||
<div data-slot="icon" style={{ "view-transition-name": `icon-${plan().id}` }}>
|
||||
<PlanIcon plan={plan().id} />
|
||||
</div>
|
||||
<p data-slot="price" style={{ "view-transition-name": `price-${plan().id}` }}>
|
||||
<span data-slot="amount">${plan().id}</span>{" "}
|
||||
<span data-slot="period">per person billed monthly</span>
|
||||
<Show when={plan().multiplier}>
|
||||
<span data-slot="multiplier">{plan().multiplier}</span>
|
||||
</Show>
|
||||
</p>
|
||||
<ul data-slot="terms" style={{ "view-transition-name": `terms-${plan().id}` }}>
|
||||
<li>Your subscription will not start immediately</li>
|
||||
<li>You will be added to the waitlist and activated soon</li>
|
||||
<li>Your card will be only charged when your subscription is activated</li>
|
||||
<li>Usage limits apply, heavily automated use may reach limits sooner</li>
|
||||
<li>Subscriptions for individuals, contact Enterprise for teams</li>
|
||||
<li>Limits may be adjusted and plans may be discontinued in the future</li>
|
||||
<li>Cancel your subscription at anytime</li>
|
||||
</ul>
|
||||
<div data-slot="actions" style={{ "view-transition-name": `actions-${plan().id}` }}>
|
||||
<button type="button" onClick={() => cancel()} data-slot="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<a href={`/black/subscribe/${plan().id}`} data-slot="continue">
|
||||
Continue
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={selectedPlan()}>
|
||||
{(plan) => (
|
||||
<div data-slot="selected-plan" style={{ "view-transition-name": "selected-plan" }}>
|
||||
<div data-slot="selected-card">
|
||||
<div data-slot="icon">
|
||||
<PlanIcon plan={plan().id} />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.1.21"
|
||||
version = "1.1.23"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.21/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.23/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.21/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.23/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.21/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.23/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.21/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.23/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.21/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.23/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -35,6 +35,7 @@ import tokyonight from "./theme/tokyonight.json" with { type: "json" }
|
||||
import vercel from "./theme/vercel.json" with { type: "json" }
|
||||
import vesper from "./theme/vesper.json" with { type: "json" }
|
||||
import zenburn from "./theme/zenburn.json" with { type: "json" }
|
||||
import carbonfox from "./theme/carbonfox.json" with { type: "json" }
|
||||
import { useKV } from "./kv"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
@@ -170,6 +171,7 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
||||
vesper,
|
||||
vercel,
|
||||
zenburn,
|
||||
carbonfox,
|
||||
}
|
||||
|
||||
function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"bg0": "#0d0d0d",
|
||||
"bg1": "#161616",
|
||||
"bg1a": "#1a1a1a",
|
||||
"bg2": "#1e1e1e",
|
||||
"bg3": "#262626",
|
||||
"bg4": "#303030",
|
||||
"fg0": "#ffffff",
|
||||
"fg1": "#f2f4f8",
|
||||
"fg2": "#a9afbc",
|
||||
"fg3": "#7d848f",
|
||||
"lbg0": "#ffffff",
|
||||
"lbg1": "#f4f4f4",
|
||||
"lbg2": "#e8e8e8",
|
||||
"lbg3": "#dcdcdc",
|
||||
"lfg0": "#000000",
|
||||
"lfg1": "#161616",
|
||||
"lfg2": "#525252",
|
||||
"lfg3": "#6f6f6f",
|
||||
"red": "#ee5396",
|
||||
"green": "#25be6a",
|
||||
"yellow": "#08bdba",
|
||||
"blue": "#78a9ff",
|
||||
"magenta": "#be95ff",
|
||||
"cyan": "#33b1ff",
|
||||
"white": "#dfdfe0",
|
||||
"orange": "#3ddbd9",
|
||||
"pink": "#ff7eb6",
|
||||
"blueBright": "#8cb6ff",
|
||||
"cyanBright": "#52c7ff",
|
||||
"greenBright": "#46c880",
|
||||
"redLight": "#9f1853",
|
||||
"greenLight": "#198038",
|
||||
"yellowLight": "#007d79",
|
||||
"blueLight": "#0043ce",
|
||||
"magentaLight": "#6929c4",
|
||||
"cyanLight": "#0072c3",
|
||||
"warning": "#f1c21b",
|
||||
"diffGreen": "#50fa7b",
|
||||
"diffRed": "#ff6b6b",
|
||||
"diffGreenBg": "#0f2418",
|
||||
"diffRedBg": "#2a1216"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "cyan",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "blue",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "pink",
|
||||
"light": "redLight"
|
||||
},
|
||||
"error": {
|
||||
"dark": "red",
|
||||
"light": "redLight"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "warning",
|
||||
"light": "yellowLight"
|
||||
},
|
||||
"success": {
|
||||
"dark": "green",
|
||||
"light": "greenLight"
|
||||
},
|
||||
"info": {
|
||||
"dark": "blue",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"text": {
|
||||
"dark": "fg1",
|
||||
"light": "lfg1"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "fg3",
|
||||
"light": "lfg3"
|
||||
},
|
||||
"background": {
|
||||
"dark": "bg1",
|
||||
"light": "lbg0"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "bg1a",
|
||||
"light": "lbg1"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "bg2",
|
||||
"light": "lbg1"
|
||||
},
|
||||
"border": {
|
||||
"dark": "bg4",
|
||||
"light": "lbg3"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "cyan",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "bg3",
|
||||
"light": "lbg2"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "diffGreen",
|
||||
"light": "greenLight"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "diffRed",
|
||||
"light": "redLight"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "fg3",
|
||||
"light": "lfg3"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "blue",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "#7dffaa",
|
||||
"light": "greenLight"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "#ff9999",
|
||||
"light": "redLight"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "diffGreenBg",
|
||||
"light": "#defbe6"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "diffRedBg",
|
||||
"light": "#fff1f1"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "bg1",
|
||||
"light": "lbg1"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "fg3",
|
||||
"light": "lfg3"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "diffGreenBg",
|
||||
"light": "#defbe6"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "diffRedBg",
|
||||
"light": "#fff1f1"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "fg1",
|
||||
"light": "lfg1"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "blueBright",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "blue",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "cyan",
|
||||
"light": "cyanLight"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "green",
|
||||
"light": "greenLight"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "fg3",
|
||||
"light": "lfg3"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "magenta",
|
||||
"light": "magentaLight"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "fg0",
|
||||
"light": "lfg0"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "bg4",
|
||||
"light": "lbg3"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "cyan",
|
||||
"light": "cyanLight"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "cyan",
|
||||
"light": "cyanLight"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "blue",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "cyan",
|
||||
"light": "cyanLight"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "fg2",
|
||||
"light": "lfg2"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "fg3",
|
||||
"light": "lfg3"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "magenta",
|
||||
"light": "magentaLight"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "blueBright",
|
||||
"light": "blueLight"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "white",
|
||||
"light": "lfg1"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "green",
|
||||
"light": "greenLight"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "orange",
|
||||
"light": "yellowLight"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "yellow",
|
||||
"light": "yellowLight"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "fg2",
|
||||
"light": "lfg2"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "fg2",
|
||||
"light": "lfg1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
type Tool,
|
||||
type ToolSet,
|
||||
extractReasoningMiddleware,
|
||||
tool,
|
||||
jsonSchema,
|
||||
} from "ai"
|
||||
import { clone, mergeDeep, pipe } from "remeda"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
@@ -140,6 +142,26 @@ export namespace LLM {
|
||||
|
||||
const tools = await resolveTools(input)
|
||||
|
||||
// LiteLLM and some Anthropic proxies require the tools parameter to be present
|
||||
// when message history contains tool calls, even if no tools are being used.
|
||||
// Add a dummy tool that is never called to satisfy this validation.
|
||||
// This is enabled for:
|
||||
// 1. Providers with "litellm" in their ID or API ID (auto-detected)
|
||||
// 2. Providers with explicit "litellmProxy: true" option (opt-in for custom gateways)
|
||||
const isLiteLLMProxy =
|
||||
provider.options?.["litellmProxy"] === true ||
|
||||
input.model.providerID.toLowerCase().includes("litellm") ||
|
||||
input.model.api.id.toLowerCase().includes("litellm")
|
||||
|
||||
if (isLiteLLMProxy && Object.keys(tools).length === 0 && hasToolCalls(input.messages)) {
|
||||
tools["_noop"] = tool({
|
||||
description:
|
||||
"Placeholder for LiteLLM/Anthropic proxy compatibility - required when message history contains tool calls but no active tools are needed",
|
||||
inputSchema: jsonSchema({ type: "object", properties: {} }),
|
||||
execute: async () => ({ output: "", title: "", metadata: {} }),
|
||||
})
|
||||
}
|
||||
|
||||
return streamText({
|
||||
onError(error) {
|
||||
l.error("stream error", {
|
||||
@@ -171,7 +193,7 @@ export namespace LLM {
|
||||
topP: params.topP,
|
||||
topK: params.topK,
|
||||
providerOptions: ProviderTransform.providerOptions(input.model, params.options),
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid" && x !== "_noop"),
|
||||
tools,
|
||||
maxOutputTokens,
|
||||
abortSignal: input.abort,
|
||||
@@ -238,4 +260,16 @@ export namespace LLM {
|
||||
}
|
||||
return input.tools
|
||||
}
|
||||
|
||||
// Check if messages contain any tool-call content
|
||||
// Used to determine if a dummy tool should be added for LiteLLM proxy compatibility
|
||||
export function hasToolCalls(messages: ModelMessage[]): boolean {
|
||||
for (const msg of messages) {
|
||||
if (!Array.isArray(msg.content)) continue
|
||||
for (const part of msg.content) {
|
||||
if (part.type === "tool-call" || part.type === "tool-result") return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,6 +533,17 @@ export namespace MessageV2 {
|
||||
errorText: part.state.error,
|
||||
callProviderMetadata: part.metadata,
|
||||
})
|
||||
// Handle pending/running tool calls to prevent dangling tool_use blocks
|
||||
// Anthropic/Claude APIs require every tool_use to have a corresponding tool_result
|
||||
if (part.state.status === "pending" || part.state.status === "running")
|
||||
assistantMessage.parts.push({
|
||||
type: ("tool-" + part.tool) as `tool-${string}`,
|
||||
state: "output-error",
|
||||
toolCallId: part.callID,
|
||||
input: part.state.input,
|
||||
errorText: "[Tool execution was interrupted]",
|
||||
callProviderMetadata: part.metadata,
|
||||
})
|
||||
}
|
||||
if (part.type === "reasoning") {
|
||||
assistantMessage.parts.push({
|
||||
|
||||
@@ -77,6 +77,7 @@ export namespace SessionSummary {
|
||||
const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart
|
||||
if (textPart && !userMsg.summary?.title) {
|
||||
const agent = await Agent.get("title")
|
||||
if (!agent) return
|
||||
const stream = await LLM.stream({
|
||||
agent,
|
||||
user: userMsg,
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { LLM } from "../../src/session/llm"
|
||||
import type { ModelMessage } from "ai"
|
||||
|
||||
describe("session.llm.hasToolCalls", () => {
|
||||
test("returns false for empty messages array", () => {
|
||||
expect(LLM.hasToolCalls([])).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for messages with only text content", () => {
|
||||
const messages: ModelMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Hello" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Hi there" }],
|
||||
},
|
||||
]
|
||||
expect(LLM.hasToolCalls(messages)).toBe(false)
|
||||
})
|
||||
|
||||
test("returns true when messages contain tool-call", () => {
|
||||
const messages = [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Run a command" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-123",
|
||||
toolName: "bash",
|
||||
},
|
||||
],
|
||||
},
|
||||
] as ModelMessage[]
|
||||
expect(LLM.hasToolCalls(messages)).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true when messages contain tool-result", () => {
|
||||
const messages = [
|
||||
{
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call-123",
|
||||
toolName: "bash",
|
||||
},
|
||||
],
|
||||
},
|
||||
] as ModelMessage[]
|
||||
expect(LLM.hasToolCalls(messages)).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false for messages with string content", () => {
|
||||
const messages: ModelMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello world",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Hi there",
|
||||
},
|
||||
]
|
||||
expect(LLM.hasToolCalls(messages)).toBe(false)
|
||||
})
|
||||
|
||||
test("returns true when tool-call is mixed with text content", () => {
|
||||
const messages = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Let me run that command" },
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-456",
|
||||
toolName: "read",
|
||||
},
|
||||
],
|
||||
},
|
||||
] as ModelMessage[]
|
||||
expect(LLM.hasToolCalls(messages)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -569,4 +569,94 @@ describe("session.message-v2.toModelMessage", () => {
|
||||
|
||||
expect(MessageV2.toModelMessage(input)).toStrictEqual([])
|
||||
})
|
||||
|
||||
test("converts pending/running tool calls to error results to prevent dangling tool_use", () => {
|
||||
const userID = "m-user"
|
||||
const assistantID = "m-assistant"
|
||||
|
||||
const input: MessageV2.WithParts[] = [
|
||||
{
|
||||
info: userInfo(userID),
|
||||
parts: [
|
||||
{
|
||||
...basePart(userID, "u1"),
|
||||
type: "text",
|
||||
text: "run tool",
|
||||
},
|
||||
] as MessageV2.Part[],
|
||||
},
|
||||
{
|
||||
info: assistantInfo(assistantID, userID),
|
||||
parts: [
|
||||
{
|
||||
...basePart(assistantID, "a1"),
|
||||
type: "tool",
|
||||
callID: "call-pending",
|
||||
tool: "bash",
|
||||
state: {
|
||||
status: "pending",
|
||||
input: { cmd: "ls" },
|
||||
raw: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
...basePart(assistantID, "a2"),
|
||||
type: "tool",
|
||||
callID: "call-running",
|
||||
tool: "read",
|
||||
state: {
|
||||
status: "running",
|
||||
input: { path: "/tmp" },
|
||||
time: { start: 0 },
|
||||
},
|
||||
},
|
||||
] as MessageV2.Part[],
|
||||
},
|
||||
]
|
||||
|
||||
const result = MessageV2.toModelMessage(input)
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "run tool" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-pending",
|
||||
toolName: "bash",
|
||||
input: { cmd: "ls" },
|
||||
providerExecuted: undefined,
|
||||
},
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-running",
|
||||
toolName: "read",
|
||||
input: { path: "/tmp" },
|
||||
providerExecuted: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call-pending",
|
||||
toolName: "bash",
|
||||
output: { type: "error-text", value: "[Tool execution was interrupted]" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call-running",
|
||||
toolName: "read",
|
||||
output: { type: "error-text", value: "[Tool execution was interrupted]" },
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -11,6 +11,7 @@ import oneDarkProThemeJson from "./themes/onedarkpro.json"
|
||||
import shadesOfPurpleThemeJson from "./themes/shadesofpurple.json"
|
||||
import nightowlThemeJson from "./themes/nightowl.json"
|
||||
import vesperThemeJson from "./themes/vesper.json"
|
||||
import carbonfoxThemeJson from "./themes/carbonfox.json"
|
||||
|
||||
export const oc1Theme = oc1ThemeJson as DesktopTheme
|
||||
export const tokyonightTheme = tokyoThemeJson as DesktopTheme
|
||||
@@ -24,6 +25,7 @@ export const oneDarkProTheme = oneDarkProThemeJson as DesktopTheme
|
||||
export const shadesOfPurpleTheme = shadesOfPurpleThemeJson as DesktopTheme
|
||||
export const nightowlTheme = nightowlThemeJson as DesktopTheme
|
||||
export const vesperTheme = vesperThemeJson as DesktopTheme
|
||||
export const carbonfoxTheme = carbonfoxThemeJson as DesktopTheme
|
||||
|
||||
export const DEFAULT_THEMES: Record<string, DesktopTheme> = {
|
||||
"oc-1": oc1Theme,
|
||||
@@ -38,4 +40,5 @@ export const DEFAULT_THEMES: Record<string, DesktopTheme> = {
|
||||
shadesofpurple: shadesOfPurpleTheme,
|
||||
nightowl: nightowlTheme,
|
||||
vesper: vesperTheme,
|
||||
carbonfox: carbonfoxTheme,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/desktop-theme.json",
|
||||
"name": "Carbonfox",
|
||||
"id": "carbonfox",
|
||||
"light": {
|
||||
"seeds": {
|
||||
"neutral": "#8e8e8e",
|
||||
"primary": "#0072c3",
|
||||
"success": "#198038",
|
||||
"warning": "#f1c21b",
|
||||
"error": "#da1e28",
|
||||
"info": "#0043ce",
|
||||
"interactive": "#0f62fe",
|
||||
"diffAdd": "#198038",
|
||||
"diffDelete": "#da1e28"
|
||||
},
|
||||
"overrides": {
|
||||
"background-base": "#ffffff",
|
||||
"background-weak": "#f4f4f4",
|
||||
"background-strong": "#e8e8e8",
|
||||
"background-stronger": "#dcdcdc",
|
||||
"surface-raised-strong": "#ffffff",
|
||||
"surface-raised-stronger": "#ffffff",
|
||||
"surface-float-base": "#161616",
|
||||
"surface-float-base-hover": "#262626",
|
||||
"text-base": "#161616",
|
||||
"text-weak": "#525252",
|
||||
"text-strong": "#000000",
|
||||
"syntax-string": "#198038",
|
||||
"syntax-primitive": "#da1e28",
|
||||
"syntax-property": "#0043ce",
|
||||
"syntax-type": "#007d79",
|
||||
"syntax-constant": "#6929c4",
|
||||
"syntax-keyword": "#525252",
|
||||
"syntax-info": "#0043ce",
|
||||
"markdown-heading": "#0043ce",
|
||||
"markdown-text": "#161616",
|
||||
"markdown-link": "#0043ce",
|
||||
"markdown-link-text": "#0072c3",
|
||||
"markdown-code": "#198038",
|
||||
"markdown-block-quote": "#525252",
|
||||
"markdown-emph": "#6929c4",
|
||||
"markdown-strong": "#161616",
|
||||
"markdown-horizontal-rule": "#c6c6c6",
|
||||
"markdown-list-item": "#0072c3",
|
||||
"markdown-list-enumeration": "#0072c3",
|
||||
"markdown-image": "#0043ce",
|
||||
"markdown-image-text": "#0072c3",
|
||||
"markdown-code-block": "#393939"
|
||||
}
|
||||
},
|
||||
"dark": {
|
||||
"seeds": {
|
||||
"neutral": "#393939",
|
||||
"primary": "#33b1ff",
|
||||
"success": "#42be65",
|
||||
"warning": "#f1c21b",
|
||||
"error": "#ff8389",
|
||||
"info": "#78a9ff",
|
||||
"interactive": "#4589ff",
|
||||
"diffAdd": "#42be65",
|
||||
"diffDelete": "#ff8389"
|
||||
},
|
||||
"overrides": {
|
||||
"background-base": "#161616",
|
||||
"background-weak": "#262626",
|
||||
"background-strong": "#0d0d0d",
|
||||
"background-stronger": "#000000",
|
||||
"surface-raised-base": "#1c1c1c",
|
||||
"surface-raised-base-hover": "#262626",
|
||||
"surface-raised-strong": "#262626",
|
||||
"surface-raised-strong-hover": "#303030",
|
||||
"surface-raised-stronger": "#303030",
|
||||
"surface-raised-stronger-hover": "#393939",
|
||||
"surface-raised-stronger-non-alpha": "#303030",
|
||||
"surface-float-base": "#0d0d0d",
|
||||
"surface-float-base-hover": "#1a1a1a",
|
||||
"surface-inset-base": "#0d0d0d",
|
||||
"surface-inset-strong": "#000000",
|
||||
"surface-base": "#1e1e1e",
|
||||
"surface-base-hover": "#262626",
|
||||
"surface-diff-add-base": "#0e3a22",
|
||||
"surface-diff-delete-base": "#4d1a1f",
|
||||
"input-base": "#262626",
|
||||
"input-hover": "#303030",
|
||||
"button-secondary-base": "#393939",
|
||||
"button-secondary-hover": "#4c4c4c",
|
||||
"border-weak-base": "#393939",
|
||||
"border-weak-hover": "#4c4c4c",
|
||||
"border-base": "#525252",
|
||||
"border-hover": "#636363",
|
||||
"border-strong-base": "#6f6f6f",
|
||||
"text-base": "#f2f4f8",
|
||||
"text-weak": "#8d8d8d",
|
||||
"text-weaker": "#6f6f6f",
|
||||
"text-strong": "#ffffff",
|
||||
"icon-base": "#8d8d8d",
|
||||
"icon-weak-base": "#6f6f6f",
|
||||
"syntax-string": "#42be65",
|
||||
"syntax-primitive": "#ff8389",
|
||||
"syntax-property": "#78a9ff",
|
||||
"syntax-type": "#08bdba",
|
||||
"syntax-constant": "#be95ff",
|
||||
"syntax-keyword": "#8d8d8d",
|
||||
"syntax-info": "#78a9ff",
|
||||
"markdown-heading": "#82cfff",
|
||||
"markdown-text": "#f2f4f8",
|
||||
"markdown-link": "#78a9ff",
|
||||
"markdown-link-text": "#33b1ff",
|
||||
"markdown-code": "#42be65",
|
||||
"markdown-block-quote": "#8d8d8d",
|
||||
"markdown-emph": "#be95ff",
|
||||
"markdown-strong": "#ffffff",
|
||||
"markdown-horizontal-rule": "#393939",
|
||||
"markdown-list-item": "#33b1ff",
|
||||
"markdown-list-enumeration": "#33b1ff",
|
||||
"markdown-image": "#78a9ff",
|
||||
"markdown-image-text": "#33b1ff",
|
||||
"markdown-code-block": "#c6c6c6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.1.21",
|
||||
"version": "1.1.23",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user