mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-02 21:46:18 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfd42f318e | ||
|
|
38290a67a1 |
No files matched your search
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"packages/ai": {
|
||||
"name": "@opencode/ai",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "3.1057.0",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -55,7 +55,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
@@ -112,7 +112,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode/cli",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode.cjs",
|
||||
"opencode2": "./bin/opencode2.cjs",
|
||||
@@ -178,7 +178,7 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@opencode/client",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/protocol": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -204,7 +204,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode/codemode",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -217,7 +217,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode/console-app",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode/console-core": "workspace:*",
|
||||
@@ -341,7 +341,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode/core",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/sdk": "1.18.21",
|
||||
@@ -492,7 +492,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode/function",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -508,7 +508,7 @@
|
||||
},
|
||||
"packages/gui-extensions": {
|
||||
"name": "@opencode/gui-extensions",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.20",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@effect/platform-node-shared": "4.0.0-rc.112",
|
||||
},
|
||||
@@ -572,7 +572,7 @@
|
||||
},
|
||||
"packages/httpapi-codegen": {
|
||||
"name": "@opencode/httpapi-codegen",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"prettier": "3.6.2",
|
||||
@@ -585,7 +585,7 @@
|
||||
},
|
||||
"packages/latex": {
|
||||
"name": "@opencode/latex",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -599,7 +599,7 @@
|
||||
},
|
||||
"packages/merman": {
|
||||
"name": "@opencode/merman",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -614,7 +614,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode/plugin",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode/ai": "workspace:*",
|
||||
@@ -653,7 +653,7 @@
|
||||
},
|
||||
"packages/plugin-browser": {
|
||||
"name": "@opencode/plugin-browser",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -683,7 +683,7 @@
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@opencode/protocol",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/schema": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -698,7 +698,7 @@
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@opencode/schema",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -722,7 +722,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@opencode/sdk",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -743,7 +743,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode/server",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@effect/platform-node-shared": "catalog:",
|
||||
@@ -765,7 +765,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode/session-ui",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode/client": "workspace:*",
|
||||
@@ -800,7 +800,7 @@
|
||||
},
|
||||
"packages/simulation": {
|
||||
"name": "@opencode/simulation",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/ai": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -820,7 +820,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode/stats-app",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -854,7 +854,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode/stats-core",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"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.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -919,7 +919,7 @@
|
||||
},
|
||||
"packages/theme": {
|
||||
"name": "@opencode/theme",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opentui/core": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -933,7 +933,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode/tui",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -967,7 +967,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode/ui",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -1002,7 +1002,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode/util",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -1040,7 +1040,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode/web",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1081,7 +1081,7 @@
|
||||
},
|
||||
"services/update": {
|
||||
"name": "@opencode/update",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"jose": "6.0.11",
|
||||
"semver": "catalog:",
|
||||
|
||||
+2
-22
@@ -13,12 +13,7 @@
|
||||
opencode,
|
||||
}:
|
||||
let
|
||||
electronPin =
|
||||
(lib.pipe ../packages/desktop/package.json [
|
||||
builtins.readFile
|
||||
builtins.fromJSON
|
||||
]).devDependencies.electron;
|
||||
electron = callPackage ./electron.nix { inherit electronPin; };
|
||||
electron = callPackage ./electron.nix { };
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "opencode-desktop";
|
||||
@@ -71,7 +66,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.hostPlatform.isLinux ''
|
||||
+ lib.optionalString stdenv.isLinux ''
|
||||
substituteInPlace \
|
||||
packages/desktop/src/main/windows/appearance.ts \
|
||||
packages/desktop/src/main/service/desktop-cli.ts \
|
||||
@@ -79,7 +74,6 @@ 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
|
||||
|
||||
@@ -95,15 +89,8 @@ 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 \
|
||||
@@ -151,13 +138,6 @@ 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";
|
||||
|
||||
+10
-8
@@ -1,10 +1,12 @@
|
||||
{ lib, pkgs, electronPin }:
|
||||
{ callPackage, path }:
|
||||
let
|
||||
# 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;
|
||||
version = (builtins.fromJSON (builtins.readFile ../packages/desktop/package.json)).devDependencies.electron;
|
||||
in
|
||||
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")
|
||||
(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=";
|
||||
}
|
||||
+6
-22
@@ -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,30 +85,14 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
'';
|
||||
|
||||
postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) ''
|
||||
# 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
|
||||
|
||||
# trick yargs into also generating zsh completions
|
||||
installShellCompletion --cmd opencode \
|
||||
--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
|
||||
--bash <($out/bin/opencode completion) \
|
||||
--zsh <(SHELL=/bin/zsh $out/bin/opencode completion)
|
||||
|
||||
installShellCompletion --cmd opencode2 \
|
||||
--bash opencode2.bash \
|
||||
--fish opencode2.fish \
|
||||
--zsh _opencode2
|
||||
--bash <($out/bin/opencode2 completion) \
|
||||
--zsh <(SHELL=/bin/zsh $out/bin/opencode2 completion)
|
||||
'';
|
||||
|
||||
nativeInstallCheckInputs = [
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "opencode",
|
||||
"description": "AI-powered development tool",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.4.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"name": "@opencode/ai",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -543,26 +543,6 @@ 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,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cli",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/client",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -447,7 +447,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/location`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -472,7 +472,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/agent`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -724,7 +724,7 @@ export function make(options: ClientOptions) {
|
||||
resume: input["resume"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404, 409],
|
||||
declaredStatuses: [400, 401, 403, 404, 409],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -743,7 +743,7 @@ export function make(options: ClientOptions) {
|
||||
delivery: input["delivery"],
|
||||
},
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404, 500],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -774,7 +774,7 @@ export function make(options: ClientOptions) {
|
||||
resume: input["resume"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404, 409],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 409],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 503],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 409, 500],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 409, 500],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 409],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 500],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 413],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 503],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 409],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 409],
|
||||
declaredStatuses: [400, 401, 403, 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, 404, 409],
|
||||
declaredStatuses: [400, 401, 403, 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, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 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, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 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, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 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, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 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, 404, 503],
|
||||
declaredStatuses: [400, 401, 403, 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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1327,7 +1327,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/mcp`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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, 404],
|
||||
declaredStatuses: [400, 401, 403, 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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1458,7 +1458,13 @@ export function make(options: ClientOptions) {
|
||||
project: {
|
||||
list: (requestOptions?: RequestOptions) =>
|
||||
request<ProjectListOutput>(
|
||||
{ method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/project`,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
update: (input: ProjectUpdateInput, requestOptions?: RequestOptions) =>
|
||||
@@ -1473,7 +1479,7 @@ export function make(options: ClientOptions) {
|
||||
commands: input["commands"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1487,7 +1493,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/form`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1502,7 +1508,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/permission/request`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1516,7 +1522,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/permission/saved`,
|
||||
query: { projectID: input?.["projectID"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1527,7 +1533,7 @@ export function make(options: ClientOptions) {
|
||||
method: "DELETE",
|
||||
path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1548,7 +1554,7 @@ export function make(options: ClientOptions) {
|
||||
agent: input["agent"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1559,7 +1565,7 @@ export function make(options: ClientOptions) {
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1570,7 +1576,7 @@ export function make(options: ClientOptions) {
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}`,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1582,7 +1588,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, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1596,7 +1602,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/fs/read/${encodePath(input.path)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
binary: true,
|
||||
},
|
||||
@@ -1609,7 +1615,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/fs/list`,
|
||||
query: { location: input?.["location"], path: input?.["path"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1621,7 +1627,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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1634,7 +1640,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"], path: input["path"] },
|
||||
body: input["payload"],
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
binaryBody: true,
|
||||
},
|
||||
@@ -1649,7 +1655,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/command`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1663,7 +1669,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/skill`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1678,7 +1684,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { input: input["input"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 500],
|
||||
declaredStatuses: [400, 401, 403, 404, 500],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1699,7 +1705,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/pty`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1718,7 +1724,7 @@ export function make(options: ClientOptions) {
|
||||
env: input?.["env"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1730,7 +1736,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1743,7 +1749,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { title: input["title"], size: input["size"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1755,7 +1761,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1909,7 +1915,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/shell`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1927,7 +1933,7 @@ export function make(options: ClientOptions) {
|
||||
metadata: input["metadata"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1939,7 +1945,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1951,7 +1957,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, 404],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1963,7 +1969,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -1977,7 +1983,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/reference`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2047,7 +2053,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2059,7 +2065,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs/base`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2071,7 +2077,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/vcs/status`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2084,7 +2090,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],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2097,7 +2103,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, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2153,7 +2159,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/websearch/provider`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2166,7 +2172,7 @@ export function make(options: ClientOptions) {
|
||||
query: { location: input["location"] },
|
||||
body: { query: input["query"], providerID: input["providerID"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 503],
|
||||
declaredStatuses: [400, 401, 403, 404, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
@@ -2180,14 +2186,20 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/config`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
shells: (requestOptions?: RequestOptions) =>
|
||||
request<ConfigShellsOutput>(
|
||||
{ method: "GET", path: `/api/config/shell`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/config/shell`,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
update: (input: ConfigUpdateInput, requestOptions?: RequestOptions) =>
|
||||
@@ -2197,7 +2209,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/experimental/config`,
|
||||
body: { shell: input["shell"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401],
|
||||
declaredStatuses: [400, 401, 403, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
|
||||
@@ -2525,6 +2525,22 @@ 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
|
||||
@@ -2735,12 +2751,11 @@ 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 && "_tag" in value && value["_tag"] === "WorktreeError"
|
||||
typeof value === "object" && value !== null && "name" in value && value["name"] === "WorktreeError"
|
||||
|
||||
export type ServerInfoOutput = ServerInfo
|
||||
|
||||
|
||||
@@ -174,25 +174,21 @@ test("declared errors are thrown as Error instances that keep the body", async (
|
||||
expect(error).toMatchObject({ _tag: "InvalidRequestError", kind: "integration_authorization" })
|
||||
})
|
||||
|
||||
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("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("client errors keep the reason and describe the failure in the message", async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/codemode",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-app",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/console-core",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-function",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-mail",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-support",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"name": "@opencode/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -593,6 +593,10 @@ 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,6 +6,7 @@ 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"
|
||||
|
||||
|
||||
@@ -26,30 +26,6 @@ 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),
|
||||
@@ -111,24 +87,7 @@ 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* 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 root = location.workspaceID ? location.directory : yield* fs.realDirectory(location.directory)
|
||||
const resolve = Effect.fnUntraced(function* (input?: RelativePath) {
|
||||
const absolute = path.resolve(location.directory, input ?? ".")
|
||||
if (!FSUtil.contains(location.directory, absolute))
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
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"
|
||||
@@ -11,6 +11,7 @@ import { FileMutation } from "./file-mutation.js"
|
||||
import { Environment } from "./environment/index.js"
|
||||
import { Formatter } from "./formatter.js"
|
||||
import { FileSystem } from "./filesystem.js"
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
import { FileSystemSearch } from "./filesystem/search.js"
|
||||
import { Generate } from "./generate.js"
|
||||
import { Form } from "./form.js"
|
||||
@@ -114,7 +115,7 @@ const nodes = [
|
||||
export const graph = LayerNode.group(nodes)
|
||||
|
||||
export type Services = LayerNode.Output<typeof graph>
|
||||
export type Error = Layer.Error<ReturnType<typeof layer>>
|
||||
export type Error = FSUtil.DirectoryError
|
||||
|
||||
export interface Options {
|
||||
// Plugins this instance is born with; empty and absent are equivalent.
|
||||
@@ -145,7 +146,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> {
|
||||
export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Services, Error> {
|
||||
const startedAt = performance.now()
|
||||
// Ordered: vanilla defaults, then caller replacements (which win over the
|
||||
// defaults), then instance bindings (which win over everything).
|
||||
@@ -156,9 +157,31 @@ export function layer(ref: Location.Ref, options: Options = {}): Layer.Layer<Ser
|
||||
InstancePlugins.node.replace(InstancePlugins.bound(options.plugins ?? [])),
|
||||
]
|
||||
|
||||
return LayerNode.compile(graph, { replacements, shared: Node.tags.values.global }).pipe(
|
||||
// Instance boot failures are defects; provided operations retain their typed errors.
|
||||
Layer.orDie,
|
||||
return Layer.unwrap(
|
||||
Effect.gen(function* () {
|
||||
// Validate before project/config discovery can turn directory access failures into defects.
|
||||
if (!ref.workspaceID)
|
||||
yield* FSUtil.Service.pipe(
|
||||
Effect.flatMap((fs) => fs.realDirectory(ref.directory)),
|
||||
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 FSUtil.DirectoryNotFoundError || error instanceof FSUtil.DirectoryAccessDeniedError
|
||||
? Effect.fail(error)
|
||||
: Effect.die(error),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Layer.tap((context) => Effect.addFinalizer(() => Context.get(context, LocationLifecycle.Service).shutdown)),
|
||||
Layer.tap(() =>
|
||||
Effect.logInfo("location services booted", {
|
||||
|
||||
@@ -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 } from "../instance.js"
|
||||
import type { Services, Error } 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, Exclude<R, Services>>
|
||||
) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | Error, Exclude<R, Services>>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Instance") {}
|
||||
|
||||
@@ -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> = yield* LayerMap.make(
|
||||
const inner: LayerMap.LayerMap<Location.Ref, LocationServices, Instance.Error> = yield* LayerMap.make(
|
||||
(ref: Location.Ref) => {
|
||||
const build: { close?: Effect.Effect<void> } = {}
|
||||
MutableHashMap.set(builds, ref, build)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
export * as PluginHost from "./host.js"
|
||||
|
||||
import { Plugin } from "@opencode/plugin/effect"
|
||||
import { SessionList } from "@opencode/schema/session-list"
|
||||
import type { IntegrationMethodRegistration } from "@opencode/plugin/effect/integration"
|
||||
import { EventManifest } from "@opencode/schema/event-manifest"
|
||||
import type { Event } from "@opencode/schema/event"
|
||||
import { ServerConfig } from "@opencode/schema/mcp"
|
||||
import { App } from "../app.js"
|
||||
import { DateTime, Effect, Schema, Stream } from "effect"
|
||||
import { Effect, Schema, Stream } from "effect"
|
||||
import { Agent } from "../agent.js"
|
||||
import { AISDK } from "../aisdk.js"
|
||||
import { Command } from "../command.js"
|
||||
@@ -527,39 +526,6 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
},
|
||||
session: {
|
||||
hook: (name, callback, options) => hooks.register("session", name, callback, options),
|
||||
list: (input) =>
|
||||
Effect.gen(function* () {
|
||||
const query =
|
||||
input?.cursor === undefined
|
||||
? (input ?? {})
|
||||
: yield* SessionList.Cursor.parse(input.cursor).pipe(Effect.mapError(() => new Error("Invalid cursor")))
|
||||
const page = yield* sessions.list({ ...query, limit: input?.limit ?? 50 })
|
||||
const first = page.data[0]
|
||||
const last = page.data.at(-1)
|
||||
return {
|
||||
data: page.data,
|
||||
cursor: {
|
||||
previous: first
|
||||
? SessionList.Cursor.make({
|
||||
...query,
|
||||
anchor: { id: first.id, time: DateTime.toEpochMillis(first.time.updated), direction: "previous" },
|
||||
})
|
||||
: undefined,
|
||||
next: last
|
||||
? SessionList.Cursor.make({
|
||||
...query,
|
||||
anchor: { id: last.id, time: DateTime.toEpochMillis(last.time.updated), direction: "next" },
|
||||
})
|
||||
: undefined,
|
||||
},
|
||||
}
|
||||
}),
|
||||
active: () =>
|
||||
sessions.active.pipe(
|
||||
Effect.map((active) =>
|
||||
Object.fromEntries(Array.from(active, (sessionID) => [sessionID, { type: "running" as const }])),
|
||||
),
|
||||
),
|
||||
create: (input) =>
|
||||
sessions.create({
|
||||
id: input?.id,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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",
|
||||
|
||||
@@ -145,7 +145,10 @@ export interface Interface {
|
||||
readonly from?: SessionMessage.ID
|
||||
readonly to?: SessionMessage.ID
|
||||
readonly context?: number
|
||||
}) => Effect.Effect<readonly FileDiff.Info[], NotFoundError | MessageNotFoundError | TurnRangeError | Snapshot.Error>
|
||||
}) => Effect.Effect<
|
||||
readonly FileDiff.Info[],
|
||||
NotFoundError | MessageNotFoundError | TurnRangeError | Snapshot.Error | FSUtil.DirectoryError
|
||||
>
|
||||
/**
|
||||
* Durable admitted session work not yet visible in projected history,
|
||||
* ordered by admission. Includes unpromoted user and synthetic inputs and
|
||||
@@ -195,7 +198,7 @@ export interface Interface {
|
||||
agents?: PromptInput.Prompt["agents"]
|
||||
skills?: PromptInput.Prompt["skills"]
|
||||
delivery?: SessionInbox.Delivery
|
||||
}) => Effect.Effect<void, NotFoundError | Command.NotFoundError | Command.ExecutionError>
|
||||
}) => Effect.Effect<void, NotFoundError | Command.NotFoundError | Command.ExecutionError | FSUtil.DirectoryError>
|
||||
readonly shell: (
|
||||
input: Parameters<Session.Handle["shell"]>[0] & { sessionID: SessionSchema.ID },
|
||||
) => ReturnType<Session.Handle["shell"]>
|
||||
@@ -218,8 +221,13 @@ export interface Interface {
|
||||
sessionID: SessionSchema.ID
|
||||
messageID: SessionMessage.ID
|
||||
files?: boolean
|
||||
}) => Effect.Effect<SessionSchema.Revert, NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error>
|
||||
readonly clear: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error>
|
||||
}) => Effect.Effect<
|
||||
SessionSchema.Revert,
|
||||
NotFoundError | MessageNotFoundError | BusyError | Snapshot.Error | FSUtil.DirectoryError
|
||||
>
|
||||
readonly clear: (
|
||||
sessionID: SessionSchema.ID,
|
||||
) => Effect.Effect<void, NotFoundError | BusyError | Snapshot.Error | FSUtil.DirectoryError>
|
||||
readonly commit: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError | BusyError>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { contentFilterError, toSessionError } from "./to-session-error.js"
|
||||
import { toSessionError } from "./to-session-error.js"
|
||||
import { SessionUsage } from "./usage.js"
|
||||
|
||||
export type Settings = {
|
||||
@@ -540,7 +540,10 @@ export const layer = Layer.effect(
|
||||
message: "Compaction summary reached the output token limit",
|
||||
})
|
||||
case "content-filter":
|
||||
return unusable(contentFilterError("Compaction summary was blocked by the provider", event.reason))
|
||||
return unusable({
|
||||
type: "provider.content-filter",
|
||||
message: "Compaction summary was blocked by the provider",
|
||||
})
|
||||
default:
|
||||
return Effect.succeed(streamed)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * as SessionGenerate from "./generate.js"
|
||||
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import { LLMClient, Message, type AIError } from "@opencode/ai"
|
||||
import { Effect } from "effect"
|
||||
import { Database } from "../database/database.js"
|
||||
@@ -14,7 +15,12 @@ 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
|
||||
export type Error =
|
||||
| AgentNotFoundError
|
||||
| Instructions.InitializationBlocked
|
||||
| SessionRunnerModel.Error
|
||||
| AIError
|
||||
| FSUtil.DirectoryError
|
||||
|
||||
/** Generates text from current Session context without mutating the Session. */
|
||||
export const generate = Effect.fn("SessionGenerate.generate")(function* (input: {
|
||||
|
||||
@@ -45,7 +45,11 @@ export interface Interface {
|
||||
delivery?: SessionInbox.Delivery
|
||||
}) => Effect.Effect<
|
||||
void,
|
||||
NotFoundError | DestinationNotFoundError | DestinationNotDirectoryError | DestinationUnavailableError
|
||||
| NotFoundError
|
||||
| DestinationNotFoundError
|
||||
| DestinationNotDirectoryError
|
||||
| DestinationUnavailableError
|
||||
| FSUtil.DirectoryError
|
||||
>
|
||||
}
|
||||
|
||||
@@ -91,7 +95,8 @@ const layer = Layer.effect(
|
||||
yield* locations.contextEffect(destination.location).pipe(
|
||||
Effect.scoped,
|
||||
Effect.catchCause((cause) => {
|
||||
if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause)
|
||||
if (Cause.hasInterruptsOnly(cause))
|
||||
return Effect.failCause(Cause.fromReasons<never>(cause.reasons.filter(Cause.isInterruptReason)))
|
||||
return Effect.logWarning("session move destination unavailable", { directory, cause }).pipe(
|
||||
Effect.andThen(Effect.fail(new DestinationUnavailableError({ directory }))),
|
||||
)
|
||||
|
||||
@@ -13,6 +13,8 @@ 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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * as SessionRunner from "./index.js"
|
||||
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import type { AIError } from "@opencode/ai"
|
||||
import { Context, Data, Effect } from "effect"
|
||||
import { SessionSchema } from "../schema.js"
|
||||
@@ -15,6 +16,7 @@ export type RunError =
|
||||
| AgentNotFoundError
|
||||
| StepFailedError
|
||||
| Instructions.InitializationBlocked
|
||||
| FSUtil.DirectoryError
|
||||
|
||||
export type Continuation = { readonly step: number }
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ 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 = {
|
||||
@@ -544,7 +543,15 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
}
|
||||
if (event.reason.normalized === "content-filter") {
|
||||
providerFailed = true
|
||||
yield* failAssistant(contentFilterError("Provider blocked the response", event.reason))
|
||||
yield* failAssistant({
|
||||
type: "provider.content-filter",
|
||||
message: [
|
||||
`Provider blocked the response${event.reason.category === undefined ? "" : ` (${event.reason.category})`}`,
|
||||
event.reason.explanation,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(": "),
|
||||
})
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
BusyError,
|
||||
CompactionConflictError,
|
||||
InboxConflictError,
|
||||
MessageNotFoundError,
|
||||
NotFoundError,
|
||||
PromptConflictError,
|
||||
SyntheticConflictError,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AIError, ToolFailure, type FinishReasonDetails } from "@opencode/ai"
|
||||
import { AIError, ToolFailure } from "@opencode/ai"
|
||||
import { Tool } from "@opencode/schema/tool"
|
||||
import { SessionError } from "@opencode/schema/session-error"
|
||||
import { Permission } from "../permission.js"
|
||||
@@ -85,15 +85,6 @@ 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 {
|
||||
|
||||
@@ -68,6 +68,7 @@ export class StrategyUnavailableError extends Schema.TaggedError<StrategyUnavail
|
||||
) {}
|
||||
|
||||
export type Error =
|
||||
| FSUtil.DirectoryError
|
||||
| Project.NotFoundError
|
||||
| SourceDirectoryNotFoundError
|
||||
| DestinationExistsError
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import { Bus } from "@opencode/core/bus"
|
||||
import { Image } from "@opencode/core/image"
|
||||
import { LocationServiceMap } from "@opencode/core/location-service-map"
|
||||
@@ -24,7 +25,7 @@ export const promptLocationNode = makeGlobalNode({
|
||||
Bus.node.replace(Layer.succeed(Bus.Service, bus)),
|
||||
Plugin.node.replace(Layer.mock(Plugin.Service, { awaitActivation: Effect.void })),
|
||||
],
|
||||
}) as Layer.Layer<LocationServices>,
|
||||
}) as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
)
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Effect, FileSystem, Layer, type PlatformError } from "effect"
|
||||
import { LayerNodePlatform } from "@opencode/util/effect/app-node-platform"
|
||||
|
||||
/** Fails platform realPath for selected paths, so FSUtil's own error classification still runs. */
|
||||
export function blockRealPath(
|
||||
fs: FileSystem.FileSystem,
|
||||
failure: (target: string) => PlatformError.PlatformError | undefined,
|
||||
) {
|
||||
return LayerNodePlatform.filesystem.replace(
|
||||
Layer.succeed(FileSystem.FileSystem, {
|
||||
...fs,
|
||||
realPath: (target) => {
|
||||
const error = failure(target)
|
||||
return error ? Effect.fail(error) : fs.realPath(target)
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Context, Deferred, Duration, Effect, Fiber, Layer, LayerMap, RcMap, Schema } from "effect"
|
||||
import { TestClock } from "effect/testing"
|
||||
@@ -65,7 +66,7 @@ const locations = Layer.effect(
|
||||
Layer.provideMerge(Form.layer),
|
||||
Layer.provide(Layer.succeed(Bus.Service, bus)),
|
||||
Layer.fresh,
|
||||
) as unknown as Layer.Layer<LocationServices>,
|
||||
) as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
{ idleTimeToLive: Duration.infinity },
|
||||
)
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { FileSystem } from "@opencode/core/filesystem"
|
||||
import { Location } from "@opencode/core/location"
|
||||
import { AbsolutePath, RelativePath } from "@opencode/core/schema"
|
||||
import { Workspace } from "@opencode/core/workspace"
|
||||
import { blockRealPath } from "./fixture/realpath"
|
||||
import { location } from "./fixture/location"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
import { it } from "./lib/effect"
|
||||
@@ -118,12 +119,7 @@ describe("FileSystem", () => {
|
||||
Location.node.replace(
|
||||
Layer.succeed(Location.Service, location({ directory: AbsolutePath.make(directory) })),
|
||||
),
|
||||
FSUtil.node.replace(
|
||||
Layer.succeed(FSUtil.Service, {
|
||||
...filesystem,
|
||||
realPath: (target) => (target === directory ? Effect.fail(cause) : filesystem.realPath(target)),
|
||||
}),
|
||||
),
|
||||
blockRealPath(filesystem, (target) => (target === directory ? cause : undefined)),
|
||||
],
|
||||
}),
|
||||
),
|
||||
@@ -145,7 +141,7 @@ describe("FileSystem", () => {
|
||||
}
|
||||
expect(result.cause.reasons.filter(Cause.isDieReason)).toMatchObject([{ defect: cause }])
|
||||
}
|
||||
}).pipe(Effect.provide(LayerNode.compile(FSUtil.node))),
|
||||
}).pipe(Effect.provide(LayerNode.compile(FSUtil.node).pipe(Layer.fresh))),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { describe, expect } from "bun:test"
|
||||
@@ -67,7 +68,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>,
|
||||
) as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
{ idleTimeToLive: Duration.infinity },
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { expect } from "bun:test"
|
||||
import { Plugin } from "@opencode/core/plugin"
|
||||
import { PluginHost } from "@opencode/core/plugin/host"
|
||||
import { PluginPromise } from "@opencode/core/plugin/promise"
|
||||
import { Session } from "@opencode/core/session"
|
||||
import { Effect } from "effect"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { PluginTestLayer } from "./fixture"
|
||||
|
||||
const it = testEffect(PluginTestLayer)
|
||||
|
||||
it.live("Effect plugins list filtered sessions with server-style cursors", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
const context = yield* PluginHost.make(plugins)
|
||||
const first = yield* context.session.create({ title: "match one" })
|
||||
yield* context.session.create({ title: "different" })
|
||||
const second = yield* context.session.create({ title: "match two" })
|
||||
const query = { directory: context.location.directory, limit: 1, order: "asc" as const, search: "match" }
|
||||
const page = yield* context.session.list(query)
|
||||
expect(page.data.map((session) => session.id)).toEqual([first.id])
|
||||
expect(page.cursor.next).toBeDefined()
|
||||
|
||||
const next = yield* context.session.list({ cursor: page.cursor.next, limit: 1 })
|
||||
expect(next.data.map((session) => session.id)).toEqual([second.id])
|
||||
const previous = yield* context.session.list({ cursor: next.cursor.previous, limit: 1 })
|
||||
expect(previous.data.map((session) => session.id)).toEqual([first.id])
|
||||
expect(
|
||||
yield* context.session.list({ cursor: "invalid" as NonNullable<typeof page.cursor.next> }).pipe(Effect.flip),
|
||||
).toMatchObject({ message: "Invalid cursor" })
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("Promise plugins list sessions and report active sessions", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
const sessions = yield* Session.Service
|
||||
const context = yield* PluginHost.make(plugins)
|
||||
const created = yield* context.session.create({ title: "running" })
|
||||
const active = yield* PluginHost.make(plugins).pipe(
|
||||
Effect.provideService(
|
||||
Session.Service,
|
||||
Session.Service.of({ ...sessions, active: Effect.succeed(new Set([created.id])) }),
|
||||
),
|
||||
)
|
||||
|
||||
yield* PluginPromise.fromPromise({
|
||||
id: "test.session-list-active",
|
||||
async setup(ctx) {
|
||||
const page = await ctx.session.list({ directory: ctx.location.directory, search: "running", limit: 1 })
|
||||
expect(page.data.map((session) => session.id)).toEqual([created.id])
|
||||
expect(page.cursor.next).toBeDefined()
|
||||
expect(await ctx.session.list({ cursor: page.cursor.next, limit: 1 })).toMatchObject({ data: [] })
|
||||
expect(await ctx.session.active()).toEqual({ [created.id]: { type: "running" } })
|
||||
},
|
||||
}).effect(active)
|
||||
}),
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { LLMClient, LLMEvent, LanguageModel, type LLMRequest } from "@opencode/ai"
|
||||
import { OpenAIChat } from "@opencode/ai/protocols"
|
||||
@@ -58,7 +59,7 @@ const locations = Layer.effect(
|
||||
Layer.provide(client),
|
||||
Layer.provide(config),
|
||||
Layer.provide(models),
|
||||
) as unknown as Layer.Layer<LocationServices>,
|
||||
) as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
),
|
||||
)
|
||||
const it = testEffect(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { AIError, TransportError } from "@opencode/ai"
|
||||
import { Database } from "@opencode/core/database/database"
|
||||
@@ -1371,7 +1372,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>,
|
||||
runner as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
),
|
||||
)
|
||||
return yield* Layer.buildWithScope(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import { describe, expect } from "bun:test"
|
||||
import path from "path"
|
||||
import { chmod, mkdir, readdir, rm } from "fs/promises"
|
||||
@@ -58,7 +59,7 @@ const itWithActiveExecution = testEffect(
|
||||
replacements: [Project.node.replace(globalProjectNode), offlineModels],
|
||||
}),
|
||||
Layer.succeed(SessionRunner.Service, { drain: () => Effect.never }),
|
||||
) as unknown as Layer.Layer<LocationServices>,
|
||||
) as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -68,7 +69,11 @@ const itWithActiveExecution = testEffect(
|
||||
const unavailableLocations = Layer.effect(
|
||||
LocationServiceMap.Service,
|
||||
LayerMap.make(
|
||||
() => Layer.effectDiscard(Effect.fail(new Error("broken location"))) as unknown as Layer.Layer<LocationServices>,
|
||||
() =>
|
||||
Layer.effectDiscard(Effect.die(new Error("broken location"))) as unknown as Layer.Layer<
|
||||
LocationServices,
|
||||
FSUtil.DirectoryError
|
||||
>,
|
||||
),
|
||||
)
|
||||
const itWithUnavailableDestination = testEffect(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import { DateTime, Effect, Fiber, Layer, LayerMap, Schema, Stream } from "effect"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
@@ -83,7 +84,7 @@ const locations = makeGlobalNode({
|
||||
restore: () => Effect.void,
|
||||
}),
|
||||
Layer.mock(Plugin.Service, { awaitActivation: Effect.void }),
|
||||
).pipe(Layer.fresh) as unknown as Layer.Layer<LocationServices>,
|
||||
).pipe(Layer.fresh) as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
)
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -2716,32 +2716,6 @@ 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,3 +1,4 @@
|
||||
import type { FSUtil } from "@opencode/util/fs-util"
|
||||
import path from "path"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer, LayerMap } from "effect"
|
||||
@@ -49,7 +50,7 @@ const locations = makeGlobalNode({
|
||||
list: () => Effect.succeed([info]),
|
||||
}),
|
||||
Layer.mock(Plugin.Service, { awaitActivation: Effect.void }),
|
||||
) as unknown as Layer.Layer<LocationServices>,
|
||||
) as unknown as Layer.Layer<LocationServices, FSUtil.DirectoryError>,
|
||||
),
|
||||
),
|
||||
deps: [],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode/desktop",
|
||||
"private": true,
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/enterprise",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/gui-extensions",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.20",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"name": "@opencode/http-recorder",
|
||||
"description": "Record and replay Effect HTTP and WebSocket traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/httpapi-codegen",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/latex",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/merman",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/plugin-browser",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"description": "OpenCode's desktop browser plugin",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/plugin",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -152,8 +152,6 @@ export interface SessionHooks {
|
||||
|
||||
export type SessionDomain = Pick<
|
||||
SessionApi<unknown>,
|
||||
| "list"
|
||||
| "active"
|
||||
| "create"
|
||||
| "get"
|
||||
| "remove"
|
||||
|
||||
@@ -579,8 +579,6 @@ export function fromPromise(plugin: Plugin) {
|
||||
register(
|
||||
host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))), options),
|
||||
),
|
||||
list: adaptApiMethod(SessionEndpoints["session.list"], host.session.list),
|
||||
active: adaptApiMethod(SessionEndpoints["session.active"], host.session.active),
|
||||
create: adaptApiMethod(SessionEndpoints["session.create"], host.session.create),
|
||||
get: adaptApiMethod(SessionEndpoints["session.get"], host.session.get),
|
||||
remove: adaptApiMethod(SessionEndpoints["session.remove"], host.session.remove),
|
||||
|
||||
@@ -152,8 +152,6 @@ export interface SessionHooks {
|
||||
|
||||
export type SessionDomain = Pick<
|
||||
SessionApi,
|
||||
| "list"
|
||||
| "active"
|
||||
| "create"
|
||||
| "get"
|
||||
| "remove"
|
||||
|
||||
@@ -19053,10 +19053,6 @@
|
||||
"WorktreeErrorEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
@@ -19082,7 +19078,7 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["_tag", "name", "data"],
|
||||
"required": ["name", "data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/protocol",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { InvalidRequestError, SessionNotFoundError } from "./errors.js"
|
||||
import {
|
||||
DirectoryNotFoundError,
|
||||
DirectoryAccessDeniedError,
|
||||
InvalidRequestError,
|
||||
SessionNotFoundError,
|
||||
} from "./errors.js"
|
||||
import { makeDefaultApi } from "./api.js"
|
||||
import type { Api } from "./api.js"
|
||||
import type { Context } from "effect"
|
||||
@@ -7,11 +12,12 @@ 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] },
|
||||
{ error: [InvalidRequestError, SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError] },
|
||||
) {}
|
||||
|
||||
type ClientApiShape = Api<
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
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",
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { Model } from "@opencode/schema/model"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { InvalidRequestError, ServiceUnavailableError } from "../errors.js"
|
||||
import {
|
||||
DirectoryNotFoundError,
|
||||
DirectoryAccessDeniedError,
|
||||
InvalidRequestError,
|
||||
ServiceUnavailableError,
|
||||
} from "../errors.js"
|
||||
|
||||
export const GenerateGroup = HttpApiGroup.make("server.generate")
|
||||
.add(
|
||||
@@ -13,7 +18,7 @@ export const GenerateGroup = HttpApiGroup.make("server.generate")
|
||||
success: Schema.Struct({
|
||||
data: Schema.Struct({ text: Schema.String }),
|
||||
}).annotate({ identifier: "GenerateTextResponse" }),
|
||||
error: [InvalidRequestError, ServiceUnavailableError],
|
||||
error: [InvalidRequestError, ServiceUnavailableError, DirectoryNotFoundError, DirectoryAccessDeniedError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.generate.text",
|
||||
|
||||
@@ -6,7 +6,12 @@ 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 { PermissionNotFoundError, SessionNotFoundError } from "../errors.js"
|
||||
import {
|
||||
DirectoryNotFoundError,
|
||||
DirectoryAccessDeniedError,
|
||||
PermissionNotFoundError,
|
||||
SessionNotFoundError,
|
||||
} from "../errors.js"
|
||||
import { LocationQuery, locationQueryOpenApi } from "./location.js"
|
||||
|
||||
export const makePermissionGroup = <
|
||||
@@ -89,7 +94,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,
|
||||
error: [SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.permission.list",
|
||||
|
||||
@@ -5,13 +5,21 @@ import { Session } from "@opencode/schema/session"
|
||||
import { SessionStats } from "@opencode/schema/session-stats"
|
||||
import { InstructionEntry } from "@opencode/schema/instruction-entry"
|
||||
import { Project } from "@opencode/schema/project"
|
||||
import { AbsolutePath, DateTimeUtcFromMillis, NonNegativeInt } from "@opencode/schema/schema"
|
||||
import { SessionList } from "@opencode/schema/session-list"
|
||||
import {
|
||||
AbsolutePath,
|
||||
DateTimeUtcFromMillis,
|
||||
NonNegativeInt,
|
||||
PositiveInt,
|
||||
RelativePath,
|
||||
statics,
|
||||
} from "@opencode/schema/schema"
|
||||
import { Event } from "@opencode/schema/event"
|
||||
import { Context, Effect, Schema, SchemaGetter, Struct } from "effect"
|
||||
import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import {
|
||||
ConflictError,
|
||||
DirectoryNotFoundError,
|
||||
DirectoryAccessDeniedError,
|
||||
CommandExecutionError,
|
||||
CommandNotFoundError,
|
||||
FormAlreadySettledError,
|
||||
@@ -37,8 +45,75 @@ import { FileDiff } from "@opencode/schema/file-diff"
|
||||
import { Form } from "@opencode/schema/form"
|
||||
import { PublicSessionMessage } from "./message.js"
|
||||
|
||||
export const SessionsCursor = SessionList.Cursor
|
||||
export type SessionsCursor = SessionList.Cursor
|
||||
const ParentIDFilter = Schema.Union([
|
||||
Session.ID,
|
||||
Schema.Null.pipe(
|
||||
Schema.encodeTo(Schema.Literal("null"), {
|
||||
decode: SchemaGetter.transform(() => null),
|
||||
encode: SchemaGetter.transform(() => "null" as const),
|
||||
}),
|
||||
),
|
||||
]).annotate({
|
||||
description: "Filter by parent session. Use null to return only root sessions.",
|
||||
})
|
||||
|
||||
const SessionsQueryFields = {
|
||||
limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
|
||||
description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
|
||||
}),
|
||||
order: Schema.optional(Schema.Union([Schema.Literal("asc"), Schema.Literal("desc")])).annotate({
|
||||
description: "Session order for the first page. Use desc for newest first or asc for oldest first.",
|
||||
}),
|
||||
search: Schema.optional(Schema.String),
|
||||
parentID: ParentIDFilter.pipe(Schema.optional),
|
||||
}
|
||||
|
||||
const SessionsDirectoryQuery = Schema.Struct({
|
||||
...SessionsQueryFields,
|
||||
directory: AbsolutePath,
|
||||
})
|
||||
|
||||
const SessionsProjectQuery = Schema.Struct({
|
||||
...SessionsQueryFields,
|
||||
project: Project.ID,
|
||||
subpath: RelativePath.pipe(Schema.optional),
|
||||
})
|
||||
|
||||
const SessionsAllQuery = Schema.Struct(SessionsQueryFields)
|
||||
|
||||
const withCursor = <Fields extends Schema.Struct.Fields>(schema: Schema.Struct<Fields>) =>
|
||||
schema.mapFields((fields) => ({
|
||||
...Struct.omit(fields, ["limit"]),
|
||||
anchor: Session.ListAnchor,
|
||||
}))
|
||||
|
||||
const SessionsCursorInput = Schema.Union([
|
||||
withCursor(SessionsDirectoryQuery),
|
||||
withCursor(SessionsProjectQuery),
|
||||
withCursor(SessionsAllQuery),
|
||||
])
|
||||
const SessionsCursorJson = Schema.fromJsonString(SessionsCursorInput)
|
||||
const encodeSessionsCursor = Schema.encodeSync(SessionsCursorJson)
|
||||
const decodeSessionsCursor = Schema.decodeUnknownEffect(SessionsCursorJson)
|
||||
const invalidCursor = "Invalid cursor" as const
|
||||
|
||||
export const SessionsCursor = Schema.String.pipe(
|
||||
Schema.brand("SessionsCursor"),
|
||||
statics((schema) => {
|
||||
const make = schema.make.bind(schema)
|
||||
return {
|
||||
make: (input: typeof SessionsCursorInput.Type) => make(Encoding.encodeBase64Url(encodeSessionsCursor(input))),
|
||||
parse: (input: string) =>
|
||||
Effect.suspend(() => {
|
||||
const result = Encoding.decodeBase64UrlString(input)
|
||||
return Result.isFailure(result)
|
||||
? Effect.fail(invalidCursor)
|
||||
: decodeSessionsCursor(result.success).pipe(Effect.mapError(() => invalidCursor))
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
export type SessionsCursor = typeof SessionsCursor.Type
|
||||
|
||||
const SessionActive = Schema.Struct({
|
||||
type: Schema.Literal("running"),
|
||||
@@ -85,7 +160,17 @@ const BooleanFromString = Schema.Literals(["true", "false"]).pipe(
|
||||
}),
|
||||
)
|
||||
|
||||
export const SessionsQuery = SessionList.Query
|
||||
const SessionsQueryCursor = SessionsCursor.annotate({
|
||||
description: "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response.",
|
||||
})
|
||||
|
||||
export const SessionsQuery = Schema.Struct({
|
||||
...SessionsQueryFields,
|
||||
directory: AbsolutePath.pipe(Schema.optional),
|
||||
project: Project.ID.pipe(Schema.optional),
|
||||
subpath: RelativePath.pipe(Schema.optional),
|
||||
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>,
|
||||
@@ -297,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],
|
||||
error: [SessionNotFoundError, InvalidRequestError, DirectoryNotFoundError, DirectoryAccessDeniedError],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.move",
|
||||
@@ -507,7 +592,14 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S, FormI ext
|
||||
}),
|
||||
}),
|
||||
success: Schema.Struct({ data: Schema.Array(FileDiff.Info) }),
|
||||
error: [InvalidRequestError, MessageNotFoundError, SessionNotFoundError, UnknownError],
|
||||
error: [
|
||||
InvalidRequestError,
|
||||
MessageNotFoundError,
|
||||
SessionNotFoundError,
|
||||
UnknownError,
|
||||
DirectoryNotFoundError,
|
||||
DirectoryAccessDeniedError,
|
||||
],
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "session.diff",
|
||||
|
||||
@@ -6,8 +6,7 @@ import { ProjectNotFoundError } from "../errors.js"
|
||||
|
||||
const root = "/api/worktree"
|
||||
|
||||
export class WorktreeError extends Schema.TaggedError<WorktreeError>()(
|
||||
"WorktreeError",
|
||||
export class WorktreeError extends Schema.Error<WorktreeError>("WorktreeError")(
|
||||
{
|
||||
name: Schema.Literal("WorktreeError"),
|
||||
data: Schema.Struct({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/schema",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
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)
|
||||
@@ -1,70 +0,0 @@
|
||||
export * as SessionList from "./session-list.js"
|
||||
|
||||
import { Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect"
|
||||
import { Project } from "./project.js"
|
||||
import { Session } from "./session.js"
|
||||
import { AbsolutePath, PositiveInt, RelativePath, statics } from "./schema.js"
|
||||
|
||||
const ParentIDFilter = Schema.Union([
|
||||
Session.ID,
|
||||
Schema.Null.pipe(
|
||||
Schema.encodeTo(Schema.Literal("null"), {
|
||||
decode: SchemaGetter.transform(() => null),
|
||||
encode: SchemaGetter.transform(() => "null" as const),
|
||||
}),
|
||||
),
|
||||
]).annotate({
|
||||
description: "Filter by parent session. Use null to return only root sessions.",
|
||||
})
|
||||
|
||||
const QueryFields = {
|
||||
limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
|
||||
description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
|
||||
}),
|
||||
order: Schema.optional(Schema.Union([Schema.Literal("asc"), Schema.Literal("desc")])).annotate({
|
||||
description: "Session order for the first page. Use desc for newest first or asc for oldest first.",
|
||||
}),
|
||||
search: Schema.optional(Schema.String),
|
||||
parentID: ParentIDFilter.pipe(Schema.optional),
|
||||
}
|
||||
|
||||
const DirectoryQuery = Schema.Struct({ ...QueryFields, directory: AbsolutePath })
|
||||
const ProjectQuery = Schema.Struct({ ...QueryFields, project: Project.ID, subpath: RelativePath.pipe(Schema.optional) })
|
||||
const AllQuery = Schema.Struct(QueryFields)
|
||||
|
||||
const withCursor = <Fields extends Schema.Struct.Fields>(schema: Schema.Struct<Fields>) =>
|
||||
schema.mapFields((fields) => ({ ...Struct.omit(fields, ["limit"]), anchor: Session.ListAnchor }))
|
||||
|
||||
const CursorInput = Schema.Union([withCursor(DirectoryQuery), withCursor(ProjectQuery), withCursor(AllQuery)])
|
||||
const CursorJson = Schema.fromJsonString(CursorInput)
|
||||
const encodeCursor = Schema.encodeSync(CursorJson)
|
||||
const decodeCursor = Schema.decodeUnknownEffect(CursorJson)
|
||||
const invalidCursor = "Invalid cursor" as const
|
||||
|
||||
export const Cursor = Schema.String.pipe(
|
||||
Schema.brand("SessionsCursor"),
|
||||
statics((schema) => {
|
||||
const make = schema.make.bind(schema)
|
||||
return {
|
||||
make: (input: typeof CursorInput.Type) => make(Encoding.encodeBase64Url(encodeCursor(input))),
|
||||
parse: (input: string) =>
|
||||
Effect.suspend(() => {
|
||||
const result = Encoding.decodeBase64UrlString(input)
|
||||
return Result.isFailure(result)
|
||||
? Effect.fail(invalidCursor)
|
||||
: decodeCursor(result.success).pipe(Effect.mapError(() => invalidCursor))
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
export type Cursor = typeof Cursor.Type
|
||||
|
||||
export const Query = Schema.Struct({
|
||||
...QueryFields,
|
||||
directory: AbsolutePath.pipe(Schema.optional),
|
||||
project: Project.ID.pipe(Schema.optional),
|
||||
subpath: RelativePath.pipe(Schema.optional),
|
||||
cursor: Cursor.annotate({
|
||||
description: "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response.",
|
||||
}).pipe(Schema.optional),
|
||||
}).annotate({ identifier: "SessionsQuery" })
|
||||
@@ -490,6 +490,15 @@ 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,6 +16,7 @@ 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"
|
||||
@@ -70,6 +71,8 @@ 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,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"name": "@opencode/sdk",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -57,7 +57,11 @@ 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> = yield* LayerMap.make(
|
||||
const instances: LayerMap.LayerMap<
|
||||
ReturnType<typeof key>,
|
||||
Instance.Services,
|
||||
Instance.Error
|
||||
> = yield* LayerMap.make(
|
||||
(input: ReturnType<typeof key>) =>
|
||||
Layer.unwrap(
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/server",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -7,6 +7,7 @@ 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* () {
|
||||
@@ -15,19 +16,23 @@ 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)),
|
||||
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,
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -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)
|
||||
? Effect.failCause(Cause.fromReasons<never>(cause.reasons.filter(Cause.isInterruptReason)))
|
||||
: Effect.fail(new ServiceUnavailableError({ message: Cause.pretty(cause), service: "location" })),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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 { response, sessionInfo } from "../location"
|
||||
import { directoryErrors, 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))
|
||||
).pipe(instances.provide(session), directoryErrors)
|
||||
return { data: requests }
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
@@ -299,6 +300,7 @@ 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()
|
||||
}),
|
||||
@@ -335,6 +337,7 @@ 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,
|
||||
),
|
||||
}
|
||||
}),
|
||||
@@ -370,6 +373,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||
}),
|
||||
),
|
||||
),
|
||||
directoryErrors,
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
@@ -388,6 +392,7 @@ 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()
|
||||
}),
|
||||
@@ -471,6 +476,7 @@ 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,
|
||||
),
|
||||
}
|
||||
}),
|
||||
@@ -485,6 +491,7 @@ 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()
|
||||
}),
|
||||
@@ -527,6 +534,7 @@ 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,
|
||||
),
|
||||
}
|
||||
}),
|
||||
@@ -588,15 +596,14 @@ 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(
|
||||
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(
|
||||
directoryErrors,
|
||||
Effect.mapError((error) =>
|
||||
error._tag === "Session.NotFoundError"
|
||||
? missingSession(error)
|
||||
: new ServiceUnavailableError({ message: error.message, service: "session generation" }),
|
||||
),
|
||||
)
|
||||
return { data: { text } }
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
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 { InvalidRequestError } from "@opencode/protocol/errors"
|
||||
import { DirectoryNotFoundError, DirectoryAccessDeniedError, InvalidRequestError } from "@opencode/protocol/errors"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { HttpServerRequest } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
@@ -12,8 +13,24 @@ 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, FSUtil.DirectoryError> =>
|
||||
error instanceof FSUtil.DirectoryNotFoundError || error instanceof FSUtil.DirectoryAccessDeniedError,
|
||||
(error): Effect.Effect<never, DirectoryNotFoundError | DirectoryAccessDeniedError> =>
|
||||
Effect.fail(
|
||||
error instanceof FSUtil.DirectoryNotFoundError
|
||||
? new DirectoryNotFoundError({ directory: AbsolutePath.make(error.directory), message: error.message })
|
||||
: new DirectoryAccessDeniedError({ directory: AbsolutePath.make(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
|
||||
@@ -61,7 +78,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))))
|
||||
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))), directoryErrors)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { Instance } from "@opencode/core/instance/service"
|
||||
import { LocationServiceMap } from "@opencode/core/location-services"
|
||||
import { Session } from "@opencode/core/session"
|
||||
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors"
|
||||
import {
|
||||
DirectoryNotFoundError,
|
||||
DirectoryAccessDeniedError,
|
||||
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 { requestRef, sessionInfo, type LocationServices } from "../location"
|
||||
import { directoryErrors, requestRef, sessionInfo, type LocationServices } from "../location"
|
||||
|
||||
export class FormLocationMiddleware extends HttpApiMiddleware.Service<
|
||||
FormLocationMiddleware,
|
||||
{ provides: LocationServices }
|
||||
>()("@opencode/HttpApiFormLocation", {
|
||||
error: [InvalidRequestError, SessionNotFoundError],
|
||||
error: [InvalidRequestError, SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError],
|
||||
}) {}
|
||||
|
||||
export const formLocationLayer = Layer.effect(
|
||||
@@ -29,11 +34,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))))
|
||||
return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))), directoryErrors)
|
||||
}
|
||||
|
||||
const session = yield* sessionInfo(sessions, route.params.sessionID)
|
||||
return yield* effect.pipe(instances.provide(session))
|
||||
return yield* effect.pipe(instances.provide(session), directoryErrors)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -3,14 +3,19 @@ import { Session } from "@opencode/core/session"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpRouter } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { InvalidRequestError, SessionNotFoundError } from "@opencode/protocol/errors"
|
||||
import { sessionInfo, type LocationServices } from "../location"
|
||||
import {
|
||||
DirectoryNotFoundError,
|
||||
DirectoryAccessDeniedError,
|
||||
InvalidRequestError,
|
||||
SessionNotFoundError,
|
||||
} from "@opencode/protocol/errors"
|
||||
import { directoryErrors, sessionInfo, type LocationServices } from "../location"
|
||||
|
||||
export class SessionLocationMiddleware extends HttpApiMiddleware.Service<
|
||||
SessionLocationMiddleware,
|
||||
{ provides: LocationServices }
|
||||
>()("@opencode/HttpApiSessionLocation", {
|
||||
error: [InvalidRequestError, SessionNotFoundError],
|
||||
error: [InvalidRequestError, SessionNotFoundError, DirectoryNotFoundError, DirectoryAccessDeniedError],
|
||||
}) {}
|
||||
|
||||
export const sessionLocationLayer = Layer.effect(
|
||||
@@ -23,7 +28,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))
|
||||
return yield* effect.pipe(instances.provide(session), directoryErrors)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -5,7 +5,11 @@ 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, Schedule, Schema } from "effect"
|
||||
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 { blockRealPath } from "../../core/test/fixture/realpath"
|
||||
import { tmpdir } from "../../core/test/fixture/tmpdir"
|
||||
import { it } from "../../core/test/lib/effect"
|
||||
import { ServerFetch } from "../src/fetch"
|
||||
@@ -18,6 +22,99 @@ 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: [
|
||||
blockRealPath(filesystem, (target) => (blocked.has(target) && input.code !== "ENOENT" ? cause : undefined)),
|
||||
],
|
||||
},
|
||||
)
|
||||
// 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) {
|
||||
@@ -105,7 +202,9 @@ 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")
|
||||
}),
|
||||
|
||||
@@ -25,12 +25,10 @@ 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}` },
|
||||
})
|
||||
@@ -111,7 +109,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({ _tag: "WorktreeError", name: "WorktreeError" })
|
||||
).rejects.toMatchObject({ 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 })
|
||||
@@ -269,11 +267,7 @@ it.live(
|
||||
body: JSON.stringify({ projectID, directory: custom.directory, force: false }),
|
||||
})
|
||||
expect(failure.status).toBe(400)
|
||||
expect(await failure.json()).toMatchObject({
|
||||
_tag: "WorktreeError",
|
||||
name: "WorktreeError",
|
||||
data: { forceRequired: true },
|
||||
})
|
||||
expect(await failure.json()).toMatchObject({ 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,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/session-ui",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -308,25 +308,15 @@ 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 working =
|
||||
const thinking =
|
||||
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
|
||||
isActive &&
|
||||
status.type === "busy" &&
|
||||
lastAssistant?.time.completed === undefined &&
|
||||
!lastAssistant?.error &&
|
||||
!lastAssistant?.retry
|
||||
const thinking =
|
||||
working &&
|
||||
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
|
||||
!lastAssistant?.retry &&
|
||||
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 }))
|
||||
@@ -336,7 +326,6 @@ 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,54 +264,6 @@ 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,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/simulation",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/stats-app",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/stats-core",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/stats-server",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/theme",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/tui",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/ui",
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.21",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
Loaded 100 of 107 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user