Merge branch 'dev' into docs-snapshot-config

This commit is contained in:
Marat Yuldashev 2025-11-26 21:23:49 +02:00 committed by GitHub
commit 672c179746
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 237 additions and 132 deletions

View file

@ -151,3 +151,4 @@
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |

View file

@ -19,7 +19,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@ -47,7 +47,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@ -74,7 +74,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@ -98,7 +98,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@ -122,7 +122,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@ -163,7 +163,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@ -191,7 +191,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@ -207,7 +207,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.114",
"version": "1.0.115",
"bin": {
"opencode": "./bin/opencode",
},
@ -294,7 +294,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@ -314,7 +314,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.114",
"version": "1.0.115",
"devDependencies": {
"@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:",
@ -325,7 +325,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@ -338,7 +338,7 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-opener": "^2",
@ -351,7 +351,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@ -383,7 +383,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"zod": "catalog:",
},
@ -393,7 +393,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@ -439,7 +439,7 @@
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.11",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.5.5",
"@pierre/precision-diffs": "0.5.7",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
@ -1214,7 +1214,7 @@
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.5.5", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-mmDHEWWQ6fmXY5qRNHqodzOxHPwLqVNbbnO/MOpXteOTjd0nVIGy5IcaNwU2WSxhxQRwaUepKyx5+wwPcZLEmw=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.5.7", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-Y+e4kJ9pT2I4NS5fE39KdoiXtwMkVPRvrwLM6O2IqO7PDCRWLBS7CYxcSgSyngEndccUll2krx66I2QnfO0Ovg=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],

