Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb29742b57 | ||
|
|
039c60170d | ||
|
|
cd87d4f9d3 | ||
|
|
988c9894f2 | ||
|
|
ae614d919f | ||
|
|
65cde7f494 | ||
|
|
98325dcdc6 |
@@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"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",
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.64",
|
"@ai-sdk/anthropic": "3.0.64",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"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",
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop-electron": {
|
"packages/desktop-electron": {
|
||||||
"name": "@opencode-ai/desktop-electron",
|
"name": "@opencode-ai/desktop-electron",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -255,7 +255,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -284,7 +284,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -300,7 +300,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -436,7 +436,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -470,7 +470,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -485,7 +485,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -520,7 +520,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -569,7 +569,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -580,7 +580,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-r1+AehuOGIOaaxfXkQGracT/6OdFRn5Ub8s7H+MeKFY=",
|
"x86_64-linux": "sha256-85wpU1oCWbthPleNIOj5d5AOuuYZ6rM7gMLZR6YJ2WU=",
|
||||||
"aarch64-linux": "sha256-WkMSRF/ZJLyzxNBjpiMR459C9G0NVOEw31tm8roPneA=",
|
"aarch64-linux": "sha256-C3A56SDQGJquCpIRj2JhIzr4A7N4cc9lxtEjl8bXDeM=",
|
||||||
"aarch64-darwin": "sha256-Z127cxFpTl8Ml7PB3CG9TcCU08oYCPuk0FECK2MQ2CI=",
|
"aarch64-darwin": "sha256-/Ij3qhGRrcLlMfl9uEacDNnGK5URxhctuQFBW4Njrog=",
|
||||||
"x86_64-darwin": "sha256-pkRoFtnVjyl+5fm+rrFyRnEwvptxylnFxPAcEv4ZOCg="
|
"x86_64-darwin": "sha256-10sOPuN4eZ75orw4FI8ztCq1+AKS2e8aAfg3Z6Yn56w="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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.3.17",
|
"version": "1.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"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,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop-electron",
|
"name": "@opencode-ai/desktop-electron",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://opencode.ai",
|
"homepage": "https://opencode.ai",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "1.3.17"
|
version = "1.4.0"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/anomalyco/opencode"
|
repository = "https://github.com/anomalyco/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/anomalyco/opencode/releases/download/v1.3.17/opencode-darwin-arm64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.0/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/anomalyco/opencode/releases/download/v1.3.17/opencode-darwin-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.0/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/anomalyco/opencode/releases/download/v1.3.17/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.0/opencode-linux-arm64.tar.gz"
|
||||||
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/anomalyco/opencode/releases/download/v1.3.17/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.0/opencode-linux-x64.tar.gz"
|
||||||
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/anomalyco/opencode/releases/download/v1.3.17/opencode-windows-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.0/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
|||||||
import { DialogVariant } from "./dialog-variant"
|
import { DialogVariant } from "./dialog-variant"
|
||||||
import { useKeybind } from "../context/keybind"
|
import { useKeybind } from "../context/keybind"
|
||||||
import * as fuzzysort from "fuzzysort"
|
import * as fuzzysort from "fuzzysort"
|
||||||
import { consoleManagedProviderLabel } from "@tui/util/provider-origin"
|
|
||||||
|
|
||||||
export function useConnected() {
|
export function useConnected() {
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
@@ -47,11 +46,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
key: item,
|
key: item,
|
||||||
value: { providerID: provider.id, modelID: model.id },
|
value: { providerID: provider.id, modelID: model.id },
|
||||||
title: model.name ?? item.modelID,
|
title: model.name ?? item.modelID,
|
||||||
description: consoleManagedProviderLabel(
|
description: provider.name,
|
||||||
sync.data.console_state.consoleManagedProviders,
|
|
||||||
provider.id,
|
|
||||||
provider.name,
|
|
||||||
),
|
|
||||||
category,
|
category,
|
||||||
disabled: provider.id === "opencode" && model.id.includes("-nano"),
|
disabled: provider.id === "opencode" && model.id.includes("-nano"),
|
||||||
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
||||||
@@ -89,9 +84,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
description: favorites.some((item) => item.providerID === provider.id && item.modelID === model)
|
description: favorites.some((item) => item.providerID === provider.id && item.modelID === model)
|
||||||
? "(Favorite)"
|
? "(Favorite)"
|
||||||
: undefined,
|
: undefined,
|
||||||
category: connected()
|
category: connected() ? provider.name : undefined,
|
||||||
? consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, provider.id, provider.name)
|
|
||||||
: undefined,
|
|
||||||
disabled: provider.id === "opencode" && model.includes("-nano"),
|
disabled: provider.id === "opencode" && model.includes("-nano"),
|
||||||
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
||||||
onSelect() {
|
onSelect() {
|
||||||
@@ -142,7 +135,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
const title = createMemo(() => {
|
const title = createMemo(() => {
|
||||||
const value = provider()
|
const value = provider()
|
||||||
if (!value) return "Select model"
|
if (!value) return "Select model"
|
||||||
return consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, value.id, value.name)
|
return value.name
|
||||||
})
|
})
|
||||||
|
|
||||||
function onSelect(providerID: string, modelID: string) {
|
function onSelect(providerID: string, modelID: string) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { DialogModel } from "./dialog-model"
|
|||||||
import { useKeyboard } from "@opentui/solid"
|
import { useKeyboard } from "@opentui/solid"
|
||||||
import { Clipboard } from "@tui/util/clipboard"
|
import { Clipboard } from "@tui/util/clipboard"
|
||||||
import { useToast } from "../ui/toast"
|
import { useToast } from "../ui/toast"
|
||||||
import { CONSOLE_MANAGED_ICON, isConsoleManagedProvider } from "@tui/util/provider-origin"
|
import { isConsoleManagedProvider } from "@tui/util/provider-origin"
|
||||||
|
|
||||||
const PROVIDER_PRIORITY: Record<string, number> = {
|
const PROVIDER_PRIORITY: Record<string, number> = {
|
||||||
opencode: 0,
|
opencode: 0,
|
||||||
@@ -49,11 +49,7 @@ export function createDialogProviderOptions() {
|
|||||||
}[provider.id],
|
}[provider.id],
|
||||||
footer: consoleManaged ? sync.data.console_state.activeOrgName : undefined,
|
footer: consoleManaged ? sync.data.console_state.activeOrgName : undefined,
|
||||||
category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
|
category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
|
||||||
gutter: consoleManaged ? (
|
gutter: connected ? <text fg={theme.success}>✓</text> : undefined,
|
||||||
<text fg={theme.textMuted}>{CONSOLE_MANAGED_ICON}</text>
|
|
||||||
) : connected ? (
|
|
||||||
<text fg={theme.success}>✓</text>
|
|
||||||
) : undefined,
|
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
if (consoleManaged) return
|
if (consoleManaged) return
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import { useToast } from "../../ui/toast"
|
|||||||
import { useKV } from "../../context/kv"
|
import { useKV } from "../../context/kv"
|
||||||
import { useTextareaKeybindings } from "../textarea-keybindings"
|
import { useTextareaKeybindings } from "../textarea-keybindings"
|
||||||
import { DialogSkill } from "../dialog-skill"
|
import { DialogSkill } from "../dialog-skill"
|
||||||
import { CONSOLE_MANAGED_ICON, consoleManagedProviderLabel } from "@tui/util/provider-origin"
|
|
||||||
|
|
||||||
export type PromptProps = {
|
export type PromptProps = {
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
@@ -96,15 +95,8 @@ export function Prompt(props: PromptProps) {
|
|||||||
const list = createMemo(() => props.placeholders?.normal ?? [])
|
const list = createMemo(() => props.placeholders?.normal ?? [])
|
||||||
const shell = createMemo(() => props.placeholders?.shell ?? [])
|
const shell = createMemo(() => props.placeholders?.shell ?? [])
|
||||||
const [auto, setAuto] = createSignal<AutocompleteRef>()
|
const [auto, setAuto] = createSignal<AutocompleteRef>()
|
||||||
const activeOrgName = createMemo(() => sync.data.console_state.activeOrgName)
|
const currentProviderLabel = createMemo(() => local.model.parsed().provider)
|
||||||
const canSwitchOrgs = createMemo(() => sync.data.console_state.switchableOrgCount > 1)
|
const hasRightContent = createMemo(() => Boolean(props.right))
|
||||||
const currentProviderLabel = createMemo(() => {
|
|
||||||
const current = local.model.current()
|
|
||||||
const provider = local.model.parsed().provider
|
|
||||||
if (!current) return provider
|
|
||||||
return consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, current.providerID, provider)
|
|
||||||
})
|
|
||||||
const hasRightContent = createMemo(() => Boolean(props.right || activeOrgName()))
|
|
||||||
|
|
||||||
function promptModelWarning() {
|
function promptModelWarning() {
|
||||||
toast.show({
|
toast.show({
|
||||||
@@ -1120,17 +1112,6 @@ export function Prompt(props: PromptProps) {
|
|||||||
<Show when={hasRightContent()}>
|
<Show when={hasRightContent()}>
|
||||||
<box flexDirection="row" gap={1} alignItems="center">
|
<box flexDirection="row" gap={1} alignItems="center">
|
||||||
{props.right}
|
{props.right}
|
||||||
<Show when={activeOrgName()}>
|
|
||||||
<text
|
|
||||||
fg={theme.textMuted}
|
|
||||||
onMouseUp={() => {
|
|
||||||
if (!canSwitchOrgs()) return
|
|
||||||
command.trigger("console.org.switch")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{`${CONSOLE_MANAGED_ICON} ${activeOrgName()}`}
|
|
||||||
</text>
|
|
||||||
</Show>
|
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
@@ -1162,7 +1143,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
<box flexDirection="row" justifyContent="space-between">
|
<box width="100%" flexDirection="row" justifyContent="space-between">
|
||||||
<Show when={status().type !== "idle"} fallback={props.hint ?? <text />}>
|
<Show when={status().type !== "idle"} fallback={props.hint ?? <text />}>
|
||||||
<box
|
<box
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export const CONSOLE_MANAGED_ICON = "⌂"
|
|
||||||
|
|
||||||
const contains = (consoleManagedProviders: string[] | ReadonlySet<string>, providerID: string) =>
|
const contains = (consoleManagedProviders: string[] | ReadonlySet<string>, providerID: string) =>
|
||||||
Array.isArray(consoleManagedProviders)
|
Array.isArray(consoleManagedProviders)
|
||||||
? consoleManagedProviders.includes(providerID)
|
? consoleManagedProviders.includes(providerID)
|
||||||
@@ -7,14 +5,3 @@ const contains = (consoleManagedProviders: string[] | ReadonlySet<string>, provi
|
|||||||
|
|
||||||
export const isConsoleManagedProvider = (consoleManagedProviders: string[] | ReadonlySet<string>, providerID: string) =>
|
export const isConsoleManagedProvider = (consoleManagedProviders: string[] | ReadonlySet<string>, providerID: string) =>
|
||||||
contains(consoleManagedProviders, providerID)
|
contains(consoleManagedProviders, providerID)
|
||||||
|
|
||||||
export const consoleManagedProviderSuffix = (
|
|
||||||
consoleManagedProviders: string[] | ReadonlySet<string>,
|
|
||||||
providerID: string,
|
|
||||||
) => (contains(consoleManagedProviders, providerID) ? ` ${CONSOLE_MANAGED_ICON}` : "")
|
|
||||||
|
|
||||||
export const consoleManagedProviderLabel = (
|
|
||||||
consoleManagedProviders: string[] | ReadonlySet<string>,
|
|
||||||
providerID: string,
|
|
||||||
providerName: string,
|
|
||||||
) => `${providerName}${consoleManagedProviderSuffix(consoleManagedProviders, providerID)}`
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const ProviderRoutes = lazy(() =>
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(
|
schema: resolver(
|
||||||
z.object({
|
z.object({
|
||||||
all: ModelsDev.Provider.array(),
|
all: Provider.Info.array(),
|
||||||
default: z.record(z.string(), z.string()),
|
default: z.record(z.string(), z.string()),
|
||||||
connected: z.array(z.string()),
|
connected: z.array(z.string()),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -906,7 +906,7 @@ export const SessionRoutes = lazy(() =>
|
|||||||
description: "Created message",
|
description: "Created message",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(MessageV2.Assistant),
|
schema: resolver(MessageV2.WithParts),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -98,6 +98,37 @@ describe("tool.registry", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await Bun.write(
|
||||||
|
path.join(opencodeDir, "package-lock.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
name: "custom-tools",
|
||||||
|
lockfileVersion: 3,
|
||||||
|
packages: {
|
||||||
|
"": {
|
||||||
|
dependencies: {
|
||||||
|
"@opencode-ai/plugin": "^0.0.0",
|
||||||
|
cowsay: "^1.6.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const cowsayDir = path.join(opencodeDir, "node_modules", "cowsay")
|
||||||
|
await fs.mkdir(cowsayDir, { recursive: true })
|
||||||
|
await Bun.write(
|
||||||
|
path.join(cowsayDir, "package.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
name: "cowsay",
|
||||||
|
type: "module",
|
||||||
|
exports: "./index.js",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await Bun.write(
|
||||||
|
path.join(cowsayDir, "index.js"),
|
||||||
|
["export function say({ text }) {", " return `moo ${text}`", "}", ""].join("\n"),
|
||||||
|
)
|
||||||
|
|
||||||
await Bun.write(
|
await Bun.write(
|
||||||
path.join(toolsDir, "cowsay.ts"),
|
path.join(toolsDir, "cowsay.ts"),
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -17,58 +17,25 @@ const ctx = {
|
|||||||
ask: async () => {},
|
ask: async () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimerID = ReturnType<typeof setTimeout>
|
async function withFetch(fetch: (req: Request) => Response | Promise<Response>, fn: (url: URL) => Promise<void>) {
|
||||||
|
using server = Bun.serve({ port: 0, fetch })
|
||||||
async function withFetch(
|
await fn(server.url)
|
||||||
mockFetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>,
|
|
||||||
fn: () => Promise<void>,
|
|
||||||
) {
|
|
||||||
const originalFetch = globalThis.fetch
|
|
||||||
globalThis.fetch = mockFetch as unknown as typeof fetch
|
|
||||||
try {
|
|
||||||
await fn()
|
|
||||||
} finally {
|
|
||||||
globalThis.fetch = originalFetch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function withTimers(fn: (state: { ids: TimerID[]; cleared: TimerID[] }) => Promise<void>) {
|
|
||||||
const set = globalThis.setTimeout
|
|
||||||
const clear = globalThis.clearTimeout
|
|
||||||
const ids: TimerID[] = []
|
|
||||||
const cleared: TimerID[] = []
|
|
||||||
|
|
||||||
globalThis.setTimeout = ((...args: Parameters<typeof setTimeout>) => {
|
|
||||||
const id = set(...args)
|
|
||||||
ids.push(id)
|
|
||||||
return id
|
|
||||||
}) as typeof setTimeout
|
|
||||||
|
|
||||||
globalThis.clearTimeout = ((id?: TimerID) => {
|
|
||||||
if (id !== undefined) cleared.push(id)
|
|
||||||
return clear(id)
|
|
||||||
}) as typeof clearTimeout
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fn({ ids, cleared })
|
|
||||||
} finally {
|
|
||||||
ids.forEach(clear)
|
|
||||||
globalThis.setTimeout = set
|
|
||||||
globalThis.clearTimeout = clear
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("tool.webfetch", () => {
|
describe("tool.webfetch", () => {
|
||||||
test("returns image responses as file attachments", async () => {
|
test("returns image responses as file attachments", async () => {
|
||||||
const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])
|
const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])
|
||||||
await withFetch(
|
await withFetch(
|
||||||
async () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }),
|
() => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }),
|
||||||
async () => {
|
async (url) => {
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const webfetch = await WebFetchTool.init()
|
const webfetch = await WebFetchTool.init()
|
||||||
const result = await webfetch.execute({ url: "https://example.com/image.png", format: "markdown" }, ctx)
|
const result = await webfetch.execute(
|
||||||
|
{ url: new URL("/image.png", url).toString(), format: "markdown" },
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
expect(result.output).toBe("Image fetched successfully")
|
expect(result.output).toBe("Image fetched successfully")
|
||||||
expect(result.attachments).toBeDefined()
|
expect(result.attachments).toBeDefined()
|
||||||
expect(result.attachments?.length).toBe(1)
|
expect(result.attachments?.length).toBe(1)
|
||||||
@@ -87,17 +54,17 @@ describe("tool.webfetch", () => {
|
|||||||
test("keeps svg as text output", async () => {
|
test("keeps svg as text output", async () => {
|
||||||
const svg = '<svg xmlns="http://www.w3.org/2000/svg"><text>hello</text></svg>'
|
const svg = '<svg xmlns="http://www.w3.org/2000/svg"><text>hello</text></svg>'
|
||||||
await withFetch(
|
await withFetch(
|
||||||
async () =>
|
() =>
|
||||||
new Response(svg, {
|
new Response(svg, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { "content-type": "image/svg+xml; charset=UTF-8" },
|
headers: { "content-type": "image/svg+xml; charset=UTF-8" },
|
||||||
}),
|
}),
|
||||||
async () => {
|
async (url) => {
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const webfetch = await WebFetchTool.init()
|
const webfetch = await WebFetchTool.init()
|
||||||
const result = await webfetch.execute({ url: "https://example.com/image.svg", format: "html" }, ctx)
|
const result = await webfetch.execute({ url: new URL("/image.svg", url).toString(), format: "html" }, ctx)
|
||||||
expect(result.output).toContain("<svg")
|
expect(result.output).toContain("<svg")
|
||||||
expect(result.attachments).toBeUndefined()
|
expect(result.attachments).toBeUndefined()
|
||||||
},
|
},
|
||||||
@@ -108,17 +75,17 @@ describe("tool.webfetch", () => {
|
|||||||
|
|
||||||
test("keeps text responses as text output", async () => {
|
test("keeps text responses as text output", async () => {
|
||||||
await withFetch(
|
await withFetch(
|
||||||
async () =>
|
() =>
|
||||||
new Response("hello from webfetch", {
|
new Response("hello from webfetch", {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { "content-type": "text/plain; charset=utf-8" },
|
headers: { "content-type": "text/plain; charset=utf-8" },
|
||||||
}),
|
}),
|
||||||
async () => {
|
async (url) => {
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const webfetch = await WebFetchTool.init()
|
const webfetch = await WebFetchTool.init()
|
||||||
const result = await webfetch.execute({ url: "https://example.com/file.txt", format: "text" }, ctx)
|
const result = await webfetch.execute({ url: new URL("/file.txt", url).toString(), format: "text" }, ctx)
|
||||||
expect(result.output).toBe("hello from webfetch")
|
expect(result.output).toBe("hello from webfetch")
|
||||||
expect(result.attachments).toBeUndefined()
|
expect(result.attachments).toBeUndefined()
|
||||||
},
|
},
|
||||||
@@ -126,29 +93,4 @@ describe("tool.webfetch", () => {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("clears timeout when fetch rejects", async () => {
|
|
||||||
await withTimers(async ({ ids, cleared }) => {
|
|
||||||
await withFetch(
|
|
||||||
async () => {
|
|
||||||
throw new Error("boom")
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
await Instance.provide({
|
|
||||||
directory: projectRoot,
|
|
||||||
fn: async () => {
|
|
||||||
const webfetch = await WebFetchTool.init()
|
|
||||||
await expect(
|
|
||||||
webfetch.execute({ url: "https://example.com/file.txt", format: "text" }, ctx),
|
|
||||||
).rejects.toThrow("boom")
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(ids).toHaveLength(1)
|
|
||||||
expect(cleared).toHaveLength(1)
|
|
||||||
expect(cleared[0]).toBe(ids[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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.3.17",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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.3.17",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -3936,7 +3936,10 @@ export type SessionShellResponses = {
|
|||||||
/**
|
/**
|
||||||
* Created message
|
* Created message
|
||||||
*/
|
*/
|
||||||
200: AssistantMessage
|
200: {
|
||||||
|
info: Message
|
||||||
|
parts: Array<Part>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionShellResponse = SessionShellResponses[keyof SessionShellResponses]
|
export type SessionShellResponse = SessionShellResponses[keyof SessionShellResponses]
|
||||||
@@ -4212,68 +4215,7 @@ export type ProviderListResponses = {
|
|||||||
* List of providers
|
* List of providers
|
||||||
*/
|
*/
|
||||||
200: {
|
200: {
|
||||||
all: Array<{
|
all: Array<Provider>
|
||||||
api?: string
|
|
||||||
name: string
|
|
||||||
env: Array<string>
|
|
||||||
id: string
|
|
||||||
npm?: string
|
|
||||||
models: {
|
|
||||||
[key: string]: {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
family?: string
|
|
||||||
release_date: string
|
|
||||||
attachment: boolean
|
|
||||||
reasoning: boolean
|
|
||||||
temperature: boolean
|
|
||||||
tool_call: boolean
|
|
||||||
interleaved?:
|
|
||||||
| true
|
|
||||||
| {
|
|
||||||
field: "reasoning_content" | "reasoning_details"
|
|
||||||
}
|
|
||||||
cost?: {
|
|
||||||
input: number
|
|
||||||
output: number
|
|
||||||
cache_read?: number
|
|
||||||
cache_write?: number
|
|
||||||
context_over_200k?: {
|
|
||||||
input: number
|
|
||||||
output: number
|
|
||||||
cache_read?: number
|
|
||||||
cache_write?: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
limit: {
|
|
||||||
context: number
|
|
||||||
input?: number
|
|
||||||
output: number
|
|
||||||
}
|
|
||||||
modalities?: {
|
|
||||||
input: Array<"text" | "audio" | "image" | "video" | "pdf">
|
|
||||||
output: Array<"text" | "audio" | "image" | "video" | "pdf">
|
|
||||||
}
|
|
||||||
experimental?: boolean
|
|
||||||
status?: "alpha" | "beta" | "deprecated"
|
|
||||||
options: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
headers?: {
|
|
||||||
[key: string]: string
|
|
||||||
}
|
|
||||||
provider?: {
|
|
||||||
npm?: string
|
|
||||||
api?: string
|
|
||||||
}
|
|
||||||
variants?: {
|
|
||||||
[key: string]: {
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
default: {
|
default: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-206
@@ -4098,7 +4098,19 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/AssistantMessage"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"info": {
|
||||||
|
"$ref": "#/components/schemas/Message"
|
||||||
|
},
|
||||||
|
"parts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Part"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["info", "parts"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4790,211 +4802,7 @@
|
|||||||
"all": {
|
"all": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"$ref": "#/components/schemas/Provider"
|
||||||
"properties": {
|
|
||||||
"api": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"npm": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"models": {
|
|
||||||
"type": "object",
|
|
||||||
"propertyNames": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"family": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"release_date": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"attachment": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"reasoning": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"temperature": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"tool_call": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"interleaved": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"const": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"field": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["reasoning_content", "reasoning_details"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["field"],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cost": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"input": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"output": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"cache_read": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"cache_write": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"context_over_200k": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"input": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"output": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"cache_read": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"cache_write": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["input", "output"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["input", "output"]
|
|
||||||
},
|
|
||||||
"limit": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"context": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"input": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"output": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["context", "output"]
|
|
||||||
},
|
|
||||||
"modalities": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"input": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["text", "audio", "image", "video", "pdf"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"output": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["text", "audio", "image", "video", "pdf"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["input", "output"]
|
|
||||||
},
|
|
||||||
"experimental": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["alpha", "beta", "deprecated"]
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"type": "object",
|
|
||||||
"propertyNames": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"additionalProperties": {}
|
|
||||||
},
|
|
||||||
"headers": {
|
|
||||||
"type": "object",
|
|
||||||
"propertyNames": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"provider": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"npm": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"api": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"variants": {
|
|
||||||
"type": "object",
|
|
||||||
"propertyNames": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "object",
|
|
||||||
"propertyNames": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"additionalProperties": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"release_date",
|
|
||||||
"attachment",
|
|
||||||
"reasoning",
|
|
||||||
"temperature",
|
|
||||||
"tool_call",
|
|
||||||
"limit",
|
|
||||||
"options"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["name", "env", "id", "models"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DIFFS_TAG_NAME, FileDiff, VirtualizedFileDiff } from "@pierre/diffs"
|
import { DIFFS_TAG_NAME, FileDiff } from "@pierre/diffs"
|
||||||
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||||
import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
|
import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
|
||||||
import { Dynamic, isServer } from "solid-js/web"
|
import { Dynamic, isServer } from "solid-js/web"
|
||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
notifyShadowReady,
|
notifyShadowReady,
|
||||||
observeViewerScheme,
|
observeViewerScheme,
|
||||||
} from "../pierre/file-runtime"
|
} from "../pierre/file-runtime"
|
||||||
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
|
||||||
import { File, type DiffFileProps, type FileProps } from "./file"
|
import { File, type DiffFileProps, type FileProps } from "./file"
|
||||||
|
|
||||||
type DiffPreload<T> = PreloadMultiFileDiffResult<T> | PreloadFileDiffResult<T>
|
type DiffPreload<T> = PreloadMultiFileDiffResult<T> | PreloadFileDiffResult<T>
|
||||||
@@ -26,7 +25,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
let container!: HTMLDivElement
|
let container!: HTMLDivElement
|
||||||
let fileDiffRef!: HTMLElement
|
let fileDiffRef!: HTMLElement
|
||||||
let fileDiffInstance: FileDiff<T> | undefined
|
let fileDiffInstance: FileDiff<T> | undefined
|
||||||
let sharedVirtualizer: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
|
|
||||||
|
|
||||||
const ready = createReadyWatcher()
|
const ready = createReadyWatcher()
|
||||||
const workerPool = useWorkerPool(props.diffStyle)
|
const workerPool = useWorkerPool(props.diffStyle)
|
||||||
@@ -51,14 +49,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
|
|
||||||
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
|
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
|
||||||
|
|
||||||
const getVirtualizer = () => {
|
|
||||||
if (sharedVirtualizer) return sharedVirtualizer.virtualizer
|
|
||||||
const result = acquireVirtualizer(container)
|
|
||||||
if (!result) return
|
|
||||||
sharedVirtualizer = result
|
|
||||||
return result.virtualizer
|
|
||||||
}
|
|
||||||
|
|
||||||
const setSelectedLines = (range: DiffFileProps<T>["selectedLines"], attempt = 0) => {
|
const setSelectedLines = (range: DiffFileProps<T>["selectedLines"], attempt = 0) => {
|
||||||
const diff = fileDiffInstance
|
const diff = fileDiffInstance
|
||||||
if (!diff) return
|
if (!diff) return
|
||||||
@@ -92,20 +82,8 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
|
|
||||||
onCleanup(observeViewerScheme(() => fileDiffRef))
|
onCleanup(observeViewerScheme(() => fileDiffRef))
|
||||||
|
|
||||||
const virtualizer = getVirtualizer()
|
|
||||||
const annotations = local.annotations ?? local.preloadedDiff.annotations ?? []
|
const annotations = local.annotations ?? local.preloadedDiff.annotations ?? []
|
||||||
fileDiffInstance = virtualizer
|
fileDiffInstance = new FileDiff<T>(
|
||||||
? new VirtualizedFileDiff<T>(
|
|
||||||
{
|
|
||||||
...createDefaultOptions(props.diffStyle),
|
|
||||||
...others,
|
|
||||||
...(local.preloadedDiff.options ?? {}),
|
|
||||||
},
|
|
||||||
virtualizer,
|
|
||||||
virtualMetrics,
|
|
||||||
workerPool,
|
|
||||||
)
|
|
||||||
: new FileDiff<T>(
|
|
||||||
{
|
{
|
||||||
...createDefaultOptions(props.diffStyle),
|
...createDefaultOptions(props.diffStyle),
|
||||||
...others,
|
...others,
|
||||||
@@ -163,8 +141,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
clearReadyWatcher(ready)
|
clearReadyWatcher(ready)
|
||||||
fileDiffInstance?.cleanUp()
|
fileDiffInstance?.cleanUp()
|
||||||
sharedVirtualizer?.release()
|
|
||||||
sharedVirtualizer = undefined
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { sampledChecksum } from "@opencode-ai/util/encode"
|
import { sampledChecksum } from "@opencode-ai/util/encode"
|
||||||
import {
|
import {
|
||||||
DEFAULT_VIRTUAL_FILE_METRICS,
|
|
||||||
type DiffLineAnnotation,
|
type DiffLineAnnotation,
|
||||||
type FileContents,
|
type FileContents,
|
||||||
type FileDiffMetadata,
|
type FileDiffMetadata,
|
||||||
@@ -10,10 +9,6 @@ import {
|
|||||||
type FileOptions,
|
type FileOptions,
|
||||||
type LineAnnotation,
|
type LineAnnotation,
|
||||||
type SelectedLineRange,
|
type SelectedLineRange,
|
||||||
type VirtualFileMetrics,
|
|
||||||
VirtualizedFile,
|
|
||||||
VirtualizedFileDiff,
|
|
||||||
Virtualizer,
|
|
||||||
} from "@pierre/diffs"
|
} from "@pierre/diffs"
|
||||||
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
@@ -40,19 +35,10 @@ import {
|
|||||||
readShadowLineSelection,
|
readShadowLineSelection,
|
||||||
} from "../pierre/file-selection"
|
} from "../pierre/file-selection"
|
||||||
import { createLineNumberSelectionBridge, restoreShadowTextSelection } from "../pierre/selection-bridge"
|
import { createLineNumberSelectionBridge, restoreShadowTextSelection } from "../pierre/selection-bridge"
|
||||||
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
|
||||||
import { getWorkerPool } from "../pierre/worker"
|
import { getWorkerPool } from "../pierre/worker"
|
||||||
import { FileMedia, type FileMediaOptions } from "./file-media"
|
import { FileMedia, type FileMediaOptions } from "./file-media"
|
||||||
import { FileSearchBar } from "./file-search"
|
import { FileSearchBar } from "./file-search"
|
||||||
|
|
||||||
const VIRTUALIZE_BYTES = 500_000
|
|
||||||
|
|
||||||
const codeMetrics = {
|
|
||||||
...DEFAULT_VIRTUAL_FILE_METRICS,
|
|
||||||
lineHeight: 24,
|
|
||||||
fileGap: 0,
|
|
||||||
} satisfies Partial<VirtualFileMetrics>
|
|
||||||
|
|
||||||
type SharedProps<T> = {
|
type SharedProps<T> = {
|
||||||
annotations?: LineAnnotation<T>[] | DiffLineAnnotation<T>[]
|
annotations?: LineAnnotation<T>[] | DiffLineAnnotation<T>[]
|
||||||
selectedLines?: SelectedLineRange | null
|
selectedLines?: SelectedLineRange | null
|
||||||
@@ -386,11 +372,6 @@ type AnnotationTarget<A> = {
|
|||||||
rerender: () => void
|
rerender: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualStrategy = {
|
|
||||||
get: () => Virtualizer | undefined
|
|
||||||
cleanup: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function useModeViewer(config: ModeConfig, adapter: ModeAdapter) {
|
function useModeViewer(config: ModeConfig, adapter: ModeAdapter) {
|
||||||
return useFileViewer({
|
return useFileViewer({
|
||||||
enableLineSelection: config.enableLineSelection,
|
enableLineSelection: config.enableLineSelection,
|
||||||
@@ -532,64 +513,6 @@ function scrollParent(el: HTMLElement): HTMLElement | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLocalVirtualStrategy(host: () => HTMLDivElement | undefined, enabled: () => boolean): VirtualStrategy {
|
|
||||||
let virtualizer: Virtualizer | undefined
|
|
||||||
let root: Document | HTMLElement | undefined
|
|
||||||
|
|
||||||
const release = () => {
|
|
||||||
virtualizer?.cleanUp()
|
|
||||||
virtualizer = undefined
|
|
||||||
root = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: () => {
|
|
||||||
if (!enabled()) {
|
|
||||||
release()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (typeof document === "undefined") return
|
|
||||||
|
|
||||||
const wrapper = host()
|
|
||||||
if (!wrapper) return
|
|
||||||
|
|
||||||
const next = scrollParent(wrapper) ?? document
|
|
||||||
if (virtualizer && root === next) return virtualizer
|
|
||||||
|
|
||||||
release()
|
|
||||||
virtualizer = new Virtualizer()
|
|
||||||
root = next
|
|
||||||
virtualizer.setup(next, next instanceof Document ? undefined : wrapper)
|
|
||||||
return virtualizer
|
|
||||||
},
|
|
||||||
cleanup: release,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSharedVirtualStrategy(host: () => HTMLDivElement | undefined): VirtualStrategy {
|
|
||||||
let shared: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
|
|
||||||
|
|
||||||
const release = () => {
|
|
||||||
shared?.release()
|
|
||||||
shared = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: () => {
|
|
||||||
if (shared) return shared.virtualizer
|
|
||||||
|
|
||||||
const container = host()
|
|
||||||
if (!container) return
|
|
||||||
|
|
||||||
const result = acquireVirtualizer(container)
|
|
||||||
if (!result) return
|
|
||||||
shared = result
|
|
||||||
return result.virtualizer
|
|
||||||
},
|
|
||||||
cleanup: release,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseLine(node: HTMLElement) {
|
function parseLine(node: HTMLElement) {
|
||||||
if (!node.dataset.line) return
|
if (!node.dataset.line) return
|
||||||
const value = parseInt(node.dataset.line, 10)
|
const value = parseInt(node.dataset.line, 10)
|
||||||
@@ -688,7 +611,7 @@ function ViewerShell(props: {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function TextViewer<T>(props: TextFileProps<T>) {
|
function TextViewer<T>(props: TextFileProps<T>) {
|
||||||
let instance: PierreFile<T> | VirtualizedFile<T> | undefined
|
let instance: PierreFile<T> | undefined
|
||||||
let viewer!: Viewer
|
let viewer!: Viewer
|
||||||
|
|
||||||
const [local, others] = splitProps(props, textKeys)
|
const [local, others] = splitProps(props, textKeys)
|
||||||
@@ -707,34 +630,12 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
return Math.max(1, total)
|
return Math.max(1, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = createMemo(() => {
|
|
||||||
const value = local.file.contents as unknown
|
|
||||||
if (typeof value === "string") return value.length
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.reduce(
|
|
||||||
(sum, part) => sum + (typeof part === "string" ? part.length + 1 : String(part).length + 1),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (value == null) return 0
|
|
||||||
return String(value).length
|
|
||||||
})
|
|
||||||
|
|
||||||
const virtual = createMemo(() => bytes() > VIRTUALIZE_BYTES)
|
|
||||||
|
|
||||||
const virtuals = createLocalVirtualStrategy(() => viewer.wrapper, virtual)
|
|
||||||
|
|
||||||
const lineFromMouseEvent = (event: MouseEvent): MouseHit => mouseHit(event, parseLine)
|
const lineFromMouseEvent = (event: MouseEvent): MouseHit => mouseHit(event, parseLine)
|
||||||
|
|
||||||
const applySelection = (range: SelectedLineRange | null) => {
|
const applySelection = (range: SelectedLineRange | null) => {
|
||||||
const current = instance
|
const current = instance
|
||||||
if (!current) return false
|
if (!current) return false
|
||||||
|
|
||||||
if (virtual()) {
|
|
||||||
current.setSelectedLines(range)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = viewer.getRoot()
|
const root = viewer.getRoot()
|
||||||
if (!root) return false
|
if (!root) return false
|
||||||
|
|
||||||
@@ -833,10 +734,7 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
const notify = () => {
|
const notify = () => {
|
||||||
notifyRendered({
|
notifyRendered({
|
||||||
viewer,
|
viewer,
|
||||||
isReady: (root) => {
|
isReady: (root) => root.querySelectorAll("[data-line]").length >= lineCount(),
|
||||||
if (virtual()) return root.querySelector("[data-line]") != null
|
|
||||||
return root.querySelectorAll("[data-line]").length >= lineCount()
|
|
||||||
},
|
|
||||||
onReady: () => {
|
onReady: () => {
|
||||||
applySelection(viewer.lastSelection)
|
applySelection(viewer.lastSelection)
|
||||||
viewer.find.refresh({ reset: true })
|
viewer.find.refresh({ reset: true })
|
||||||
@@ -855,17 +753,11 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const opts = options()
|
const opts = options()
|
||||||
const workerPool = getWorkerPool("unified")
|
const workerPool = getWorkerPool("unified")
|
||||||
const isVirtual = virtual()
|
|
||||||
|
|
||||||
const virtualizer = virtuals.get()
|
|
||||||
|
|
||||||
renderViewer({
|
renderViewer({
|
||||||
viewer,
|
viewer,
|
||||||
current: instance,
|
current: instance,
|
||||||
create: () =>
|
create: () => new PierreFile<T>(opts, workerPool),
|
||||||
isVirtual && virtualizer
|
|
||||||
? new VirtualizedFile<T>(opts, virtualizer, codeMetrics, workerPool)
|
|
||||||
: new PierreFile<T>(opts, workerPool),
|
|
||||||
assign: (value) => {
|
assign: (value) => {
|
||||||
instance = value
|
instance = value
|
||||||
},
|
},
|
||||||
@@ -892,7 +784,6 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
instance?.cleanUp()
|
instance?.cleanUp()
|
||||||
instance = undefined
|
instance = undefined
|
||||||
virtuals.cleanup()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return <ViewerShell mode="text" viewer={viewer} class={local.class} classList={local.classList} />
|
return <ViewerShell mode="text" viewer={viewer} class={local.class} classList={local.classList} />
|
||||||
@@ -988,8 +879,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
adapter,
|
adapter,
|
||||||
)
|
)
|
||||||
|
|
||||||
const virtuals = createSharedVirtualStrategy(() => viewer.container)
|
|
||||||
|
|
||||||
const large = createMemo(() => {
|
const large = createMemo(() => {
|
||||||
if (local.fileDiff) {
|
if (local.fileDiff) {
|
||||||
const before = local.fileDiff.deletionLines.join("")
|
const before = local.fileDiff.deletionLines.join("")
|
||||||
@@ -1052,7 +941,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const opts = options()
|
const opts = options()
|
||||||
const workerPool = large() ? getWorkerPool("unified") : getWorkerPool(props.diffStyle)
|
const workerPool = large() ? getWorkerPool("unified") : getWorkerPool(props.diffStyle)
|
||||||
const virtualizer = virtuals.get()
|
|
||||||
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
|
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
|
||||||
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
|
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
|
||||||
const done = preserve(viewer)
|
const done = preserve(viewer)
|
||||||
@@ -1067,10 +955,7 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
renderViewer({
|
renderViewer({
|
||||||
viewer,
|
viewer,
|
||||||
current: instance,
|
current: instance,
|
||||||
create: () =>
|
create: () => new FileDiff<T>(opts, workerPool),
|
||||||
virtualizer
|
|
||||||
? new VirtualizedFileDiff<T>(opts, virtualizer, virtualMetrics, workerPool)
|
|
||||||
: new FileDiff<T>(opts, workerPool),
|
|
||||||
assign: (value) => {
|
assign: (value) => {
|
||||||
instance = value
|
instance = value
|
||||||
},
|
},
|
||||||
@@ -1108,7 +993,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
instance?.cleanUp()
|
instance?.cleanUp()
|
||||||
instance = undefined
|
instance = undefined
|
||||||
virtuals.cleanup()
|
|
||||||
dragSide = undefined
|
dragSide = undefined
|
||||||
dragEndSide = undefined
|
dragEndSide = undefined
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import type { LineCommentEditorProps } from "./line-comment"
|
|||||||
import { normalize, text, type ViewDiff } from "./session-diff"
|
import { normalize, text, type ViewDiff } from "./session-diff"
|
||||||
|
|
||||||
const MAX_DIFF_CHANGED_LINES = 500
|
const MAX_DIFF_CHANGED_LINES = 500
|
||||||
const REVIEW_MOUNT_MARGIN = 300
|
|
||||||
|
|
||||||
export type SessionReviewDiffStyle = "unified" | "split"
|
export type SessionReviewDiffStyle = "unified" | "split"
|
||||||
|
|
||||||
@@ -139,14 +138,11 @@ type SessionReviewSelection = {
|
|||||||
export const SessionReview = (props: SessionReviewProps) => {
|
export const SessionReview = (props: SessionReviewProps) => {
|
||||||
let scroll: HTMLDivElement | undefined
|
let scroll: HTMLDivElement | undefined
|
||||||
let focusToken = 0
|
let focusToken = 0
|
||||||
let frame: number | undefined
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const fileComponent = useFileComponent()
|
const fileComponent = useFileComponent()
|
||||||
const anchors = new Map<string, HTMLElement>()
|
const anchors = new Map<string, HTMLElement>()
|
||||||
const nodes = new Map<string, HTMLDivElement>()
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
open: [] as string[],
|
open: [] as string[],
|
||||||
visible: {} as Record<string, boolean>,
|
|
||||||
force: {} as Record<string, boolean>,
|
force: {} as Record<string, boolean>,
|
||||||
selection: null as SessionReviewSelection | null,
|
selection: null as SessionReviewSelection | null,
|
||||||
commenting: null as SessionReviewSelection | null,
|
commenting: null as SessionReviewSelection | null,
|
||||||
@@ -174,44 +170,7 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified")
|
const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified")
|
||||||
const hasDiffs = () => files().length > 0
|
const hasDiffs = () => files().length > 0
|
||||||
|
|
||||||
const syncVisible = () => {
|
|
||||||
frame = undefined
|
|
||||||
if (!scroll) return
|
|
||||||
|
|
||||||
const root = scroll.getBoundingClientRect()
|
|
||||||
const top = root.top - REVIEW_MOUNT_MARGIN
|
|
||||||
const bottom = root.bottom + REVIEW_MOUNT_MARGIN
|
|
||||||
const openSet = new Set(open())
|
|
||||||
const next: Record<string, boolean> = {}
|
|
||||||
|
|
||||||
for (const [file, el] of nodes) {
|
|
||||||
if (!openSet.has(file)) continue
|
|
||||||
const rect = el.getBoundingClientRect()
|
|
||||||
if (rect.bottom < top || rect.top > bottom) continue
|
|
||||||
next[file] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const prev = untrack(() => store.visible)
|
|
||||||
const prevKeys = Object.keys(prev)
|
|
||||||
const nextKeys = Object.keys(next)
|
|
||||||
if (prevKeys.length === nextKeys.length && nextKeys.every((file) => prev[file])) return
|
|
||||||
setStore("visible", next)
|
|
||||||
}
|
|
||||||
|
|
||||||
const queue = () => {
|
|
||||||
if (frame !== undefined) return
|
|
||||||
frame = requestAnimationFrame(syncVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pinned = (file: string) =>
|
|
||||||
props.focusedComment?.file === file ||
|
|
||||||
props.focusedFile === file ||
|
|
||||||
selection()?.file === file ||
|
|
||||||
commenting()?.file === file ||
|
|
||||||
opened()?.file === file
|
|
||||||
|
|
||||||
const handleScroll: JSX.EventHandler<HTMLDivElement, Event> = (event) => {
|
const handleScroll: JSX.EventHandler<HTMLDivElement, Event> = (event) => {
|
||||||
queue()
|
|
||||||
const next = props.onScroll
|
const next = props.onScroll
|
||||||
if (!next) return
|
if (!next) return
|
||||||
if (Array.isArray(next)) {
|
if (Array.isArray(next)) {
|
||||||
@@ -222,21 +181,9 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
;(next as JSX.EventHandler<HTMLDivElement, Event>)(event)
|
;(next as JSX.EventHandler<HTMLDivElement, Event>)(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (frame === undefined) return
|
|
||||||
cancelAnimationFrame(frame)
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
props.open
|
|
||||||
files()
|
|
||||||
queue()
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleChange = (next: string[]) => {
|
const handleChange = (next: string[]) => {
|
||||||
props.onOpenChange?.(next)
|
props.onOpenChange?.(next)
|
||||||
if (props.open === undefined) setStore("open", next)
|
if (props.open === undefined) setStore("open", next)
|
||||||
queue()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExpandOrCollapseAll = () => {
|
const handleExpandOrCollapseAll = () => {
|
||||||
@@ -350,7 +297,6 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
viewportRef={(el) => {
|
viewportRef={(el) => {
|
||||||
scroll = el
|
scroll = el
|
||||||
props.scrollRef?.(el)
|
props.scrollRef?.(el)
|
||||||
queue()
|
|
||||||
}}
|
}}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
classList={{
|
classList={{
|
||||||
@@ -363,11 +309,9 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
<Accordion multiple value={open()} onChange={handleChange}>
|
<Accordion multiple value={open()} onChange={handleChange}>
|
||||||
<For each={items()}>
|
<For each={items()}>
|
||||||
{(diff) => {
|
{(diff) => {
|
||||||
let wrapper: HTMLDivElement | undefined
|
|
||||||
const file = diff.file
|
const file = diff.file
|
||||||
|
|
||||||
const expanded = createMemo(() => open().includes(file))
|
const expanded = createMemo(() => open().includes(file))
|
||||||
const mounted = createMemo(() => expanded() && (!!store.visible[file] || pinned(file)))
|
|
||||||
const force = () => !!store.force[file]
|
const force = () => !!store.force[file]
|
||||||
|
|
||||||
const comments = createMemo(() => grouped().get(file) ?? [])
|
const comments = createMemo(() => grouped().get(file) ?? [])
|
||||||
@@ -458,8 +402,6 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
anchors.delete(file)
|
anchors.delete(file)
|
||||||
nodes.delete(file)
|
|
||||||
queue()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleLineSelected = (range: SelectedLineRange | null) => {
|
const handleLineSelected = (range: SelectedLineRange | null) => {
|
||||||
@@ -542,21 +484,11 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
<div
|
<div
|
||||||
data-slot="session-review-diff-wrapper"
|
data-slot="session-review-diff-wrapper"
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
wrapper = el
|
|
||||||
anchors.set(file, el)
|
anchors.set(file, el)
|
||||||
nodes.set(file, el)
|
|
||||||
queue()
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={expanded()}>
|
<Show when={expanded()}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={!mounted() && !tooLarge()}>
|
|
||||||
<div
|
|
||||||
data-slot="session-review-diff-placeholder"
|
|
||||||
class="rounded-lg border border-border-weak-base bg-background-stronger/40"
|
|
||||||
style={{ height: "160px" }}
|
|
||||||
/>
|
|
||||||
</Match>
|
|
||||||
<Match when={tooLarge()}>
|
<Match when={tooLarge()}>
|
||||||
<div data-slot="session-review-large-diff">
|
<div data-slot="session-review-large-diff">
|
||||||
<div data-slot="session-review-large-diff-title">
|
<div data-slot="session-review-large-diff-title">
|
||||||
|
|||||||
@@ -94,9 +94,15 @@
|
|||||||
|
|
||||||
[data-slot="session-turn-diffs-header"] {
|
[data-slot="session-turn-diffs-header"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
padding-top: 4px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
|
position: sticky;
|
||||||
|
top: var(--sticky-accordion-top, 0px);
|
||||||
|
z-index: 20;
|
||||||
|
background-color: var(--background-stronger);
|
||||||
|
height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-turn-diffs-label"] {
|
[data-slot="session-turn-diffs-label"] {
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ export function SessionTurn(
|
|||||||
<div data-component="session-turn-diffs-content">
|
<div data-component="session-turn-diffs-content">
|
||||||
<Accordion
|
<Accordion
|
||||||
multiple
|
multiple
|
||||||
style={{ "--sticky-accordion-offset": "40px" }}
|
style={{ "--sticky-accordion-offset": "44px" }}
|
||||||
value={expanded()}
|
value={expanded()}
|
||||||
onChange={(value) => setState("expanded", Array.isArray(value) ? value : value ? [value] : [])}
|
onChange={(value) => setState("expanded", Array.isArray(value) ? value : value ? [value] : [])}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
import { type VirtualFileMetrics, Virtualizer } from "@pierre/diffs"
|
|
||||||
|
|
||||||
type Target = {
|
|
||||||
key: Document | HTMLElement
|
|
||||||
root: Document | HTMLElement
|
|
||||||
content: HTMLElement | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
type Entry = {
|
|
||||||
virtualizer: Virtualizer
|
|
||||||
refs: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const cache = new WeakMap<Document | HTMLElement, Entry>()
|
|
||||||
|
|
||||||
export const virtualMetrics: Partial<VirtualFileMetrics> = {
|
|
||||||
lineHeight: 24,
|
|
||||||
hunkSeparatorHeight: 24,
|
|
||||||
fileGap: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollable(value: string) {
|
|
||||||
return value === "auto" || value === "scroll" || value === "overlay"
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollRoot(container: HTMLElement) {
|
|
||||||
let node = container.parentElement
|
|
||||||
while (node) {
|
|
||||||
const style = getComputedStyle(node)
|
|
||||||
if (scrollable(style.overflowY)) return node
|
|
||||||
node = node.parentElement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function target(container: HTMLElement): Target | undefined {
|
|
||||||
if (typeof document === "undefined") return
|
|
||||||
|
|
||||||
const review = container.closest("[data-component='session-review']")
|
|
||||||
if (review instanceof HTMLElement) {
|
|
||||||
const root = scrollRoot(container) ?? review
|
|
||||||
const content = review.querySelector("[data-slot='session-review-container']")
|
|
||||||
return {
|
|
||||||
key: review,
|
|
||||||
root,
|
|
||||||
content: content instanceof HTMLElement ? content : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = scrollRoot(container)
|
|
||||||
if (root) {
|
|
||||||
const content = root.querySelector("[role='log']")
|
|
||||||
return {
|
|
||||||
key: root,
|
|
||||||
root,
|
|
||||||
content: content instanceof HTMLElement ? content : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: document,
|
|
||||||
root: document,
|
|
||||||
content: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function acquireVirtualizer(container: HTMLElement) {
|
|
||||||
const resolved = target(container)
|
|
||||||
if (!resolved) return
|
|
||||||
|
|
||||||
let entry = cache.get(resolved.key)
|
|
||||||
if (!entry) {
|
|
||||||
const virtualizer = new Virtualizer()
|
|
||||||
virtualizer.setup(resolved.root, resolved.content)
|
|
||||||
entry = {
|
|
||||||
virtualizer,
|
|
||||||
refs: 0,
|
|
||||||
}
|
|
||||||
cache.set(resolved.key, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.refs += 1
|
|
||||||
let done = false
|
|
||||||
|
|
||||||
return {
|
|
||||||
virtualizer: entry.virtualizer,
|
|
||||||
release() {
|
|
||||||
if (done) return
|
|
||||||
done = true
|
|
||||||
|
|
||||||
const current = cache.get(resolved.key)
|
|
||||||
if (!current) return
|
|
||||||
|
|
||||||
current.refs -= 1
|
|
||||||
if (current.refs > 0) return
|
|
||||||
|
|
||||||
current.virtualizer.cleanUp()
|
|
||||||
cache.delete(resolved.key)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"displayName": "opencode",
|
"displayName": "opencode",
|
||||||
"description": "opencode for VS Code",
|
"description": "opencode for VS Code",
|
||||||
"version": "1.3.17",
|
"version": "1.4.0",
|
||||||
"publisher": "sst-dev",
|
"publisher": "sst-dev",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
Reference in New Issue
Block a user