Jelajahi Sumber

Merge branch 'dev' of https://github.com/sst/opencode into dev

David Hill 2 bulan lalu
induk
melakukan
58b30d678a

+ 3 - 3
.opencode/opencode.jsonc

@@ -1,9 +1,9 @@
 {
 {
   "$schema": "https://opencode.ai/config.json",
   "$schema": "https://opencode.ai/config.json",
   "plugin": ["opencode-openai-codex-auth"],
   "plugin": ["opencode-openai-codex-auth"],
-  "enterprise": {
-    "url": "https://enterprise.dev.opencode.ai",
-  },
+  // "enterprise": {
+  //   "url": "https://enterprise.dev.opencode.ai",
+  // },
   "provider": {
   "provider": {
     "opencode": {
     "opencode": {
       "options": {
       "options": {

+ 1 - 0
STATS.md

@@ -157,3 +157,4 @@
 | 2025-11-29 | 908,689 (+6,948)  | 863,361 (+6,879)  | 1,772,050 (+13,827) |
 | 2025-11-29 | 908,689 (+6,948)  | 863,361 (+6,879)  | 1,772,050 (+13,827) |
 | 2025-11-30 | 916,116 (+7,427)  | 870,194 (+6,833)  | 1,786,310 (+14,260) |
 | 2025-11-30 | 916,116 (+7,427)  | 870,194 (+6,833)  | 1,786,310 (+14,260) |
 | 2025-12-01 | 925,898 (+9,782)  | 876,500 (+6,306)  | 1,802,398 (+16,088) |
 | 2025-12-01 | 925,898 (+9,782)  | 876,500 (+6,306)  | 1,802,398 (+16,088) |
+| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |

+ 17 - 17
bun.lock

@@ -20,7 +20,7 @@
     },
     },
     "packages/console/app": {
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
       "name": "@opencode-ai/console-app",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
         "@ibm/plex": "6.4.1",
@@ -48,7 +48,7 @@
     },
     },
     "packages/console/core": {
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
       "name": "@opencode-ai/console-core",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
         "@jsx-email/render": "1.1.1",
@@ -75,7 +75,7 @@
     },
     },
     "packages/console/function": {
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
       "name": "@opencode-ai/console-function",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/openai": "2.0.2",
         "@ai-sdk/openai": "2.0.2",
@@ -99,7 +99,7 @@
     },
     },
     "packages/console/mail": {
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
       "name": "@opencode-ai/console-mail",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
         "@jsx-email/cli": "1.4.3",
@@ -123,7 +123,7 @@
     },
     },
     "packages/desktop": {
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
       "name": "@opencode-ai/desktop",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
@@ -164,7 +164,7 @@
     },
     },
     "packages/enterprise": {
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
       "name": "@opencode-ai/enterprise",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/util": "workspace:*",
         "@opencode-ai/util": "workspace:*",
@@ -192,7 +192,7 @@
     },
     },
     "packages/function": {
     "packages/function": {
       "name": "@opencode-ai/function",
       "name": "@opencode-ai/function",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "22.0.0",
         "@octokit/rest": "22.0.0",
@@ -208,7 +208,7 @@
     },
     },
     "packages/opencode": {
     "packages/opencode": {
       "name": "opencode",
       "name": "opencode",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "bin": {
       "bin": {
         "opencode": "./bin/opencode",
         "opencode": "./bin/opencode",
       },
       },
@@ -297,7 +297,7 @@
     },
     },
     "packages/plugin": {
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
       "name": "@opencode-ai/plugin",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "zod": "catalog:",
         "zod": "catalog:",
@@ -317,7 +317,7 @@
     },
     },
     "packages/sdk/js": {
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
       "name": "@opencode-ai/sdk",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "devDependencies": {
       "devDependencies": {
         "@hey-api/openapi-ts": "0.81.0",
         "@hey-api/openapi-ts": "0.81.0",
         "@tsconfig/node22": "catalog:",
         "@tsconfig/node22": "catalog:",
@@ -328,7 +328,7 @@
     },
     },
     "packages/slack": {
     "packages/slack": {
       "name": "@opencode-ai/slack",
       "name": "@opencode-ai/slack",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
         "@slack/bolt": "^3.17.1",
@@ -341,7 +341,7 @@
     },
     },
     "packages/tauri": {
     "packages/tauri": {
       "name": "@opencode-ai/tauri",
       "name": "@opencode-ai/tauri",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@tauri-apps/api": "^2",
         "@tauri-apps/api": "^2",
         "@tauri-apps/plugin-opener": "^2",
         "@tauri-apps/plugin-opener": "^2",
@@ -354,7 +354,7 @@
     },
     },
     "packages/ui": {
     "packages/ui": {
       "name": "@opencode-ai/ui",
       "name": "@opencode-ai/ui",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
@@ -386,7 +386,7 @@
     },
     },
     "packages/util": {
     "packages/util": {
       "name": "@opencode-ai/util",
       "name": "@opencode-ai/util",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "zod": "catalog:",
         "zod": "catalog:",
       },
       },
@@ -397,7 +397,7 @@
     },
     },
     "packages/web": {
     "packages/web": {
       "name": "@opencode-ai/web",
       "name": "@opencode-ai/web",
-      "version": "1.0.126",
+      "version": "1.0.127",
       "dependencies": {
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
         "@astrojs/markdown-remark": "6.3.1",
@@ -443,7 +443,7 @@
     "@hono/zod-validator": "0.4.2",
     "@hono/zod-validator": "0.4.2",
     "@kobalte/core": "0.13.11",
     "@kobalte/core": "0.13.11",
     "@openauthjs/openauth": "0.0.0-20250322224806",
     "@openauthjs/openauth": "0.0.0-20250322224806",
-    "@pierre/precision-diffs": "0.5.7",
+    "@pierre/precision-diffs": "0.6.0-beta.3",
     "@solidjs/meta": "0.29.4",
     "@solidjs/meta": "0.29.4",
     "@solidjs/router": "0.15.4",
     "@solidjs/router": "0.15.4",
     "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
     "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
@@ -1218,7 +1218,7 @@
 
 
     "@petamoriken/float16": ["@petamoriken/[email protected]", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
     "@petamoriken/float16": ["@petamoriken/[email protected]", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
 
 
-    "@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=="],
+    "@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.3", "", { "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-1FBm9jhLWZvs7BqN3yG2Wh9SpGuO1us2QsKZlQqSwyCctMr9DRGzYQJ9lF6yR03LHzXs3fuIzO++d9sCObYzrQ=="],
 
 
     "@pkgjs/parseargs": ["@pkgjs/[email protected]", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
     "@pkgjs/parseargs": ["@pkgjs/[email protected]", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
 
 

+ 3 - 3
flake.lock

@@ -2,11 +2,11 @@
   "nodes": {
   "nodes": {
     "nixpkgs": {
     "nixpkgs": {
       "locked": {
       "locked": {
-        "lastModified": 1764587062,
-        "narHash": "sha256-hdFa0TAVQAQLDF31cEW3enWmBP+b592OvHs6WVe3D8k=",
+        "lastModified": 1764611609,
+        "narHash": "sha256-yU9BNcP0oadUKupw0UKmO9BKDOVIg9NStdJosEbXf8U=",
         "owner": "NixOS",
         "owner": "NixOS",
         "repo": "nixpkgs",
         "repo": "nixpkgs",
-        "rev": "c1cb7d097cb250f6e1904aacd5f2ba5ffd8a49ce",
+        "rev": "8c29968b3a942f2903f90797f9623737c215737c",
         "type": "github"
         "type": "github"
       },
       },
       "original": {
       "original": {

+ 1 - 1
nix/hashes.json

@@ -1,3 +1,3 @@
 {
 {
-  "nodeModules": "sha256-9BfJ3dFq/UYyhsnK3Sfx6rb6CT8bCvFOFOqD2+W1WQE="
+  "nodeModules": "sha256-HyH219sZn4gOPyVg/bij7K3mfZ0MnBSM/7NmsOyrD4o="
 }
 }

+ 1 - 1
package.json

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

+ 1 - 1
packages/console/app/package.json

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

+ 1 - 1
packages/console/core/package.json

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

+ 1 - 1
packages/console/function/package.json

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

+ 1 - 1
packages/console/mail/package.json

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

+ 1 - 1
packages/desktop/package.json

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

+ 4 - 0
packages/desktop/src/pages/session.tsx

@@ -30,6 +30,7 @@ import { useSync } from "@/context/sync"
 import { useSession } from "@/context/session"
 import { useSession } from "@/context/session"
 import { useLayout } from "@/context/layout"
 import { useLayout } from "@/context/layout"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { Diff } from "@opencode-ai/ui/diff"
 
 
 export default function Page() {
 export default function Page() {
   const layout = useLayout()
   const layout = useLayout()
@@ -357,6 +358,7 @@ export default function Page() {
                           content: "pb-20",
                           content: "pb-20",
                           container: "w-full " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
                           container: "w-full " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
                         }}
                         }}
+                        diffComponent={Diff}
                       />
                       />
                     </div>
                     </div>
                   </Match>
                   </Match>
@@ -405,6 +407,7 @@ export default function Page() {
                       container: "px-6",
                       container: "px-6",
                     }}
                     }}
                     diffs={session.diffs()}
                     diffs={session.diffs()}
+                    diffComponent={Diff}
                     actions={
                     actions={
                       <Tooltip value="Open in tab">
                       <Tooltip value="Open in tab">
                         <IconButton
                         <IconButton
@@ -436,6 +439,7 @@ export default function Page() {
                     container: "px-6",
                     container: "px-6",
                   }}
                   }}
                   diffs={session.diffs()}
                   diffs={session.diffs()}
+                  diffComponent={Diff}
                   split
                   split
                 />
                 />
               </div>
               </div>

+ 1 - 1
packages/enterprise/package.json

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

+ 10 - 1
packages/enterprise/src/routes/share/[shareID].tsx

@@ -18,6 +18,10 @@ import z from "zod"
 import NotFound from "../[...404]"
 import NotFound from "../[...404]"
 import { Tabs } from "@opencode-ai/ui/tabs"
 import { Tabs } from "@opencode-ai/ui/tabs"
 import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
 import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
+import { Diff } from "@opencode-ai/ui/diff-ssr"
+import { clientOnly } from "@solidjs/start"
+
+const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
 
 
 const SessionDataMissingError = NamedError.create(
 const SessionDataMissingError = NamedError.create(
   "SessionDataMissingError",
   "SessionDataMissingError",
@@ -230,6 +234,7 @@ export default function () {
                                 "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
                                 "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
                               container: "px-4",
                               container: "px-4",
                             }}
                             }}
+                            diffComponent={ClientOnlyDiff}
                           />
                           />
                         )}
                         )}
                       </For>
                       </For>
@@ -299,6 +304,7 @@ export default function () {
                                 content: "flex flex-col justify-between items-start",
                                 content: "flex flex-col justify-between items-start",
                                 container: "w-full pb-20 " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
                                 container: "w-full pb-20 " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
                               }}
                               }}
