mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-02 05:26:16 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbe1fbbc40 | ||
|
|
d259ae7163 | ||
|
|
bb381e8bdd | ||
|
|
81efb7f146 | ||
|
|
77c5f7a516 | ||
|
|
4a18a86653 | ||
|
|
c07905012f | ||
|
|
e067948dd5 | ||
|
|
64744ce034 | ||
|
|
840cd232a8 | ||
|
|
ce97dd2805 | ||
|
|
f00390665f | ||
|
|
a241b41545 | ||
|
|
8c46c09f8e | ||
|
|
cb7f6b7ba4 | ||
|
|
81c967c4e1 |
No files matched your search
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"packages/ai": {
|
||||
"name": "@opencode/ai",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "3.1057.0",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -55,7 +55,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
@@ -112,7 +112,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode/cli",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode.cjs",
|
||||
"opencode2": "./bin/opencode2.cjs",
|
||||
@@ -178,7 +178,7 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@opencode/client",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/protocol": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -204,7 +204,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode/codemode",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -217,7 +217,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode/console-app",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -253,7 +253,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode/console-core",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode/console-function",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opencode/console-core": "workspace:*",
|
||||
@@ -297,7 +297,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode/console-mail",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -321,7 +321,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode/console-support",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode/console-core": "workspace:*",
|
||||
@@ -341,7 +341,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode/core",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@ai-sdk/cohere": "3.0.27",
|
||||
"@ai-sdk/gateway": "3.0.104",
|
||||
@@ -410,7 +410,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode/desktop",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"electron-context-menu": "5.0.0",
|
||||
@@ -455,7 +455,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode/enterprise",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/sdk": "1.18.21",
|
||||
@@ -492,7 +492,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode/function",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -508,7 +508,7 @@
|
||||
},
|
||||
"packages/gui-extensions": {
|
||||
"name": "@opencode/gui-extensions",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
"@dnd-kit/dom": "0.5.0",
|
||||
@@ -553,7 +553,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode/http-recorder",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@effect/platform-node-shared": "4.0.0-rc.112",
|
||||
},
|
||||
@@ -572,7 +572,7 @@
|
||||
},
|
||||
"packages/httpapi-codegen": {
|
||||
"name": "@opencode/httpapi-codegen",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"prettier": "3.6.2",
|
||||
@@ -585,7 +585,7 @@
|
||||
},
|
||||
"packages/latex": {
|
||||
"name": "@opencode/latex",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -599,7 +599,7 @@
|
||||
},
|
||||
"packages/merman": {
|
||||
"name": "@opencode/merman",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -614,7 +614,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode/plugin",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode/ai": "workspace:*",
|
||||
@@ -653,7 +653,7 @@
|
||||
},
|
||||
"packages/plugin-browser": {
|
||||
"name": "@opencode/plugin-browser",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -683,7 +683,7 @@
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@opencode/protocol",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/schema": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -698,7 +698,7 @@
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@opencode/schema",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -722,7 +722,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@opencode/sdk",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -743,7 +743,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode/server",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@effect/platform-node-shared": "catalog:",
|
||||
@@ -765,7 +765,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode/session-ui",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode/client": "workspace:*",
|
||||
@@ -800,7 +800,7 @@
|
||||
},
|
||||
"packages/simulation": {
|
||||
"name": "@opencode/simulation",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/ai": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -820,7 +820,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode/stats-app",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -854,7 +854,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode/stats-core",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -873,7 +873,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode/stats-server",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -919,7 +919,7 @@
|
||||
},
|
||||
"packages/theme": {
|
||||
"name": "@opencode/theme",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opentui/core": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -933,7 +933,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode/tui",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -967,7 +967,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode/ui",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -1002,7 +1002,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode/util",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -1040,7 +1040,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode/web",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1081,7 +1081,7 @@
|
||||
},
|
||||
"services/update": {
|
||||
"name": "@opencode/update",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"jose": "6.0.11",
|
||||
"semver": "catalog:",
|
||||
|
||||
+22
-2
@@ -13,7 +13,12 @@
|
||||
opencode,
|
||||
}:
|
||||
let
|
||||
electron = callPackage ./electron.nix { };
|
||||
electronPin =
|
||||
(lib.pipe ../packages/desktop/package.json [
|
||||
builtins.readFile
|
||||
builtins.fromJSON
|
||||
]).devDependencies.electron;
|
||||
electron = callPackage ./electron.nix { inherit electronPin; };
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "opencode-desktop";
|
||||
@@ -66,7 +71,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
''
|
||||
# https://github.com/electron/electron/issues/31121
|
||||
# mac builds use a .app bundle which doesnt have this issue
|
||||
+ lib.optionalString stdenv.isLinux ''
|
||||
+ lib.optionalString stdenv.hostPlatform.isLinux ''
|
||||
substituteInPlace \
|
||||
packages/desktop/src/main/windows/appearance.ts \
|
||||
packages/desktop/src/main/service/desktop-cli.ts \
|
||||
@@ -74,6 +79,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
'';
|
||||
|
||||
preBuild = ''
|
||||
echo "electron ${electron.version} from nixpkgs ${lib.version}, package.json pins ${electronPin}"
|
||||
cp -r "${electron.dist}" $HOME/.electron-dist
|
||||
chmod -R u+w $HOME/.electron-dist
|
||||
|
||||
@@ -89,8 +95,15 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
|
||||
export OPENCODE_CLI_DIST="$TMPDIR/desktop-cli"
|
||||
cli_package=$(bun -e 'import { getCurrentCli } from "./scripts/utils.ts"; console.log(getCurrentCli().package.replace("@opencode/", ""))')
|
||||
# copyBuiltCliToResources joins this dist with the npm package name getCurrentCli()
|
||||
# reports, not the Nix build's name. It reads only .version from the manifest and
|
||||
# writes it as opencode-cli.version beside the binary.
|
||||
mkdir -p "$OPENCODE_CLI_DIST/$cli_package/bin"
|
||||
cp ${lib.getExe opencode} "$OPENCODE_CLI_DIST/$cli_package/bin/opencode"
|
||||
# OPENCODE_VERSION is what the bundled CLI prints for --version, so the manifest
|
||||
# and the executable cannot drift.
|
||||
bun -e 'await Bun.write(process.argv[1], JSON.stringify({ version: process.env.OPENCODE_VERSION }) + "\n")' \
|
||||
"$OPENCODE_CLI_DIST/$cli_package/package.json"
|
||||
|
||||
bun run build
|
||||
npx electron-builder --dir \
|
||||
@@ -138,6 +151,13 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
"libc.musl-x86_64.so.1"
|
||||
];
|
||||
|
||||
passthru = {
|
||||
# electronVersion is what ships; electronPin is what packages/desktop/package.json
|
||||
# asks for. They differ whenever nixpkgs carries no release of the pinned minor.
|
||||
electronVersion = electron.version;
|
||||
inherit electronPin;
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "OpenCode Desktop App";
|
||||
mainProgram = "opencode-desktop";
|
||||
|
||||
+8
-10
@@ -1,12 +1,10 @@
|
||||
{ callPackage, path }:
|
||||
{ lib, pkgs, electronPin }:
|
||||
let
|
||||
version = (builtins.fromJSON (builtins.readFile ../packages/desktop/package.json)).devDependencies.electron;
|
||||
# Nixpkgs owns the release hashes, so bumping the pin no longer means editing this repo. Only the
|
||||
# major is delegated, so what gets built trails the pin whenever nixpkgs has not shipped it yet.
|
||||
# That is safe: the bundle ships one native addon, node-pty's Node-API prebuild, and
|
||||
# only the win32 WSL runtime loads it, so nothing in the main process binds the
|
||||
# Electron ABI.
|
||||
major = lib.versions.major electronPin;
|
||||
in
|
||||
(callPackage (path + "/pkgs/development/tools/electron/binary/generic.nix") { }) version {
|
||||
# Electron 42.10.1 SHASUMS256.txt; update with the desktop package version.
|
||||
aarch64-linux = "20e68d6c4e47f3ebf59de7c6b1f8b8bec6a6ebda6a451132f9b465f3f13ce467";
|
||||
x86_64-linux = "2452b27112d92387471fa2488aafac85d79ea3f2ee1216c0abd5150d6c12362b";
|
||||
aarch64-darwin = "ac7194a3dfd81930ba35355c01620262c1254752859b42dcb8f4b9e4d174a871";
|
||||
# fetchzip hashes the unpacked headers, not the release tarball.
|
||||
headers = "sha256-4eUy3BZVvxTl7KUOsxio7769lL6ag/ecbeK+qLURWMI=";
|
||||
}
|
||||
pkgs."electron_${major}-bin" or (throw "nixpkgs ${lib.version} carries no prebuilt electron ${major}: run `nix flake update nixpkgs`, or pin a major nixpkgs still carries")
|
||||
+22
-6
@@ -12,7 +12,7 @@
|
||||
installShellFiles,
|
||||
versionCheckHook,
|
||||
writableTmpDirAsHomeHook,
|
||||
node_modules ? callPackage ./node-modules.nix { },
|
||||
node_modules ? callPackage ./node_modules.nix { },
|
||||
}:
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
pname = "opencode";
|
||||
@@ -85,14 +85,30 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
'';
|
||||
|
||||
postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) ''
|
||||
# trick yargs into also generating zsh completions
|
||||
# v2 dropped the `completion` subcommand; --completions is the global flag.
|
||||
# --completions also accepts sh, which emits the same script as bash.
|
||||
# staged to files, substitute below rejects anything that is not a regular file
|
||||
$out/bin/opencode --completions bash > opencode.bash
|
||||
$out/bin/opencode --completions zsh > _opencode
|
||||
$out/bin/opencode --completions fish > opencode.fish
|
||||
|
||||
installShellCompletion --cmd opencode \
|
||||
--bash <($out/bin/opencode completion) \
|
||||
--zsh <(SHELL=/bin/zsh $out/bin/opencode completion)
|
||||
--bash opencode.bash \
|
||||
--fish opencode.fish \
|
||||
--zsh _opencode
|
||||
|
||||
# OPENCODE_CLI_NAME is a build-time define, so the opencode2 copies are
|
||||
# renamed rather than regenerated. --replace-fail is a global literal
|
||||
# substitution, so any lowercase opencode that later appears in a
|
||||
# description or help text ships as opencode2 in the opencode2 copy.
|
||||
substitute opencode.bash opencode2.bash --replace-fail opencode opencode2
|
||||
substitute _opencode _opencode2 --replace-fail opencode opencode2
|
||||
substitute opencode.fish opencode2.fish --replace-fail opencode opencode2
|
||||
|
||||
installShellCompletion --cmd opencode2 \
|
||||
--bash <($out/bin/opencode2 completion) \
|
||||
--zsh <(SHELL=/bin/zsh $out/bin/opencode2 completion)
|
||||
--bash opencode2.bash \
|
||||
--fish opencode2.fish \
|
||||
--zsh _opencode2
|
||||
'';
|
||||
|
||||
nativeInstallCheckInputs = [
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "opencode",
|
||||
"description": "AI-powered development tool",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.4.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"name": "@opencode/ai",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -8,6 +8,29 @@ import { expectSessionTitle } from "../utils/waits"
|
||||
|
||||
test.use({ viewport: { width: 1440, height: 900 } })
|
||||
|
||||
for (const view of ["desktop", "mobile"] as const) {
|
||||
test(`offers Git initialization for a project without VCS (${view})`, async ({ page }) => {
|
||||
if (view === "mobile") await page.setViewportSize({ width: 390, height: 844 })
|
||||
const requests: { directory: string; provider?: string }[] = []
|
||||
const workspace = await openSession(page, {
|
||||
name: "ReviewWithoutGit",
|
||||
project: { vcs: undefined },
|
||||
onVcsInit: (input) => requests.push(input),
|
||||
})
|
||||
if (view === "desktop") await page.getByRole("button", { name: "Toggle review" }).click()
|
||||
else await page.getByRole("tablist", { name: "Session view" }).getByRole("tab", { name: "Changes" }).click()
|
||||
|
||||
const panel = view === "desktop" ? page.locator("#review-panel") : page.locator('[data-component="session-review"]')
|
||||
await expect(panel.getByText("Track, review, and undo changes in this project")).toBeVisible()
|
||||
await expect(panel.getByRole("button", { name: "Git changes" })).toHaveCount(0)
|
||||
const init = panel.getByRole("button", { name: "Create Git repository" })
|
||||
await init.click()
|
||||
await expect.poll(() => requests).toEqual([{ directory: workspace.directory, provider: "git" }])
|
||||
await expect(panel.getByRole("button", { name: "Git changes" })).toBeVisible()
|
||||
await expect(init).toHaveCount(0)
|
||||
})
|
||||
}
|
||||
|
||||
test("open file tab browses, searches, and tracks missing files", async ({ page }) => {
|
||||
const searches: { query: string; dirs?: string; limit?: number }[] = []
|
||||
const directory = "C:/OpenCode/ReviewOpenFile"
|
||||
|
||||
@@ -543,6 +543,26 @@ test.describe("Working", () => {
|
||||
await expect(working).toHaveCount(0)
|
||||
})
|
||||
|
||||
test("keeps Working instead of Used 1 Thought while reasoning is the only activity", async ({ page }) => {
|
||||
const timeline = await setupTimeline(page, {
|
||||
messages: [
|
||||
userMessage(),
|
||||
assistantMessage([reasoningPart("prt_only_thought", "Still considering the answer.")], { completed: false }),
|
||||
],
|
||||
settings: { timelineDetail: detailed },
|
||||
})
|
||||
const working = page.locator('[data-component="session-working"]')
|
||||
const group = page.locator('[data-component="collapsed-tool-group"]')
|
||||
await expect(working).toBeVisible()
|
||||
await expect(group).toHaveCount(0)
|
||||
await expect(page.getByRole("button", { name: "Used 1 Thought" })).toHaveCount(0)
|
||||
|
||||
await timeline.send(partUpdated(toolPart("prt_after_thought", "read", "running", { filePath: "package.json" })))
|
||||
await expect(group).toBeVisible()
|
||||
await expect(group).toHaveAttribute("data-timeline-part-ids", `${assistantID}:reasoning:0,prt_after_thought`)
|
||||
await expect(working).toBeVisible()
|
||||
})
|
||||
|
||||
for (const name of ["shell", "patch", "subagent"] as const) {
|
||||
test(`hides Working during ${name} input and execution, then restores it on completion`, async ({ page }) => {
|
||||
const timeline = await setupTimeline(page, {
|
||||
|
||||
@@ -169,6 +169,7 @@ const Group = HttpApiGroup.make("mock")
|
||||
)
|
||||
.add(HttpApiEndpoint.get("formRequests", "/api/form", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("vcs", "/api/vcs", { success: Json }))
|
||||
.add(HttpApiEndpoint.post("vcsInit", "/api/vcs/init", { success: NoContent, error: Unsupported }))
|
||||
.add(HttpApiEndpoint.get("vcsStatus", "/api/vcs/status", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("vcsBranches", "/api/vcs/branch", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("vcsDiff", "/api/vcs/diff", { query: Query, success: Json }))
|
||||
|
||||
@@ -43,6 +43,8 @@ export interface MockServerConfig {
|
||||
cursor?: string
|
||||
}
|
||||
vcs?: { current: string; default: string }
|
||||
// Initializes the mock project's VCS; without a handler this mutation answers 501.
|
||||
onVcsInit?: (input: { directory: string; provider?: string }) => void
|
||||
vcsDiff?: unknown[] | ((input: { mode?: string }) => unknown[])
|
||||
// Benchmark latency only. Tests hold message pages with `beforeMessagesResponse`.
|
||||
messageDelay?: number
|
||||
@@ -817,6 +819,24 @@ function mockHandlers(
|
||||
location: location(config),
|
||||
data: { branch: config.vcs ?? { current: "main", default: "main" } },
|
||||
}),
|
||||
vcsInit: (ctx) => {
|
||||
if (!config.onVcsInit) return unsupported("initialize VCS", "onVcsInit")
|
||||
return Effect.sync(() => {
|
||||
const url = new URL(ctx.request.url, "http://localhost")
|
||||
const provider = url.searchParams.get("provider") ?? undefined
|
||||
config.onVcsInit?.({ directory: requestDirectory(config, ctx.request), provider })
|
||||
const project = config.project as { id: string; vcs?: string }
|
||||
project.vcs = provider ?? "git"
|
||||
state.emit([
|
||||
{
|
||||
id: "evt_vcs_initialized",
|
||||
type: "worktree.updated",
|
||||
created: Date.now(),
|
||||
data: { projectID: project.id },
|
||||
},
|
||||
])
|
||||
}).pipe(Effect.andThen(noContent))
|
||||
},
|
||||
vcsStatus: () => Effect.succeed({ location: location(config), data: [] }),
|
||||
vcsBranches: () => Effect.succeed({ location: location(config), data: ["main"] }),
|
||||
vcsDiff: (ctx) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cli",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/client",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -2270,6 +2270,13 @@ export interface WorktreeApi<E = never> {
|
||||
readonly refresh: WorktreeRefreshOperation<E>
|
||||
}
|
||||
|
||||
export type VcsInitInput = {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly provider?: string | undefined
|
||||
}
|
||||
export type VcsInitOutput = void
|
||||
export type VcsInitOperation<E = never> = (input?: VcsInitInput) => Effect.Effect<VcsInitOutput, E>
|
||||
|
||||
export type VcsGetInput = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||
export type VcsGetOutput = { readonly location: Location.PublicRef; readonly data: Vcs.Info }
|
||||
export type VcsGetOperation<E = never> = (input?: VcsGetInput) => Effect.Effect<VcsGetOutput, E>
|
||||
@@ -2300,6 +2307,7 @@ export type VcsDiffOutput = { readonly location: Location.PublicRef; readonly da
|
||||
export type VcsDiffOperation<E = never> = (input: VcsDiffInput) => Effect.Effect<VcsDiffOutput, E>
|
||||
|
||||
export interface VcsApi<E = never> {
|
||||
readonly init: VcsInitOperation<E>
|
||||
readonly get: VcsGetOperation<E>
|
||||
readonly base: VcsBaseOperation<E>
|
||||
readonly status: VcsStatusOperation<E>
|
||||
|
||||
@@ -242,6 +242,8 @@ import type {
|
||||
WorktreeRemoveOutput,
|
||||
WorktreeRefreshInput,
|
||||
WorktreeRefreshOutput,
|
||||
VcsInitInput,
|
||||
VcsInitOutput,
|
||||
VcsGetInput,
|
||||
VcsGetOutput,
|
||||
VcsBaseInput,
|
||||
@@ -1481,6 +1483,13 @@ const adaptGroupWorktree = (raw: RawClient["server.worktree"]) => ({
|
||||
refresh: EndpointWorktreeRefresh(raw),
|
||||
})
|
||||
|
||||
const EndpointVcsInit = (raw: RawClient["server.vcs"]) => (input?: VcsInitInput) =>
|
||||
preserveEffect<VcsInitOutput>()(
|
||||
raw["vcs.init"]({ query: { location: input?.["location"], provider: input?.["provider"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const EndpointVcsGet = (raw: RawClient["server.vcs"]) => (input?: VcsGetInput) =>
|
||||
preserveEffect<VcsGetOutput>()(
|
||||
raw["vcs.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
@@ -1511,6 +1520,7 @@ const EndpointVcsDiff = (raw: RawClient["server.vcs"]) => (input: VcsDiffInput)
|
||||
)
|
||||
|
||||
const adaptGroupVcs = (raw: RawClient["server.vcs"]) => ({
|
||||
init: EndpointVcsInit(raw),
|
||||
get: EndpointVcsGet(raw),
|
||||
base: EndpointVcsBase(raw),
|
||||
status: EndpointVcsStatus(raw),
|
||||
|
||||
@@ -240,6 +240,8 @@ import type {
|
||||
WorktreeRemoveOutput,
|
||||
WorktreeRefreshInput,
|
||||
WorktreeRefreshOutput,
|
||||
VcsInitInput,
|
||||
VcsInitOutput,
|
||||
VcsGetInput,
|
||||
VcsGetOutput,
|
||||
VcsBaseInput,
|
||||
@@ -2040,6 +2042,18 @@ export function make(options: ClientOptions) {
|
||||
),
|
||||
},
|
||||
vcs: {
|
||||
init: (input?: VcsInitInput, requestOptions?: RequestOptions) =>
|
||||
request<VcsInitOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/vcs/init`,
|
||||
query: { location: input?.["location"], provider: input?.["provider"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 409, 501, 503],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
get: (input?: VcsGetInput, requestOptions?: RequestOptions) =>
|
||||
request<VcsGetOutput>(
|
||||
{
|
||||
|
||||
@@ -2735,11 +2735,20 @@ export const isShellNotFoundError = (value: unknown): value is ShellNotFoundErro
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ShellNotFoundError"
|
||||
|
||||
export type WorktreeError = {
|
||||
readonly _tag: "WorktreeError"
|
||||
readonly name: "WorktreeError"
|
||||
readonly data: { readonly message: string; readonly forceRequired?: boolean | undefined }
|
||||
}
|
||||
export const isWorktreeError = (value: unknown): value is WorktreeError =>
|
||||
typeof value === "object" && value !== null && "name" in value && value["name"] === "WorktreeError"
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "WorktreeError"
|
||||
|
||||
export type VcsInitNotSupportedError = {
|
||||
readonly _tag: "VcsInitNotSupportedError"
|
||||
readonly providerID: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isVcsInitNotSupportedError = (value: unknown): value is VcsInitNotSupportedError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "VcsInitNotSupportedError"
|
||||
|
||||
export type ServerInfoOutput = ServerInfo
|
||||
|
||||
@@ -6494,6 +6503,19 @@ export type WorktreeRefreshInput = { readonly projectID: { readonly projectID: s
|
||||
|
||||
export type WorktreeRefreshOutput = void
|
||||
|
||||
export type VcsInitInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly provider?: string | undefined
|
||||
}["location"]
|
||||
readonly provider?: {
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly provider?: string | undefined
|
||||
}["provider"]
|
||||
}
|
||||
|
||||
export type VcsInitOutput = void
|
||||
|
||||
export type VcsGetInput = {
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
}
|
||||
|
||||
@@ -174,21 +174,25 @@ test("declared errors are thrown as Error instances that keep the body", async (
|
||||
expect(error).toMatchObject({ _tag: "InvalidRequestError", kind: "integration_authorization" })
|
||||
})
|
||||
|
||||
test("declared errors with a data envelope read the nested message", async () => {
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async () =>
|
||||
Response.json({ name: "WorktreeError", data: { message: "Worktree directory unavailable" } }, { status: 400 }),
|
||||
})
|
||||
const error = await client.worktree.create({ projectID: "prj_test" }).then(
|
||||
() => undefined,
|
||||
(cause: unknown) => cause,
|
||||
)
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
if (!(error instanceof Error)) throw error
|
||||
expect(error.message).toBe("Worktree directory unavailable")
|
||||
expect(error.name).toBe("WorktreeError")
|
||||
expect(error).toMatchObject({ name: "WorktreeError", data: { message: "Worktree directory unavailable" } })
|
||||
test("worktree errors retain the existing envelope with or without a tag", async () => {
|
||||
for (const body of [
|
||||
{ name: "WorktreeError", data: { message: "Worktree directory unavailable" } },
|
||||
{ _tag: "WorktreeError", name: "WorktreeError", data: { message: "Worktree directory unavailable" } },
|
||||
]) {
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async () => Response.json(body, { status: 400 }),
|
||||
})
|
||||
const error = await client.worktree.create({ projectID: "prj_test" }).then(
|
||||
() => undefined,
|
||||
(cause: unknown) => cause,
|
||||
)
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
if (!(error instanceof Error)) throw error
|
||||
expect(error.message).toBe("Worktree directory unavailable")
|
||||
expect(error.name).toBe("WorktreeError")
|
||||
expect(error).toMatchObject(body)
|
||||
}
|
||||
})
|
||||
|
||||
test("client errors keep the reason and describe the failure in the message", async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/codemode",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-app",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/console-core",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-function",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-mail",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-support",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"name": "@opencode/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -593,10 +593,6 @@ function toolMessage(input: LLMRequest["messages"][number]) {
|
||||
}
|
||||
}
|
||||
|
||||
function text(part: ContentPart) {
|
||||
return part.type === "text" ? [part.text] : []
|
||||
}
|
||||
|
||||
function userPart(part: ContentPart): UserContent {
|
||||
if (part.type === "text") return [{ type: "text", text: part.text }]
|
||||
if (part.type === "media")
|
||||
|
||||
@@ -6,7 +6,6 @@ import path from "path"
|
||||
import { Effect, FiberMap, PubSub, Semaphore, Stream } from "effect"
|
||||
import { Config } from "../../config.js"
|
||||
import { Watcher } from "../../filesystem/watcher.js"
|
||||
import { AbsolutePath } from "../../schema.js"
|
||||
import { Skill } from "../../skill.js"
|
||||
import { SkillFile } from "./skill-file.js"
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
const FOLDERS = new Set([
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
".pnpm-store",
|
||||
"vendor",
|
||||
".npm",
|
||||
"dist",
|
||||
"build",
|
||||
"out",
|
||||
".next",
|
||||
"target",
|
||||
"bin",
|
||||
"obj",
|
||||
".git",
|
||||
".svn",
|
||||
".hg",
|
||||
".vscode",
|
||||
".idea",
|
||||
".turbo",
|
||||
".output",
|
||||
"desktop",
|
||||
".sst",
|
||||
".cache",
|
||||
".webkit-cache",
|
||||
"__pycache__",
|
||||
".pytest_cache",
|
||||
"mypy_cache",
|
||||
".history",
|
||||
".gradle",
|
||||
])
|
||||
|
||||
const FILES = [
|
||||
"**/*.swp",
|
||||
"**/*.swo",
|
||||
"**/*.pyc",
|
||||
"**/.DS_Store",
|
||||
"**/Thumbs.db",
|
||||
"**/logs/**",
|
||||
"**/tmp/**",
|
||||
"**/temp/**",
|
||||
"**/*.log",
|
||||
"**/coverage/**",
|
||||
"**/.nyc_output/**",
|
||||
]
|
||||
|
||||
export const PATTERNS = [...FILES, ...FOLDERS, `**/{${Array.from(FOLDERS).join(",")}}/**`]
|
||||
|
||||
export * as Ignore from "./ignore.js"
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Effect } from "effect"
|
||||
import { define } from "@opencode/plugin/effect/plugin"
|
||||
import { Integration } from "../../integration.js"
|
||||
import { Provider } from "../../provider.js"
|
||||
|
||||
export const LLMGatewayPlugin = define({
|
||||
id: "opencode.provider.llmgateway",
|
||||
|
||||
@@ -23,7 +23,8 @@ export const Plugin = define({
|
||||
id: "opencode.vcs.git",
|
||||
effect: Effect.fn("VcsGitPlugin")(function* (ctx) {
|
||||
const location = yield* Location.Service
|
||||
if (location.vcs?.type !== "git") return
|
||||
// Markerless locations need this definition to initialize their first repository.
|
||||
if (location.vcs && location.vcs.type !== "git") return
|
||||
|
||||
const processes = yield* AppProcess.Service
|
||||
const adapter = make(processes, {
|
||||
@@ -35,6 +36,13 @@ export const Plugin = define({
|
||||
editor.add({
|
||||
id: "git",
|
||||
name: "Git",
|
||||
init: (input) =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* processes.run(
|
||||
ChildProcess.make(gitExecutable, ["init"], { cwd: input.worktree, stdin: "ignore" }),
|
||||
)
|
||||
if (result.exitCode !== 0) return yield* Effect.fail(new Error("Git initialization failed"))
|
||||
}),
|
||||
info: () => adapter.info(),
|
||||
base: () => adapter.base(),
|
||||
branches: (input) => adapter.branches({ search: input.search, limit: input.limit }),
|
||||
|
||||
@@ -26,7 +26,8 @@ export const Plugin = define({
|
||||
id: "opencode.vcs.hg",
|
||||
effect: Effect.fn("VcsHgPlugin")(function* (ctx) {
|
||||
const location = yield* Location.Service
|
||||
if (location.vcs?.type !== "hg") return
|
||||
// Markerless locations need this definition to initialize their first repository.
|
||||
if (location.vcs && location.vcs.type !== "hg") return
|
||||
|
||||
const processes = yield* AppProcess.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
@@ -39,6 +40,18 @@ export const Plugin = define({
|
||||
editor.add({
|
||||
id: "hg",
|
||||
name: "Mercurial",
|
||||
init: (input) =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* processes.run(
|
||||
ChildProcess.make("hg", ["init"], {
|
||||
cwd: input.worktree,
|
||||
env: { HGPLAIN: "1" },
|
||||
extendEnv: true,
|
||||
stdin: "ignore",
|
||||
}),
|
||||
)
|
||||
if (result.exitCode !== 0) return yield* Effect.fail(new Error("Mercurial initialization failed"))
|
||||
}),
|
||||
info: () => adapter.info(),
|
||||
branches: (input) => adapter.branches({ search: input.search, limit: input.limit }),
|
||||
status: () => adapter.status(),
|
||||
|
||||
@@ -32,7 +32,7 @@ import { SessionModelRequest } from "./model-request.js"
|
||||
import { SessionProviderContext } from "./provider-context.js"
|
||||
import { SessionRunnerRetry } from "./runner/retry.js"
|
||||
import { toLLMMessages } from "./runner/to-llm-message.js"
|
||||
import { toSessionError } from "./to-session-error.js"
|
||||
import { contentFilterError, toSessionError } from "./to-session-error.js"
|
||||
import { SessionUsage } from "./usage.js"
|
||||
|
||||
export type Settings = {
|
||||
@@ -540,10 +540,7 @@ export const layer = Layer.effect(
|
||||
message: "Compaction summary reached the output token limit",
|
||||
})
|
||||
case "content-filter":
|
||||
return unusable({
|
||||
type: "provider.content-filter",
|
||||
message: "Compaction summary was blocked by the provider",
|
||||
})
|
||||
return unusable(contentFilterError("Compaction summary was blocked by the provider", event.reason))
|
||||
default:
|
||||
return Effect.succeed(streamed)
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ import { SessionMessage } from "./message.js"
|
||||
import { SessionSchema } from "./schema.js"
|
||||
import { SessionMessageTable } from "./sql.js"
|
||||
|
||||
export { MessageNotFoundError }
|
||||
|
||||
interface BoundaryInput {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly messageID: SessionMessage.ID
|
||||
|
||||
@@ -12,6 +12,7 @@ import { SessionSchema } from "../schema.js"
|
||||
import { SessionError } from "@opencode/schema/session-error"
|
||||
import { Money } from "@opencode/schema/money"
|
||||
import { SessionUsage } from "../usage.js"
|
||||
import { contentFilterError } from "../to-session-error.js"
|
||||
import type { Tool } from "../../tool.js"
|
||||
|
||||
type Input = {
|
||||
@@ -543,15 +544,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
}
|
||||
if (event.reason.normalized === "content-filter") {
|
||||
providerFailed = true
|
||||
yield* failAssistant({
|
||||
type: "provider.content-filter",
|
||||
message: [
|
||||
`Provider blocked the response${event.reason.category === undefined ? "" : ` (${event.reason.category})`}`,
|
||||
event.reason.explanation,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(": "),
|
||||
})
|
||||
yield* failAssistant(contentFilterError("Provider blocked the response", event.reason))
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
BusyError,
|
||||
CompactionConflictError,
|
||||
InboxConflictError,
|
||||
MessageNotFoundError,
|
||||
NotFoundError,
|
||||
PromptConflictError,
|
||||
SyntheticConflictError,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AIError, ToolFailure } from "@opencode/ai"
|
||||
import { AIError, ToolFailure, type FinishReasonDetails } from "@opencode/ai"
|
||||
import { Tool } from "@opencode/schema/tool"
|
||||
import { SessionError } from "@opencode/schema/session-error"
|
||||
import { Permission } from "../permission.js"
|
||||
@@ -85,6 +85,15 @@ export function toSessionError(cause: unknown): SessionError.Error {
|
||||
return { type: "unknown", message: cause instanceof Error ? cause.message : String(cause) }
|
||||
}
|
||||
|
||||
export function contentFilterError(summary: string, reason: FinishReasonDetails): SessionError.Error {
|
||||
return {
|
||||
type: "provider.content-filter",
|
||||
message: [reason.category === undefined ? summary : `${summary} (${reason.category})`, reason.explanation]
|
||||
.filter(Boolean)
|
||||
.join(": "),
|
||||
}
|
||||
}
|
||||
|
||||
function providerError(type: string, reason: AIError["reason"]): SessionError.Error {
|
||||
const status = reason.http?.status
|
||||
return {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { VcsEvent } from "@opencode/schema/vcs-event"
|
||||
import { makeLocationNode } from "@opencode/util/effect/app-node"
|
||||
import { FSUtil } from "@opencode/util/fs-util"
|
||||
import { Location } from "./location.js"
|
||||
import { Project } from "./project.js"
|
||||
import { Bus } from "./bus.js"
|
||||
import { State } from "./state.js"
|
||||
import { emptyPatch, MAX_TOTAL_PATCH_BYTES, PATCH_CONTEXT_LINES } from "./vcs/patch.js"
|
||||
@@ -20,6 +21,10 @@ export class DiffError extends Schema.TaggedError<DiffError>()("Vcs.DiffError",
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class InitializeError extends Schema.TaggedError<InitializeError>()("Vcs.InitializeError", {
|
||||
kind: Schema.Literals(["missing", "conflict", "unknown", "unsupported", "failed"]),
|
||||
}) {}
|
||||
|
||||
export interface DiffOptions {
|
||||
readonly context?: number
|
||||
readonly base?: string
|
||||
@@ -40,6 +45,7 @@ export interface Adapter {
|
||||
|
||||
export interface Interface extends Adapter, State.Transformable<VcsEditor> {
|
||||
readonly base: () => Effect.Effect<Base | null, DiffError>
|
||||
readonly initialize: (providerID: string) => Effect.Effect<void, InitializeError>
|
||||
}
|
||||
|
||||
interface Data {
|
||||
@@ -124,7 +130,9 @@ const layer = Layer.effect(
|
||||
const provider = selected()
|
||||
const next: Info = provider
|
||||
? {
|
||||
...(yield* protect(provider, "info", provider.info(scope).pipe(Effect.flatMap(decodeInfo)), { branch: {} })),
|
||||
...(yield* protect(provider, "info", provider.info(scope).pipe(Effect.flatMap(decodeInfo)), {
|
||||
branch: {},
|
||||
})),
|
||||
provider: provider.id,
|
||||
}
|
||||
: { branch: {} }
|
||||
@@ -166,6 +174,24 @@ const layer = Layer.effect(
|
||||
return Service.of({
|
||||
transform: state.transform,
|
||||
reload: state.reload,
|
||||
initialize: Effect.fn("Vcs.initialize")(function* (providerID: string) {
|
||||
if (!(yield* fs.isDir(location.project.directory))) return yield* new InitializeError({ kind: "missing" })
|
||||
if (yield* Project.root(fs, location.project.directory)) return yield* new InitializeError({ kind: "conflict" })
|
||||
const provider = state.get().providers.get(providerID)
|
||||
if (!provider) return yield* new InitializeError({ kind: "unknown" })
|
||||
if (!provider.init) return yield* new InitializeError({ kind: "unsupported" })
|
||||
yield* provider
|
||||
.init(scope)
|
||||
.pipe(
|
||||
Effect.catchCause((cause) =>
|
||||
Cause.hasInterrupts(cause)
|
||||
? Effect.failCause(cause).pipe(Effect.orDie)
|
||||
: Effect.logWarning("vcs initialization failed", { provider: providerID, cause }).pipe(
|
||||
Effect.andThen(Effect.fail(new InitializeError({ kind: "failed" }))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
info: Effect.fn("Vcs.info")(function* () {
|
||||
return current.info
|
||||
}),
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { Ignore } from "@opencode/core/filesystem/ignore"
|
||||
// @ts-ignore
|
||||
import { createWrapper } from "@parcel/watcher/wrapper.js"
|
||||
|
||||
test("parcel patterns ignore built-in folders at any depth", async () => {
|
||||
let ignoreGlobs: string[] = []
|
||||
const watcher = createWrapper({
|
||||
subscribe: async (
|
||||
_directory: string,
|
||||
_callback: (...args: unknown[]) => unknown,
|
||||
options: { ignoreGlobs?: string[] },
|
||||
) => {
|
||||
ignoreGlobs = options.ignoreGlobs ?? []
|
||||
},
|
||||
})
|
||||
await watcher.subscribe("/tmp/project", () => {}, { ignore: Ignore.PATTERNS })
|
||||
const patterns = ignoreGlobs.map((source) => new RegExp(source))
|
||||
|
||||
for (const path of [
|
||||
"nested/node_modules",
|
||||
"nested/node_modules/package/index.js",
|
||||
"nested/.git",
|
||||
"nested/.git/HEAD",
|
||||
"nested/dist",
|
||||
"nested/dist/index.js",
|
||||
]) {
|
||||
expect(patterns.some((pattern) => pattern.test(path))).toBe(true)
|
||||
}
|
||||
expect(patterns.some((pattern) => pattern.test("nested/src/index.ts"))).toBe(false)
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { expect } from "bun:test"
|
||||
import { Plugin } from "@opencode/plugin"
|
||||
import type { VcsDefinition } from "@opencode/plugin/effect/vcs"
|
||||
import { PluginPromise } from "@opencode/core/plugin/promise"
|
||||
import { State } from "@opencode/core/state"
|
||||
import { Effect } from "effect"
|
||||
import { it } from "../lib/effect"
|
||||
import { host } from "./host"
|
||||
|
||||
it.live("Promise VCS initializer reaches the registered provider with its location scope", () =>
|
||||
Effect.gen(function* () {
|
||||
const calls: string[] = []
|
||||
const state = State.create({
|
||||
initial: () => new Map<string, VcsDefinition>(),
|
||||
editor: (providers) => ({
|
||||
add: (definition: VcsDefinition) => providers.set(definition.id, definition),
|
||||
default: { get: () => undefined, set: (_selection: string) => {} },
|
||||
}),
|
||||
})
|
||||
const context = host()
|
||||
const plugin = PluginPromise.fromPromise(
|
||||
Plugin.define({
|
||||
id: "init-vcs",
|
||||
async setup(ctx) {
|
||||
await ctx.vcs.transform((editor) =>
|
||||
editor.add({
|
||||
id: "custom",
|
||||
name: "Custom VCS",
|
||||
init: async (scope, { signal }) => {
|
||||
expect(signal.aborted).toBe(false)
|
||||
calls.push(scope.worktree)
|
||||
},
|
||||
info: async () => ({ branch: {} }),
|
||||
branches: async () => [],
|
||||
status: async () => [],
|
||||
diff: async () => [],
|
||||
}),
|
||||
)
|
||||
},
|
||||
}),
|
||||
)
|
||||
yield* plugin.effect(host({ vcs: { ...context.vcs, transform: state.transform, reload: state.reload } }))
|
||||
const init = state.get().get("custom")?.init
|
||||
if (!init) return yield* Effect.die("Initializer was not registered")
|
||||
yield* init({ directory: "/workspace", worktree: "/workspace", canonical: "/workspace" })
|
||||
expect(calls).toEqual(["/workspace"])
|
||||
}),
|
||||
)
|
||||
@@ -2716,6 +2716,32 @@ describe("SessionRunnerLLM", () => {
|
||||
)
|
||||
})
|
||||
|
||||
scenario("explains a compaction blocked by the provider", function* (s) {
|
||||
yield* s.llm.push(TestLLM.text("Earlier answer", "history"))
|
||||
yield* s.runPrompt("Earlier question")
|
||||
yield* s.llm.push(
|
||||
TestLLM.complete({
|
||||
reason: {
|
||||
normalized: "content-filter",
|
||||
raw: "refusal",
|
||||
category: "cyber",
|
||||
explanation: "This request was declined because it could enable cyber harm.",
|
||||
},
|
||||
}),
|
||||
)
|
||||
const compaction = yield* s.session.compact({ sessionID })
|
||||
yield* s.resume
|
||||
|
||||
expect((yield* s.messages).find((message) => message.id === compaction.id)).toMatchObject({
|
||||
status: "failed",
|
||||
error: {
|
||||
type: "provider.content-filter",
|
||||
message:
|
||||
"Compaction summary was blocked by the provider (cyber): This request was declined because it could enable cyber harm.",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
for (const header of [false, true]) {
|
||||
scenario(`stops compaction retries through the ${header ? "provider header" : "retry hook"}`, function* (s) {
|
||||
yield* s.llm.push(TestLLM.text("Earlier answer", "history"))
|
||||
|
||||
@@ -103,6 +103,29 @@ const provider = (input: Partial<VcsDefinition> = {}) =>
|
||||
}) satisfies VcsDefinition
|
||||
|
||||
describe("Vcs", () => {
|
||||
it.live("routes initialization through the requested provider before a repository exists", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const vcs = yield* Vcs.Service
|
||||
const calls: string[] = []
|
||||
yield* vcs.transform((editor) => {
|
||||
editor.add(provider({ id: "read-only" }))
|
||||
editor.add(
|
||||
provider({
|
||||
id: "custom",
|
||||
init: (input) => Effect.sync(() => void calls.push(input.worktree)),
|
||||
}),
|
||||
)
|
||||
})
|
||||
expect(yield* vcs.initialize("missing").pipe(Effect.flip)).toMatchObject({ kind: "unknown" })
|
||||
expect(yield* vcs.initialize("read-only").pipe(Effect.flip)).toMatchObject({ kind: "unsupported" })
|
||||
expect(calls).toEqual([])
|
||||
yield* vcs.initialize("custom")
|
||||
expect(calls).toEqual([directory])
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("returns empty results outside version control", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode/desktop",
|
||||
"private": true,
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/enterprise",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/gui-extensions",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { FileDiffInfo } from "@opencode/client/promise"
|
||||
import type { SessionReviewLineComment } from "@opencode/session-ui/session-review"
|
||||
import { previewSelectedLines } from "@opencode/session-ui/pierre/selection-bridge"
|
||||
import { checksum } from "@opencode/util/encode"
|
||||
import { showToast } from "@opencode/ui/toast"
|
||||
import { createQuery, useQueryClient } from "@tanstack/solid-query"
|
||||
import { debounce } from "@solid-primitives/scheduled"
|
||||
import { createComputed, createEffect, createMemo, on, onCleanup } from "solid-js"
|
||||
@@ -46,6 +47,7 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
|
||||
scroll: undefined as HTMLDivElement | undefined,
|
||||
pendingFile: undefined as string | undefined,
|
||||
deferRender: false,
|
||||
initializingGit: false,
|
||||
// The filter is transient by design: a persisted filter would silently hide
|
||||
// files after a reload.
|
||||
filter: "",
|
||||
@@ -218,6 +220,45 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
|
||||
if (!stored()) return false
|
||||
return !diffQuery.isPending
|
||||
}
|
||||
const lifetime = { disposed: false }
|
||||
onCleanup(() => {
|
||||
lifetime.disposed = true
|
||||
})
|
||||
const initializeGit = () => {
|
||||
if (state.initializingGit) return
|
||||
const location = view.location
|
||||
if (!location || !view.server.connected) {
|
||||
showToast({ variant: "error", title: ctx.t("common.requestFailed") })
|
||||
return
|
||||
}
|
||||
const key = view.key
|
||||
const sessionID = view.id
|
||||
setState("initializingGit", true)
|
||||
void view.server.client.vcs
|
||||
.init({ location, provider: "git" })
|
||||
.then(async () => {
|
||||
if (lifetime.disposed || ctx.signal.aborted || view.key !== key) return
|
||||
const data = view.server.data
|
||||
data.project.invalidate()
|
||||
data.session.invalidate(sessionID)
|
||||
data.location.invalidate(location)
|
||||
data.location.vcs.invalidate(location)
|
||||
await data.project.sync()
|
||||
await data.session.sync(sessionID)
|
||||
await Promise.all([data.location.syncInfo(location), data.location.vcs.sync(location)])
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (lifetime.disposed || ctx.signal.aborted || view.key !== key) return
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: ctx.t("common.requestFailed"),
|
||||
description: error instanceof Error ? error.message : undefined,
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
if (!lifetime.disposed && !ctx.signal.aborted && view.key === key) setState("initializingGit", false)
|
||||
})
|
||||
}
|
||||
const loadDiff = async (path: string, version?: number): Promise<FileDiffInfo | undefined> => {
|
||||
const source = diffs().find((diff) => diff.file === path)
|
||||
const valid = (diff: FileDiffInfo | undefined): FileDiffInfo | undefined => {
|
||||
@@ -445,6 +486,8 @@ export function createReviewModel(input: { ctx: Context; view: SessionView; dema
|
||||
kinds,
|
||||
focusFile,
|
||||
hasChanges,
|
||||
initializeGit,
|
||||
initializingGit: () => state.initializingGit,
|
||||
loadDiff,
|
||||
mode,
|
||||
noGit: createMemo(() => !!view.project && !view.project.vcs),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Match, Show, Switch } from "solid-js"
|
||||
import { SessionReviewEmptyChangesV2 } from "@opencode/session-ui/v2/session-review-empty-changes-v2"
|
||||
import { SessionReviewEmptyNoGitV2 } from "@opencode/session-ui/v2/session-review-empty-no-git-v2"
|
||||
import { Select } from "@opencode/ui/select"
|
||||
import { useExtension } from "../sdk"
|
||||
import type { ChangeMode, ReviewModel } from "./model"
|
||||
@@ -12,7 +13,7 @@ export function ReviewTitle(props: { review: ReviewModel }) {
|
||||
return ctx.t("ui.sessionReview.title.lastTurn")
|
||||
}
|
||||
return (
|
||||
<Show when={props.review.canReview()}>
|
||||
<Show when={props.review.canReview() && props.review.options().length > 0}>
|
||||
<Select
|
||||
options={props.review.options()}
|
||||
current={props.review.mode()}
|
||||
@@ -28,7 +29,6 @@ export function ReviewTitle(props: { review: ReviewModel }) {
|
||||
export function ReviewEmpty(props: { review: ReviewModel; loadingClass: string }) {
|
||||
const ctx = useExtension()
|
||||
const loading = () => !props.review.ready()
|
||||
const noGit = () => props.review.noGit()
|
||||
const text = () => {
|
||||
if (props.review.mode() === "git") return ctx.t("empty.git")
|
||||
if (props.review.mode() === "branch") return ctx.t("empty.branch")
|
||||
@@ -39,14 +39,9 @@ export function ReviewEmpty(props: { review: ReviewModel; loadingClass: string }
|
||||
<Match when={loading()}>
|
||||
<div class={props.loadingClass}>{ctx.t("loadingChanges")}</div>
|
||||
</Match>
|
||||
<Match when={noGit()}>
|
||||
<div class="h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="text-14-medium text-text-strong">{ctx.t("git.title")}</div>
|
||||
<div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}>
|
||||
{ctx.t("git.description")}
|
||||
</div>
|
||||
</div>
|
||||
<Match when={props.review.noGit()}>
|
||||
<div class="h-full flex flex-col">
|
||||
<SessionReviewEmptyNoGitV2 pending={props.review.initializingGit()} onInitGit={props.review.initializeGit} />
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
@@ -61,16 +56,13 @@ export function ReviewEmpty(props: { review: ReviewModel; loadingClass: string }
|
||||
export function ReviewPanelEmpty(props: { review: ReviewModel }) {
|
||||
const ctx = useExtension()
|
||||
const loading = () => !props.review.ready()
|
||||
const noGit = () => props.review.noGit()
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={loading()}>
|
||||
<div class="px-6 py-4 text-text-weak">{ctx.t("loadingChanges")}</div>
|
||||
</Match>
|
||||
<Match when={noGit()}>
|
||||
<div class="h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6">
|
||||
<div class="text-14-regular text-text-weak max-w-56">{ctx.t("git.description")}</div>
|
||||
</div>
|
||||
<Match when={props.review.noGit()}>
|
||||
<SessionReviewEmptyNoGitV2 pending={props.review.initializingGit()} onInitGit={props.review.initializeGit} />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<SessionReviewEmptyChangesV2 />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"name": "@opencode/http-recorder",
|
||||
"description": "Record and replay Effect HTTP and WebSocket traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/httpapi-codegen",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/latex",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/merman",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/plugin-browser",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"description": "OpenCode's desktop browser plugin",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/plugin",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface VcsDiffInput extends VcsScope {
|
||||
export interface VcsDefinition {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly init?: (input: VcsScope) => Effect.Effect<void, unknown>
|
||||
readonly info: (input: VcsScope) => Effect.Effect<Vcs.Info, unknown>
|
||||
readonly base?: (input: VcsScope) => Effect.Effect<Vcs.Base | null, unknown>
|
||||
readonly branches: (input: VcsBranchesInput) => Effect.Effect<Vcs.BranchList, unknown>
|
||||
@@ -33,7 +34,7 @@ export interface VcsDefinition {
|
||||
readonly diff: (input: VcsDiffInput) => Effect.Effect<readonly FileDiff.Info[], unknown>
|
||||
}
|
||||
|
||||
export interface VcsDomain extends VcsApi<unknown> {
|
||||
export interface VcsDomain extends Omit<VcsApi<unknown>, "init"> {
|
||||
readonly transform: Transform<VcsEditor>
|
||||
readonly reload: () => Effect.Effect<void>
|
||||
}
|
||||
|
||||
@@ -518,10 +518,12 @@ export function fromPromise(plugin: Plugin) {
|
||||
host.vcs.transform((editor) => {
|
||||
callback({
|
||||
add: (definition) => {
|
||||
const init = definition.init?.bind(definition)
|
||||
const base = definition.base?.bind(definition)
|
||||
editor.add({
|
||||
id: definition.id,
|
||||
name: definition.name,
|
||||
init: init ? (input) => attempt((signal) => init(input, { signal })) : undefined,
|
||||
info: (input) => attempt((signal) => definition.info(input, { signal })),
|
||||
base: base ? (input) => attempt((signal) => base(input, { signal })) : undefined,
|
||||
branches: (input) => attempt((signal) => definition.branches(input, { signal })),
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface VcsDiffInput extends VcsScope {
|
||||
export interface VcsDefinition {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly init?: (input: VcsScope, context: { readonly signal: AbortSignal }) => Promise<void>
|
||||
readonly info: (input: VcsScope, context: { readonly signal: AbortSignal }) => Promise<Vcs.Info>
|
||||
readonly base?: (input: VcsScope, context: { readonly signal: AbortSignal }) => Promise<Vcs.Base | null>
|
||||
readonly branches: (input: VcsBranchesInput, context: { readonly signal: AbortSignal }) => Promise<Vcs.BranchList>
|
||||
@@ -32,7 +33,7 @@ export interface VcsDefinition {
|
||||
readonly diff: (input: VcsDiffInput, context: { readonly signal: AbortSignal }) => Promise<readonly FileDiff.Info[]>
|
||||
}
|
||||
|
||||
export interface VcsDomain extends VcsApi {
|
||||
export interface VcsDomain extends Omit<VcsApi, "init"> {
|
||||
readonly transform: Transform<VcsEditor>
|
||||
readonly reload: () => Promise<void>
|
||||
}
|
||||
|
||||
+628
-20
@@ -48,6 +48,106 @@
|
||||
"summary": "Get server info"
|
||||
}
|
||||
},
|
||||
"/api/pair": {
|
||||
"post": {
|
||||
"tags": ["server"],
|
||||
"operationId": "server.pair",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PairingCode",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PairingCode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a short-lived, single-use code for a /auth/connect/:code pairing link.",
|
||||
"summary": "Create pairing code"
|
||||
}
|
||||
},
|
||||
"/auth/connect/{code}": {
|
||||
"get": {
|
||||
"tags": ["server"],
|
||||
"operationId": "server.connect",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "code",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PairingSession",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PairingSession"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Redeem a pairing code. Browsers receive a session cookie and a redirect to the web app; requests that accept JSON receive a session token to use as the password.",
|
||||
"summary": "Redeem pairing code"
|
||||
}
|
||||
},
|
||||
"/api/location": {
|
||||
"get": {
|
||||
"tags": ["location"],
|
||||
@@ -886,9 +986,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "SessionNotFoundError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a session at the requested location.",
|
||||
"description": "Create a session at the requested location. A parentID creates a linked child session at its parent's location.",
|
||||
"summary": "Create session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -907,6 +1017,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"parentID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^ses"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -4508,6 +4629,21 @@
|
||||
"pattern": "^frm_"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
@@ -4591,21 +4727,6 @@
|
||||
"pattern": "^frm_"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
@@ -7197,6 +7318,125 @@
|
||||
"summary": "List MCP resources"
|
||||
}
|
||||
},
|
||||
"/api/credential": {
|
||||
"get": {
|
||||
"tags": ["credential"],
|
||||
"operationId": "credential.list",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Credential.Entry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "List every stored integration credential, including its secret value.",
|
||||
"summary": "List credentials"
|
||||
},
|
||||
"post": {
|
||||
"tags": ["credential"],
|
||||
"operationId": "credential.create",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/Credential.Entry"
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "ConflictError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConflictErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Store an integration credential. It becomes the integration's active credential unless activate is false and the integration already has one. Fails with a conflict when the requested ID already exists.",
|
||||
"summary": "Create credential",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Credential.CreateInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/credential/{credentialID}": {
|
||||
"patch": {
|
||||
"tags": ["credential"],
|
||||
@@ -11451,6 +11691,124 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/vcs/init": {
|
||||
"post": {
|
||||
"tags": ["vcs"],
|
||||
"operationId": "vcs.init",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false,
|
||||
"style": "deepObject",
|
||||
"explode": true
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "<No Content>"
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "ConflictError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConflictErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"501": {
|
||||
"description": "VcsInitNotSupportedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VcsInitNotSupportedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "ServiceUnavailableError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Initialize a repository using the selected VCS provider in a markerless project's directory and refresh its location services. Omitting provider defaults to git; built-in git and hg providers support initialization. An unknown provider returns 400; a registered provider without init returns 501.",
|
||||
"summary": "Initialize VCS repository"
|
||||
}
|
||||
},
|
||||
"/api/vcs": {
|
||||
"get": {
|
||||
"tags": ["vcs"],
|
||||
@@ -13237,6 +13595,27 @@
|
||||
"required": ["command"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.Capabilities": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"input": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.CostEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13328,7 +13707,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"capabilities": {
|
||||
"$ref": "#/components/schemas/Model.Capabilities"
|
||||
"$ref": "#/components/schemas/Config.Model.Capabilities"
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
@@ -13433,8 +13812,27 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"headerTimeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chunkTimeout": {
|
||||
"type": "number"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
@@ -13631,6 +14029,9 @@
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["key", "oauth"]
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Connection.Status"
|
||||
}
|
||||
},
|
||||
"required": ["type", "id", "label", "method"],
|
||||
@@ -13645,6 +14046,9 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Connection.Status"
|
||||
}
|
||||
},
|
||||
"required": ["type", "name"],
|
||||
@@ -13660,6 +14064,128 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Connection.Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["needs_auth"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["status", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.CreateInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"integrationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/components/schemas/Credential.Value"
|
||||
},
|
||||
"activate": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["integrationID", "value"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Entry": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"integrationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/components/schemas/Credential.Value"
|
||||
}
|
||||
},
|
||||
"required": ["id", "integrationID", "label", "active", "value"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Key": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["key"]
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
"configuration": {
|
||||
"$ref": "#/components/schemas/Form.Answer_1"
|
||||
}
|
||||
},
|
||||
"required": ["type", "key"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.OAuth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["oauth"]
|
||||
},
|
||||
"methodID": {
|
||||
"type": "string"
|
||||
},
|
||||
"refresh": {
|
||||
"type": "string"
|
||||
},
|
||||
"access": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["type", "methodID", "refresh", "access", "expires"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Value": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Credential.OAuth"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Credential.Key"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileDiff.Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13746,6 +14272,12 @@
|
||||
"$ref": "#/components/schemas/Form.Value"
|
||||
}
|
||||
},
|
||||
"Form.Answer_1": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/Form.Value"
|
||||
}
|
||||
},
|
||||
"Form.BooleanField": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15709,6 +16241,29 @@
|
||||
"Money.USDPerMillionTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"PairingCode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_in": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["code", "expires_in"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PairingSession": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["token"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Permission.Effect": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "deny", "ask"]
|
||||
@@ -16601,8 +17156,27 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"headerTimeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chunkTimeout": {
|
||||
"type": "number"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
@@ -18234,6 +18808,16 @@
|
||||
"type": "integer",
|
||||
"minimum": 100,
|
||||
"maximum": 599
|
||||
},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["body"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["type", "message"],
|
||||
@@ -18653,6 +19237,9 @@
|
||||
"exit": {
|
||||
"type": "number"
|
||||
},
|
||||
"signal": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -18929,6 +19516,23 @@
|
||||
"type": "string",
|
||||
"enum": ["working", "branch", "committed"]
|
||||
},
|
||||
"VcsInitNotSupportedErrorEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["VcsInitNotSupportedError"]
|
||||
},
|
||||
"providerID": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["_tag", "providerID", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"WebSearch.Provider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -19053,6 +19657,10 @@
|
||||
"WorktreeErrorEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
@@ -19078,7 +19686,7 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"],
|
||||
"required": ["_tag", "name", "data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/protocol",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -3,9 +3,20 @@ import { Location } from "@opencode/schema/location"
|
||||
import { NonNegativeInt, PositiveInt, optional } from "@opencode/schema/schema"
|
||||
import { Vcs } from "@opencode/schema/vcs"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import { LocationQuery, locationQueryOpenApi } from "./location.js"
|
||||
import { ServiceUnavailableError } from "../errors.js"
|
||||
import { ConflictError, InvalidRequestError, ServiceUnavailableError } from "../errors.js"
|
||||
|
||||
const InitQuery = Schema.Struct({
|
||||
...LocationQuery.fields,
|
||||
provider: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
export class VcsInitNotSupportedError extends Schema.TaggedError<VcsInitNotSupportedError>()(
|
||||
"VcsInitNotSupportedError",
|
||||
{ providerID: Schema.String, message: Schema.String },
|
||||
{ httpApiStatus: 501 },
|
||||
) {}
|
||||
|
||||
const BranchesQuery = Schema.Struct({
|
||||
...LocationQuery.fields,
|
||||
@@ -21,6 +32,22 @@ const DiffQuery = Schema.Struct({
|
||||
})
|
||||
|
||||
export const VcsGroup = HttpApiGroup.make("server.vcs")
|
||||
.add(
|
||||
HttpApiEndpoint.post("vcs.init", "/api/vcs/init", {
|
||||
query: InitQuery,
|
||||
success: HttpApiSchema.NoContent,
|
||||
error: [ConflictError, InvalidRequestError, VcsInitNotSupportedError, ServiceUnavailableError],
|
||||
})
|
||||
.annotateMerge(locationQueryOpenApi)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "vcs.init",
|
||||
summary: "Initialize VCS repository",
|
||||
description:
|
||||
"Initialize a repository using the selected VCS provider in a markerless project's directory and refresh its location services. Omitting provider defaults to git; built-in git and hg providers support initialization. An unknown provider returns 400; a registered provider without init returns 501.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("vcs.get", "/api/vcs", {
|
||||
query: LocationQuery,
|
||||
|
||||
@@ -6,7 +6,8 @@ import { ProjectNotFoundError } from "../errors.js"
|
||||
|
||||
const root = "/api/worktree"
|
||||
|
||||
export class WorktreeError extends Schema.Error<WorktreeError>("WorktreeError")(
|
||||
export class WorktreeError extends Schema.TaggedError<WorktreeError>()(
|
||||
"WorktreeError",
|
||||
{
|
||||
name: Schema.Literal("WorktreeError"),
|
||||
data: Schema.Struct({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/schema",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export * as IdeEvent from "./ide-event.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Event } from "./event.js"
|
||||
|
||||
export const Installed = Event.ephemeral({
|
||||
type: "ide.installed",
|
||||
schema: {
|
||||
ide: Schema.String,
|
||||
},
|
||||
})
|
||||
|
||||
export const Definitions = Event.inventory(Installed)
|
||||
@@ -490,15 +490,6 @@ export type Assistant = Omit<Types.DeepMutable<Schema.Schema.Type<typeof Assista
|
||||
export const Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "SessionV1.Message" })
|
||||
export type Info = User | Assistant
|
||||
|
||||
export const WithParts = Schema.Struct({
|
||||
info: Info,
|
||||
parts: Schema.Array(Part),
|
||||
})
|
||||
export type WithParts = {
|
||||
info: Info
|
||||
parts: Part[]
|
||||
}
|
||||
|
||||
const options = {
|
||||
durable: {
|
||||
aggregate: "sessionID",
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
} from "../src/index.js"
|
||||
import { EventManifest } from "../src/event-manifest.js"
|
||||
import { FileSystemV1 } from "../src/filesystem-v1.js"
|
||||
import { IdeEvent } from "../src/ide-event.js"
|
||||
import { McpEvent } from "../src/mcp-event.js"
|
||||
import { SessionEvent } from "../src/session-event.js"
|
||||
import { SessionID } from "../src/session-id.js"
|
||||
@@ -71,8 +70,6 @@ describe("public event manifest", () => {
|
||||
expect(Reference.Event.Definitions).toEqual([Reference.Event.Updated])
|
||||
expect(McpEvent.Definitions).toEqual([McpEvent.ToolsChanged, McpEvent.ResourcesChanged, McpEvent.StatusChanged])
|
||||
expect(EventManifest.Latest.has("mcp.browser.open.failed")).toBe(false)
|
||||
expect(EventManifest.Latest.has("ide.installed")).toBe(false)
|
||||
expect(IdeEvent.Definitions).toEqual([IdeEvent.Installed])
|
||||
expect(EventManifest.Durable.get("session.step.ended.1")).toBe(SessionEvent.Step.Ended)
|
||||
expect(EventManifest.Durable.has("session.step.ended.2")).toBe(false)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"name": "@opencode/sdk",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/server",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,13 +1,59 @@
|
||||
import { Vcs } from "@opencode/core/vcs"
|
||||
import { ServiceUnavailableError } from "@opencode/protocol/errors"
|
||||
import { Project } from "@opencode/core/project"
|
||||
import { Worktree } from "@opencode/core/worktree"
|
||||
import { Bus } from "@opencode/core/bus"
|
||||
import { Plugin } from "@opencode/core/plugin"
|
||||
import { Location } from "@opencode/core/location"
|
||||
import { LocationServiceMap } from "@opencode/core/location-services"
|
||||
import { ConflictError, InvalidRequestError, ServiceUnavailableError } from "@opencode/protocol/errors"
|
||||
import { VcsInitNotSupportedError } from "@opencode/protocol/groups/vcs"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../location"
|
||||
|
||||
export const VcsHandler = HttpApiBuilder.group(Api, "server.vcs", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const project = yield* Project.Service
|
||||
const bus = yield* Bus.Service
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
return handlers
|
||||
.handle("vcs.init", (ctx) =>
|
||||
Effect.gen(function* () {
|
||||
const location = yield* Location.Service
|
||||
const directory = location.project.directory
|
||||
const providerID = ctx.query.provider ?? "git"
|
||||
yield* Plugin.awaitActivation
|
||||
const vcs = yield* Vcs.Service
|
||||
yield* vcs.initialize(providerID).pipe(
|
||||
Effect.mapError((error) => {
|
||||
if (error.kind === "missing")
|
||||
return new InvalidRequestError({ message: "Project directory does not exist", field: "location" })
|
||||
if (error.kind === "conflict")
|
||||
return new ConflictError({ message: "Project already has version control", resource: directory })
|
||||
if (error.kind === "unknown")
|
||||
return new InvalidRequestError({
|
||||
message: "Unknown VCS provider",
|
||||
field: "provider",
|
||||
})
|
||||
if (error.kind === "unsupported")
|
||||
return new VcsInitNotSupportedError({
|
||||
providerID,
|
||||
message: "VCS provider does not support initialization",
|
||||
})
|
||||
return new ServiceUnavailableError({ service: providerID, message: "VCS initialization failed" })
|
||||
}),
|
||||
)
|
||||
const resolved = yield* project.resolve(directory)
|
||||
if (!resolved.vcs)
|
||||
return yield* new ServiceUnavailableError({ service: providerID, message: "VCS initialization failed" })
|
||||
yield* locations.invalidate(
|
||||
Location.Ref.make({ directory: location.directory, workspaceID: location.workspaceID }),
|
||||
)
|
||||
yield* bus.publish(Worktree.Event.Updated, { projectID: resolved.id })
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle("vcs.get", () =>
|
||||
response(
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -96,6 +96,7 @@ const vcsLayer = Layer.succeed(
|
||||
Vcs.Service,
|
||||
Vcs.Service.of({
|
||||
base: () => Effect.succeed(null),
|
||||
initialize: () => Effect.fail(new Vcs.InitializeError({ kind: "unsupported" })),
|
||||
transform: () => Effect.succeed({ dispose: Effect.void }),
|
||||
reload: () => Effect.void,
|
||||
info: () => Effect.succeed({ branch: {} }),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "node:path"
|
||||
import { $ } from "bun"
|
||||
import { expect } from "bun:test"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { SdkPlugins } from "@opencode/core/plugin/sdk"
|
||||
import { Effect, Layer, Schedule } from "effect"
|
||||
import { tmpdir } from "../../core/test/fixture/tmpdir"
|
||||
@@ -8,6 +8,204 @@ import { it } from "../../core/test/lib/effect"
|
||||
import { startServer } from "./fixture/server"
|
||||
import { ServerFetch } from "../src/fetch"
|
||||
|
||||
it.live(
|
||||
"initializes Git in a markerless project and refreshes its location",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-vcs-init-")))
|
||||
yield* Effect.promise(() => Bun.write(path.join(tmp.path, "hello.txt"), "hello\n"))
|
||||
const server = yield* startServer(path.join(tmp.path, "config"))
|
||||
const url = new URL("/api/vcs/init", server.base)
|
||||
url.searchParams.set("location[directory]", tmp.path)
|
||||
const before = yield* Effect.promise(() =>
|
||||
fetch(new URL(`/api/location${url.search}`, server.base), { headers: server.headers }),
|
||||
)
|
||||
expect(before.status).toBe(200)
|
||||
const original = yield* Effect.promise(() => before.json())
|
||||
const initialized = yield* Effect.promise(() => fetch(url, { method: "POST", headers: server.headers }))
|
||||
expect(initialized.status).toBe(204)
|
||||
expect(yield* Effect.promise(() => $`git -C ${tmp.path} rev-parse --is-inside-work-tree`.text())).toBe("true\n")
|
||||
url.pathname = "/api/location"
|
||||
const location = yield* Effect.promise(() => fetch(url, { headers: server.headers }))
|
||||
expect(location.status).toBe(200)
|
||||
const refreshed = yield* Effect.promise(() => location.json())
|
||||
expect(refreshed).toMatchObject({ project: { directory: tmp.path } })
|
||||
expect(refreshed.project.id).not.toBe(original.project.id)
|
||||
const projects = yield* Effect.promise(() =>
|
||||
fetch(new URL("/api/project", server.base), { headers: server.headers }),
|
||||
)
|
||||
expect(yield* Effect.promise(() => projects.json())).toContainEqual(
|
||||
expect.objectContaining({ id: refreshed.project.id, vcs: "git" }),
|
||||
)
|
||||
url.pathname = "/api/vcs"
|
||||
const vcs = yield* Effect.tryPromise({
|
||||
try: async () => {
|
||||
const response = await fetch(url, { headers: server.headers })
|
||||
const body: unknown = await response.json()
|
||||
if (!isRecord(body) || !isRecord(body.data) || body.data.provider !== "git")
|
||||
throw new Error("Git provider not ready")
|
||||
return body
|
||||
},
|
||||
catch: (cause) => cause,
|
||||
}).pipe(Effect.retry(Schedule.spaced("10 millis")), Effect.timeout("2 seconds"))
|
||||
expect(vcs).toMatchObject({ data: { provider: "git" } })
|
||||
url.pathname = "/api/vcs/diff"
|
||||
url.searchParams.set("mode", "working")
|
||||
const diff = yield* Effect.promise(() => fetch(url, { headers: server.headers }))
|
||||
expect(diff.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => diff.json())).toMatchObject({
|
||||
data: expect.arrayContaining([expect.objectContaining({ file: "hello.txt" })]),
|
||||
})
|
||||
url.searchParams.delete("mode")
|
||||
url.pathname = "/api/vcs/init"
|
||||
const repeated = yield* Effect.promise(() => fetch(url, { method: "POST", headers: server.headers }))
|
||||
expect(repeated.status).toBe(409)
|
||||
}),
|
||||
15_000,
|
||||
)
|
||||
|
||||
it.live(
|
||||
"does not create a project directory while initializing Git",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-vcs-missing-")))
|
||||
const server = yield* startServer(path.join(tmp.path, "config"))
|
||||
const directory = path.join(tmp.path, "absent")
|
||||
const url = new URL("/api/vcs/init", server.base)
|
||||
url.searchParams.set("location[directory]", directory)
|
||||
const response = yield* Effect.promise(() => fetch(url, { method: "POST", headers: server.headers }))
|
||||
expect(response.status).not.toBe(204)
|
||||
expect(yield* Effect.promise(() => Bun.file(path.join(directory, ".git", "HEAD")).exists())).toBe(false)
|
||||
}),
|
||||
15_000,
|
||||
)
|
||||
|
||||
it.live(
|
||||
"accepts an explicit Git provider and rejects unknown providers without initializing",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-vcs-provider-")))
|
||||
const server = yield* startServer(path.join(tmp.path, "config"))
|
||||
const url = new URL("/api/vcs/init", server.base)
|
||||
url.searchParams.set("location[directory]", tmp.path)
|
||||
url.searchParams.set("provider", "unknown")
|
||||
const unsupported = yield* Effect.promise(() => fetch(url, { method: "POST", headers: server.headers }))
|
||||
expect(unsupported.status).toBe(400)
|
||||
expect(yield* Effect.promise(() => unsupported.json())).toMatchObject({
|
||||
_tag: "InvalidRequestError",
|
||||
field: "provider",
|
||||
})
|
||||
expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".git", "HEAD")).exists())).toBe(false)
|
||||
expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".hg", "requires")).exists())).toBe(false)
|
||||
url.searchParams.set("provider", "git")
|
||||
const initialized = yield* Effect.promise(() => fetch(url, { method: "POST", headers: server.headers }))
|
||||
expect(initialized.status).toBe(204)
|
||||
expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".git", "HEAD")).exists())).toBe(true)
|
||||
}),
|
||||
15_000,
|
||||
)
|
||||
|
||||
it.live("returns 501 when a registered VCS provider has no initializer", () =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-vcs-no-init-")))
|
||||
const handler = yield* ServerFetch.make(
|
||||
{
|
||||
database: { path: ":memory:" },
|
||||
config: { directory: tmp.path },
|
||||
fs: { filewatcher: false },
|
||||
models: { fetch: false },
|
||||
},
|
||||
{
|
||||
overrides: [
|
||||
SdkPlugins.node.replace(
|
||||
Layer.succeed(
|
||||
SdkPlugins.Service,
|
||||
SdkPlugins.Service.of({
|
||||
register: () => Effect.void,
|
||||
all: () => [
|
||||
{
|
||||
id: "read-only-vcs",
|
||||
revision: "test",
|
||||
effect: (ctx) =>
|
||||
ctx.vcs
|
||||
.transform((editor) => {
|
||||
editor.add({
|
||||
id: "read-only",
|
||||
name: "Read-only VCS",
|
||||
info: () => Effect.succeed({ branch: {} }),
|
||||
branches: () => Effect.succeed([]),
|
||||
status: () => Effect.succeed([]),
|
||||
diff: () => Effect.succeed([]),
|
||||
})
|
||||
})
|
||||
.pipe(Effect.asVoid),
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
const url = new URL("http://opencode.local/api/vcs/init")
|
||||
url.searchParams.set("location[directory]", tmp.path)
|
||||
url.searchParams.set("provider", "read-only")
|
||||
const response = yield* Effect.promise(() => handler(new Request(url, { method: "POST" })))
|
||||
expect(response.status).toBe(501)
|
||||
expect(yield* Effect.promise(() => response.json())).toMatchObject({
|
||||
_tag: "VcsInitNotSupportedError",
|
||||
providerID: "read-only",
|
||||
})
|
||||
expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".git", "HEAD")).exists())).toBe(false)
|
||||
expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".hg", "requires")).exists())).toBe(false)
|
||||
}),
|
||||
)
|
||||
|
||||
const describeHg = Bun.which("hg") ? describe : describe.skip
|
||||
|
||||
describeHg("Mercurial initialization", () => {
|
||||
it.live(
|
||||
"initializes and serves an untracked-file diff through the Hg provider",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-vcs-hg-init-")))
|
||||
yield* Effect.promise(() => Bun.write(path.join(tmp.path, "hello.txt"), "hello\n"))
|
||||
const server = yield* startServer(path.join(tmp.path, "config"))
|
||||
const url = new URL("/api/vcs/init", server.base)
|
||||
url.searchParams.set("location[directory]", tmp.path)
|
||||
url.searchParams.set("provider", "hg")
|
||||
const initialized = yield* Effect.promise(() => fetch(url, { method: "POST", headers: server.headers }))
|
||||
expect(initialized.status).toBe(204)
|
||||
expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".hg", "requires")).exists())).toBe(true)
|
||||
url.pathname = "/api/vcs"
|
||||
const info = yield* Effect.tryPromise({
|
||||
try: async () => {
|
||||
const response = await fetch(url, { headers: server.headers })
|
||||
const body: unknown = await response.json()
|
||||
if (!isRecord(body) || !isRecord(body.data) || body.data.provider !== "hg")
|
||||
throw new Error("Mercurial provider not ready")
|
||||
return body
|
||||
},
|
||||
catch: (cause) => cause,
|
||||
}).pipe(Effect.retry(Schedule.spaced("10 millis")), Effect.timeout("2 seconds"))
|
||||
expect(info).toMatchObject({ data: { provider: "hg" } })
|
||||
url.pathname = "/api/vcs/diff"
|
||||
url.searchParams.set("mode", "working")
|
||||
const diff = yield* Effect.promise(() => fetch(url, { headers: server.headers }))
|
||||
expect(diff.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => diff.json())).toMatchObject({
|
||||
data: expect.arrayContaining([expect.objectContaining({ file: "hello.txt" })]),
|
||||
})
|
||||
url.pathname = "/api/vcs/init"
|
||||
url.searchParams.delete("mode")
|
||||
url.searchParams.set("provider", "git")
|
||||
const repeated = yield* Effect.promise(() => fetch(url, { method: "POST", headers: server.headers }))
|
||||
expect(repeated.status).toBe(409)
|
||||
}),
|
||||
15_000,
|
||||
)
|
||||
})
|
||||
|
||||
it.live(
|
||||
"serves lazy review bases, committed diffs, and unavailable-base errors",
|
||||
() =>
|
||||
|
||||
@@ -25,10 +25,12 @@ it.live("list reads saved inventory even when its checkout is missing, without b
|
||||
await fs.rm(directory, { recursive: true })
|
||||
expect(await api.worktree.list({ projectID: session.projectID })).toEqual([{ directory }])
|
||||
await expect(api.worktree.create({ projectID: session.projectID })).rejects.toMatchObject({
|
||||
_tag: "WorktreeError",
|
||||
name: "WorktreeError",
|
||||
data: { message: `Worktree directory unavailable: ${directory}` },
|
||||
})
|
||||
await expect(api.worktree.refresh({ projectID: session.projectID })).rejects.toMatchObject({
|
||||
_tag: "WorktreeError",
|
||||
name: "WorktreeError",
|
||||
data: { message: `Worktree directory unavailable: ${directory}` },
|
||||
})
|
||||
@@ -109,7 +111,7 @@ it.live("remove loads canonical strategies and enforces project ownership", () =
|
||||
const foreign = await api.session.create({ location: { directory: other } })
|
||||
await expect(
|
||||
api.worktree.remove({ projectID: foreign.projectID, directory: linked, force: true }),
|
||||
).rejects.toMatchObject({ name: "WorktreeError" })
|
||||
).rejects.toMatchObject({ _tag: "WorktreeError", name: "WorktreeError" })
|
||||
expect(await fs.stat(linked).then((stat) => stat.isDirectory())).toBe(true)
|
||||
await api.worktree.remove({ projectID: session.projectID, directory: linked, force: false })
|
||||
expect(await api.debug.location.list()).toContainEqual({ directory })
|
||||
@@ -267,7 +269,11 @@ it.live(
|
||||
body: JSON.stringify({ projectID, directory: custom.directory, force: false }),
|
||||
})
|
||||
expect(failure.status).toBe(400)
|
||||
expect(await failure.json()).toMatchObject({ data: { forceRequired: true } })
|
||||
expect(await failure.json()).toMatchObject({
|
||||
_tag: "WorktreeError",
|
||||
name: "WorktreeError",
|
||||
data: { forceRequired: true },
|
||||
})
|
||||
expect(await Bun.file(path.join(custom.directory, "dirty.txt")).text()).toBe("keep me")
|
||||
expect(await api.worktree.list({ projectID })).toEqual(rows)
|
||||
expect(await api.debug.location.list()).toContainEqual({ directory: first })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/session-ui",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -308,15 +308,25 @@ export namespace Timeline {
|
||||
const previousUserMessage = index > 0
|
||||
const compaction = entries.some((entry) => entry.type === "notice" && entry.message.type === "compaction")
|
||||
const lastContent = lastAssistant?.content.at(-1)
|
||||
const thinking =
|
||||
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
|
||||
const working =
|
||||
isActive &&
|
||||
status.type === "busy" &&
|
||||
lastAssistant?.time.completed === undefined &&
|
||||
!lastAssistant?.error &&
|
||||
!lastAssistant?.retry &&
|
||||
!lastAssistant?.retry
|
||||
const thinking =
|
||||
working &&
|
||||
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
|
||||
lastContent?.type === "reasoning" &&
|
||||
lastContent.time?.completed === undefined
|
||||
const thoughtOnly =
|
||||
working &&
|
||||
detail?.thinking.placement === "grouped" &&
|
||||
assistantMessages.every((message) =>
|
||||
message.content.every(
|
||||
(content) => content.type === "reasoning" || !isRenderable(content, showReasoning, detail),
|
||||
),
|
||||
)
|
||||
|
||||
if (previousUserMessage) rows.push(new TimelineRow.TurnGap({ userMessageID: turnID }))
|
||||
if (userMessage) rows.push(new TimelineRow.UserMessage({ userMessageID: turnID }))
|
||||
@@ -326,6 +336,7 @@ export namespace Timeline {
|
||||
// An assistant message can produce several rows because its content parts are
|
||||
// rendered separately. Notices end a segment so none of those rows cross it.
|
||||
const appendAssistantSegment = (messages: SessionMessageAssistant[]) => {
|
||||
if (thoughtOnly) return
|
||||
const refs = messages.flatMap((message, messageIndex) =>
|
||||
contentEntries(message)
|
||||
.filter(
|
||||
|
||||
@@ -264,6 +264,54 @@ describe("current session timeline rows", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("omits a grouped reasoning-only row while busy until other content or completion", () => {
|
||||
const thought = { type: "reasoning", text: "Thinking", time: { created: 1, completed: 2 } } as const
|
||||
const rows = (content: SessionMessageAssistant["content"], busy: boolean) =>
|
||||
createTimelineProjection({
|
||||
sessionMessages: storyDocument(content, busy).messages,
|
||||
status: { type: busy ? "busy" : "idle" },
|
||||
reasoningMode: "compact",
|
||||
timelineDetail: timelinePresets[2].value,
|
||||
}).rows
|
||||
|
||||
expect(rows([{ ...thought, time: { created: 1 } }], true).map((row) => row._tag)).toEqual(["UserMessage"])
|
||||
expect(rows([thought], true).map((row) => row._tag)).toEqual(["UserMessage"])
|
||||
expect(rows([thought, { type: "text", text: "" }], true).map((row) => row._tag)).toEqual(["UserMessage"])
|
||||
expect(rows([thought, { type: "text", text: "Answer" }], true).map((row) => row._tag)).toEqual([
|
||||
"UserMessage",
|
||||
"AssistantPart",
|
||||
"AssistantPart",
|
||||
])
|
||||
expect(
|
||||
rows([thought, storyTool("read", "read", "running", { filePath: "package.json" })], true).map((row) => row._tag),
|
||||
).toEqual(["UserMessage", "AssistantPart"])
|
||||
expect(rows([thought], false).map((row) => row._tag)).toEqual(["UserMessage", "AssistantPart"])
|
||||
|
||||
const failed = storyDocument([thought], true)
|
||||
const messages = failed.messages.map((message) =>
|
||||
message.type === "assistant" ? { ...message, error: { type: "provider.error", message: "Failed" } } : message,
|
||||
)
|
||||
expect(
|
||||
createTimelineProjection({
|
||||
sessionMessages: messages,
|
||||
status: failed.status,
|
||||
reasoningMode: "compact",
|
||||
timelineDetail: timelinePresets[2].value,
|
||||
}).rows.map((row) => row._tag),
|
||||
).toEqual(["UserMessage", "AssistantPart", "Error"])
|
||||
|
||||
expect(
|
||||
createTimelineProjection({
|
||||
sessionMessages: failed.messages.map((message) =>
|
||||
message.type === "assistant" ? { ...message, error: { type: "Interrupted", message: "Stopped" } } : message,
|
||||
),
|
||||
status: failed.status,
|
||||
reasoningMode: "compact",
|
||||
timelineDetail: { ...timelinePresets[2].value, notices: { placement: "hidden" } },
|
||||
}).rows.map((row) => row._tag),
|
||||
).toEqual(["UserMessage", "AssistantPart"])
|
||||
})
|
||||
|
||||
test("stops thinking on idle, message completion, errors and retries", () => {
|
||||
const document = storyDocument([{ type: "reasoning", text: "Current thought" }], true)
|
||||
expect(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/simulation",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/stats-app",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/stats-core",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/stats-server",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/theme",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/tui",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/ui",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/util",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/update",
|
||||
"version": "2.0.21",
|
||||
"version": "2.0.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
+628
-20
@@ -48,6 +48,106 @@
|
||||
"summary": "Get server info"
|
||||
}
|
||||
},
|
||||
"/api/pair": {
|
||||
"post": {
|
||||
"tags": ["server"],
|
||||
"operationId": "server.pair",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PairingCode",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PairingCode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a short-lived, single-use code for a /auth/connect/:code pairing link.",
|
||||
"summary": "Create pairing code"
|
||||
}
|
||||
},
|
||||
"/auth/connect/{code}": {
|
||||
"get": {
|
||||
"tags": ["server"],
|
||||
"operationId": "server.connect",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "code",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PairingSession",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PairingSession"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Redeem a pairing code. Browsers receive a session cookie and a redirect to the web app; requests that accept JSON receive a session token to use as the password.",
|
||||
"summary": "Redeem pairing code"
|
||||
}
|
||||
},
|
||||
"/api/location": {
|
||||
"get": {
|
||||
"tags": ["location"],
|
||||
@@ -886,9 +986,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "SessionNotFoundError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a session at the requested location.",
|
||||
"description": "Create a session at the requested location. A parentID creates a linked child session at its parent's location.",
|
||||
"summary": "Create session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -907,6 +1017,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"parentID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^ses"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -4508,6 +4629,21 @@
|
||||
"pattern": "^frm_"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
@@ -4591,21 +4727,6 @@
|
||||
"pattern": "^frm_"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
@@ -7197,6 +7318,125 @@
|
||||
"summary": "List MCP resources"
|
||||
}
|
||||
},
|
||||
"/api/credential": {
|
||||
"get": {
|
||||
"tags": ["credential"],
|
||||
"operationId": "credential.list",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Credential.Entry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "List every stored integration credential, including its secret value.",
|
||||
"summary": "List credentials"
|
||||
},
|
||||
"post": {
|
||||
"tags": ["credential"],
|
||||
"operationId": "credential.create",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/Credential.Entry"
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "ConflictError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConflictErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Store an integration credential. It becomes the integration's active credential unless activate is false and the integration already has one. Fails with a conflict when the requested ID already exists.",
|
||||
"summary": "Create credential",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Credential.CreateInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/credential/{credentialID}": {
|
||||
"patch": {
|
||||
"tags": ["credential"],
|
||||
@@ -11451,6 +11691,124 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/vcs/init": {
|
||||
"post": {
|
||||
"tags": ["vcs"],
|
||||
"operationId": "vcs.init",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false,
|
||||
"style": "deepObject",
|
||||
"explode": true
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "<No Content>"
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "ConflictError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConflictErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"501": {
|
||||
"description": "VcsInitNotSupportedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VcsInitNotSupportedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "ServiceUnavailableError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Initialize a repository using the selected VCS provider in a markerless project's directory and refresh its location services. Omitting provider defaults to git; built-in git and hg providers support initialization. An unknown provider returns 400; a registered provider without init returns 501.",
|
||||
"summary": "Initialize VCS repository"
|
||||
}
|
||||
},
|
||||
"/api/vcs": {
|
||||
"get": {
|
||||
"tags": ["vcs"],
|
||||
@@ -13237,6 +13595,27 @@
|
||||
"required": ["command"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.Capabilities": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"input": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.CostEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13328,7 +13707,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"capabilities": {
|
||||
"$ref": "#/components/schemas/Model.Capabilities"
|
||||
"$ref": "#/components/schemas/Config.Model.Capabilities"
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
@@ -13433,8 +13812,27 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"headerTimeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chunkTimeout": {
|
||||
"type": "number"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
@@ -13631,6 +14029,9 @@
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["key", "oauth"]
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Connection.Status"
|
||||
}
|
||||
},
|
||||
"required": ["type", "id", "label", "method"],
|
||||
@@ -13645,6 +14046,9 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Connection.Status"
|
||||
}
|
||||
},
|
||||
"required": ["type", "name"],
|
||||
@@ -13660,6 +14064,128 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Connection.Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["needs_auth"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["status", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.CreateInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"integrationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/components/schemas/Credential.Value"
|
||||
},
|
||||
"activate": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["integrationID", "value"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Entry": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"integrationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/components/schemas/Credential.Value"
|
||||
}
|
||||
},
|
||||
"required": ["id", "integrationID", "label", "active", "value"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Key": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["key"]
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
"configuration": {
|
||||
"$ref": "#/components/schemas/Form.Answer_1"
|
||||
}
|
||||
},
|
||||
"required": ["type", "key"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.OAuth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["oauth"]
|
||||
},
|
||||
"methodID": {
|
||||
"type": "string"
|
||||
},
|
||||
"refresh": {
|
||||
"type": "string"
|
||||
},
|
||||
"access": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["type", "methodID", "refresh", "access", "expires"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Value": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Credential.OAuth"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Credential.Key"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileDiff.Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13746,6 +14272,12 @@
|
||||
"$ref": "#/components/schemas/Form.Value"
|
||||
}
|
||||
},
|
||||
"Form.Answer_1": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/Form.Value"
|
||||
}
|
||||
},
|
||||
"Form.BooleanField": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15709,6 +16241,29 @@
|
||||
"Money.USDPerMillionTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"PairingCode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_in": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["code", "expires_in"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PairingSession": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["token"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Permission.Effect": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "deny", "ask"]
|
||||
@@ -16601,8 +17156,27 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"headerTimeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chunkTimeout": {
|
||||
"type": "number"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
@@ -18234,6 +18808,16 @@
|
||||
"type": "integer",
|
||||
"minimum": 100,
|
||||
"maximum": 599
|
||||
},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["body"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["type", "message"],
|
||||
@@ -18653,6 +19237,9 @@
|
||||
"exit": {
|
||||
"type": "number"
|
||||
},
|
||||
"signal": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -18929,6 +19516,23 @@
|
||||
"type": "string",
|
||||
"enum": ["working", "branch", "committed"]
|
||||
},
|
||||
"VcsInitNotSupportedErrorEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["VcsInitNotSupportedError"]
|
||||
},
|
||||
"providerID": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["_tag", "providerID", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"WebSearch.Provider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -19053,6 +19657,10 @@
|
||||
"WorktreeErrorEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
@@ -19078,7 +19686,7 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"],
|
||||
"required": ["_tag", "name", "data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -48,6 +48,106 @@
|
||||
"summary": "Get server info"
|
||||
}
|
||||
},
|
||||
"/api/pair": {
|
||||
"post": {
|
||||
"tags": ["server"],
|
||||
"operationId": "server.pair",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PairingCode",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PairingCode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a short-lived, single-use code for a /auth/connect/:code pairing link.",
|
||||
"summary": "Create pairing code"
|
||||
}
|
||||
},
|
||||
"/auth/connect/{code}": {
|
||||
"get": {
|
||||
"tags": ["server"],
|
||||
"operationId": "server.connect",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "code",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PairingSession",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PairingSession"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Redeem a pairing code. Browsers receive a session cookie and a redirect to the web app; requests that accept JSON receive a session token to use as the password.",
|
||||
"summary": "Redeem pairing code"
|
||||
}
|
||||
},
|
||||
"/api/location": {
|
||||
"get": {
|
||||
"tags": ["location"],
|
||||
@@ -886,9 +986,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "SessionNotFoundError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a session at the requested location.",
|
||||
"description": "Create a session at the requested location. A parentID creates a linked child session at its parent's location.",
|
||||
"summary": "Create session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -907,6 +1017,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"parentID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^ses"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -4508,6 +4629,21 @@
|
||||
"pattern": "^frm_"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
@@ -4591,21 +4727,6 @@
|
||||
"pattern": "^frm_"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
@@ -7197,6 +7318,125 @@
|
||||
"summary": "List MCP resources"
|
||||
}
|
||||
},
|
||||
"/api/credential": {
|
||||
"get": {
|
||||
"tags": ["credential"],
|
||||
"operationId": "credential.list",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Credential.Entry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "List every stored integration credential, including its secret value.",
|
||||
"summary": "List credentials"
|
||||
},
|
||||
"post": {
|
||||
"tags": ["credential"],
|
||||
"operationId": "credential.create",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/Credential.Entry"
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "ConflictError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConflictErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Store an integration credential. It becomes the integration's active credential unless activate is false and the integration already has one. Fails with a conflict when the requested ID already exists.",
|
||||
"summary": "Create credential",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Credential.CreateInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/credential/{credentialID}": {
|
||||
"patch": {
|
||||
"tags": ["credential"],
|
||||
@@ -11451,6 +11691,124 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/vcs/init": {
|
||||
"post": {
|
||||
"tags": ["vcs"],
|
||||
"operationId": "vcs.init",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false,
|
||||
"style": "deepObject",
|
||||
"explode": true
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "<No Content>"
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "ConflictError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConflictErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"501": {
|
||||
"description": "VcsInitNotSupportedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VcsInitNotSupportedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "ServiceUnavailableError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Initialize a repository using the selected VCS provider in a markerless project's directory and refresh its location services. Omitting provider defaults to git; built-in git and hg providers support initialization. An unknown provider returns 400; a registered provider without init returns 501.",
|
||||
"summary": "Initialize VCS repository"
|
||||
}
|
||||
},
|
||||
"/api/vcs": {
|
||||
"get": {
|
||||
"tags": ["vcs"],
|
||||
@@ -13237,6 +13595,27 @@
|
||||
"required": ["command"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.Capabilities": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"input": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.CostEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13328,7 +13707,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"capabilities": {
|
||||
"$ref": "#/components/schemas/Model.Capabilities"
|
||||
"$ref": "#/components/schemas/Config.Model.Capabilities"
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
@@ -13433,8 +13812,27 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"headerTimeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chunkTimeout": {
|
||||
"type": "number"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
@@ -13631,6 +14029,9 @@
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["key", "oauth"]
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Connection.Status"
|
||||
}
|
||||
},
|
||||
"required": ["type", "id", "label", "method"],
|
||||
@@ -13645,6 +14046,9 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Connection.Status"
|
||||
}
|
||||
},
|
||||
"required": ["type", "name"],
|
||||
@@ -13660,6 +14064,128 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Connection.Status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["needs_auth"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["status", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.CreateInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"integrationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/components/schemas/Credential.Value"
|
||||
},
|
||||
"activate": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["integrationID", "value"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Entry": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"integrationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/components/schemas/Credential.Value"
|
||||
}
|
||||
},
|
||||
"required": ["id", "integrationID", "label", "active", "value"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Key": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["key"]
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
"configuration": {
|
||||
"$ref": "#/components/schemas/Form.Answer_1"
|
||||
}
|
||||
},
|
||||
"required": ["type", "key"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.OAuth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["oauth"]
|
||||
},
|
||||
"methodID": {
|
||||
"type": "string"
|
||||
},
|
||||
"refresh": {
|
||||
"type": "string"
|
||||
},
|
||||
"access": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["type", "methodID", "refresh", "access", "expires"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Credential.Value": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Credential.OAuth"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Credential.Key"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileDiff.Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13746,6 +14272,12 @@
|
||||
"$ref": "#/components/schemas/Form.Value"
|
||||
}
|
||||
},
|
||||
"Form.Answer_1": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/Form.Value"
|
||||
}
|
||||
},
|
||||
"Form.BooleanField": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15709,6 +16241,29 @@
|
||||
"Money.USDPerMillionTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"PairingCode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_in": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["code", "expires_in"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PairingSession": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["token"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Permission.Effect": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "deny", "ask"]
|
||||
@@ -16601,8 +17156,27 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"headerTimeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"chunkTimeout": {
|
||||
"type": "number"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
@@ -18234,6 +18808,16 @@
|
||||
"type": "integer",
|
||||
"minimum": 100,
|
||||
"maximum": 599
|
||||
},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["body"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["type", "message"],
|
||||
@@ -18653,6 +19237,9 @@
|
||||
"exit": {
|
||||
"type": "number"
|
||||
},
|
||||
"signal": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -18929,6 +19516,23 @@
|
||||
"type": "string",
|
||||
"enum": ["working", "branch", "committed"]
|
||||
},
|
||||
"VcsInitNotSupportedErrorEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["VcsInitNotSupportedError"]
|
||||
},
|
||||
"providerID": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["_tag", "providerID", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"WebSearch.Provider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -19053,6 +19657,10 @@
|
||||
"WorktreeErrorEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_tag": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["WorktreeError"]
|
||||
@@ -19078,7 +19686,7 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"],
|
||||
"required": ["_tag", "name", "data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1086,6 +1086,11 @@ Provider callbacks receive the current location, working-copy root, canonical pr
|
||||
store. Diff callbacks also receive the selected mode, requested context, and output byte budget. Repository discovery
|
||||
continues to use OpenCode's built-in Git and Mercurial detectors.
|
||||
|
||||
Providers can optionally implement `init(scope)` to create a repository in `scope.worktree`. Initialization selects
|
||||
the provider by ID (Git by default), not by the detected repository type. Register a provider with `init` even when
|
||||
the location has no repository yet; OpenCode checks that the directory exists and has no repository before calling
|
||||
it, then rediscovers the project. New repository formats still need discovery support beyond this callback.
|
||||
|
||||
Schemas: [`Vcs.Info`](/api#schema-Vcs.Info), [`Vcs.FileStatus`](/api#schema-Vcs.FileStatus),
|
||||
[`FileDiff.Info`](/api#schema-FileDiff.Info).
|
||||
|
||||
@@ -1098,10 +1103,15 @@ interface VcsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
interface VcsDomain extends VcsApi<unknown> {
|
||||
interface VcsDomain extends Omit<VcsApi<unknown>, "init"> {
|
||||
readonly transform: Transform<VcsEditor>
|
||||
readonly reload: () => Effect.Effect<void>
|
||||
}
|
||||
|
||||
interface VcsDefinition {
|
||||
readonly init?: (scope: VcsScope) => Effect.Effect<void, unknown>
|
||||
// Existing info, branches, status, and diff callbacks are required.
|
||||
}
|
||||
```
|
||||
|
||||
### Websearch
|
||||
|
||||
@@ -1045,6 +1045,11 @@ Provider callbacks receive the current location, working-copy root, canonical pr
|
||||
store. Diff callbacks also receive the selected mode, requested context, and output byte budget. Repository discovery
|
||||
continues to use OpenCode's built-in Git and Mercurial detectors.
|
||||
|
||||
Providers can optionally implement `init(scope, { signal })` to create a repository in `scope.worktree`. Initialization
|
||||
selects the provider by ID (Git by default), not by the detected repository type. Register a provider with `init` even
|
||||
when the location has no repository yet; OpenCode checks that the directory exists and has no repository before calling
|
||||
it, then rediscovers the project. New repository formats still need discovery support beyond this callback.
|
||||
|
||||
#### Reference
|
||||
|
||||
Schemas: [`Vcs.Info`](/api#schema-Vcs.Info), [`Vcs.FileStatus`](/api#schema-Vcs.FileStatus),
|
||||
@@ -1067,6 +1072,11 @@ interface VcsEditor {
|
||||
set(providerID: string): void
|
||||
}
|
||||
}
|
||||
|
||||
interface VcsDefinition {
|
||||
init?(scope: VcsScope, context: { signal: AbortSignal }): Promise<void>
|
||||
// Existing info, branches, status, and diff callbacks are required.
|
||||
}
|
||||
```
|
||||
|
||||
### Worktrees
|
||||
|
||||
Reference in new issue
Block a user