Compare commits

..
Author SHA1 Message Date
opencode d259ae7163 sync release versions for v2.0.22 2026-10-02 04:23:05 +00:00
Kit Langton bb381e8bdd refactor(core): drop unused SessionRevert error re-export (#52661) 2026-10-02 04:14:44 +00:00
81efb7f146 fix(app): keep Working during reasoning-only turns (#51090)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-10-02 12:14:04 +08:00
Kit Langton 77c5f7a516 refactor(schema): remove unused SessionV1.WithParts (#52662) 2026-10-02 04:11:35 +00:00
Kit Langton 4a18a86653 refactor(schema): remove unused IDE event definition (#52660) 2026-10-02 04:11:15 +00:00
Jérôme BenoitandTest User c07905012f fix(nix): repair three packaging defects on the v2 branch (#51891)
Co-authored-by: Test User <test@test.com>
2026-10-01 23:09:54 -05:00
opencode-agent[bot]andrekram1-node e067948dd5 refactor(worktree): tag errors without changing the API (#52631)
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-10-01 23:09:27 -05:00
Aiden Cline 64744ce034 fix(core): explain why the provider blocked a compaction (#52664) 2026-10-01 23:07:32 -05:00
Kit Langton 840cd232a8 refactor(core): remove orphaned filesystem ignore patterns (#52658) 2026-10-02 04:05:58 +00:00
Kit Langton ce97dd2805 refactor(core): drop unused imports (#52659) 2026-10-02 04:05:40 +00:00
Kit Langton f00390665f refactor(core): remove unused aisdk text helper (#52657) 2026-10-02 04:05:05 +00:00
97 changed files with 499 additions and 734 deletions

No files matched your search

+36 -36
View File
@@ -33,7 +33,7 @@
},
"packages/ai": {
"name": "@opencode/ai",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@aws-sdk/credential-providers": "3.1057.0",
"@opencode/schema": "workspace:*",
@@ -55,7 +55,7 @@
},
"packages/app": {
"name": "@opencode/app",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0",
@@ -112,7 +112,7 @@
},
"packages/cli": {
"name": "@opencode/cli",
"version": "2.0.21",
"version": "2.0.22",
"bin": {
"opencode": "./bin/opencode.cjs",
"opencode2": "./bin/opencode2.cjs",
@@ -178,7 +178,7 @@
},
"packages/client": {
"name": "@opencode/client",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/protocol": "workspace:*",
"@opencode/schema": "workspace:*",
@@ -204,7 +204,7 @@
},
"packages/codemode": {
"name": "@opencode/codemode",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"acorn": "8.15.0",
"effect": "catalog:",
@@ -217,7 +217,7 @@
},
"packages/console/app": {
"name": "@opencode/console-app",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -253,7 +253,7 @@
},
"packages/console/core": {
"name": "@opencode/console-core",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -280,7 +280,7 @@
},
"packages/console/function": {
"name": "@opencode/console-function",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@openauthjs/openauth": "0.0.0-20250322224806",
"@opencode/console-core": "workspace:*",
@@ -297,7 +297,7 @@
},
"packages/console/mail": {
"name": "@opencode/console-mail",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -321,7 +321,7 @@
},
"packages/console/support": {
"name": "@opencode/console-support",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@opencode/console-core": "workspace:*",
@@ -341,7 +341,7 @@
},
"packages/core": {
"name": "@opencode/core",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@ai-sdk/cohere": "3.0.27",
"@ai-sdk/gateway": "3.0.104",
@@ -410,7 +410,7 @@
},
"packages/desktop": {
"name": "@opencode/desktop",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@zip.js/zip.js": "2.7.62",
"electron-context-menu": "5.0.0",
@@ -455,7 +455,7 @@
},
"packages/enterprise": {
"name": "@opencode/enterprise",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@hono/standard-validator": "catalog:",
"@opencode-ai/sdk": "1.18.21",
@@ -492,7 +492,7 @@
},
"packages/function": {
"name": "@opencode/function",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -508,7 +508,7 @@
},
"packages/gui-extensions": {
"name": "@opencode/gui-extensions",
"version": "2.0.20",
"version": "2.0.22",
"dependencies": {
"@dnd-kit/abstract": "0.5.0",
"@dnd-kit/dom": "0.5.0",
@@ -553,7 +553,7 @@
},
"packages/http-recorder": {
"name": "@opencode/http-recorder",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@effect/platform-node-shared": "4.0.0-rc.112",
},
@@ -572,7 +572,7 @@
},
"packages/httpapi-codegen": {
"name": "@opencode/httpapi-codegen",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"effect": "catalog:",
"prettier": "3.6.2",
@@ -585,7 +585,7 @@
},
"packages/latex": {
"name": "@opencode/latex",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/plugin": "workspace:*",
"@opentui/core": "catalog:",
@@ -599,7 +599,7 @@
},
"packages/merman": {
"name": "@opencode/merman",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/plugin": "workspace:*",
"@opentui/core": "catalog:",
@@ -614,7 +614,7 @@
},
"packages/plugin": {
"name": "@opencode/plugin",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@ai-sdk/provider": "3.0.8",
"@opencode/ai": "workspace:*",
@@ -653,7 +653,7 @@
},
"packages/plugin-browser": {
"name": "@opencode/plugin-browser",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/plugin": "workspace:*",
"@opencode/schema": "workspace:*",
@@ -683,7 +683,7 @@
},
"packages/protocol": {
"name": "@opencode/protocol",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/schema": "workspace:*",
"effect": "catalog:",
@@ -698,7 +698,7 @@
},
"packages/schema": {
"name": "@opencode/schema",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@standard-schema/spec": "catalog:",
"effect": "catalog:",
@@ -722,7 +722,7 @@
},
"packages/sdk": {
"name": "@opencode/sdk",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/client": "workspace:*",
"@opencode/core": "workspace:*",
@@ -743,7 +743,7 @@
},
"packages/server": {
"name": "@opencode/server",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@effect/platform-node": "catalog:",
"@effect/platform-node-shared": "catalog:",
@@ -765,7 +765,7 @@
},
"packages/session-ui": {
"name": "@opencode/session-ui",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode/client": "workspace:*",
@@ -800,7 +800,7 @@
},
"packages/simulation": {
"name": "@opencode/simulation",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/ai": "workspace:*",
"@opencode/core": "workspace:*",
@@ -820,7 +820,7 @@
},
"packages/stats/app": {
"name": "@opencode/stats-app",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@ibm/plex": "6.4.1",
"@kobalte/core": "catalog:",
@@ -854,7 +854,7 @@
},
"packages/stats/core": {
"name": "@opencode/stats-core",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0",
@@ -873,7 +873,7 @@
},
"packages/stats/server": {
"name": "@opencode/stats-server",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:",
@@ -919,7 +919,7 @@
},
"packages/theme": {
"name": "@opencode/theme",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opentui/core": "catalog:",
"effect": "catalog:",
@@ -933,7 +933,7 @@
},
"packages/tui": {
"name": "@opencode/tui",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@opencode/client": "workspace:*",
"@opencode/core": "workspace:*",
@@ -967,7 +967,7 @@
},
"packages/ui": {
"name": "@opencode/ui",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@kobalte/core": "catalog:",
"@pierre/diffs": "catalog:",
@@ -1002,7 +1002,7 @@
},
"packages/util": {
"name": "@opencode/util",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@effect/opentelemetry": "catalog:",
"@effect/platform-node": "catalog:",
@@ -1040,7 +1040,7 @@
},
"packages/web": {
"name": "@opencode/web",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -1081,7 +1081,7 @@
},
"services/update": {
"name": "@opencode/update",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"jose": "6.0.11",
"semver": "catalog:",
+22 -2
View File
@@ -13,7 +13,12 @@
opencode,
}:
let
electron = callPackage ./electron.nix { };
electronPin =
(lib.pipe ../packages/desktop/package.json [
builtins.readFile
builtins.fromJSON
]).devDependencies.electron;
electron = callPackage ./electron.nix { inherit electronPin; };
in
stdenv.mkDerivation (finalAttrs: {
pname = "opencode-desktop";
@@ -66,7 +71,7 @@ stdenv.mkDerivation (finalAttrs: {
''
# https://github.com/electron/electron/issues/31121
# mac builds use a .app bundle which doesnt have this issue
+ lib.optionalString stdenv.isLinux ''
+ lib.optionalString stdenv.hostPlatform.isLinux ''
substituteInPlace \
packages/desktop/src/main/windows/appearance.ts \
packages/desktop/src/main/service/desktop-cli.ts \
@@ -74,6 +79,7 @@ stdenv.mkDerivation (finalAttrs: {
'';
preBuild = ''
echo "electron ${electron.version} from nixpkgs ${lib.version}, package.json pins ${electronPin}"
cp -r "${electron.dist}" $HOME/.electron-dist
chmod -R u+w $HOME/.electron-dist
@@ -89,8 +95,15 @@ stdenv.mkDerivation (finalAttrs: {
export OPENCODE_CLI_DIST="$TMPDIR/desktop-cli"
cli_package=$(bun -e 'import { getCurrentCli } from "./scripts/utils.ts"; console.log(getCurrentCli().package.replace("@opencode/", ""))')
# copyBuiltCliToResources joins this dist with the npm package name getCurrentCli()
# reports, not the Nix build's name. It reads only .version from the manifest and
# writes it as opencode-cli.version beside the binary.
mkdir -p "$OPENCODE_CLI_DIST/$cli_package/bin"
cp ${lib.getExe opencode} "$OPENCODE_CLI_DIST/$cli_package/bin/opencode"
# OPENCODE_VERSION is what the bundled CLI prints for --version, so the manifest
# and the executable cannot drift.
bun -e 'await Bun.write(process.argv[1], JSON.stringify({ version: process.env.OPENCODE_VERSION }) + "\n")' \
"$OPENCODE_CLI_DIST/$cli_package/package.json"
bun run build
npx electron-builder --dir \
@@ -138,6 +151,13 @@ stdenv.mkDerivation (finalAttrs: {
"libc.musl-x86_64.so.1"
];
passthru = {
# electronVersion is what ships; electronPin is what packages/desktop/package.json
# asks for. They differ whenever nixpkgs carries no release of the pinned minor.
electronVersion = electron.version;
inherit electronPin;
};
meta = {
description = "OpenCode Desktop App";
mainProgram = "opencode-desktop";
+8 -10
View File
@@ -1,12 +1,10 @@
{ callPackage, path }:
{ lib, pkgs, electronPin }:
let
version = (builtins.fromJSON (builtins.readFile ../packages/desktop/package.json)).devDependencies.electron;
# Nixpkgs owns the release hashes, so bumping the pin no longer means editing this repo. Only the
# major is delegated, so what gets built trails the pin whenever nixpkgs has not shipped it yet.
# That is safe: the bundle ships one native addon, node-pty's Node-API prebuild, and
# only the win32 WSL runtime loads it, so nothing in the main process binds the
# Electron ABI.
major = lib.versions.major electronPin;
in
(callPackage (path + "/pkgs/development/tools/electron/binary/generic.nix") { }) version {
# Electron 42.10.1 SHASUMS256.txt; update with the desktop package version.
aarch64-linux = "20e68d6c4e47f3ebf59de7c6b1f8b8bec6a6ebda6a451132f9b465f3f13ce467";
x86_64-linux = "2452b27112d92387471fa2488aafac85d79ea3f2ee1216c0abd5150d6c12362b";
aarch64-darwin = "ac7194a3dfd81930ba35355c01620262c1254752859b42dcb8f4b9e4d174a871";
# fetchzip hashes the unpacked headers, not the release tarball.
headers = "sha256-4eUy3BZVvxTl7KUOsxio7769lL6ag/ecbeK+qLURWMI=";
}
pkgs."electron_${major}-bin" or (throw "nixpkgs ${lib.version} carries no prebuilt electron ${major}: run `nix flake update nixpkgs`, or pin a major nixpkgs still carries")
+22 -6
View File
@@ -12,7 +12,7 @@
installShellFiles,
versionCheckHook,
writableTmpDirAsHomeHook,
node_modules ? callPackage ./node-modules.nix { },
node_modules ? callPackage ./node_modules.nix { },
}:
stdenvNoCC.mkDerivation (finalAttrs: {
pname = "opencode";
@@ -85,14 +85,30 @@ stdenvNoCC.mkDerivation (finalAttrs: {
'';
postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) ''
# trick yargs into also generating zsh completions
# v2 dropped the `completion` subcommand; --completions is the global flag.
# --completions also accepts sh, which emits the same script as bash.
# staged to files, substitute below rejects anything that is not a regular file
$out/bin/opencode --completions bash > opencode.bash
$out/bin/opencode --completions zsh > _opencode
$out/bin/opencode --completions fish > opencode.fish
installShellCompletion --cmd opencode \
--bash <($out/bin/opencode completion) \
--zsh <(SHELL=/bin/zsh $out/bin/opencode completion)
--bash opencode.bash \
--fish opencode.fish \
--zsh _opencode
# OPENCODE_CLI_NAME is a build-time define, so the opencode2 copies are
# renamed rather than regenerated. --replace-fail is a global literal
# substitution, so any lowercase opencode that later appears in a
# description or help text ships as opencode2 in the opencode2 copy.
substitute opencode.bash opencode2.bash --replace-fail opencode opencode2
substitute _opencode _opencode2 --replace-fail opencode opencode2
substitute opencode.fish opencode2.fish --replace-fail opencode opencode2
installShellCompletion --cmd opencode2 \
--bash <($out/bin/opencode2 completion) \
--zsh <(SHELL=/bin/zsh $out/bin/opencode2 completion)
--bash opencode2.bash \
--fish opencode2.fish \
--zsh _opencode2
'';
nativeInstallCheckInputs = [
+1 -1
View File
@@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/package.json",
"name": "opencode",
"description": "AI-powered development tool",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"packageManager": "bun@1.4.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "2.0.21",
"version": "2.0.22",
"name": "@opencode/ai",
"type": "module",
"license": "MIT",
@@ -543,6 +543,26 @@ test.describe("Working", () => {
await expect(working).toHaveCount(0)
})
test("keeps Working instead of Used 1 Thought while reasoning is the only activity", async ({ page }) => {
const timeline = await setupTimeline(page, {
messages: [
userMessage(),
assistantMessage([reasoningPart("prt_only_thought", "Still considering the answer.")], { completed: false }),
],
settings: { timelineDetail: detailed },
})
const working = page.locator('[data-component="session-working"]')
const group = page.locator('[data-component="collapsed-tool-group"]')
await expect(working).toBeVisible()
await expect(group).toHaveCount(0)
await expect(page.getByRole("button", { name: "Used 1 Thought" })).toHaveCount(0)
await timeline.send(partUpdated(toolPart("prt_after_thought", "read", "running", { filePath: "package.json" })))
await expect(group).toBeVisible()
await expect(group).toHaveAttribute("data-timeline-part-ids", `${assistantID}:reasoning:0,prt_after_thought`)
await expect(working).toBeVisible()
})
for (const name of ["shell", "patch", "subagent"] as const) {
test(`hides Working during ${name} input and execution, then restores it on completion`, async ({ page }) => {
const timeline = await setupTimeline(page, {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/app",
"version": "2.0.21",
"version": "2.0.22",
"description": "",
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cli",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"bin": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/client",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
+93 -105
View File
@@ -447,7 +447,7 @@ export function make(options: ClientOptions) {
path: `/api/location`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -472,7 +472,7 @@ export function make(options: ClientOptions) {
path: `/api/agent`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -484,7 +484,7 @@ export function make(options: ClientOptions) {
path: `/api/agent/${encodeURIComponent(input.agentID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -498,7 +498,7 @@ export function make(options: ClientOptions) {
path: `/api/plugin`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -511,7 +511,7 @@ export function make(options: ClientOptions) {
query: { location: input?.["location"] },
body: { target: input?.["target"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -524,7 +524,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { targets: input["targets"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: true,
},
requestOptions,
@@ -655,7 +655,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/fork`,
body: { before: input["before"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -667,7 +667,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/agent`,
body: { agent: input["agent"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -679,7 +679,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/model`,
body: { model: input["model"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -691,7 +691,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}`,
body: { title: input["title"], metadata: input["metadata"], permissions: input["permissions"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -703,7 +703,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/move`,
body: { directory: input["directory"], delivery: input["delivery"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -724,7 +724,7 @@ export function make(options: ClientOptions) {
resume: input["resume"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -743,7 +743,7 @@ export function make(options: ClientOptions) {
delivery: input["delivery"],
},
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 500],
declaredStatuses: [400, 401, 404, 500],
empty: true,
},
requestOptions,
@@ -755,7 +755,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/skill`,
body: { id: input["id"], resume: input["resume"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -774,7 +774,7 @@ export function make(options: ClientOptions) {
resume: input["resume"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -786,7 +786,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/shell`,
body: { id: input["id"], command: input["command"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -798,7 +798,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`,
body: { id: input["id"], delivery: input["delivery"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -809,7 +809,7 @@ export function make(options: ClientOptions) {
method: "POST",
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/wait`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 404, 503],
empty: true,
},
requestOptions,
@@ -822,7 +822,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`,
body: { messageID: input["messageID"], files: input["files"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409, 500],
declaredStatuses: [400, 401, 404, 409, 500],
empty: false,
},
requestOptions,
@@ -833,7 +833,7 @@ export function make(options: ClientOptions) {
method: "DELETE",
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409, 500],
declaredStatuses: [400, 401, 404, 409, 500],
empty: true,
},
requestOptions,
@@ -844,7 +844,7 @@ export function make(options: ClientOptions) {
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: true,
},
requestOptions,
@@ -868,7 +868,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/diff`,
query: { from: input["from"], to: input["to"], context: input["context"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 500],
declaredStatuses: [400, 401, 404, 500],
empty: false,
},
requestOptions,
@@ -917,7 +917,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/instructions/entries`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -929,7 +929,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/instructions/entries/${encodeURIComponent(input.key)}`,
body: { value: input["value"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 413],
declaredStatuses: [400, 401, 404, 413],
empty: true,
},
requestOptions,
@@ -940,7 +940,7 @@ export function make(options: ClientOptions) {
method: "DELETE",
path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/instructions/entries/${encodeURIComponent(input.key)}`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -954,7 +954,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/generate`,
body: { prompt: input["prompt"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 404, 503],
empty: false,
},
requestOptions,
@@ -978,7 +978,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/interrupt`,
query: { resume: input["resume"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -989,7 +989,7 @@ export function make(options: ClientOptions) {
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/background`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1014,7 +1014,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1026,7 +1026,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/form`,
body: { id: input["id"], title: input["title"], metadata: input["metadata"], fields: input["fields"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: false,
},
requestOptions,
@@ -1037,7 +1037,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1049,7 +1049,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/reply`,
body: { answer: input["answer"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: true,
},
requestOptions,
@@ -1061,7 +1061,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}`,
query: { message: input["message"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404, 409],
declaredStatuses: [400, 401, 404, 409],
empty: true,
},
requestOptions,
@@ -1114,7 +1114,7 @@ export function make(options: ClientOptions) {
path: `/api/model`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1126,7 +1126,7 @@ export function make(options: ClientOptions) {
path: `/api/model/default`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1140,7 +1140,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/generate`,
body: { prompt: input["prompt"], model: input["model"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1154,7 +1154,7 @@ export function make(options: ClientOptions) {
path: `/api/provider`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -1166,7 +1166,7 @@ export function make(options: ClientOptions) {
path: `/api/provider/${encodeURIComponent(input.providerID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 404, 503],
empty: false,
},
requestOptions,
@@ -1180,7 +1180,7 @@ export function make(options: ClientOptions) {
path: `/api/integration`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1192,7 +1192,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1206,7 +1206,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { url: input["url"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1221,7 +1221,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { key: input["key"], answer: input["answer"], label: input["label"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1236,7 +1236,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { methodID: input["methodID"], answer: input["answer"], label: input["label"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1248,7 +1248,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1261,7 +1261,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { code: input["code"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1273,7 +1273,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1288,7 +1288,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { methodID: input["methodID"], label: input["label"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1300,7 +1300,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/command/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1312,7 +1312,7 @@ export function make(options: ClientOptions) {
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/command/${encodeURIComponent(input.attemptID)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1327,7 +1327,7 @@ export function make(options: ClientOptions) {
path: `/api/mcp`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1340,7 +1340,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { config: input["config"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1352,7 +1352,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1364,7 +1364,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}/connect`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1376,7 +1376,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/mcp/${encodeURIComponent(input.server)}/disconnect`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1389,7 +1389,7 @@ export function make(options: ClientOptions) {
path: `/api/mcp/resource`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1458,13 +1458,7 @@ export function make(options: ClientOptions) {
project: {
list: (requestOptions?: RequestOptions) =>
request<ProjectListOutput>(
{
method: "GET",
path: `/api/project`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
empty: false,
},
{ method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
requestOptions,
),
update: (input: ProjectUpdateInput, requestOptions?: RequestOptions) =>
@@ -1479,7 +1473,7 @@ export function make(options: ClientOptions) {
commands: input["commands"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1493,7 +1487,7 @@ export function make(options: ClientOptions) {
path: `/api/form`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1508,7 +1502,7 @@ export function make(options: ClientOptions) {
path: `/api/permission/request`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1522,7 +1516,7 @@ export function make(options: ClientOptions) {
path: `/api/permission/saved`,
query: { projectID: input?.["projectID"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1533,7 +1527,7 @@ export function make(options: ClientOptions) {
method: "DELETE",
path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1554,7 +1548,7 @@ export function make(options: ClientOptions) {
agent: input["agent"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1565,7 +1559,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1576,7 +1570,7 @@ export function make(options: ClientOptions) {
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1588,7 +1582,7 @@ export function make(options: ClientOptions) {
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}/reply`,
body: { decision: input["decision"], message: input["message"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1602,7 +1596,7 @@ export function make(options: ClientOptions) {
path: `/api/fs/read/${encodePath(input.path)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
binary: true,
},
@@ -1615,7 +1609,7 @@ export function make(options: ClientOptions) {
path: `/api/fs/list`,
query: { location: input?.["location"], path: input?.["path"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1627,7 +1621,7 @@ export function make(options: ClientOptions) {
path: `/api/fs/find`,
query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1640,7 +1634,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"], path: input["path"] },
body: input["payload"],
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
binaryBody: true,
},
@@ -1655,7 +1649,7 @@ export function make(options: ClientOptions) {
path: `/api/command`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1669,7 +1663,7 @@ export function make(options: ClientOptions) {
path: `/api/skill`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1684,7 +1678,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { input: input["input"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 500],
declaredStatuses: [400, 401, 500],
empty: false,
},
requestOptions,
@@ -1705,7 +1699,7 @@ export function make(options: ClientOptions) {
path: `/api/pty`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1724,7 +1718,7 @@ export function make(options: ClientOptions) {
env: input?.["env"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1736,7 +1730,7 @@ export function make(options: ClientOptions) {
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1749,7 +1743,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { title: input["title"], size: input["size"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1761,7 +1755,7 @@ export function make(options: ClientOptions) {
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: true,
},
requestOptions,
@@ -1915,7 +1909,7 @@ export function make(options: ClientOptions) {
path: `/api/shell`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1933,7 +1927,7 @@ export function make(options: ClientOptions) {
metadata: input["metadata"],
},
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -1945,7 +1939,7 @@ export function make(options: ClientOptions) {
path: `/api/shell/${encodeURIComponent(input.id)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1957,7 +1951,7 @@ export function make(options: ClientOptions) {
path: `/api/shell/${encodeURIComponent(input.id)}/output`,
query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
@@ -1969,7 +1963,7 @@ export function make(options: ClientOptions) {
path: `/api/shell/${encodeURIComponent(input.id)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -1983,7 +1977,7 @@ export function make(options: ClientOptions) {
path: `/api/reference`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2053,7 +2047,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2065,7 +2059,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/base`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2077,7 +2071,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/status`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2090,7 +2084,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/branch`,
query: { location: input?.["location"], search: input?.["search"], limit: input?.["limit"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
@@ -2103,7 +2097,7 @@ export function make(options: ClientOptions) {
path: `/api/vcs/diff`,
query: { location: input["location"], mode: input["mode"], base: input["base"], context: input["context"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2159,7 +2153,7 @@ export function make(options: ClientOptions) {
path: `/api/websearch/provider`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2172,7 +2166,7 @@ export function make(options: ClientOptions) {
query: { location: input["location"] },
body: { query: input["query"], providerID: input["providerID"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404, 503],
declaredStatuses: [400, 401, 503],
empty: false,
},
requestOptions,
@@ -2186,20 +2180,14 @@ export function make(options: ClientOptions) {
path: `/api/config`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
),
shells: (requestOptions?: RequestOptions) =>
request<ConfigShellsOutput>(
{
method: "GET",
path: `/api/config/shell`,
successStatus: 200,
declaredStatuses: [400, 401, 403, 404],
empty: false,
},
{ method: "GET", path: `/api/config/shell`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
requestOptions,
),
update: (input: ConfigUpdateInput, requestOptions?: RequestOptions) =>
@@ -2209,7 +2197,7 @@ export function make(options: ClientOptions) {
path: `/api/experimental/config`,
body: { shell: input["shell"] },
successStatus: 204,
declaredStatuses: [400, 401, 403, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
+2 -17
View File
@@ -2525,22 +2525,6 @@ export type UnauthorizedError = { readonly _tag: "UnauthorizedError"; readonly m
export const isUnauthorizedError = (value: unknown): value is UnauthorizedError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnauthorizedError"
export type DirectoryAccessDeniedError = {
readonly _tag: "DirectoryAccessDeniedError"
readonly directory: string
readonly message: string
}
export const isDirectoryAccessDeniedError = (value: unknown): value is DirectoryAccessDeniedError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "DirectoryAccessDeniedError"
export type DirectoryNotFoundError = {
readonly _tag: "DirectoryNotFoundError"
readonly directory: string
readonly message: string
}
export const isDirectoryNotFoundError = (value: unknown): value is DirectoryNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "DirectoryNotFoundError"
export type ServiceUnavailableError = {
readonly _tag: "ServiceUnavailableError"
readonly message: string
@@ -2751,11 +2735,12 @@ export const isShellNotFoundError = (value: unknown): value is ShellNotFoundErro
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ShellNotFoundError"
export type WorktreeError = {
readonly _tag: "WorktreeError"
readonly name: "WorktreeError"
readonly data: { readonly message: string; readonly forceRequired?: boolean | undefined }
}
export const isWorktreeError = (value: unknown): value is WorktreeError =>
typeof value === "object" && value !== null && "name" in value && value["name"] === "WorktreeError"
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "WorktreeError"
export type ServerInfoOutput = ServerInfo
+19 -15
View File
@@ -174,21 +174,25 @@ test("declared errors are thrown as Error instances that keep the body", async (
expect(error).toMatchObject({ _tag: "InvalidRequestError", kind: "integration_authorization" })
})
test("declared errors with a data envelope read the nested message", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () =>
Response.json({ name: "WorktreeError", data: { message: "Worktree directory unavailable" } }, { status: 400 }),
})
const error = await client.worktree.create({ projectID: "prj_test" }).then(
() => undefined,
(cause: unknown) => cause,
)
expect(error).toBeInstanceOf(Error)
if (!(error instanceof Error)) throw error
expect(error.message).toBe("Worktree directory unavailable")
expect(error.name).toBe("WorktreeError")
expect(error).toMatchObject({ name: "WorktreeError", data: { message: "Worktree directory unavailable" } })
test("worktree errors retain the existing envelope with or without a tag", async () => {
for (const body of [
{ name: "WorktreeError", data: { message: "Worktree directory unavailable" } },
{ _tag: "WorktreeError", name: "WorktreeError", data: { message: "Worktree directory unavailable" } },
]) {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () => Response.json(body, { status: 400 }),
})
const error = await client.worktree.create({ projectID: "prj_test" }).then(
() => undefined,
(cause: unknown) => cause,
)
expect(error).toBeInstanceOf(Error)
if (!(error instanceof Error)) throw error
expect(error.message).toBe("Worktree directory unavailable")
expect(error.name).toBe("WorktreeError")
expect(error).toMatchObject(body)
}
})
test("client errors keep the reason and describe the failure in the message", async () => {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/codemode",
"version": "2.0.21",
"version": "2.0.22",
"description": "Effect-native confined code execution over schema-described tools",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/console-app",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/console-core",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/console-function",
"version": "2.0.21",
"version": "2.0.22",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/console-mail",
"version": "2.0.21",
"version": "2.0.22",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/console-support",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "2.0.21",
"version": "2.0.22",
"name": "@opencode/core",
"type": "module",
"license": "MIT",
-4
View File
@@ -593,10 +593,6 @@ function toolMessage(input: LLMRequest["messages"][number]) {
}
}
function text(part: ContentPart) {
return part.type === "text" ? [part.text] : []
}
function userPart(part: ContentPart): UserContent {
if (part.type === "text") return [{ type: "text", text: part.text }]
if (part.type === "media")
@@ -6,7 +6,6 @@ import path from "path"
import { Effect, FiberMap, PubSub, Semaphore, Stream } from "effect"
import { Config } from "../../config.js"
import { Watcher } from "../../filesystem/watcher.js"
import { AbsolutePath } from "../../schema.js"
import { Skill } from "../../skill.js"
import { SkillFile } from "./skill-file.js"
+42 -3
View File
@@ -7,8 +7,6 @@ import { FSUtil } from "@opencode/util/fs-util"
import { Location } from "./location.js"
import { AbsolutePath, PositiveInt, RelativePath } from "./schema.js"
import { FileSystemSearch } from "./filesystem/search.js"
import { FileSystemDirectory } from "./filesystem/directory.js"
export { DirectoryNotFoundError, DirectoryAccessDeniedError } from "./filesystem/directory.js"
import { Entry, FileSystem, FindInput, Write } from "@opencode/schema/filesystem"
export { Entry, Match, Submatch } from "@opencode/schema/filesystem"
@@ -28,6 +26,30 @@ export class NotFoundError extends Schema.TaggedError<NotFoundError>()("FileSyst
path: RelativePath,
}) {}
export class DirectoryNotFoundError extends Schema.TaggedError<DirectoryNotFoundError>()(
"FileSystem.DirectoryNotFoundError",
{
directory: AbsolutePath,
cause: Schema.Defect(),
},
) {
override get message() {
return `Directory not found: ${this.directory}`
}
}
export class DirectoryAccessDeniedError extends Schema.TaggedError<DirectoryAccessDeniedError>()(
"FileSystem.DirectoryAccessDeniedError",
{
directory: AbsolutePath,
cause: Schema.Defect(),
},
) {
override get message() {
return `Access denied to directory: ${this.directory}`
}
}
export const Content = Schema.Struct({
uri: Schema.String,
name: Schema.String.pipe(Schema.optional),
@@ -89,7 +111,24 @@ const baseLayer = Layer.effect(
// configured directory as canonical; local placements keep symlink
// canonicalization. This skip is boot-only: resolve/read/list below still
// access the host filesystem per operation (tracked in #44568).
const root = location.workspaceID ? location.directory : yield* FileSystemDirectory.resolve(location.directory)
const root = location.workspaceID
? location.directory
: yield* fs.realPath(location.directory).pipe(
Effect.catch((cause): Effect.Effect<never, DirectoryNotFoundError | DirectoryAccessDeniedError> => {
if (cause.reason._tag === "NotFound")
return Effect.fail(new DirectoryNotFoundError({ directory: location.directory, cause }))
// macOS privacy denials arrive as Unknown with an EPERM cause.
if (
cause.reason._tag === "PermissionDenied" ||
(cause.reason._tag === "Unknown" &&
cause.reason.cause instanceof Error &&
"code" in cause.reason.cause &&
cause.reason.cause.code === "EPERM")
)
return Effect.fail(new DirectoryAccessDeniedError({ directory: location.directory, cause }))
return Effect.die(cause)
}),
)
const resolve = Effect.fnUntraced(function* (input?: RelativePath) {
const absolute = path.resolve(location.directory, input ?? ".")
if (!FSUtil.contains(location.directory, absolute))
-44
View File
@@ -1,44 +0,0 @@
export * as FileSystemDirectory from "./directory.js"
import { Effect, Schema } from "effect"
import { FSUtil } from "@opencode/util/fs-util"
import { AbsolutePath } from "../schema.js"
export class DirectoryNotFoundError extends Schema.TaggedError<DirectoryNotFoundError>()(
"FileSystem.DirectoryNotFoundError",
{ directory: AbsolutePath, cause: Schema.Defect() },
) {
override get message() {
return `Directory not found: ${this.directory}`
}
}
export class DirectoryAccessDeniedError extends Schema.TaggedError<DirectoryAccessDeniedError>()(
"FileSystem.DirectoryAccessDeniedError",
{ directory: AbsolutePath, cause: Schema.Defect() },
) {
override get message() {
return `Access denied to directory: ${this.directory}`
}
}
export type Error = DirectoryNotFoundError | DirectoryAccessDeniedError
export const resolve = Effect.fn("FileSystemDirectory.resolve")(function* (directory: AbsolutePath) {
const fs = yield* FSUtil.Service
return yield* fs.realPath(directory).pipe(
Effect.catch((cause): Effect.Effect<never, Error> => {
if (cause.reason._tag === "NotFound") return Effect.fail(new DirectoryNotFoundError({ directory, cause }))
// macOS privacy denials arrive as Unknown with an EPERM cause.
if (
cause.reason._tag === "PermissionDenied" ||
(cause.reason._tag === "Unknown" &&
cause.reason.cause instanceof Error &&
"code" in cause.reason.cause &&
cause.reason.cause.code === "EPERM")
)
return Effect.fail(new DirectoryAccessDeniedError({ directory, cause }))
return Effect.die(cause)
}),
)
})
-48
View File
@@ -1,48 +0,0 @@
const FOLDERS = new Set([
"node_modules",
"bower_components",
".pnpm-store",
"vendor",
".npm",
"dist",
"build",
"out",
".next",
"target",
"bin",
"obj",
".git",
".svn",
".hg",
".vscode",
".idea",
".turbo",
".output",
"desktop",
".sst",
".cache",
".webkit-cache",
"__pycache__",
".pytest_cache",
"mypy_cache",
".history",
".gradle",
])
const FILES = [
"**/*.swp",
"**/*.swo",
"**/*.pyc",
"**/.DS_Store",
"**/Thumbs.db",
"**/logs/**",
"**/tmp/**",
"**/temp/**",
"**/*.log",
"**/coverage/**",
"**/.nyc_output/**",
]
export const PATTERNS = [...FILES, ...FOLDERS, `**/{${Array.from(FOLDERS).join(",")}}/**`]
export * as Ignore from "./ignore.js"
+5 -29
View File
@@ -11,8 +11,6 @@ import { FileMutation } from "./file-mutation.js"
import { Environment } from "./environment/index.js"
import { Formatter } from "./formatter.js"
import { FileSystem } from "./filesystem.js"
import { FileSystemDirectory } from "./filesystem/directory.js"
import { FSUtil } from "@opencode/util/fs-util"
import { FileSystemSearch } from "./filesystem/search.js"
import { Generate } from "./generate.js"
import { Form } from "./form.js"
@@ -116,7 +114,7 @@ const nodes = [
export const graph = LayerNode.group(nodes)
export type Services = LayerNode.Output<typeof graph>
export type Error = FileSystemDirectory.Error
export type Error = Layer.Error<ReturnType<typeof layer>>
export interface Options {
// Plugins this instance is born with; empty and absent are equivalent.
@@ -147,7 +145,7 @@ const vanillaReplacements: LayerNode.Replacements = [
]
// One instance is one compiled, fresh copy of the graph standing on a directory.
export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Services, Error> {
export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Services> {
const startedAt = performance.now()
// Ordered: vanilla defaults, then caller replacements (which win over the
// defaults), then instance bindings (which win over everything).
@@ -158,31 +156,9 @@ export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Ser
InstancePlugins.node.replace(InstancePlugins.bound(options.plugins ?? [])),
]
return Layer.unwrap(
Effect.gen(function* () {
// Validate before project/config discovery can turn directory access failures into defects.
if (!ref.workspaceID)
yield* FileSystemDirectory.resolve(ref.directory).pipe(
Effect.provide(LayerNode.compile(FSUtil.node, { replacements: options.replacements })),
)
return LayerNode.compile(graph, { replacements, shared: Node.tags.values.global })
}),
).pipe(
// Unavailable directories are expected; other instance boot failures remain defects.
Layer.catchCause(
(cause): Layer.Layer<Services, Error> =>
Layer.unwrap(
Effect.failCause(cause).pipe(
Effect.catch(
(error): Effect.Effect<never, Error> =>
error instanceof FileSystemDirectory.DirectoryNotFoundError ||
error instanceof FileSystemDirectory.DirectoryAccessDeniedError
? Effect.fail(error)
: Effect.die(error),
),
),
),
),
return LayerNode.compile(graph, { replacements, shared: Node.tags.values.global }).pipe(
// Instance boot failures are defects; provided operations retain their typed errors.
Layer.orDie,
Layer.tap((context) => Effect.addFinalizer(() => Context.get(context, LocationLifecycle.Service).shutdown)),
Layer.tap(() =>
Effect.logInfo("location services booted", {
+2 -2
View File
@@ -5,13 +5,13 @@ import { Context, type Effect } from "effect"
import type { Session } from "@opencode/schema/session"
import { Node } from "@opencode/util/effect/app-node"
import { LayerNode } from "@opencode/util/effect/layer-node"
import type { Services, Error } from "../instance.js"
import type { Services } from "../instance.js"
/** Selects Session capabilities; implementations own caching and lifetime. */
export interface Interface {
readonly provide: (
session: Session.Info,
) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | Error, Exclude<R, Services>>
) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Services>>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/Instance") {}
+1 -1
View File
@@ -17,7 +17,7 @@ export function buildLocationServiceMap(
Effect.gen(function* () {
const owner = yield* Effect.scope
const builds = MutableHashMap.empty<Location.Ref, { close?: Effect.Effect<void> }>()
const inner: LayerMap.LayerMap<Location.Ref, LocationServices, Instance.Error> = yield* LayerMap.make(
const inner: LayerMap.LayerMap<Location.Ref, LocationServices> = yield* LayerMap.make(
(ref: Location.Ref) => {
const build: { close?: Effect.Effect<void> } = {}
MutableHashMap.set(builds, ref, build)
@@ -1,7 +1,6 @@
import { Effect } from "effect"
import { define } from "@opencode/plugin/effect/plugin"
import { Integration } from "../../integration.js"
import { Provider } from "../../provider.js"
export const LLMGatewayPlugin = define({
id: "opencode.provider.llmgateway",
+4 -13
View File
@@ -12,7 +12,6 @@ import { SessionMessage } from "./session/message.js"
import { PromptInput } from "@opencode/schema/prompt-input"
import { Bus } from "./bus.js"
import { Instance } from "./instance/service.js"
import type { FileSystemDirectory } from "./filesystem/directory.js"
import { Database } from "./database/database.js"
import { SessionProjector } from "./session/projector.js"
import { SessionMessageTable } from "./session/sql.js"
@@ -146,10 +145,7 @@ export interface Interface {
readonly from?: SessionMessage.ID
readonly to?: SessionMessage.ID
readonly context?: number
}) => Effect.Effect<
readonly FileDiff.Info[],
NotFoundError | MessageNotFoundError | TurnRangeError | Snapshot.Error | FileSystemDirectory.Error
>
}) => Effect.Effect<readonly FileDiff.Info[], NotFoundError | MessageNotFoundError | TurnRangeError | Snapshot.Error>
/**
* Durable admitted session work not yet visible in projected history,
* ordered by admission. Includes unpromoted user and synthetic inputs and
@@ -199,7 +195,7 @@ export interface Interface {
agents?: PromptInput.Prompt["agents"]
skills?: PromptInput.Prompt["skills"]
delivery?: SessionInbox.Delivery
}) => Effect.Effect<void, NotFoundError | Command.NotFoundError | Command.ExecutionError | FileSystemDirectory.Error>
}) => Effect.Effect<void, NotFoundError | Command.NotFoundError | Command.ExecutionError>
readonly shell: (
input: Parameters<Session.Handle["shell"]>[0] & { sessionID: SessionSchema.ID },
) => ReturnType<Session.Handle["shell"]>
@@ -222,13 +218,8 @@ export interface Interface {
sessionID: SessionSchema.ID
messageID: SessionMessage.ID
files?: boolean
}) => Effect.Effect<
SessionSchema.Revert,
NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error | FileSystemDirectory.Error
>
readonly clear: (
sessionID: SessionSchema.ID,
) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error | FileSystemDirectory.Error>
}) => Effect.Effect<SessionSchema.Revert, NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error>
readonly clear: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error>
readonly commit: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError>
}
}
+2 -5
View File
@@ -32,7 +32,7 @@ import { SessionModelRequest } from "./model-request.js"
import { SessionProviderContext } from "./provider-context.js"
import { SessionRunnerRetry } from "./runner/retry.js"
import { toLLMMessages } from "./runner/to-llm-message.js"
import { toSessionError } from "./to-session-error.js"
import { contentFilterError, toSessionError } from "./to-session-error.js"
import { SessionUsage } from "./usage.js"
export type Settings = {
@@ -540,10 +540,7 @@ export const layer = Layer.effect(
message: "Compaction summary reached the output token limit",
})
case "content-filter":
return unusable({
type: "provider.content-filter",
message: "Compaction summary was blocked by the provider",
})
return unusable(contentFilterError("Compaction summary was blocked by the provider", event.reason))
default:
return Effect.succeed(streamed)
}
+1 -7
View File
@@ -1,5 +1,4 @@
export * as SessionGenerate from "./generate.js"
import type { FileSystemDirectory } from "../filesystem/directory.js"
import { LLMClient, Message, type AIError } from "@opencode/ai"
import { Effect } from "effect"
@@ -15,12 +14,7 @@ import { SessionModelRequest } from "./model-request.js"
import type { SessionRunnerModel } from "./runner/model.js"
import type { SessionSchema } from "./schema.js"
export type Error =
| AgentNotFoundError
| Instructions.InitializationBlocked
| SessionRunnerModel.Error
| AIError
| FileSystemDirectory.Error
export type Error = AgentNotFoundError | Instructions.InitializationBlocked | SessionRunnerModel.Error | AIError
/** Generates text from current Session context without mutating the Session. */
export const generate = Effect.fn("SessionGenerate.generate")(function* (input: {
+2 -8
View File
@@ -1,5 +1,4 @@
export * as SessionMove from "./move.js"
import type { FileSystemDirectory } from "../filesystem/directory.js"
import type { Session } from "@opencode/schema/session"
import { makeGlobalNode } from "@opencode/util/effect/app-node"
@@ -46,11 +45,7 @@ export interface Interface {
delivery?: SessionInbox.Delivery
}) => Effect.Effect<
void,
| NotFoundError
| DestinationNotFoundError
| DestinationNotDirectoryError
| DestinationUnavailableError
| FileSystemDirectory.Error
NotFoundError | DestinationNotFoundError | DestinationNotDirectoryError | DestinationUnavailableError
>
}
@@ -96,8 +91,7 @@ const layer = Layer.effect(
yield* locations.contextEffect(destination.location).pipe(
Effect.scoped,
Effect.catchCause((cause) => {
if (Cause.hasInterruptsOnly(cause))
return Effect.failCause(Cause.fromReasons<never>(cause.reasons.filter(Cause.isInterruptReason)))
if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause)
return Effect.logWarning("session move destination unavailable", { directory, cause }).pipe(
Effect.andThen(Effect.fail(new DestinationUnavailableError({ directory }))),
)
-2
View File
@@ -13,8 +13,6 @@ import { SessionMessage } from "./message.js"
import { SessionSchema } from "./schema.js"
import { SessionMessageTable } from "./sql.js"
export { MessageNotFoundError }
interface BoundaryInput {
readonly sessionID: SessionSchema.ID
readonly messageID: SessionMessage.ID
@@ -7,7 +7,6 @@ import type { Promotable } from "../inbox.js"
import type { AgentNotFoundError, MessageDecodeError, StepFailedError } from "../error.js"
import { SessionRunnerModel } from "./model.js"
import type { Instructions } from "../../instructions/index.js"
import type { FileSystemDirectory } from "../../filesystem/directory.js"
export type RunError =
| AIError
@@ -16,7 +15,6 @@ export type RunError =
| AgentNotFoundError
| StepFailedError
| Instructions.InitializationBlocked
| FileSystemDirectory.Error
export type Continuation = { readonly step: number }
@@ -12,6 +12,7 @@ import { SessionSchema } from "../schema.js"
import { SessionError } from "@opencode/schema/session-error"
import { Money } from "@opencode/schema/money"
import { SessionUsage } from "../usage.js"
import { contentFilterError } from "../to-session-error.js"
import type { Tool } from "../../tool.js"
type Input = {
@@ -543,15 +544,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
}
if (event.reason.normalized === "content-filter") {
providerFailed = true
yield* failAssistant({
type: "provider.content-filter",
message: [
`Provider blocked the response${event.reason.category === undefined ? "" : ` (${event.reason.category})`}`,
event.reason.explanation,
]
.filter(Boolean)
.join(": "),
})
yield* failAssistant(contentFilterError("Provider blocked the response", event.reason))
return
}
return
-1
View File
@@ -15,7 +15,6 @@ import {
BusyError,
CompactionConflictError,
InboxConflictError,
MessageNotFoundError,
NotFoundError,
PromptConflictError,
SyntheticConflictError,
+10 -1
View File
@@ -1,4 +1,4 @@
import { AIError, ToolFailure } from "@opencode/ai"
import { AIError, ToolFailure, type FinishReasonDetails } from "@opencode/ai"
import { Tool } from "@opencode/schema/tool"
import { SessionError } from "@opencode/schema/session-error"
import { Permission } from "../permission.js"
@@ -85,6 +85,15 @@ export function toSessionError(cause: unknown): SessionError.Error {
return { type: "unknown", message: cause instanceof Error ? cause.message : String(cause) }
}
export function contentFilterError(summary: string, reason: FinishReasonDetails): SessionError.Error {
return {
type: "provider.content-filter",
message: [reason.category === undefined ? summary : `${summary} (${reason.category})`, reason.explanation]
.filter(Boolean)
.join(": "),
}
}
function providerError(type: string, reason: AIError["reason"]): SessionError.Error {
const status = reason.http?.status
return {
-2
View File
@@ -1,5 +1,4 @@
export * as Worktree from "./worktree.js"
import type { FileSystemDirectory } from "./filesystem/directory.js"
import { Context, Effect, Layer, Schema } from "effect"
import { and, asc, desc, eq, isNull, sql } from "drizzle-orm"
@@ -69,7 +68,6 @@ export class StrategyUnavailableError extends Schema.TaggedError<StrategyUnavail
) {}
export type Error =
| FileSystemDirectory.Error
| Project.NotFoundError
| SourceDirectoryNotFoundError
| DestinationExistsError
@@ -1,31 +0,0 @@
import { expect, test } from "bun:test"
import { Ignore } from "@opencode/core/filesystem/ignore"
// @ts-ignore
import { createWrapper } from "@parcel/watcher/wrapper.js"
test("parcel patterns ignore built-in folders at any depth", async () => {
let ignoreGlobs: string[] = []
const watcher = createWrapper({
subscribe: async (
_directory: string,
_callback: (...args: unknown[]) => unknown,
options: { ignoreGlobs?: string[] },
) => {
ignoreGlobs = options.ignoreGlobs ?? []
},
})
await watcher.subscribe("/tmp/project", () => {}, { ignore: Ignore.PATTERNS })
const patterns = ignoreGlobs.map((source) => new RegExp(source))
for (const path of [
"nested/node_modules",
"nested/node_modules/package/index.js",
"nested/.git",
"nested/.git/HEAD",
"nested/dist",
"nested/dist/index.js",
]) {
expect(patterns.some((pattern) => pattern.test(path))).toBe(true)
}
expect(patterns.some((pattern) => pattern.test("nested/src/index.ts"))).toBe(false)
})
@@ -2,7 +2,6 @@ import { Bus } from "@opencode/core/bus"
import { Image } from "@opencode/core/image"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Plugin } from "@opencode/core/plugin"
import { PluginHooks } from "@opencode/core/plugin/hooks"
import { Skill } from "@opencode/core/skill"
@@ -25,7 +24,7 @@ export const promptLocationNode = makeGlobalNode({
Bus.node.replace(Layer.succeed(Bus.Service, bus)),
Plugin.node.replace(Layer.mock(Plugin.Service, { awaitActivation: Effect.void })),
],
}) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
}) as Layer.Layer<LocationServices>,
)
}),
),
+1 -2
View File
@@ -1,5 +1,4 @@
import { describe, expect } from "bun:test"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Context, Deferred, Duration, Effect, Fiber, Layer, LayerMap, RcMap, Schema } from "effect"
import { TestClock } from "effect/testing"
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
@@ -66,7 +65,7 @@ const locations = Layer.effect(
Layer.provideMerge(Form.layer),
Layer.provide(Layer.succeed(Bus.Service, bus)),
Layer.fresh,
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
{ idleTimeToLive: Duration.infinity },
)
return {
+1 -2
View File
@@ -27,7 +27,6 @@ import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
import { LayerNode } from "@opencode/util/effect/layer-node"
import { Global } from "@opencode/util/global"
import { LocationServiceMap, type LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { LocationActivity } from "@opencode/core/location-activity"
import { Location } from "@opencode/core/location"
import { LocationWatcher } from "@opencode/core/filesystem/location-watcher"
@@ -68,7 +67,7 @@ const activityLocations = Layer.effect(
workspaceID: ref.workspaceID,
project: { id: Project.ID.global, directory: ref.directory, canonical: ref.directory },
}),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
{ idleTimeToLive: Duration.infinity },
),
)
+1 -2
View File
@@ -9,7 +9,6 @@ import { Bus } from "@opencode/core/bus"
import { Location } from "@opencode/core/location"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { AbsolutePath } from "@opencode/core/schema"
import { Session } from "@opencode/core/session"
@@ -59,7 +58,7 @@ const locations = Layer.effect(
Layer.provide(client),
Layer.provide(config),
Layer.provide(models),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
),
)
const it = testEffect(
+1 -2
View File
@@ -9,7 +9,6 @@ import { Job } from "@opencode/core/job"
import { KV } from "@opencode/core/kv"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { ProjectTable } from "@opencode/core/project/sql"
import { AbsolutePath } from "@opencode/core/schema"
@@ -1372,7 +1371,7 @@ function buildExecution(
() =>
// The local execution test only needs the Session runner from the Location graph.
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
runner as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
runner as unknown as Layer.Layer<LocationServices>,
),
)
return yield* Layer.buildWithScope(
+2 -7
View File
@@ -12,7 +12,6 @@ import { Instance } from "@opencode/core/instance"
import { Location } from "@opencode/core/location"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { AbsolutePath } from "@opencode/core/schema"
import { Session } from "@opencode/core/session"
@@ -59,7 +58,7 @@ const itWithActiveExecution = testEffect(
replacements: [Project.node.replace(globalProjectNode), offlineModels],
}),
Layer.succeed(SessionRunner.Service, { drain: () => Effect.never }),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
),
),
),
@@ -69,11 +68,7 @@ const itWithActiveExecution = testEffect(
const unavailableLocations = Layer.effect(
LocationServiceMap.Service,
LayerMap.make(
() =>
Layer.effectDiscard(Effect.die(new Error("broken location"))) as unknown as Layer.Layer<
LocationServices,
FileSystemDirectory.Error
>,
() => Layer.effectDiscard(Effect.fail(new Error("broken location"))) as unknown as Layer.Layer<LocationServices>,
),
)
const itWithUnavailableDestination = testEffect(
+1 -2
View File
@@ -26,7 +26,6 @@ import { SessionInboxTable, SessionMessageTable, SessionTable } from "@opencode/
import { SessionStore } from "@opencode/core/session/store"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Image } from "@opencode/core/image"
import { Plugin } from "@opencode/core/plugin"
import { PluginHooks } from "@opencode/core/plugin/hooks"
@@ -84,7 +83,7 @@ const locations = makeGlobalNode({
restore: () => Effect.void,
}),
Layer.mock(Plugin.Service, { awaitActivation: Effect.void }),
).pipe(Layer.fresh) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
).pipe(Layer.fresh) as unknown as Layer.Layer<LocationServices>,
)
}),
),
+26
View File
@@ -2716,6 +2716,32 @@ describe("SessionRunnerLLM", () => {
)
})
scenario("explains a compaction blocked by the provider", function* (s) {
yield* s.llm.push(TestLLM.text("Earlier answer", "history"))
yield* s.runPrompt("Earlier question")
yield* s.llm.push(
TestLLM.complete({
reason: {
normalized: "content-filter",
raw: "refusal",
category: "cyber",
explanation: "This request was declined because it could enable cyber harm.",
},
}),
)
const compaction = yield* s.session.compact({ sessionID })
yield* s.resume
expect((yield* s.messages).find((message) => message.id === compaction.id)).toMatchObject({
status: "failed",
error: {
type: "provider.content-filter",
message:
"Compaction summary was blocked by the provider (cyber): This request was declined because it could enable cyber harm.",
},
})
})
for (const header of [false, true]) {
scenario(`stops compaction retries through the ${header ? "provider header" : "retry hook"}`, function* (s) {
yield* s.llm.push(TestLLM.text("Earlier answer", "history"))
+1 -2
View File
@@ -10,7 +10,6 @@ import { Image } from "@opencode/core/image"
import { Location } from "@opencode/core/location"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import type { LocationServices } from "@opencode/core/location-services"
import type { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { Project } from "@opencode/core/project"
import { Plugin } from "@opencode/core/plugin"
import { PluginHooks } from "@opencode/core/plugin/hooks"
@@ -50,7 +49,7 @@ const locations = makeGlobalNode({
list: () => Effect.succeed([info]),
}),
Layer.mock(Plugin.Service, { awaitActivation: Effect.void }),
) as unknown as Layer.Layer<LocationServices, FileSystemDirectory.Error>,
) as unknown as Layer.Layer<LocationServices>,
),
),
deps: [],
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode/desktop",
"private": true,
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/enterprise",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/function",
"version": "2.0.21",
"version": "2.0.22",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/gui-extensions",
"version": "2.0.20",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "2.0.21",
"version": "2.0.22",
"name": "@opencode/http-recorder",
"description": "Record and replay Effect HTTP and WebSocket traffic with deterministic cassettes",
"type": "module",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/httpapi-codegen",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/latex",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/merman",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/plugin-browser",
"version": "2.0.21",
"version": "2.0.22",
"description": "OpenCode's desktop browser plugin",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/plugin",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"scripts": {
+5 -1
View File
@@ -19053,6 +19053,10 @@
"WorktreeErrorEncoded": {
"type": "object",
"properties": {
"_tag": {
"type": "string",
"enum": ["WorktreeError"]
},
"name": {
"type": "string",
"enum": ["WorktreeError"]
@@ -19078,7 +19082,7 @@
"additionalProperties": false
}
},
"required": ["name", "data"],
"required": ["_tag", "name", "data"],
"additionalProperties": false
}
},
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/protocol",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
+2 -8
View File
@@ -1,9 +1,4 @@
import {
DirectoryNotFoundError,
DirectoryAccessDeniedError,
InvalidRequestError,
SessionNotFoundError,
} from "./errors.js"
import { InvalidRequestError, SessionNotFoundError } from "./errors.js"
import { makeDefaultApi } from "./api.js"
import type { Api } from "./api.js"
import type { Context } from "effect"
@@ -12,12 +7,11 @@ import type { EventGroup } from "./groups/event.js"
class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware>()(
"@opencode/client/LocationMiddleware",
{ error: [DirectoryNotFoundError, DirectoryAccessDeniedError] },
) {}
class SessionLocationMiddleware extends HttpApiMiddleware.Service<SessionLocationMiddleware>()(
"@opencode/client/SessionLocationMiddleware",
{ error: [InvalidRequestError, SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError] },
{ error: [InvalidRequestError, SessionNotFoundError] },
) {}
type ClientApiShape = Api<
-13
View File
@@ -1,18 +1,5 @@
import { Schema } from "effect"
import { Skill } from "@opencode/schema/skill"
import { AbsolutePath } from "@opencode/schema/schema"
export class DirectoryNotFoundError extends Schema.TaggedError<DirectoryNotFoundError>()(
"DirectoryNotFoundError",
{ directory: AbsolutePath, message: Schema.String },
{ httpApiStatus: 404 },
) {}
export class DirectoryAccessDeniedError extends Schema.TaggedError<DirectoryAccessDeniedError>()(
"DirectoryAccessDeniedError",
{ directory: AbsolutePath, message: Schema.String },
{ httpApiStatus: 403 },
) {}
export class InvalidRequestError extends Schema.TaggedError<InvalidRequestError>()(
"InvalidRequestError",
+2 -7
View File
@@ -1,12 +1,7 @@
import { Model } from "@opencode/schema/model"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import {
DirectoryNotFoundError,
DirectoryAccessDeniedError,
InvalidRequestError,
ServiceUnavailableError,
} from "../errors.js"
import { InvalidRequestError, ServiceUnavailableError } from "../errors.js"
export const GenerateGroup = HttpApiGroup.make("server.generate")
.add(
@@ -18,7 +13,7 @@ export const GenerateGroup = HttpApiGroup.make("server.generate")
success: Schema.Struct({
data: Schema.Struct({ text: Schema.String }),
}).annotate({ identifier: "GenerateTextResponse" }),
error: [InvalidRequestError, ServiceUnavailableError, DirectoryNotFoundError, DirectoryAccessDeniedError],
error: [InvalidRequestError, ServiceUnavailableError],
}).annotateMerge(
OpenApi.annotations({
identifier: "experimental.generate.text",
+2 -7
View File
@@ -6,12 +6,7 @@ import { Project } from "@opencode/schema/project"
import { Session } from "@opencode/schema/session"
import { Context, Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import {
DirectoryNotFoundError,
DirectoryAccessDeniedError,
PermissionNotFoundError,
SessionNotFoundError,
} from "../errors.js"
import { PermissionNotFoundError, SessionNotFoundError } from "../errors.js"
import { LocationQuery, locationQueryOpenApi } from "./location.js"
export const makePermissionGroup = <
@@ -94,7 +89,7 @@ export const makePermissionGroup = <
HttpApiEndpoint.get("session.permission.list", "/api/session/:sessionID/permission", {
params: { sessionID: Session.ID },
success: Schema.Struct({ data: Schema.Array(Permission.Request) }),
error: [SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError],
error: SessionNotFoundError,
}).annotateMerge(
OpenApi.annotations({
identifier: "session.permission.list",
+11 -16
View File
@@ -18,8 +18,6 @@ import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import {
ConflictError,
DirectoryNotFoundError,
DirectoryAccessDeniedError,
CommandExecutionError,
CommandNotFoundError,
FormAlreadySettledError,
@@ -172,10 +170,12 @@ export const SessionsQuery = Schema.Struct({
cursor: SessionsQueryCursor.pipe(Schema.optional),
}).annotate({ identifier: "SessionsQuery" })
export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S, FormI extends HttpApiMiddleware.AnyId, FormS>(
sessionLocationMiddleware: Context.Key<I, S>,
formLocationMiddleware: Context.Key<FormI, FormS>,
) =>
export const makeSessionGroup = <
I extends HttpApiMiddleware.AnyId,
S,
FormI extends HttpApiMiddleware.AnyId,
FormS,
>(sessionLocationMiddleware: Context.Key<I, S>, formLocationMiddleware: Context.Key<FormI, FormS>) =>
HttpApiGroup.make("server.session")
.add(
HttpApiEndpoint.get("session.list", "/api/session", {
@@ -382,7 +382,7 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S, FormI ext
params: { sessionID: Session.ID },
payload: Schema.Struct({ ...Location.PublicRef.fields, delivery: SessionInbox.Delivery.pipe(Schema.optional) }),
success: HttpApiSchema.NoContent,
error: [SessionNotFoundError, InvalidRequestError, DirectoryNotFoundError, DirectoryAccessDeniedError],
error: [SessionNotFoundError, InvalidRequestError],
}).annotateMerge(
OpenApi.annotations({
identifier: "session.move",
@@ -562,7 +562,9 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S, FormI ext
error: [SessionNotFoundError, SessionBusyError],
})
.middleware(sessionLocationMiddleware)
.annotateMerge(OpenApi.annotations({ identifier: "session.revert.commit", summary: "Commit staged revert" })),
.annotateMerge(
OpenApi.annotations({ identifier: "session.revert.commit", summary: "Commit staged revert" }),
),
)
.add(
HttpApiEndpoint.get("session.context", "/api/session/:sessionID/context", {
@@ -592,14 +594,7 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S, FormI ext
}),
}),
success: Schema.Struct({ data: Schema.Array(FileDiff.Info) }),
error: [
InvalidRequestError,
MessageNotFoundError,
SessionNotFoundError,
UnknownError,
DirectoryNotFoundError,
DirectoryAccessDeniedError,
],
error: [InvalidRequestError, MessageNotFoundError, SessionNotFoundError, UnknownError],
}).annotateMerge(
OpenApi.annotations({
identifier: "session.diff",
+2 -1
View File
@@ -6,7 +6,8 @@ import { ProjectNotFoundError } from "../errors.js"
const root = "/api/worktree"
export class WorktreeError extends Schema.Error<WorktreeError>("WorktreeError")(
export class WorktreeError extends Schema.TaggedError<WorktreeError>()(
"WorktreeError",
{
name: Schema.Literal("WorktreeError"),
data: Schema.Struct({
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/schema",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
-13
View File
@@ -1,13 +0,0 @@
export * as IdeEvent from "./ide-event.js"
import { Schema } from "effect"
import { Event } from "./event.js"
export const Installed = Event.ephemeral({
type: "ide.installed",
schema: {
ide: Schema.String,
},
})
export const Definitions = Event.inventory(Installed)
-9
View File
@@ -490,15 +490,6 @@ export type Assistant = Omit<Types.DeepMutable<Schema.Schema.Type<typeof Assista
export const Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "SessionV1.Message" })
export type Info = User | Assistant
export const WithParts = Schema.Struct({
info: Info,
parts: Schema.Array(Part),
})
export type WithParts = {
info: Info
parts: Part[]
}
const options = {
durable: {
aggregate: "sessionID",
@@ -16,7 +16,6 @@ import {
} from "../src/index.js"
import { EventManifest } from "../src/event-manifest.js"
import { FileSystemV1 } from "../src/filesystem-v1.js"
import { IdeEvent } from "../src/ide-event.js"
import { McpEvent } from "../src/mcp-event.js"
import { SessionEvent } from "../src/session-event.js"
import { SessionID } from "../src/session-id.js"
@@ -71,8 +70,6 @@ describe("public event manifest", () => {
expect(Reference.Event.Definitions).toEqual([Reference.Event.Updated])
expect(McpEvent.Definitions).toEqual([McpEvent.ToolsChanged, McpEvent.ResourcesChanged, McpEvent.StatusChanged])
expect(EventManifest.Latest.has("mcp.browser.open.failed")).toBe(false)
expect(EventManifest.Latest.has("ide.installed")).toBe(false)
expect(IdeEvent.Definitions).toEqual([IdeEvent.Installed])
expect(EventManifest.Durable.get("session.step.ended.1")).toBe(SessionEvent.Step.Ended)
expect(EventManifest.Durable.has("session.step.ended.2")).toBe(false)
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "2.0.21",
"version": "2.0.22",
"name": "@opencode/sdk",
"type": "module",
"license": "MIT",
+1 -5
View File
@@ -57,11 +57,7 @@ export function layer(options: Options, replacements: () => LayerNode.Replacemen
...LocationServiceMap.canonical(session.location),
})
const provide = (session: Session.Info) => Effect.provide(instances.get(key(session)))
const instances: LayerMap.LayerMap<
ReturnType<typeof key>,
Instance.Services,
Instance.Error
> = yield* LayerMap.make(
const instances: LayerMap.LayerMap<ReturnType<typeof key>, Instance.Services> = yield* LayerMap.make(
(input: ReturnType<typeof key>) =>
Layer.unwrap(
Effect.gen(function* () {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/server",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
+13 -18
View File
@@ -7,7 +7,6 @@ import { Global } from "@opencode/util/global"
import { Effect } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api"
import { directoryErrors } from "../location"
export const GenerateHandler = HttpApiBuilder.group(Api, "server.generate", (handlers) =>
Effect.gen(function* () {
@@ -16,23 +15,19 @@ export const GenerateHandler = HttpApiBuilder.group(Api, "server.generate", (han
const services = locations.get(Location.Ref.make({ directory: AbsolutePath.make(global.config) }))
return handlers.handle(
"generate.text",
Effect.fn("server.generate.text")(
function* (request) {
const generate = yield* Generate.Service
const text = yield* generate
.text(request.payload)
.pipe(
Effect.mapError((error) =>
error._tag === "Generate.ModelSelectionError"
? new InvalidRequestError({ message: error.message })
: new ServiceUnavailableError({ message: error.message, service: error.service }),
),
)
return { data: { text } }
},
Effect.provide(services),
directoryErrors,
),
Effect.fn("server.generate.text")(function* (request) {
const generate = yield* Generate.Service
const text = yield* generate
.text(request.payload)
.pipe(
Effect.mapError((error) =>
error._tag === "Generate.ModelSelectionError"
? new InvalidRequestError({ message: error.message })
: new ServiceUnavailableError({ message: error.message, service: error.service }),
),
)
return { data: { text } }
}, Effect.provide(services)),
)
}),
)
+1 -1
View File
@@ -24,7 +24,7 @@ export const LocationHandler = HttpApiBuilder.group(Api, "server.location", (han
Effect.provideService(LocationServiceMap.Service, locations),
Effect.catchCause((cause) =>
Cause.hasInterruptsOnly(cause)
? Effect.failCause(Cause.fromReasons<never>(cause.reasons.filter(Cause.isInterruptReason)))
? Effect.failCause(cause)
: Effect.fail(new ServiceUnavailableError({ message: Cause.pretty(cause), service: "location" })),
),
),
+2 -2
View File
@@ -7,7 +7,7 @@ import { Effect } from "effect"
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
import { Api } from "../api"
import { PermissionNotFoundError } from "@opencode/protocol/errors"
import { directoryErrors, response, sessionInfo } from "../location"
import { response, sessionInfo } from "../location"
import { missingSession } from "./session-error"
function missingRequest(id: Permission.ID) {
@@ -62,7 +62,7 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
const session = yield* sessionInfo(sessions, ctx.params.sessionID)
const requests = yield* Permission.Service.use((permission) =>
permission.forSession(ctx.params.sessionID),
).pipe(instances.provide(session), directoryErrors)
).pipe(instances.provide(session))
return { data: requests }
}),
)
+9 -16
View File
@@ -23,7 +23,6 @@ import {
SkillNotFoundError,
} from "@opencode/protocol/errors"
import { AbsolutePath } from "@opencode/core/schema"
import { directoryErrors } from "../location"
import { failedMessageDecode, failedSnapshot, missingMessage, missingSession } from "./session-error"
const DefaultSessionsLimit = 50
@@ -300,7 +299,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
Effect.catchTag("Session.DestinationUnavailableError", (error) =>
Effect.fail(new InvalidRequestError({ message: `Directory is unavailable: ${error.directory}` })),
),
directoryErrors,
)
return HttpApiSchema.NoContent.make()
}),
@@ -337,7 +335,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
Effect.catchTag("Session.SkillNotFoundError", (error) =>
Effect.fail(new InvalidRequestError({ message: `Skill not found: ${error.skill}`, field: "skills" })),
),
directoryErrors,
),
}
}),
@@ -373,7 +370,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
}),
),
),
directoryErrors,
)
return HttpApiSchema.NoContent.make()
}),
@@ -392,7 +388,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
Effect.catchTag("Session.SkillNotFoundError", (error) =>
Effect.fail(new SkillNotFoundError({ skill: error.skill, message: `Skill not found: ${error.skill}` })),
),
directoryErrors,
)
return HttpApiSchema.NoContent.make()
}),
@@ -476,7 +471,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
Effect.catchTag("Session.MessageNotFoundError", missingMessage),
Effect.catchTag("Session.BusyError", busySession),
Effect.catchTag("Snapshot.Error", failedSnapshot("stage session revert", ctx.params.sessionID)),
directoryErrors,
),
}
}),
@@ -491,7 +485,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
Effect.catchTag("Session.NotFoundError", missingSession),
Effect.catchTag("Session.BusyError", busySession),
Effect.catchTag("Snapshot.Error", failedSnapshot("clear session revert", ctx.params.sessionID)),
directoryErrors,
)
return HttpApiSchema.NoContent.make()
}),
@@ -534,7 +527,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
(error) => new InvalidRequestError({ message: error.message, field: error.field }),
),
Effect.catchTag("Snapshot.Error", failedSnapshot("diff session turn", ctx.params.sessionID)),
directoryErrors,
),
}
}),
@@ -596,14 +588,15 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
.handle(
"session.generate",
Effect.fn(function* (ctx) {
const text = yield* session.generate({ sessionID: ctx.params.sessionID, prompt: ctx.payload.prompt }).pipe(
directoryErrors,
Effect.mapError((error) =>
error._tag === "Session.NotFoundError"
? missingSession(error)
: new ServiceUnavailableError({ message: error.message, service: "session generation" }),
),
)
const text = yield* session
.generate({ sessionID: ctx.params.sessionID, prompt: ctx.payload.prompt })
.pipe(
Effect.mapError((error) =>
error._tag === "Session.NotFoundError"
? missingSession(error)
: new ServiceUnavailableError({ message: error.message, service: "session generation" }),
),
)
return { data: { text } }
}),
)
+2 -20
View File
@@ -2,8 +2,7 @@ import { Location } from "@opencode/core/location"
import { LocationServiceMap } from "@opencode/core/location-services"
import { AbsolutePath } from "@opencode/core/schema"
import { Session } from "@opencode/core/session"
import { DirectoryNotFoundError, DirectoryAccessDeniedError, InvalidRequestError } from "@opencode/protocol/errors"
import { FileSystemDirectory } from "@opencode/core/filesystem/directory"
import { InvalidRequestError } from "@opencode/protocol/errors"
import { Effect, Layer, Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
@@ -13,25 +12,8 @@ export type LocationServices = Layer.Success<ReturnType<(typeof LocationServiceM
export class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware, { provides: LocationServices }>()(
"@opencode/HttpApiLocation",
{ error: [DirectoryNotFoundError, DirectoryAccessDeniedError] },
) {}
export function directoryErrors<A, E, R>(effect: Effect.Effect<A, E, R>) {
return effect.pipe(
Effect.catchIf(
(error): error is Extract<E, FileSystemDirectory.Error> =>
error instanceof FileSystemDirectory.DirectoryNotFoundError ||
error instanceof FileSystemDirectory.DirectoryAccessDeniedError,
(error): Effect.Effect<never, DirectoryNotFoundError | DirectoryAccessDeniedError> =>
Effect.fail(
error instanceof FileSystemDirectory.DirectoryNotFoundError
? new DirectoryNotFoundError({ directory: error.directory, message: error.message })
: new DirectoryAccessDeniedError({ directory: error.directory, message: error.message }),
),
),
)
}
export function response<A, E, R>(data: Effect.Effect<A, E, R>) {
return Effect.gen(function* () {
const location = yield* Location.Service
@@ -79,7 +61,7 @@ export const layer = Layer.effect(
return LocationMiddleware.of((effect) =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))), directoryErrors)
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))))
}),
)
}),
@@ -1,22 +1,17 @@
import { Instance } from "@opencode/core/instance/service"
import { LocationServiceMap } from "@opencode/core/location-services"
import { Session } from "@opencode/core/session"
import {
DirectoryNotFoundError,
DirectoryAccessDeniedError,
InvalidRequestError,
SessionNotFoundError,
} from "@opencode/protocol/errors"
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors"
import { Effect, Layer } from "effect"
import { HttpRouter, HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import { directoryErrors, requestRef, sessionInfo, type LocationServices } from "../location"
import { requestRef, sessionInfo, type LocationServices } from "../location"
export class FormLocationMiddleware extends HttpApiMiddleware.Service<
FormLocationMiddleware,
{ provides: LocationServices }
>()("@opencode/HttpApiFormLocation", {
error: [InvalidRequestError, SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError],
error: [InvalidRequestError, SessionNotFoundError],
}) {}
export const formLocationLayer = Layer.effect(
@@ -34,11 +29,11 @@ export const formLocationLayer = Layer.effect(
// the session row lookup because some MCP elicitations cannot currently be attributed to
// a real session. Keep this undocumented and remove once elicitations carry session ownership.
const request = yield* HttpServerRequest.HttpServerRequest
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))), directoryErrors)
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))))
}
const session = yield* sessionInfo(sessions, route.params.sessionID)
return yield* effect.pipe(instances.provide(session), directoryErrors)
return yield* effect.pipe(instances.provide(session))
}),
)
}),
@@ -3,19 +3,14 @@ import { Session } from "@opencode/core/session"
import { Effect, Layer } from "effect"
import { HttpRouter } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import {
DirectoryNotFoundError,
DirectoryAccessDeniedError,
InvalidRequestError,
SessionNotFoundError,
} from "@opencode/protocol/errors"
import { directoryErrors, sessionInfo, type LocationServices } from "../location"
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors"
import { sessionInfo, type LocationServices } from "../location"
export class SessionLocationMiddleware extends HttpApiMiddleware.Service<
SessionLocationMiddleware,
{ provides: LocationServices }
>()("@opencode/HttpApiSessionLocation", {
error: [InvalidRequestError, SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError],
error: [InvalidRequestError, SessionNotFoundError],
}) {}
export const sessionLocationLayer = Layer.effect(
@@ -28,7 +23,7 @@ export const sessionLocationLayer = Layer.effect(
Effect.gen(function* () {
const route = yield* HttpRouter.RouteContext
const session = yield* sessionInfo(sessions, route.params.sessionID)
return yield* effect.pipe(instances.provide(session), directoryErrors)
return yield* effect.pipe(instances.provide(session))
}),
)
}),
+2 -106
View File
@@ -5,10 +5,7 @@ import path from "node:path"
import { Agent } from "@opencode/schema/agent"
import { Integration } from "@opencode/schema/integration"
import { ServerInfo } from "@opencode/protocol/groups/server"
import { Effect, Layer, PlatformError, Schedule, Schema } from "effect"
import { FSUtil } from "@opencode/util/fs-util"
import { LayerNode } from "@opencode/util/effect/layer-node"
import { Session } from "@opencode/schema/session"
import { Effect, Schedule, Schema } from "effect"
import { tmpdir } from "../../core/test/fixture/tmpdir"
import { it } from "../../core/test/lib/effect"
import { ServerFetch } from "../src/fetch"
@@ -21,105 +18,6 @@ const options = {
fs: { filewatcher: false },
} as const
for (const input of [
{ code: "ENOENT", reason: "NotFound", status: 404, tag: "DirectoryNotFoundError", message: "Directory not found" },
{
code: "EACCES",
reason: "PermissionDenied",
status: 403,
tag: "DirectoryAccessDeniedError",
message: "Access denied to directory",
},
{
code: "EPERM",
reason: "Unknown",
status: 403,
tag: "DirectoryAccessDeniedError",
message: "Access denied to directory",
},
{ code: "EIO", reason: "Unknown", status: 500 },
] as const) {
it.live(`returns directory HTTP errors for ${input.code} and retries after recovery`, () =>
Effect.gen(function* () {
const config = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-directory-errors-")))
const directory = path.join(config.path, "project")
const filesystem = yield* FSUtil.Service.pipe(Effect.provide(LayerNode.compile(FSUtil.node)))
const blocked = new Set<string>()
const cause = PlatformError.systemError({
_tag: input.reason,
module: "FileSystem",
method: "realPath",
pathOrDescriptor: directory,
cause: Object.assign(new Error(input.code), { code: input.code }),
})
const handler = yield* ServerFetch.make(
{ ...options, config: { directory: config.path } },
{
overrides: [
FSUtil.node.replace(
Layer.succeed(FSUtil.Service, {
...filesystem,
realPath: (target) =>
blocked.has(target) && input.code !== "ENOENT" ? Effect.fail(cause) : filesystem.realPath(target),
}),
),
],
},
)
// Session creation only resolves its placement; it does not boot the Location graph.
const created = yield* Effect.promise(() =>
handler(
new Request("http://opencode.local/api/session", {
method: "POST",
headers: { "content-type": "application/json", "x-opencode-directory": encodeURIComponent(directory) },
body: JSON.stringify({ location: { directory } }),
}),
),
)
expect(created.status).toBe(200)
const session = Schema.decodeUnknownSync(Schema.Struct({ data: Session.Info }))(
yield* Effect.promise(() => created.json()),
).data
blocked.add(directory)
if (input.code !== "ENOENT") yield* Effect.promise(() => fs.mkdir(directory))
const endpoints = [
"/api/model",
"/api/integration",
`/api/session/${session.id}/permission`,
`/api/experimental/session/${session.id}/instructions/entries`,
`/api/session/${session.id}/form`,
"/api/session/global/form",
]
for (const endpoint of endpoints) {
const response = yield* Effect.promise(() =>
handler(
new Request(`http://opencode.local${endpoint}`, {
headers: { "x-opencode-directory": encodeURIComponent(directory) },
}),
),
)
expect({ endpoint, status: response.status }).toEqual({ endpoint, status: input.status })
if (input.status !== 500)
expect(yield* Effect.promise(() => response.json())).toEqual({
_tag: input.tag,
directory,
message: `${input.message}: ${directory}`,
})
}
blocked.clear()
if (input.code === "ENOENT") yield* Effect.promise(() => fs.mkdir(directory))
const recovered = yield* Effect.promise(() =>
handler(
new Request("http://opencode.local/api/model", {
headers: { "x-opencode-directory": encodeURIComponent(directory) },
}),
),
)
expect(recovered.status).toBe(200)
}),
)
}
type Handler = (request: Request) => Promise<Response>
function occupy(port: number, cancel = false) {
@@ -207,9 +105,7 @@ it.live("serves the HttpApi and enforces Basic auth like the Node server", () =>
),
)
expect(response.status).toBe(200)
const body = yield* Effect.promise(() => response.json()).pipe(
Effect.flatMap(Schema.decodeUnknownEffect(ServerInfo)),
)
const body = yield* Effect.promise(() => response.json()).pipe(Effect.flatMap(Schema.decodeUnknownEffect(ServerInfo)))
expect(body.version).toBe("test-version")
expect(body.paths.tmp).toEndWith("opencode")
}),
+8 -2
View File
@@ -25,10 +25,12 @@ it.live("list reads saved inventory even when its checkout is missing, without b
await fs.rm(directory, { recursive: true })
expect(await api.worktree.list({ projectID: session.projectID })).toEqual([{ directory }])
await expect(api.worktree.create({ projectID: session.projectID })).rejects.toMatchObject({
_tag: "WorktreeError",
name: "WorktreeError",
data: { message: `Worktree directory unavailable: ${directory}` },
})
await expect(api.worktree.refresh({ projectID: session.projectID })).rejects.toMatchObject({
_tag: "WorktreeError",
name: "WorktreeError",
data: { message: `Worktree directory unavailable: ${directory}` },
})
@@ -109,7 +111,7 @@ it.live("remove loads canonical strategies and enforces project ownership", () =
const foreign = await api.session.create({ location: { directory: other } })
await expect(
api.worktree.remove({ projectID: foreign.projectID, directory: linked, force: true }),
).rejects.toMatchObject({ name: "WorktreeError" })
).rejects.toMatchObject({ _tag: "WorktreeError", name: "WorktreeError" })
expect(await fs.stat(linked).then((stat) => stat.isDirectory())).toBe(true)
await api.worktree.remove({ projectID: session.projectID, directory: linked, force: false })
expect(await api.debug.location.list()).toContainEqual({ directory })
@@ -267,7 +269,11 @@ it.live(
body: JSON.stringify({ projectID, directory: custom.directory, force: false }),
})
expect(failure.status).toBe(400)
expect(await failure.json()).toMatchObject({ data: { forceRequired: true } })
expect(await failure.json()).toMatchObject({
_tag: "WorktreeError",
name: "WorktreeError",
data: { forceRequired: true },
})
expect(await Bun.file(path.join(custom.directory, "dirty.txt")).text()).toBe("keep me")
expect(await api.worktree.list({ projectID })).toEqual(rows)
expect(await api.debug.location.list()).toContainEqual({ directory: first })
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/session-ui",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+14 -3
View File
@@ -308,15 +308,25 @@ export namespace Timeline {
const previousUserMessage = index > 0
const compaction = entries.some((entry) => entry.type === "notice" && entry.message.type === "compaction")
const lastContent = lastAssistant?.content.at(-1)
const thinking =
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
const working =
isActive &&
status.type === "busy" &&
lastAssistant?.time.completed === undefined &&
!lastAssistant?.error &&
!lastAssistant?.retry &&
!lastAssistant?.retry
const thinking =
working &&
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
lastContent?.type === "reasoning" &&
lastContent.time?.completed === undefined
const thoughtOnly =
working &&
detail?.thinking.placement === "grouped" &&
assistantMessages.every((message) =>
message.content.every(
(content) => content.type === "reasoning" || !isRenderable(content, showReasoning, detail),
),
)
if (previousUserMessage) rows.push(new TimelineRow.TurnGap({ userMessageID: turnID }))
if (userMessage) rows.push(new TimelineRow.UserMessage({ userMessageID: turnID }))
@@ -326,6 +336,7 @@ export namespace Timeline {
// An assistant message can produce several rows because its content parts are
// rendered separately. Notices end a segment so none of those rows cross it.
const appendAssistantSegment = (messages: SessionMessageAssistant[]) => {
if (thoughtOnly) return
const refs = messages.flatMap((message, messageIndex) =>
contentEntries(message)
.filter(
@@ -264,6 +264,54 @@ describe("current session timeline rows", () => {
})
})
test("omits a grouped reasoning-only row while busy until other content or completion", () => {
const thought = { type: "reasoning", text: "Thinking", time: { created: 1, completed: 2 } } as const
const rows = (content: SessionMessageAssistant["content"], busy: boolean) =>
createTimelineProjection({
sessionMessages: storyDocument(content, busy).messages,
status: { type: busy ? "busy" : "idle" },
reasoningMode: "compact",
timelineDetail: timelinePresets[2].value,
}).rows
expect(rows([{ ...thought, time: { created: 1 } }], true).map((row) => row._tag)).toEqual(["UserMessage"])
expect(rows([thought], true).map((row) => row._tag)).toEqual(["UserMessage"])
expect(rows([thought, { type: "text", text: "" }], true).map((row) => row._tag)).toEqual(["UserMessage"])
expect(rows([thought, { type: "text", text: "Answer" }], true).map((row) => row._tag)).toEqual([
"UserMessage",
"AssistantPart",
"AssistantPart",
])
expect(
rows([thought, storyTool("read", "read", "running", { filePath: "package.json" })], true).map((row) => row._tag),
).toEqual(["UserMessage", "AssistantPart"])
expect(rows([thought], false).map((row) => row._tag)).toEqual(["UserMessage", "AssistantPart"])
const failed = storyDocument([thought], true)
const messages = failed.messages.map((message) =>
message.type === "assistant" ? { ...message, error: { type: "provider.error", message: "Failed" } } : message,
)
expect(
createTimelineProjection({
sessionMessages: messages,
status: failed.status,
reasoningMode: "compact",
timelineDetail: timelinePresets[2].value,
}).rows.map((row) => row._tag),
).toEqual(["UserMessage", "AssistantPart", "Error"])
expect(
createTimelineProjection({
sessionMessages: failed.messages.map((message) =>
message.type === "assistant" ? { ...message, error: { type: "Interrupted", message: "Stopped" } } : message,
),
status: failed.status,
reasoningMode: "compact",
timelineDetail: { ...timelinePresets[2].value, notices: { placement: "hidden" } },
}).rows.map((row) => row._tag),
).toEqual(["UserMessage", "AssistantPart"])
})
test("stops thinking on idle, message completion, errors and retries", () => {
const document = storyDocument([{ type: "reasoning", text: "Current thought" }], true)
expect(
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/simulation",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/stats-app",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/stats-core",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/stats-server",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/theme",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/tui",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/ui",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/util",
"version": "2.0.21",
"version": "2.0.22",
"type": "module",
"license": "MIT",
"repository": {
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@opencode/web",
"type": "module",
"license": "MIT",
"version": "2.0.21",
"version": "2.0.22",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "2.0.21",
"version": "2.0.22",
"publisher": "sst-dev",
"repository": {
"type": "git",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/update",
"version": "2.0.21",
"version": "2.0.22",
"private": true,
"type": "module",
"scripts": {