+                              diffComponent={ClientOnlyDiff}
                             >
                             >
                               <div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
                               <div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
                                 <Logo class="w-58.5 opacity-12" />
                                 <Logo class="w-58.5 opacity-12" />
@@ -311,6 +317,7 @@ export default function () {
                             <SessionReview
                             <SessionReview
                               class="@4xl:hidden"
                               class="@4xl:hidden"
                               diffs={diffs()}
                               diffs={diffs()}
+                              diffComponent={Diff}
                               classes={{
                               classes={{
                                 root: "pb-20",
                                 root: "pb-20",
                                 header: "px-6",
                                 header: "px-6",
@@ -318,9 +325,10 @@ export default function () {
                               }}
                               }}
                             />
                             />
                             <SessionReview
                             <SessionReview
-                              class="hidden @4xl:flex"
                               split
                               split
+                              class="hidden @4xl:flex"
                               diffs={splitDiffs()}
                               diffs={splitDiffs()}
+                              diffComponent={Diff}
                               classes={{
                               classes={{
                                 root: "pb-20",
                                 root: "pb-20",
                                 header: "px-6",
                                 header: "px-6",
@@ -352,6 +360,7 @@ export default function () {
                               <div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
                               <div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
                                 <SessionReview
                                 <SessionReview
                                   diffs={diffs()}
                                   diffs={diffs()}
+                                  diffComponent={Diff}
                                   classes={{
                                   classes={{
                                     root: "pb-20",
                                     root: "pb-20",
                                     header: "px-4",
                                     header: "px-4",

+ 6 - 6
packages/extensions/zed/extension.toml

@@ -1,7 +1,7 @@
 id = "opencode"
 id = "opencode"
 name = "OpenCode"
 name = "OpenCode"
 description = "The AI coding agent built for the terminal"
 description = "The AI coding agent built for the terminal"
-version = "1.0.126"
+version = "1.0.127"
 schema_version = 1
 schema_version = 1
 authors = ["Anomaly"]
 authors = ["Anomaly"]
 repository = "https://github.com/sst/opencode"
 repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
 icon = "./icons/opencode.svg"
 icon = "./icons/opencode.svg"
 
 
 [agent_servers.opencode.targets.darwin-aarch64]
 [agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-darwin-arm64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-darwin-arm64.zip"
 cmd = "./opencode"
 cmd = "./opencode"
 args = ["acp"]
 args = ["acp"]
 
 
 [agent_servers.opencode.targets.darwin-x86_64]
 [agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-darwin-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-darwin-x64.zip"
 cmd = "./opencode"
 cmd = "./opencode"
 args = ["acp"]
 args = ["acp"]
 
 
 [agent_servers.opencode.targets.linux-aarch64]
 [agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-linux-arm64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-linux-arm64.zip"
 cmd = "./opencode"
 cmd = "./opencode"
 args = ["acp"]
 args = ["acp"]
 
 
 [agent_servers.opencode.targets.linux-x86_64]
 [agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-linux-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-linux-x64.zip"
 cmd = "./opencode"
 cmd = "./opencode"
 args = ["acp"]
 args = ["acp"]
 
 
 [agent_servers.opencode.targets.windows-x86_64]
 [agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-windows-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-windows-x64.zip"
 cmd = "./opencode.exe"
 cmd = "./opencode.exe"
 args = ["acp"]
 args = ["acp"]

+ 1 - 1
packages/function/package.json

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

+ 1 - 1
packages/opencode/package.json

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

+ 48 - 0
packages/opencode/src/cli/cmd/tui/context/theme.tsx

@@ -169,6 +169,9 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
         throw new Error(`Color reference "${c}" not found in defs or theme`)
         throw new Error(`Color reference "${c}" not found in defs or theme`)
       }
       }
     }
     }
+    if (typeof c === "number") {
+      return ansiToRgba(c)
+    }
     return resolveColor(c[mode])
     return resolveColor(c[mode])
   }
   }
 
 
@@ -203,6 +206,51 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
   } as Theme
   } as Theme
 }
 }
 
 
+function ansiToRgba(code: number): RGBA {
+  // Standard ANSI colors (0-15)
+  if (code < 16) {
+    const ansiColors = [
+      "#000000", // Black
+      "#800000", // Red
+      "#008000", // Green
+      "#808000", // Yellow
+      "#000080", // Blue
+      "#800080", // Magenta
+      "#008080", // Cyan
+      "#c0c0c0", // White
+      "#808080", // Bright Black
+      "#ff0000", // Bright Red
+      "#00ff00", // Bright Green
+      "#ffff00", // Bright Yellow
+      "#0000ff", // Bright Blue
+      "#ff00ff", // Bright Magenta
+      "#00ffff", // Bright Cyan
+      "#ffffff", // Bright White
+    ]
+    return RGBA.fromHex(ansiColors[code] ?? "#000000")
+  }
+
+  // 6x6x6 Color Cube (16-231)
+  if (code < 232) {
+    const index = code - 16
+    const b = index % 6
+    const g = Math.floor(index / 6) % 6
+    const r = Math.floor(index / 36)
+
+    const val = (x: number) => (x === 0 ? 0 : x * 40 + 55)
+    return RGBA.fromInts(val(r), val(g), val(b))
+  }
+
+  // Grayscale Ramp (232-255)
+  if (code < 256) {
+    const gray = (code - 232) * 10 + 8
+    return RGBA.fromInts(gray, gray, gray)
+  }
+
+  // Fallback for invalid codes
+  return RGBA.fromInts(0, 0, 0)
+}
+
 export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
 export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
   name: "Theme",
   name: "Theme",
   init: (props: { mode: "dark" | "light" }) => {
   init: (props: { mode: "dark" | "light" }) => {

+ 1 - 1
packages/plugin/package.json

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

+ 1 - 1
packages/sdk/js/package.json

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

+ 1 - 1
packages/slack/package.json

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

+ 1 - 1
packages/tauri/package.json

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

+ 2 - 2
packages/ui/package.json

@@ -1,10 +1,10 @@
 {
 {
   "name": "@opencode-ai/ui",
   "name": "@opencode-ai/ui",
-  "version": "1.0.126",
+  "version": "1.0.127",
   "type": "module",
   "type": "module",
   "exports": {
   "exports": {
     "./*": "./src/components/*.tsx",
     "./*": "./src/components/*.tsx",
-    "./pierre": "./src/components/pierre.ts",
+    "./pierre": "./src/pierre/index.ts",
     "./hooks": "./src/hooks/index.ts",
     "./hooks": "./src/hooks/index.ts",
     "./context": "./src/context/index.ts",
     "./context": "./src/context/index.ts",
     "./context/*": "./src/context/*.tsx",
     "./context/*": "./src/context/*.tsx",

+ 24 - 5
packages/ui/src/components/code.tsx

@@ -1,6 +1,22 @@
 import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/precision-diffs"
 import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/precision-diffs"
 import { ComponentProps, createEffect, splitProps } from "solid-js"
 import { ComponentProps, createEffect, splitProps } from "solid-js"
-import { createDefaultOptions, styleVariables } from "./pierre"
+import { createDefaultOptions, styleVariables } from "../pierre"
+import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker"
+import { workerFactory } from "../pierre/worker"
+
+const workerPool = getOrCreateWorkerPoolSingleton({
+  poolOptions: {
+    workerFactory,
+    // poolSize defaults to 8. More workers = more parallelism but
+    // also more memory. Too many can actually slow things down.
+    // poolSize: 8,
+  },
+  highlighterOptions: {
+    theme: "OpenCode",
+    // Optionally preload languages to avoid lazy-loading delays
+    // langs: ["typescript", "javascript", "css", "html"],
+  },
+})
 
 
 export type CodeProps<T = {}> = FileOptions<T> & {
 export type CodeProps<T = {}> = FileOptions<T> & {
   file: FileContents
   file: FileContents
@@ -14,10 +30,13 @@ export function Code<T>(props: CodeProps<T>) {
   const [local, others] = splitProps(props, ["file", "class", "classList", "annotations"])
   const [local, others] = splitProps(props, ["file", "class", "classList", "annotations"])
 
 
   createEffect(() => {
   createEffect(() => {
-    const instance = new File<T>({
-      ...createDefaultOptions<T>("unified"),
-      ...others,
-    })
+    const instance = new File<T>(
+      {
+        ...createDefaultOptions<T>("unified"),
+        ...others,
+      },
+      workerPool,
+    )
 
 
     container.innerHTML = ""
     container.innerHTML = ""
     instance.render({
     instance.render({

+ 75 - 0
packages/ui/src/components/diff-ssr.tsx

@@ -0,0 +1,75 @@
+import { FileDiff } from "@pierre/precision-diffs"
+import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
+import { onCleanup, onMount, Show, splitProps } from "solid-js"
+import { isServer } from "solid-js/web"
+import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre"
+
+export type SSRDiffProps<T = {}> = DiffProps<T> & {
+  preloadedDiff: PreloadMultiFileDiffResult<T>
+}
+
+export function Diff<T>(props: SSRDiffProps<T>) {
+  let container!: HTMLDivElement
+  let fileDiffRef!: HTMLElement
+  const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
+
+  let fileDiffInstance: FileDiff<T> | undefined
+  const cleanupFunctions: Array<() => void> = []
+
+  onMount(() => {
+    if (isServer || !props.preloadedDiff) return
+    fileDiffInstance = new FileDiff<T>({
+      ...createDefaultOptions(props.diffStyle),
+      ...others,
+      ...props.preloadedDiff,
+    })
+    // @ts-expect-error - fileContainer is private but needed for SSR hydration
+    fileDiffInstance.fileContainer = fileDiffRef
+    fileDiffInstance.hydrate({
+      oldFile: local.before,
+      newFile: local.after,
+      lineAnnotations: local.annotations,
+      fileContainer: fileDiffRef,
+      containerWrapper: container,
+    })
+
+    // Hydrate annotation slots with interactive SolidJS components
+    // if (props.annotations.length > 0 && props.renderAnnotation != null) {
+    //   for (const annotation of props.annotations) {
+    //     const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
+    //     const slotElement = fileDiffRef.querySelector(
+    //       `[slot="${slotName}"]`
+    //     ) as HTMLElement;
+    //
+    //     if (slotElement != null) {
+    //       // Clear the static server-rendered content from the slot
+    //       slotElement.innerHTML = '';
+    //
+    //       // Mount a fresh SolidJS component into this slot using render().
+    //       // This enables full SolidJS reactivity (signals, effects, etc.)
+    //       const dispose = render(
+    //         () => props.renderAnnotation!(annotation),
+    //         slotElement
+    //       );
+    //       cleanupFunctions.push(dispose);
+    //     }
+    //   }
+    // }
+  })
+
+  onCleanup(() => {
+    // Clean up FileDiff event handlers and dispose SolidJS components
+    fileDiffInstance?.cleanUp()
+    cleanupFunctions.forEach((dispose) => dispose())
+  })
+
+  return (
+    <div data-component="diff" style={styleVariables} ref={container}>
+      <file-diff ref={fileDiffRef} id="ssr-diff">
+        <Show when={isServer}>
+          <template shadowrootmode="open" innerHTML={props.preloadedDiff.prerenderedHTML} />
+        </Show>
+      </file-diff>
+    </div>
+  )
+}

+ 27 - 71
packages/ui/src/components/diff.tsx

@@ -1,17 +1,22 @@
-import { type FileContents, FileDiff, type DiffLineAnnotation, FileDiffOptions } from "@pierre/precision-diffs"
-import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
-import { ComponentProps, createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
-import { isServer } from "solid-js/web"
-import { createDefaultOptions, styleVariables } from "./pierre"
-
-export type DiffProps<T = {}> = FileDiffOptions<T> & {
-  preloadedDiff?: PreloadMultiFileDiffResult<T>
-  before: FileContents
-  after: FileContents
-  annotations?: DiffLineAnnotation<T>[]
-  class?: string
-  classList?: ComponentProps<"div">["classList"]
-}
+import { FileDiff } from "@pierre/precision-diffs"
+import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker"
+import { createEffect, onCleanup, splitProps } from "solid-js"
+import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
+import { workerFactory } from "../pierre/worker"
+
+const workerPool = getOrCreateWorkerPoolSingleton({
+  poolOptions: {
+    workerFactory,
+    // poolSize defaults to 8. More workers = more parallelism but
+    // also more memory. Too many can actually slow things down.
+    // poolSize: 8,
+  },
+  highlighterOptions: {
+    theme: "OpenCode",
+    // Optionally preload languages to avoid lazy-loading delays
+    // langs: ["typescript", "javascript", "css", "html"],
+  },
+})
 
 
 // interface ThreadMetadata {
 // interface ThreadMetadata {
 //   threadId: string
 //   threadId: string
@@ -21,21 +26,21 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & {
 
 
 export function Diff<T>(props: DiffProps<T>) {
 export function Diff<T>(props: DiffProps<T>) {
   let container!: HTMLDivElement
   let container!: HTMLDivElement
-  let fileDiffRef!: HTMLElement
   const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
   const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
 
 
   let fileDiffInstance: FileDiff<T> | undefined
   let fileDiffInstance: FileDiff<T> | undefined
   const cleanupFunctions: Array<() => void> = []
   const cleanupFunctions: Array<() => void> = []
 
 
   createEffect(() => {
   createEffect(() => {
-    if (props.preloadedDiff) return
     container.innerHTML = ""
     container.innerHTML = ""
     if (!fileDiffInstance) {
     if (!fileDiffInstance) {
-      fileDiffInstance = new FileDiff<T>({
-        ...createDefaultOptions(props.diffStyle),
-        ...others,
-        ...(props.preloadedDiff ?? {}),
-      })
+      fileDiffInstance = new FileDiff<T>(
+        {
+          ...createDefaultOptions(props.diffStyle),
+          ...others,
+        },
+        workerPool,
+      )
     }
     }
     fileDiffInstance.render({
     fileDiffInstance.render({
       oldFile: local.before,
       oldFile: local.before,
@@ -45,60 +50,11 @@ export function Diff<T>(props: DiffProps<T>) {
     })
     })
   })
   })
 
 
-  onMount(() => {
-    if (isServer || !props.preloadedDiff) return
-    fileDiffInstance = new FileDiff<T>({
-      ...createDefaultOptions(props.diffStyle),
-      ...others,
-      ...(props.preloadedDiff ?? {}),
-    })
-    // @ts-expect-error - fileContainer is private but needed for SSR hydration
-    fileDiffInstance.fileContainer = fileDiffRef
-    fileDiffInstance.hydrate({
-      oldFile: local.before,
-      newFile: local.after,
-      lineAnnotations: local.annotations,
-      fileContainer: fileDiffRef,
-      containerWrapper: container,
-    })
-
-    // Hydrate annotation slots with interactive SolidJS components
-    // if (props.annotations.length > 0 && props.renderAnnotation != null) {
-    //   for (const annotation of props.annotations) {
-    //     const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
-    //     const slotElement = fileDiffRef.querySelector(
-    //       `[slot="${slotName}"]`
-    //     ) as HTMLElement;
-    //
-    //     if (slotElement != null) {
-    //       // Clear the static server-rendered content from the slot
-    //       slotElement.innerHTML = '';
-    //
-    //       // Mount a fresh SolidJS component into this slot using render().
-    //       // This enables full SolidJS reactivity (signals, effects, etc.)
-    //       const dispose = render(
-    //         () => props.renderAnnotation!(annotation),
-    //         slotElement
-    //       );
-    //       cleanupFunctions.push(dispose);
-    //     }
-    //   }
-    // }
-  })
-
   onCleanup(() => {
   onCleanup(() => {
     // Clean up FileDiff event handlers and dispose SolidJS components
     // Clean up FileDiff event handlers and dispose SolidJS components
     fileDiffInstance?.cleanUp()
     fileDiffInstance?.cleanUp()
     cleanupFunctions.forEach((dispose) => dispose())
     cleanupFunctions.forEach((dispose) => dispose())
   })
   })
 
 
-  return (
-    <div data-component="diff" style={styleVariables} ref={container}>
-      <file-diff ref={fileDiffRef} id="ssr-diff">
-        <Show when={isServer && props.preloadedDiff}>
-          {(preloadedDiff) => <template shadowrootmode="open" innerHTML={preloadedDiff().prerenderedHTML} />}
-        </Show>
-      </file-diff>
-    </div>
-  )
+  return <div data-component="diff" style={styleVariables} ref={container} />
 }
 }

+ 26 - 6
packages/ui/src/components/message-part.tsx

@@ -1,4 +1,4 @@
-import { Component, createMemo, For, Match, Show, Switch } from "solid-js"
+import { Component, createMemo, For, Match, Show, Switch, ValidComponent } from "solid-js"
 import { Dynamic } from "solid-js/web"
 import { Dynamic } from "solid-js/web"
 import {
 import {
   AssistantMessage,
   AssistantMessage,
@@ -13,7 +13,6 @@ import { GenericTool } from "./basic-tool"
 import { Card } from "./card"
 import { Card } from "./card"
 import { Icon } from "./icon"
 import { Icon } from "./icon"
 import { Checkbox } from "./checkbox"
 import { Checkbox } from "./checkbox"
-import { Diff } from "./diff"
 import { DiffChanges } from "./diff-changes"
 import { DiffChanges } from "./diff-changes"
 import { Markdown } from "./markdown"
 import { Markdown } from "./markdown"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
@@ -23,12 +22,14 @@ import { unwrap } from "solid-js/store"
 export interface MessageProps {
 export interface MessageProps {
   message: MessageType
   message: MessageType
   parts: PartType[]
   parts: PartType[]
+  diffComponent: ValidComponent
   sanitize?: RegExp
   sanitize?: RegExp
 }
 }
 
 
 export interface MessagePartProps {
 export interface MessagePartProps {
   part: PartType
   part: PartType
   message: MessageType
   message: MessageType
+  diffComponent: ValidComponent
   hideDetails?: boolean
   hideDetails?: boolean
   sanitize?: RegExp
   sanitize?: RegExp
 }
 }
@@ -53,6 +54,7 @@ export function Message(props: MessageProps) {
             message={assistantMessage() as AssistantMessage}
             message={assistantMessage() as AssistantMessage}
             parts={props.parts}
             parts={props.parts}
             sanitize={props.sanitize}
             sanitize={props.sanitize}
+            diffComponent={props.diffComponent}
           />
           />
         )}
         )}
       </Match>
       </Match>
@@ -60,7 +62,12 @@ export function Message(props: MessageProps) {
   )
   )
 }
 }
 
 
-export function AssistantMessageDisplay(props: { message: AssistantMessage; parts: PartType[]; sanitize?: RegExp }) {
+export function AssistantMessageDisplay(props: {
+  message: AssistantMessage
+  parts: PartType[]
+  sanitize?: RegExp
+  diffComponent: ValidComponent
+}) {
   const filteredParts = createMemo(() => {
   const filteredParts = createMemo(() => {
     return props.parts?.filter((x) => {
     return props.parts?.filter((x) => {
       if (x.type === "reasoning") return false
       if (x.type === "reasoning") return false
@@ -68,7 +75,11 @@ export function AssistantMessageDisplay(props: { message: AssistantMessage; part
     })
     })
   })
   })
   return (
   return (
-    <For each={filteredParts()}>{(part) => <Part part={part} message={props.message} sanitize={props.sanitize} />}</For>
+    <For each={filteredParts()}>
+      {(part) => (
+        <Part part={part} message={props.message} sanitize={props.sanitize} diffComponent={props.diffComponent} />
+      )}
+    </For>
   )
   )
 }
 }
 
 
@@ -87,7 +98,13 @@ export function Part(props: MessagePartProps) {
   const part = createMemo(() => sanitizePart(unwrap(props.part), props.sanitize))
   const part = createMemo(() => sanitizePart(unwrap(props.part), props.sanitize))
   return (
   return (
     <Show when={component()}>
     <Show when={component()}>
-      <Dynamic component={component()} part={part()} message={props.message} hideDetails={props.hideDetails} />
+      <Dynamic
+        component={component()}
+        part={part()}
+        message={props.message}
+        diffComponent={props.diffComponent}
+        hideDetails={props.hideDetails}
+      />
     </Show>
     </Show>
   )
   )
 }
 }
@@ -96,6 +113,7 @@ export interface ToolProps {
   input: Record<string, any>
   input: Record<string, any>
   metadata: Record<string, any>
   metadata: Record<string, any>
   tool: string
   tool: string
+  diffComponent: ValidComponent
   output?: string
   output?: string
   hideDetails?: boolean
   hideDetails?: boolean
 }
 }
@@ -162,6 +180,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
             component={render}
             component={render}
             input={input}
             input={input}
             tool={part.tool}
             tool={part.tool}
+            diffComponent={props.diffComponent}
             metadata={metadata}
             metadata={metadata}
             output={part.state.status === "completed" ? part.state.output : undefined}
             output={part.state.status === "completed" ? part.state.output : undefined}
             hideDetails={props.hideDetails}
             hideDetails={props.hideDetails}
@@ -361,7 +380,8 @@ ToolRegistry.register({
       >
       >
         <Show when={props.metadata.filediff}>
         <Show when={props.metadata.filediff}>
           <div data-component="edit-content">
           <div data-component="edit-content">
-            <Diff
+            <Dynamic
+              component={props.diffComponent}
               before={{
               before={{
                 name: getFilename(props.metadata.filediff.path),
                 name: getFilename(props.metadata.filediff.path),
                 contents: props.metadata.filediff.before,
                 contents: props.metadata.filediff.before,

+ 25 - 3
packages/ui/src/components/message-progress.tsx

@@ -1,10 +1,27 @@
-import { For, JSXElement, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
+import {
+  For,
+  JSXElement,
+  Match,
+  Show,
+  Switch,
+  ValidComponent,
+  createEffect,
+  createMemo,
+  createSignal,
+  onCleanup,
+} from "solid-js"
 import { Part } from "./message-part"
 import { Part } from "./message-part"
 import { Spinner } from "./spinner"
 import { Spinner } from "./spinner"
 import { useData } from "../context/data"
 import { useData } from "../context/data"
 import type { AssistantMessage as AssistantMessageType, ToolPart } from "@opencode-ai/sdk"
 import type { AssistantMessage as AssistantMessageType, ToolPart } from "@opencode-ai/sdk"
 
 
-export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) {
+export interface MessageProgressProps {
+  assistantMessages: () => AssistantMessageType[]
+  diffComponent: ValidComponent
+  done?: boolean
+}
+
+export function MessageProgress(props: MessageProgressProps) {
   const data = useData()
   const data = useData()
   const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined))
   const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined))
   const parts = createMemo(() => props.assistantMessages().flatMap((m) => data.store.part[m.id]))
   const parts = createMemo(() => props.assistantMessages().flatMap((m) => data.store.part[m.id]))
@@ -155,7 +172,12 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
                       )
                       )
                       return (
                       return (
                         <div data-slot="message-progress-item">
                         <div data-slot="message-progress-item">
-                          <Part message={message()!} part={part} sanitize={sanitizer()} />
+                          <Part
+                            message={message()!}
+                            part={part}
+                            sanitize={sanitizer()}
+                            diffComponent={props.diffComponent}
+                          />
                         </div>
                         </div>
                       )
                       )
                     }}
                     }}

+ 5 - 3
packages/ui/src/components/session-review.tsx

@@ -1,15 +1,15 @@
 import { Accordion } from "./accordion"
 import { Accordion } from "./accordion"
 import { Button } from "./button"
 import { Button } from "./button"
-import { Diff } from "./diff"
 import { DiffChanges } from "./diff-changes"
 import { DiffChanges } from "./diff-changes"
 import { FileIcon } from "./file-icon"
 import { FileIcon } from "./file-icon"
 import { Icon } from "./icon"
 import { Icon } from "./icon"
 import { StickyAccordionHeader } from "./sticky-accordion-header"
 import { StickyAccordionHeader } from "./sticky-accordion-header"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
-import { For, Match, Show, Switch, type JSX } from "solid-js"
+import { For, Match, Show, Switch, ValidComponent, type JSX } from "solid-js"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { type FileDiff } from "@opencode-ai/sdk"
 import { type FileDiff } from "@opencode-ai/sdk"
 import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
 import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
+import { Dynamic } from "solid-js/web"
 
 
 export interface SessionReviewProps {
 export interface SessionReviewProps {
   split?: boolean
   split?: boolean
@@ -18,6 +18,7 @@ export interface SessionReviewProps {
   classes?: { root?: string; header?: string; container?: string }
   classes?: { root?: string; header?: string; container?: string }
   actions?: JSX.Element
   actions?: JSX.Element
   diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[]
   diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[]
+  diffComponent: ValidComponent
 }
 }
 
 
 export const SessionReview = (props: SessionReviewProps) => {
 export const SessionReview = (props: SessionReviewProps) => {
@@ -96,7 +97,8 @@ export const SessionReview = (props: SessionReviewProps) => {
                   </Accordion.Trigger>
                   </Accordion.Trigger>
                 </StickyAccordionHeader>
                 </StickyAccordionHeader>
                 <Accordion.Content data-slot="session-review-accordion-content">
                 <Accordion.Content data-slot="session-review-accordion-content">
-                  <Diff
+                  <Dynamic
+                    component={props.diffComponent}
                     preloadedDiff={diff.preloaded}
                     preloadedDiff={diff.preloaded}
                     diffStyle={props.split ? "split" : "unified"}
                     diffStyle={props.split ? "split" : "unified"}
                     before={{
                     before={{

+ 31 - 6
packages/ui/src/components/session-turn.tsx

@@ -2,7 +2,18 @@ import { AssistantMessage } from "@opencode-ai/sdk"
 import { useData } from "../context"
 import { useData } from "../context"
 import { Binary } from "@opencode-ai/util/binary"
 import { Binary } from "@opencode-ai/util/binary"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
-import { createEffect, createMemo, createSignal, For, Match, onMount, ParentProps, Show, Switch } from "solid-js"
+import {
+  createEffect,
+  createMemo,
+  createSignal,
+  For,
+  Match,
+  onMount,
+  ParentProps,
+  Show,
+  Switch,
+  ValidComponent,
+} from "solid-js"
 import { DiffChanges } from "./diff-changes"
 import { DiffChanges } from "./diff-changes"
 import { Typewriter } from "./typewriter"
 import { Typewriter } from "./typewriter"
 import { Message } from "./message-part"
 import { Message } from "./message-part"
@@ -11,10 +22,10 @@ import { Accordion } from "./accordion"
 import { StickyAccordionHeader } from "./sticky-accordion-header"
 import { StickyAccordionHeader } from "./sticky-accordion-header"
 import { FileIcon } from "./file-icon"
 import { FileIcon } from "./file-icon"
 import { Icon } from "./icon"
 import { Icon } from "./icon"
-import { Diff } from "./diff"
 import { Card } from "./card"
 import { Card } from "./card"
 import { MessageProgress } from "./message-progress"
 import { MessageProgress } from "./message-progress"
 import { Collapsible } from "./collapsible"
 import { Collapsible } from "./collapsible"
+import { Dynamic } from "solid-js/web"
 
 
 export function SessionTurn(
 export function SessionTurn(
   props: ParentProps<{
   props: ParentProps<{
@@ -25,6 +36,7 @@ export function SessionTurn(
       content?: string
       content?: string
       container?: string
       container?: string
     }
     }
+    diffComponent: ValidComponent
   }>,
   }>,
 ) {
 ) {
   const data = useData()
   const data = useData()
@@ -117,7 +129,7 @@ export function SessionTurn(
                   </div>
                   </div>
                 </div>
                 </div>
                 <div data-slot="session-turn-message-content">
                 <div data-slot="session-turn-message-content">
-                  <Message message={msg()} parts={parts()} sanitize={sanitizer()} />
+                  <Message message={msg()} parts={parts()} sanitize={sanitizer()} diffComponent={props.diffComponent} />
                 </div>
                 </div>
                 {/* Summary */}
                 {/* Summary */}
                 <Show when={completed()}>
                 <Show when={completed()}>
@@ -167,7 +179,8 @@ export function SessionTurn(
                               </Accordion.Trigger>
                               </Accordion.Trigger>
                             </StickyAccordionHeader>
                             </StickyAccordionHeader>
                             <Accordion.Content data-slot="session-turn-accordion-content">
                             <Accordion.Content data-slot="session-turn-accordion-content">
-                              <Diff
+                              <Dynamic
+                                component={props.diffComponent}
                                 before={{
                                 before={{
                                   name: diff.file!,
                                   name: diff.file!,
                                   contents: diff.before!,
                                   contents: diff.before!,
@@ -193,7 +206,11 @@ export function SessionTurn(
                 <div data-slot="session-turn-response-section">
                 <div data-slot="session-turn-response-section">
                   <Switch>
                   <Switch>
                     <Match when={!completed()}>
                     <Match when={!completed()}>
-                      <MessageProgress assistantMessages={assistantMessages} done={!messageWorking()} />
+                      <MessageProgress
+                        assistantMessages={assistantMessages}
+                        done={!messageWorking()}
+                        diffComponent={props.diffComponent}
+                      />
                     </Match>
                     </Match>
                     <Match when={completed() && hasToolPart()}>
                     <Match when={completed() && hasToolPart()}>
                       <Collapsible variant="ghost" open={detailsExpanded()} onOpenChange={setDetailsExpanded}>
                       <Collapsible variant="ghost" open={detailsExpanded()} onOpenChange={setDetailsExpanded}>
@@ -224,10 +241,18 @@ export function SessionTurn(
                                       message={assistantMessage}
                                       message={assistantMessage}
                                       parts={parts().filter((p) => p?.id !== last()?.id)}
                                       parts={parts().filter((p) => p?.id !== last()?.id)}
                                       sanitize={sanitizer()}
                                       sanitize={sanitizer()}
+                                      diffComponent={props.diffComponent}
                                     />
                                     />
                                   )
                                   )
                                 }
                                 }
-                                return <Message message={assistantMessage} parts={parts()} sanitize={sanitizer()} />
+                                return (
+                                  <Message
+                                    message={assistantMessage}
+                                    parts={parts()}
+                                    sanitize={sanitizer()}
+                                    diffComponent={props.diffComponent}
+                                  />
+                                )
                               }}
                               }}
                             </For>
                             </For>
                             <Show when={error()}>
                             <Show when={error()}>

+ 10 - 1
packages/ui/src/components/pierre.ts → packages/ui/src/pierre/index.ts

@@ -1,4 +1,13 @@
-import { FileDiffOptions } from "@pierre/precision-diffs"
+import { DiffLineAnnotation, FileContents, FileDiffOptions } from "@pierre/precision-diffs"
+import { ComponentProps } from "solid-js"
+
+export type DiffProps<T = {}> = FileDiffOptions<T> & {
+  before: FileContents
+  after: FileContents
+  annotations?: DiffLineAnnotation<T>[]
+  class?: string
+  classList?: ComponentProps<"div">["classList"]
+}
 
 
 export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"]) {
 export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"]) {
   return {
   return {

+ 5 - 0
packages/ui/src/pierre/worker.ts

@@ -0,0 +1,5 @@
+import ShikiWorkerUrl from "@pierre/precision-diffs/worker/shiki-worker.js?worker&url"
+
+export function workerFactory(): Worker {
+  return new Worker(ShikiWorkerUrl, { type: "module" })
+}

+ 1 - 1
packages/util/package.json

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

+ 1 - 1
packages/web/package.json

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

+ 1 - 1
sdks/vscode/package.json

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