6
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1763934636,
"narHash": "sha256-9glbI7f1uU+yzQCq5LwLgdZqx6svOhZWkd4JRY265fc=",
"lastModified": 1764081664,
"narHash": "sha256-sUoHmPr/EwXzRMpv1u/kH+dXuvJEyyF2Q7muE+t0EU4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ee09932cedcef15aaf476f9343d1dea2cb77e261",
"rev": "dc205f7b4fdb04c8b7877b43edb7b73be7730081",
"type": "github"
},
"original": {

View file

@ -1,3 +1,3 @@
{
"nodeModules": "sha256-cieNNPXZd4Bg9bZtRq2H8L99e24U8p5d+d76SE7SeJc="
"nodeModules": "sha256-XFJXjBWbHIUWKdSOZkGW7VmUPBVtGRgLHM2/1xVThFc="
}

View file

@ -30,7 +30,7 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.5.5",
"@pierre/precision-diffs": "0.5.7",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"ai": "5.0.97",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.0.114",
"version": "1.0.115",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.114",
"version": "1.0.115",
"private": true,
"type": "module",
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.114",
"version": "1.0.115",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.114",
"version": "1.0.115",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.114",
"version": "1.0.115",
"description": "",
"type": "module",
"scripts": {

View file

@ -333,7 +333,7 @@ export default function Page() {
flex: layout.review.state() === "pane",
}}
>
<div class="relative shrink-0 px-6 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-2xl mx-auto">
<div class="relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-2xl mx-auto">
<Switch>
<Match when={session.id}>
<div class="flex items-start justify-start h-full min-h-0">
@ -364,7 +364,7 @@ export default function Page() {
<SessionTurn
sessionID={session.id!}
messageID={session.messages.active()?.id!}
classes={{ root: "pb-20 flex-1 min-w-0", content: "pb-20" }}
classes={{ root: "pb-20 flex-1 min-w-0", content: "pb-20", container: "px-6" }}
/>
</div>
</Match>

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.0.114",
"version": "1.0.115",
"private": true,
"type": "module",
"scripts": {

View file

@ -190,8 +190,8 @@ export default function () {
)
const turns = () => (
<div class="relative mt-2 pt-6 pb-8 px-4 min-w-0 w-full h-full overflow-y-auto no-scrollbar">
{title()}
<div class="relative mt-2 pt-6 pb-8 min-w-0 w-full h-full overflow-y-auto no-scrollbar">
<div class="px-4">{title()}</div>
<div class="flex flex-col gap-15 items-start justify-start mt-4">
<For each={messages()}>
{(message) => (
@ -202,18 +202,20 @@ export default function () {
root: "min-w-0 w-full relative",
content:
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
container: "px-4",
}}
/>
)}
</For>
</div>
<div class="flex items-center justify-center pt-20 pb-8 shrink-0">
<div class="px-4 flex items-center justify-center pt-20 pb-8 shrink-0">
<Logo class="w-58.5 opacity-12" />
</div>
</div>
)
const wide = createMemo(() => diffs().length === 0)
const columnPadding = () => (wide() ? "px-6" : "px-21 @4xl:px-6")
return (
<div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col">
@ -245,11 +247,10 @@ export default function () {
<div
classList={{
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full mx-auto": true,
"px-21 @4xl:px-6 max-w-2xl": !wide(),
"px-6 max-w-2xl": wide(),
"max-w-2xl": true,
}}
>
{title()}
<div class={columnPadding()}>{title()}</div>
<div class="flex items-start justify-start h-full min-h-0">
<Show when={messages().length > 1}>
<>
@ -285,9 +286,13 @@ export default function () {
<SessionTurn
sessionID={data().sessionID}
messageID={store.messageId ?? firstUserMessage()!.id!}
classes={{ root: "grow", content: "flex flex-col justify-between", container: "pb-20" }}
classes={{
root: "grow",
content: "flex flex-col justify-between",
container: `${columnPadding()} pb-20`,
}}
>
<div class="flex items-center justify-center pb-8 shrink-0">
<div class={`${columnPadding()} flex items-center justify-center pb-8 shrink-0`}>
<Logo class="w-58.5 opacity-12" />
</div>
</SessionTurn>

View file

@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The AI coding agent built for the terminal"
version = "1.0.114"
version = "1.0.115"
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.114/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.115/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.114/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.115/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.114/opencode-linux-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.115/opencode-linux-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.114/opencode-linux-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.115/opencode-linux-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.114/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.115/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.0.114",
"version": "1.0.115",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View file

@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.114",
"version": "1.0.115",
"name": "opencode",
"type": "module",
"private": true,

View file

@ -480,7 +480,7 @@ function App() {
<box paddingLeft={1} paddingRight={1}>
<text fg={theme.textMuted}>
{process.cwd().replace(Global.Path.home, "~")}
{sync.data.vcs?.vcs?.branch ? `:${sync.data.vcs.vcs.branch}` : ""}
{sync.data.vcs?.branch ? `:${sync.data.vcs.branch}` : ""}
</text>
</box>
</box>

View file

@ -496,6 +496,40 @@ export function Prompt(props: PromptProps) {
}
const exit = useExit()
function pasteText(text: string, virtualText: string) {
const currentOffset = input.visualCursor.offset
const extmarkStart = currentOffset
const extmarkEnd = extmarkStart + virtualText.length
input.insertText(virtualText + " ")
const extmarkId = input.extmarks.create({
start: extmarkStart,
end: extmarkEnd,
virtual: true,
styleId: pasteStyleId,
typeId: promptPartTypeId,
})
setStore(
produce((draft) => {
const partIndex = draft.prompt.parts.length
draft.prompt.parts.push({
type: "text" as const,
text,
source: {
text: {
start: extmarkStart,
end: extmarkEnd,
value: virtualText,
},
},
})
draft.extmarkToPartIndex.set(extmarkId, partIndex)
}),
)
}
async function pasteImage(file: { filename?: string; content: string; mime: string }) {
const currentOffset = input.visualCursor.offset
const extmarkStart = currentOffset
@ -709,12 +743,21 @@ export function Prompt(props: PromptProps) {
if (!isUrl) {
try {
const file = Bun.file(filepath)
// Handle SVG as raw text content, not as base64 image
if (file.type === "image/svg+xml") {
event.preventDefault()
const content = await file.text().catch(() => {})
if (content) {
pasteText(content, `[SVG: ${file.name ?? "image"}]`)
return
}
}
if (file.type.startsWith("image/")) {
event.preventDefault()
const content = await file
.arrayBuffer()
.then((buffer) => Buffer.from(buffer).toString("base64"))
.catch(console.error)
.catch(() => {})
if (content) {
await pasteImage({
filename: file.name,
@ -733,41 +776,7 @@ export function Prompt(props: PromptProps) {
!sync.data.config.experimental?.disable_paste_summary
) {
event.preventDefault()
const currentOffset = input.visualCursor.offset
const virtualText = `[Pasted ~${lineCount} lines]`
const textToInsert = virtualText + " "
const extmarkStart = currentOffset
const extmarkEnd = extmarkStart + virtualText.length
input.insertText(textToInsert)
const extmarkId = input.extmarks.create({
start: extmarkStart,
end: extmarkEnd,
virtual: true,
styleId: pasteStyleId,
typeId: promptPartTypeId,
})
const part = {
type: "text" as const,
text: pastedContent,
source: {
text: {
start: extmarkStart,
end: extmarkEnd,
value: virtualText,
},
},
}
setStore(
produce((draft) => {
const partIndex = draft.prompt.parts.length
draft.prompt.parts.push(part)
draft.extmarkToPartIndex.set(extmarkId, partIndex)
}),
)
pasteText(pastedContent, `[Pasted ~${lineCount} lines]`)
return
}
}}

View file

@ -1,6 +1,6 @@
import { useRenderer } from "@opentui/solid"
import { createSimpleContext } from "./helper"
import { FormatError } from "@/cli/error"
import { FormatError, FormatUnknownError } from "@/cli/error"
export const { use: useExit, provider: ExitProvider } = createSimpleContext({
name: "Exit",
@ -10,8 +10,10 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({
renderer.destroy()
await input.onExit?.()
if (reason) {
const formatted = FormatError(reason) ?? JSON.stringify(reason)
process.stderr.write(formatted + "\n")
const formatted = FormatError(reason) ?? FormatUnknownError(reason)
if (formatted) {
process.stderr.write(formatted + "\n")
}
}
process.exit(0)
}

View file

@ -23,6 +23,7 @@ import { createSimpleContext } from "./helper"
import type { Snapshot } from "@/snapshot"
import { useExit } from "./exit"
import { batch, onMount } from "solid-js"
import { Log } from "@/util/log"
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
name: "Sync",
@ -243,7 +244,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
case "vcs.branch.updated": {
setStore("vcs", "vcs", { branch: event.properties.branch })
setStore("vcs", { branch: event.properties.branch })
break
}
}
@ -290,6 +291,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
})
})
.catch(async (e) => {
Log.Default.error("tui bootstrap failed", {
error: e instanceof Error ? e.message : String(e),
name: e instanceof Error ? e.name : undefined,
stack: e instanceof Error ? e.stack : undefined,
})
await exit(e)
})
}

View file

@ -38,3 +38,18 @@ export function FormatError(input: unknown) {
if (UI.CancelledError.isInstance(input)) return ""
}
export function FormatUnknownError(input: unknown): string {
if (input instanceof Error) {
return input.stack ?? `${input.name}: ${input.message}`
}
if (typeof input === "object" && input !== null) {
try {
const json = JSON.stringify(input, null, 2)
if (json && json !== "{}") return json
} catch {}
}
return String(input)
}

View file

@ -18,6 +18,15 @@ export namespace Vcs {
),
}
export const Info = z
.object({
branch: z.string(),
})
.meta({
ref: "VcsInfo",
})
export type Info = z.infer<typeof Info>
async function currentBranch() {
return $`git rev-parse --abbrev-ref HEAD`
.quiet()

View file

@ -376,22 +376,7 @@ export namespace Server {
description: "VCS info",
content: {
"application/json": {
schema: resolver(
z
.object({
worktree: z.string(),
directory: z.string(),
projectID: z.string(),
vcs: z
.object({
branch: z.string(),
})
.optional(),
})
.meta({
ref: "VcsInfo",
}),
),
schema: resolver(Vcs.Info),
},
},
},
@ -400,10 +385,7 @@ export namespace Server {
async (c) => {
const branch = await Vcs.branch()
return c.json({
worktree: Instance.worktree,
directory: Instance.directory,
projectID: Instance.project.id,
vcs: Instance.project.vcs ? { branch } : undefined,
branch,
})
},
)

View file

@ -150,9 +150,12 @@ export namespace SessionPrompt {
},
]
const files = ConfigMarkdown.files(template)
const seen = new Set<string>()
await Promise.all(
files.map(async (match) => {
const name = match[1]
if (seen.has(name)) return
seen.add(name)
const filepath = name.startsWith("~/")
? path.join(os.homedir(), name.slice(2))
: path.resolve(Instance.worktree, name)

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.0.114",
"version": "1.0.115",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.0.114",
"version": "1.0.115",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",

View file

@ -1264,12 +1264,7 @@ export type Path = {
}
export type VcsInfo = {
worktree: string
directory: string
projectID: string
vcs?: {
branch: string
}
branch: string
}
export type NotFoundError = {

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.0.114",
"version": "1.0.115",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",

View file

@ -1,7 +1,7 @@
{
"name": "@opencode-ai/tauri",
"private": true,
"version": "1.0.114",
"version": "1.0.115",
"type": "module",
"scripts": {
"dev": "vite",

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.0.114",
"version": "1.0.115",
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",

View file

@ -7,7 +7,7 @@ import type { AssistantMessage as AssistantMessageType, ToolPart } from "@openco
export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) {
const data = useData()
const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined))
const parts = createMemo(() => props.assistantMessages().flatMap((m) => data.part[m.id]))
const parts = createMemo(() => props.assistantMessages().flatMap((m) => data.store.part[m.id]))
const done = createMemo(() => props.done ?? false)
const currentTask = createMemo(
() =>
@ -27,8 +27,10 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
let resolved = parts()
const task = currentTask()
if (task && task.state && "metadata" in task.state && task.state.metadata?.sessionId) {
const messages = data.message[task.state.metadata.sessionId as string]?.filter((m) => m.role === "assistant")
resolved = messages?.flatMap((m) => data.part[m.id]) ?? parts()
const messages = data.store.message[task.state.metadata.sessionId as string]?.filter(
(m) => m.role === "assistant",
)
resolved = messages?.flatMap((m) => data.store.part[m.id]) ?? parts()
}
return resolved
})
@ -149,7 +151,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
{(p) => {
const part = p() as ToolPart
const message = createMemo(() =>
data.message[part.sessionID].find((m) => m.id === part.messageID),
data.store.message[part.sessionID].find((m) => m.id === part.messageID),
)
return (
<div data-slot="message-progress-item">

View file

@ -28,11 +28,11 @@ export function SessionTurn(
}>,
) {
const data = useData()
const match = Binary.search(data.session, props.sessionID, (s) => s.id)
const match = Binary.search(data.store.session, props.sessionID, (s) => s.id)
if (!match.found) throw new Error(`Session ${props.sessionID} not found`)
const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined))
const messages = createMemo(() => (props.sessionID ? (data.message[props.sessionID] ?? []) : []))
const messages = createMemo(() => (props.sessionID ? (data.store.message[props.sessionID] ?? []) : []))
const userMessages = createMemo(() =>
messages()
.filter((m) => m.role === "user")
@ -45,7 +45,7 @@ export function SessionTurn(
const status = createMemo(
() =>
data.session_status[props.sessionID] ?? {
data.store.session_status[props.sessionID] ?? {
type: "idle",
},
)
@ -65,9 +65,9 @@ export function SessionTurn(
const assistantMessages = createMemo(() => {
return messages()?.filter((m) => m.role === "assistant" && m.parentID == msg().id) as AssistantMessage[]
})
const assistantMessageParts = createMemo(() => assistantMessages()?.flatMap((m) => data.part[m.id]))
const assistantMessageParts = createMemo(() => assistantMessages()?.flatMap((m) => data.store.part[m.id]))
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
const parts = createMemo(() => data.part[msg().id])
const parts = createMemo(() => data.store.part[msg().id])
const lastTextPart = createMemo(() =>
assistantMessageParts()
.filter((p) => p?.type === "text")
@ -212,7 +212,7 @@ export function SessionTurn(
<div data-slot="session-turn-collapsible-content-inner">
<For each={assistantMessages()}>
{(assistantMessage) => {
const parts = createMemo(() => data.part[assistantMessage.id])
const parts = createMemo(() => data.store.part[assistantMessage.id])
const last = createMemo(() =>
parts()
.filter((p) => p?.type === "text")

View file

@ -24,6 +24,6 @@ type Data = {
export const { use: useData, provider: DataProvider } = createSimpleContext({
name: "Data",
init: (props: { data: Data; directory: string }) => {
return { ...props.data, directory: props.directory }
return { store: props.data, directory: props.directory }
},
})

View file

@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
"version": "1.0.114",
"version": "1.0.115",
"private": true,
"type": "module",
"exports": {

View file

@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
"version": "1.0.114",
"version": "1.0.115",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",

View file

@ -652,6 +652,82 @@ The `global` region improves availability and reduces errors at no extra cost. U
---
### llama.cpp
You can configure opencode to use local models through [llama.cpp's](https://github.com/ggml-org/llama.cpp) llama-server utility
```json title="opencode.json" "llama.cpp" {5, 6, 8, 10-14}
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"llama.cpp": {
"npm": "@ai-sdk/openai-compatible",
"name": "llama-server (local)",
"options": {
"baseURL": "http://127.0.0.1:8080/v1"
},
"models": {
"qwen3-coder:a3b": {
"name": "Qwen3-Coder: a3b-30b (local)"
}
},
"limit": {
"context": 128000,
"output": 65536
}
}
}
}
```
In this example:
- `llama.cpp` is the custom provider ID. This can be any string you want.
- `npm` specifies the package to use for this provider. Here, `@ai-sdk/openai-compatible` is used for any OpenAI-compatible API.
- `name` is the display name for the provider in the UI.
- `options.baseURL` is the endpoint for the local server.
- `models` is a map of model IDs to their configurations. The model name will be displayed in the model selection list.
---
### IO.NET
IO.NET offers 17 models optimized for various use cases:
1. Head over to the [IO.NET console](https://ai.io.net/), create an account, and generate an API key.
2. Run `opencode auth login` and select **IO.NET**.
```bash
$ opencode auth login
┌ Add credential
◆ Select provider
│ ● IO.NET
│ ...
```
3. Enter your IO.NET API key.
```bash
$ opencode auth login
┌ Add credential
◇ Select provider
│ IO.NET
◇ Enter your API key
│ _
```
4. Run the `/models` command to select a model.
---
### LM Studio
You can configure opencode to use local models through LM Studio.

View file

@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.0.114",
"version": "1.0.115",
"publisher": "sst-dev",
"repository": {
"type": "git",