Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
296250f1b7 | ||
|
|
443214871e | ||
|
|
1c2416b6de | ||
|
|
d86c10816d | ||
|
|
04a634a80d |
@@ -27,3 +27,4 @@ target
|
|||||||
opencode-dev
|
opencode-dev
|
||||||
logs/
|
logs/
|
||||||
*.bun-build
|
*.bun-build
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"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",
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"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",
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"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",
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -246,7 +246,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -376,7 +376,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -396,7 +396,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.90.10",
|
"@hey-api/openapi-ts": "0.90.10",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -420,7 +420,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -462,7 +462,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -473,7 +473,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"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.2.9",
|
"version": "1.2.10",
|
||||||
"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.2.9",
|
"version": "1.2.10",
|
||||||
"$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.2.9",
|
"version": "1.2.10",
|
||||||
"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",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ use crate::windows::{LoadingWindow, MainWindow};
|
|||||||
#[derive(Clone, serde::Serialize, specta::Type, Debug)]
|
#[derive(Clone, serde::Serialize, specta::Type, Debug)]
|
||||||
struct ServerReadyData {
|
struct ServerReadyData {
|
||||||
url: String,
|
url: String,
|
||||||
|
username: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
|
is_sidecar: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, serde::Serialize, specta::Type, Debug)]
|
#[derive(Clone, Copy, serde::Serialize, specta::Type, Debug)]
|
||||||
@@ -605,6 +607,7 @@ async fn initialize(app: AppHandle) {
|
|||||||
child,
|
child,
|
||||||
health_check,
|
health_check,
|
||||||
url,
|
url,
|
||||||
|
username,
|
||||||
password,
|
password,
|
||||||
} => {
|
} => {
|
||||||
let app = app.clone();
|
let app = app.clone();
|
||||||
@@ -631,7 +634,7 @@ async fn initialize(app: AppHandle) {
|
|||||||
|
|
||||||
app.state::<ServerState>().set_child(Some(child));
|
app.state::<ServerState>().set_child(Some(child));
|
||||||
|
|
||||||
Ok(ServerReadyData { url, password })
|
Ok(ServerReadyData { url, username,password, is_sidecar: true })
|
||||||
}
|
}
|
||||||
.map(move |res| {
|
.map(move |res| {
|
||||||
let _ = server_ready_tx.send(res);
|
let _ = server_ready_tx.send(res);
|
||||||
@@ -641,7 +644,9 @@ async fn initialize(app: AppHandle) {
|
|||||||
ServerConnection::Existing { url } => {
|
ServerConnection::Existing { url } => {
|
||||||
let _ = server_ready_tx.send(Ok(ServerReadyData {
|
let _ = server_ready_tx.send(Ok(ServerReadyData {
|
||||||
url: url.to_string(),
|
url: url.to_string(),
|
||||||
|
username: None,
|
||||||
password: None,
|
password: None,
|
||||||
|
is_sidecar: false,
|
||||||
}));
|
}));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -719,6 +724,7 @@ enum ServerConnection {
|
|||||||
},
|
},
|
||||||
CLI {
|
CLI {
|
||||||
url: String,
|
url: String,
|
||||||
|
username: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
child: CommandChild,
|
child: CommandChild,
|
||||||
health_check: server::HealthCheck,
|
health_check: server::HealthCheck,
|
||||||
@@ -730,12 +736,16 @@ async fn setup_server_connection(app: AppHandle) -> ServerConnection {
|
|||||||
|
|
||||||
tracing::info!(?custom_url, "Attempting server connection");
|
tracing::info!(?custom_url, "Attempting server connection");
|
||||||
|
|
||||||
if let Some(url) = custom_url
|
if let Some(url) = &custom_url
|
||||||
&& server::check_health_or_ask_retry(&app, &url).await
|
&& server::check_health_or_ask_retry(&app, url).await
|
||||||
{
|
{
|
||||||
tracing::info!(%url, "Connected to custom server");
|
tracing::info!(%url, "Connected to custom server");
|
||||||
|
// If the default server is already local, no need to also spawn a sidecar
|
||||||
|
if server::is_localhost_url(url) {
|
||||||
return ServerConnection::Existing { url: url.clone() };
|
return ServerConnection::Existing { url: url.clone() };
|
||||||
}
|
}
|
||||||
|
// Remote default server: fall through and also spawn a local sidecar
|
||||||
|
}
|
||||||
|
|
||||||
let local_port = get_sidecar_port();
|
let local_port = get_sidecar_port();
|
||||||
let hostname = "127.0.0.1";
|
let hostname = "127.0.0.1";
|
||||||
@@ -755,6 +765,7 @@ async fn setup_server_connection(app: AppHandle) -> ServerConnection {
|
|||||||
|
|
||||||
ServerConnection::CLI {
|
ServerConnection::CLI {
|
||||||
url: local_url,
|
url: local_url,
|
||||||
|
username: Some("opencode".to_string()),
|
||||||
password: Some(password),
|
password: Some(password),
|
||||||
child,
|
child,
|
||||||
health_check,
|
health_check,
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ pub async fn check_health(url: &str, password: Option<&str>) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(3));
|
let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(7));
|
||||||
|
|
||||||
if url_is_localhost(&url) {
|
if url_is_localhost(&url) {
|
||||||
// Some environments set proxy variables (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) without
|
// Some environments set proxy variables (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) without
|
||||||
@@ -178,6 +178,10 @@ pub async fn check_health(url: &str, password: Option<&str>) -> bool {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_localhost_url(url: &str) -> bool {
|
||||||
|
reqwest::Url::parse(url).is_ok_and(|u| url_is_localhost(&u))
|
||||||
|
}
|
||||||
|
|
||||||
fn url_is_localhost(url: &reqwest::Url) -> bool {
|
fn url_is_localhost(url: &reqwest::Url) -> bool {
|
||||||
url.host_str().is_some_and(|host| {
|
url.host_str().is_some_and(|host| {
|
||||||
host.eq_ignore_ascii_case("localhost")
|
host.eq_ignore_ascii_case("localhost")
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export type LoadingWindowComplete = null;
|
|||||||
|
|
||||||
export type ServerReadyData = {
|
export type ServerReadyData = {
|
||||||
url: string,
|
url: string,
|
||||||
|
username: string | null,
|
||||||
password: string | null,
|
password: string | null,
|
||||||
|
is_sidecar: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" };
|
export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" };
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { relaunch } from "@tauri-apps/plugin-process"
|
|||||||
import { open as shellOpen } from "@tauri-apps/plugin-shell"
|
import { open as shellOpen } from "@tauri-apps/plugin-shell"
|
||||||
import { Store } from "@tauri-apps/plugin-store"
|
import { Store } from "@tauri-apps/plugin-store"
|
||||||
import { check, type Update } from "@tauri-apps/plugin-updater"
|
import { check, type Update } from "@tauri-apps/plugin-updater"
|
||||||
import { type Accessor, createResource, type JSX, onCleanup, onMount, Show } from "solid-js"
|
import { createResource, type JSX, onCleanup, onMount, Show } from "solid-js"
|
||||||
import { render } from "solid-js/web"
|
import { render } from "solid-js/web"
|
||||||
import pkg from "../package.json"
|
import pkg from "../package.json"
|
||||||
import { initI18n, t } from "./i18n"
|
import { initI18n, t } from "./i18n"
|
||||||
@@ -31,7 +31,7 @@ import { UPDATER_ENABLED } from "./updater"
|
|||||||
import { webviewZoom } from "./webview-zoom"
|
import { webviewZoom } from "./webview-zoom"
|
||||||
import "./styles.css"
|
import "./styles.css"
|
||||||
import { Channel } from "@tauri-apps/api/core"
|
import { Channel } from "@tauri-apps/api/core"
|
||||||
import { commands, type InitStep } from "./bindings"
|
import { commands, ServerReadyData, type InitStep } from "./bindings"
|
||||||
import { createMenu } from "./menu"
|
import { createMenu } from "./menu"
|
||||||
|
|
||||||
const root = document.getElementById("root")
|
const root = document.getElementById("root")
|
||||||
@@ -452,16 +452,19 @@ render(() => {
|
|||||||
<AppBaseProviders>
|
<AppBaseProviders>
|
||||||
<ServerGate>
|
<ServerGate>
|
||||||
{(data) => {
|
{(data) => {
|
||||||
const server: ServerConnection.Sidecar = {
|
const http = {
|
||||||
|
url: data.url,
|
||||||
|
username: data.username ?? undefined,
|
||||||
|
password: data.password ?? undefined,
|
||||||
|
}
|
||||||
|
const server: ServerConnection.Any = data.is_sidecar
|
||||||
|
? {
|
||||||
displayName: "Local Server",
|
displayName: "Local Server",
|
||||||
type: "sidecar",
|
type: "sidecar",
|
||||||
variant: "base",
|
variant: "base",
|
||||||
http: {
|
http,
|
||||||
url: data().url,
|
|
||||||
username: "opencode",
|
|
||||||
password: data().password ?? undefined,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
: { type: "http", http }
|
||||||
|
|
||||||
function Inner() {
|
function Inner() {
|
||||||
const cmd = useCommand()
|
const cmd = useCommand()
|
||||||
@@ -485,10 +488,8 @@ render(() => {
|
|||||||
)
|
)
|
||||||
}, root!)
|
}, root!)
|
||||||
|
|
||||||
type ServerReadyData = { url: string; password: string | null }
|
|
||||||
|
|
||||||
// Gate component that waits for the server to be ready
|
// Gate component that waits for the server to be ready
|
||||||
function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.Element }) {
|
function ServerGate(props: { children: (data: ServerReadyData) => JSX.Element }) {
|
||||||
const [serverData] = createResource(() => commands.awaitInitialization(new Channel<InitStep>() as any))
|
const [serverData] = createResource(() => commands.awaitInitialization(new Channel<InitStep>() as any))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -516,7 +517,7 @@ function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(data) => props.children(data)}
|
{(data) => props.children(data())}
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"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.2.9"
|
version = "1.2.10"
|
||||||
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.2.9/opencode-darwin-arm64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.10/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.2.9/opencode-darwin-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.10/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.2.9/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.10/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.2.9/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.10/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.2.9/opencode-windows-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.10/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.2.9",
|
"version": "1.2.10",
|
||||||
"$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.2.9",
|
"version": "1.2.10",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
import path from "path"
|
|
||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { Instance } from "../../src/project/instance"
|
|
||||||
import { Session } from "../../src/session"
|
|
||||||
import { MessageV2 } from "../../src/session/message-v2"
|
|
||||||
import { SessionPrompt } from "../../src/session/prompt"
|
|
||||||
import { tmpdir } from "../fixture/fixture"
|
|
||||||
|
|
||||||
describe("session.prompt missing file", () => {
|
|
||||||
test("does not fail the prompt when a file part is missing", async () => {
|
|
||||||
await using tmp = await tmpdir({
|
|
||||||
git: true,
|
|
||||||
config: {
|
|
||||||
agent: {
|
|
||||||
build: {
|
|
||||||
model: "openai/gpt-5.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => {
|
|
||||||
const session = await Session.create({})
|
|
||||||
|
|
||||||
const missing = path.join(tmp.path, "does-not-exist.ts")
|
|
||||||
const msg = await SessionPrompt.prompt({
|
|
||||||
sessionID: session.id,
|
|
||||||
agent: "build",
|
|
||||||
noReply: true,
|
|
||||||
parts: [
|
|
||||||
{ type: "text", text: "please review @does-not-exist.ts" },
|
|
||||||
{
|
|
||||||
type: "file",
|
|
||||||
mime: "text/plain",
|
|
||||||
url: `file://${missing}`,
|
|
||||||
filename: "does-not-exist.ts",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (msg.info.role !== "user") throw new Error("expected user message")
|
|
||||||
|
|
||||||
const hasFailure = msg.parts.some(
|
|
||||||
(part) => part.type === "text" && part.synthetic && part.text.includes("Read tool failed to read"),
|
|
||||||
)
|
|
||||||
expect(hasFailure).toBe(true)
|
|
||||||
|
|
||||||
await Session.remove(session.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("keeps stored part order stable when file resolution is async", async () => {
|
|
||||||
await using tmp = await tmpdir({
|
|
||||||
git: true,
|
|
||||||
config: {
|
|
||||||
agent: {
|
|
||||||
build: {
|
|
||||||
model: "openai/gpt-5.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => {
|
|
||||||
const session = await Session.create({})
|
|
||||||
|
|
||||||
const missing = path.join(tmp.path, "still-missing.ts")
|
|
||||||
const msg = await SessionPrompt.prompt({
|
|
||||||
sessionID: session.id,
|
|
||||||
agent: "build",
|
|
||||||
noReply: true,
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
type: "file",
|
|
||||||
mime: "text/plain",
|
|
||||||
url: `file://${missing}`,
|
|
||||||
filename: "still-missing.ts",
|
|
||||||
},
|
|
||||||
{ type: "text", text: "after-file" },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (msg.info.role !== "user") throw new Error("expected user message")
|
|
||||||
|
|
||||||
const stored = await MessageV2.get({
|
|
||||||
sessionID: session.id,
|
|
||||||
messageID: msg.info.id,
|
|
||||||
})
|
|
||||||
const text = stored.parts.filter((part) => part.type === "text").map((part) => part.text)
|
|
||||||
|
|
||||||
expect(text[0]?.startsWith("Called the Read tool with the following input:")).toBe(true)
|
|
||||||
expect(text[1]?.includes("Read tool failed to read")).toBe(true)
|
|
||||||
expect(text[2]).toBe("after-file")
|
|
||||||
|
|
||||||
await Session.remove(session.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import path from "path"
|
|
||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { fileURLToPath } from "url"
|
|
||||||
import { Instance } from "../../src/project/instance"
|
|
||||||
import { Log } from "../../src/util/log"
|
|
||||||
import { Session } from "../../src/session"
|
|
||||||
import { SessionPrompt } from "../../src/session/prompt"
|
|
||||||
import { MessageV2 } from "../../src/session/message-v2"
|
|
||||||
import { tmpdir } from "../fixture/fixture"
|
|
||||||
|
|
||||||
Log.init({ print: false })
|
|
||||||
|
|
||||||
describe("session.prompt special characters", () => {
|
|
||||||
test("handles filenames with # character", async () => {
|
|
||||||
await using tmp = await tmpdir({
|
|
||||||
git: true,
|
|
||||||
init: async (dir) => {
|
|
||||||
await Bun.write(path.join(dir, "file#name.txt"), "special content\n")
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => {
|
|
||||||
const session = await Session.create({})
|
|
||||||
const template = "Read @file#name.txt"
|
|
||||||
const parts = await SessionPrompt.resolvePromptParts(template)
|
|
||||||
const fileParts = parts.filter((part) => part.type === "file")
|
|
||||||
|
|
||||||
expect(fileParts.length).toBe(1)
|
|
||||||
expect(fileParts[0].filename).toBe("file#name.txt")
|
|
||||||
|
|
||||||
// Verify the URL is properly encoded (# should be %23)
|
|
||||||
expect(fileParts[0].url).toContain("%23")
|
|
||||||
|
|
||||||
// Verify the URL can be correctly converted back to a file path
|
|
||||||
const decodedPath = fileURLToPath(fileParts[0].url)
|
|
||||||
expect(decodedPath).toBe(path.join(tmp.path, "file#name.txt"))
|
|
||||||
|
|
||||||
const message = await SessionPrompt.prompt({
|
|
||||||
sessionID: session.id,
|
|
||||||
parts,
|
|
||||||
noReply: true,
|
|
||||||
})
|
|
||||||
const stored = await MessageV2.get({ sessionID: session.id, messageID: message.info.id })
|
|
||||||
|
|
||||||
// Verify the file content was read correctly
|
|
||||||
const textParts = stored.parts.filter((part) => part.type === "text")
|
|
||||||
const hasContent = textParts.some((part) => part.text.includes("special content"))
|
|
||||||
expect(hasContent).toBe(true)
|
|
||||||
|
|
||||||
await Session.remove(session.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { Instance } from "../../src/project/instance"
|
|
||||||
import { Session } from "../../src/session"
|
|
||||||
import { SessionPrompt } from "../../src/session/prompt"
|
|
||||||
import { tmpdir } from "../fixture/fixture"
|
|
||||||
|
|
||||||
describe("session.prompt agent variant", () => {
|
|
||||||
test("applies agent variant only when using agent model", async () => {
|
|
||||||
const prev = process.env.OPENAI_API_KEY
|
|
||||||
process.env.OPENAI_API_KEY = "test-openai-key"
|
|
||||||
|
|
||||||
try {
|
|
||||||
await using tmp = await tmpdir({
|
|
||||||
git: true,
|
|
||||||
config: {
|
|
||||||
agent: {
|
|
||||||
build: {
|
|
||||||
model: "openai/gpt-5.2",
|
|
||||||
variant: "xhigh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => {
|
|
||||||
const session = await Session.create({})
|
|
||||||
|
|
||||||
const other = await SessionPrompt.prompt({
|
|
||||||
sessionID: session.id,
|
|
||||||
agent: "build",
|
|
||||||
model: { providerID: "opencode", modelID: "kimi-k2.5-free" },
|
|
||||||
noReply: true,
|
|
||||||
parts: [{ type: "text", text: "hello" }],
|
|
||||||
})
|
|
||||||
if (other.info.role !== "user") throw new Error("expected user message")
|
|
||||||
expect(other.info.variant).toBeUndefined()
|
|
||||||
|
|
||||||
const match = await SessionPrompt.prompt({
|
|
||||||
sessionID: session.id,
|
|
||||||
agent: "build",
|
|
||||||
noReply: true,
|
|
||||||
parts: [{ type: "text", text: "hello again" }],
|
|
||||||
})
|
|
||||||
if (match.info.role !== "user") throw new Error("expected user message")
|
|
||||||
expect(match.info.model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
|
|
||||||
expect(match.info.variant).toBe("xhigh")
|
|
||||||
|
|
||||||
const override = await SessionPrompt.prompt({
|
|
||||||
sessionID: session.id,
|
|
||||||
agent: "build",
|
|
||||||
noReply: true,
|
|
||||||
variant: "high",
|
|
||||||
parts: [{ type: "text", text: "hello third" }],
|
|
||||||
})
|
|
||||||
if (override.info.role !== "user") throw new Error("expected user message")
|
|
||||||
expect(override.info.variant).toBe("high")
|
|
||||||
|
|
||||||
await Session.remove(session.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
if (prev === undefined) delete process.env.OPENAI_API_KEY
|
|
||||||
else process.env.OPENAI_API_KEY = prev
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
import path from "path"
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { fileURLToPath } from "url"
|
||||||
|
import { Instance } from "../../src/project/instance"
|
||||||
|
import { Session } from "../../src/session"
|
||||||
|
import { MessageV2 } from "../../src/session/message-v2"
|
||||||
|
import { SessionPrompt } from "../../src/session/prompt"
|
||||||
|
import { Log } from "../../src/util/log"
|
||||||
|
import { tmpdir } from "../fixture/fixture"
|
||||||
|
|
||||||
|
Log.init({ print: false })
|
||||||
|
|
||||||
|
describe("session.prompt missing file", () => {
|
||||||
|
test("does not fail the prompt when a file part is missing", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
git: true,
|
||||||
|
config: {
|
||||||
|
agent: {
|
||||||
|
build: {
|
||||||
|
model: "openai/gpt-5.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const session = await Session.create({})
|
||||||
|
|
||||||
|
const missing = path.join(tmp.path, "does-not-exist.ts")
|
||||||
|
const msg = await SessionPrompt.prompt({
|
||||||
|
sessionID: session.id,
|
||||||
|
agent: "build",
|
||||||
|
noReply: true,
|
||||||
|
parts: [
|
||||||
|
{ type: "text", text: "please review @does-not-exist.ts" },
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
mime: "text/plain",
|
||||||
|
url: `file://${missing}`,
|
||||||
|
filename: "does-not-exist.ts",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (msg.info.role !== "user") throw new Error("expected user message")
|
||||||
|
|
||||||
|
const hasFailure = msg.parts.some(
|
||||||
|
(part) => part.type === "text" && part.synthetic && part.text.includes("Read tool failed to read"),
|
||||||
|
)
|
||||||
|
expect(hasFailure).toBe(true)
|
||||||
|
|
||||||
|
await Session.remove(session.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("keeps stored part order stable when file resolution is async", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
git: true,
|
||||||
|
config: {
|
||||||
|
agent: {
|
||||||
|
build: {
|
||||||
|
model: "openai/gpt-5.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const session = await Session.create({})
|
||||||
|
|
||||||
|
const missing = path.join(tmp.path, "still-missing.ts")
|
||||||
|
const msg = await SessionPrompt.prompt({
|
||||||
|
sessionID: session.id,
|
||||||
|
agent: "build",
|
||||||
|
noReply: true,
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
mime: "text/plain",
|
||||||
|
url: `file://${missing}`,
|
||||||
|
filename: "still-missing.ts",
|
||||||
|
},
|
||||||
|
{ type: "text", text: "after-file" },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (msg.info.role !== "user") throw new Error("expected user message")
|
||||||
|
|
||||||
|
const stored = await MessageV2.get({
|
||||||
|
sessionID: session.id,
|
||||||
|
messageID: msg.info.id,
|
||||||
|
})
|
||||||
|
const text = stored.parts.filter((part) => part.type === "text").map((part) => part.text)
|
||||||
|
|
||||||
|
expect(text[0]?.startsWith("Called the Read tool with the following input:")).toBe(true)
|
||||||
|
expect(text[1]?.includes("Read tool failed to read")).toBe(true)
|
||||||
|
expect(text[2]).toBe("after-file")
|
||||||
|
|
||||||
|
await Session.remove(session.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("session.prompt special characters", () => {
|
||||||
|
test("handles filenames with # character", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
git: true,
|
||||||
|
init: async (dir) => {
|
||||||
|
await Bun.write(path.join(dir, "file#name.txt"), "special content\n")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const session = await Session.create({})
|
||||||
|
const template = "Read @file#name.txt"
|
||||||
|
const parts = await SessionPrompt.resolvePromptParts(template)
|
||||||
|
const fileParts = parts.filter((part) => part.type === "file")
|
||||||
|
|
||||||
|
expect(fileParts.length).toBe(1)
|
||||||
|
expect(fileParts[0].filename).toBe("file#name.txt")
|
||||||
|
expect(fileParts[0].url).toContain("%23")
|
||||||
|
|
||||||
|
const decodedPath = fileURLToPath(fileParts[0].url)
|
||||||
|
expect(decodedPath).toBe(path.join(tmp.path, "file#name.txt"))
|
||||||
|
|
||||||
|
const message = await SessionPrompt.prompt({
|
||||||
|
sessionID: session.id,
|
||||||
|
parts,
|
||||||
|
noReply: true,
|
||||||
|
})
|
||||||
|
const stored = await MessageV2.get({ sessionID: session.id, messageID: message.info.id })
|
||||||
|
const textParts = stored.parts.filter((part) => part.type === "text")
|
||||||
|
const hasContent = textParts.some((part) => part.text.includes("special content"))
|
||||||
|
expect(hasContent).toBe(true)
|
||||||
|
|
||||||
|
await Session.remove(session.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("session.prompt agent variant", () => {
|
||||||
|
test("applies agent variant only when using agent model", async () => {
|
||||||
|
const prev = process.env.OPENAI_API_KEY
|
||||||
|
process.env.OPENAI_API_KEY = "test-openai-key"
|
||||||
|
|
||||||
|
try {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
git: true,
|
||||||
|
config: {
|
||||||
|
agent: {
|
||||||
|
build: {
|
||||||
|
model: "openai/gpt-5.2",
|
||||||
|
variant: "xhigh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const session = await Session.create({})
|
||||||
|
|
||||||
|
const other = await SessionPrompt.prompt({
|
||||||
|
sessionID: session.id,
|
||||||
|
agent: "build",
|
||||||
|
model: { providerID: "opencode", modelID: "kimi-k2.5-free" },
|
||||||
|
noReply: true,
|
||||||
|
parts: [{ type: "text", text: "hello" }],
|
||||||
|
})
|
||||||
|
if (other.info.role !== "user") throw new Error("expected user message")
|
||||||
|
expect(other.info.variant).toBeUndefined()
|
||||||
|
|
||||||
|
const match = await SessionPrompt.prompt({
|
||||||
|
sessionID: session.id,
|
||||||
|
agent: "build",
|
||||||
|
noReply: true,
|
||||||
|
parts: [{ type: "text", text: "hello again" }],
|
||||||
|
})
|
||||||
|
if (match.info.role !== "user") throw new Error("expected user message")
|
||||||
|
expect(match.info.model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
|
||||||
|
expect(match.info.variant).toBe("xhigh")
|
||||||
|
|
||||||
|
const override = await SessionPrompt.prompt({
|
||||||
|
sessionID: session.id,
|
||||||
|
agent: "build",
|
||||||
|
noReply: true,
|
||||||
|
variant: "high",
|
||||||
|
parts: [{ type: "text", text: "hello third" }],
|
||||||
|
})
|
||||||
|
if (override.info.role !== "user") throw new Error("expected user message")
|
||||||
|
expect(override.info.variant).toBe("high")
|
||||||
|
|
||||||
|
await Session.remove(session.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
if (prev === undefined) delete process.env.OPENAI_API_KEY
|
||||||
|
else process.env.OPENAI_API_KEY = prev
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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.2.9",
|
"version": "1.2.10",
|
||||||
"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.2.9",
|
"version": "1.2.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -13,15 +13,15 @@
|
|||||||
"./client": "./src/client.ts",
|
"./client": "./src/client.ts",
|
||||||
"./server": "./src/server.ts",
|
"./server": "./src/server.ts",
|
||||||
"./v2": {
|
"./v2": {
|
||||||
"types": "./dist/src/v2/index.d.ts",
|
"types": "./dist/v2/index.d.ts",
|
||||||
"default": "./src/v2/index.ts"
|
"default": "./src/v2/index.ts"
|
||||||
},
|
},
|
||||||
"./v2/client": {
|
"./v2/client": {
|
||||||
"types": "./dist/src/v2/client.d.ts",
|
"types": "./dist/v2/client.d.ts",
|
||||||
"default": "./src/v2/client.ts"
|
"default": "./src/v2/client.ts"
|
||||||
},
|
},
|
||||||
"./v2/gen/client": {
|
"./v2/gen/client": {
|
||||||
"types": "./dist/src/v2/gen/client/index.d.ts",
|
"types": "./dist/v2/gen/client/index.d.ts",
|
||||||
"default": "./src/v2/gen/client/index.ts"
|
"default": "./src/v2/gen/client/index.ts"
|
||||||
},
|
},
|
||||||
"./v2/server": "./src/v2/server.ts"
|
"./v2/server": "./src/v2/server.ts"
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"lib": ["es2022", "dom", "dom.iterable"],
|
"lib": ["es2022", "dom", "dom.iterable"],
|
||||||
"composite": true
|
"composite": true,
|
||||||
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"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.2.9",
|
"version": "1.2.10",
|
||||||
"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",
|
||||||
|
|||||||
@@ -79,6 +79,32 @@ This creates two tools: `math_add` and `math_multiply`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### Name collisions with built-in tools
|
||||||
|
|
||||||
|
Custom tools are keyed by tool name. If a custom tool uses the same name as a built-in tool, the custom tool takes precedence.
|
||||||
|
|
||||||
|
For example, this file replaces the built-in `bash` tool:
|
||||||
|
|
||||||
|
```ts title=".opencode/tools/bash.ts"
|
||||||
|
import { tool } from "@opencode-ai/plugin"
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description: "Restricted bash wrapper",
|
||||||
|
args: {
|
||||||
|
command: tool.schema.string(),
|
||||||
|
},
|
||||||
|
async execute(args) {
|
||||||
|
return `blocked: ${args.command}`
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Prefer unique names unless you intentionally want to replace a built-in tool. If you want to disable a built in tool but not override it, use [permissions](/docs/permissions).
|
||||||
|
:::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Arguments
|
### Arguments
|
||||||
|
|
||||||
You can use `tool.schema`, which is just [Zod](https://zod.dev), to define argument types.
|
You can use `tool.schema`, which is just [Zod](https://zod.dev), to define argument types.
|
||||||
|
|||||||
@@ -308,6 +308,10 @@ The `tool` helper creates a custom tool that opencode can call. It takes a Zod s
|
|||||||
|
|
||||||
Your custom tools will be available to opencode alongside built-in tools.
|
Your custom tools will be available to opencode alongside built-in tools.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If a plugin tool uses the same name as a built-in tool, the plugin tool takes precedence.
|
||||||
|
:::
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"displayName": "opencode",
|
"displayName": "opencode",
|
||||||
"description": "opencode for VS Code",
|
"description": "opencode for VS Code",
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"publisher": "sst-dev",
|
"publisher": "sst-dev",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
Reference in New Issue
Block a user