diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 51d3018cbb0b..00ba2fc8669b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -56,7 +56,6 @@ - `cargo fmt --check` - Code formatting (rustfmt) - `cargo test --jobs 2` - All tests - `cargo clippy --all-targets -- -D warnings` - Linting (clippy) -- `just check-openapi-schema` - OpenAPI schema validation **Desktop app checks:** - `pnpm install --frozen-lockfile` - Fresh dependency install (in `ui/desktop/`) diff --git a/.github/workflows/bundle-desktop-intel.yml b/.github/workflows/bundle-desktop-intel.yml index 3ac28f404d2a..c2a26bf352d6 100644 --- a/.github/workflows/bundle-desktop-intel.yml +++ b/.github/workflows/bundle-desktop-intel.yml @@ -73,11 +73,11 @@ jobs: key: intel-macos-deployment-target-12 - - name: Build goose-server for Intel macOS (x86_64) + - name: Build desktop backend for Intel macOS (x86_64) run: | source ./bin/activate-hermit rustup target add x86_64-apple-darwin - cargo build --release -p goose-server --target x86_64-apple-darwin + cargo build --release -p goose-cli --bin goose --target x86_64-apple-darwin @@ -95,9 +95,13 @@ jobs: # Check disk space after cleanup df -h - - name: Copy binaries into Electron folder + - name: Copy backend binary into Electron folder run: | - cp target/x86_64-apple-darwin/release/goosed ui/desktop/src/bin/goosed + mkdir -p ui/desktop/src/bin + rm -f ui/desktop/src/bin/goose + cp target/x86_64-apple-darwin/release/goose ui/desktop/src/bin/goose + chmod +x ui/desktop/src/bin/goose + ls -la ui/desktop/src/bin/ - name: Cache pnpm dependencies uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 diff --git a/.github/workflows/bundle-desktop-linux.yml b/.github/workflows/bundle-desktop-linux.yml index 18ba931a6c48..ad3b84f1946a 100644 --- a/.github/workflows/bundle-desktop-linux.yml +++ b/.github/workflows/bundle-desktop-linux.yml @@ -124,7 +124,7 @@ jobs: with: key: linux-${{ matrix.build-on }}-${{ matrix.variant }} - - name: Build goosed binary + - name: Build desktop backend binary env: RUST_LOG: debug RUST_BACKTRACE: 1 @@ -137,15 +137,16 @@ jobs: FEATURE_ARGS=(--features vulkan) fi - cargo build --release --target ${TARGET} -p goose-server "${FEATURE_ARGS[@]}" + cargo build --release --target ${TARGET} -p goose-cli --bin goose "${FEATURE_ARGS[@]}" - - name: Copy binaries into Electron folder + - name: Copy backend binary into Electron folder run: | - echo "Copying binaries to ui/desktop/src/bin/" + echo "Copying backend binary to ui/desktop/src/bin/" export TARGET="x86_64-unknown-linux-gnu" mkdir -p ui/desktop/src/bin - cp target/$TARGET/release/goosed ui/desktop/src/bin/ - chmod +x ui/desktop/src/bin/goosed + rm -f ui/desktop/src/bin/goose + cp target/$TARGET/release/goose ui/desktop/src/bin/ + chmod +x ui/desktop/src/bin/goose ls -la ui/desktop/src/bin/ - name: Free Rust build artifacts before packaging diff --git a/.github/workflows/bundle-desktop-windows.yml b/.github/workflows/bundle-desktop-windows.yml index 7990a39d3274..6ceb29b82f8d 100644 --- a/.github/workflows/bundle-desktop-windows.yml +++ b/.github/workflows/bundle-desktop-windows.yml @@ -113,28 +113,33 @@ jobs: env: CUDA_COMPUTE_CAP: ${{ inputs.windows_variant == 'cuda' && '80' || '' }} run: | - Write-Output "Building Windows executable..." - if ("${{ inputs.windows_variant }}" -eq "cuda") { - cargo build --release --target x86_64-pc-windows-msvc -p goose-server --features cuda + $isCuda = "${{ inputs.windows_variant }}" -eq "cuda" + + Write-Output "Building Windows ACP backend" + if ($isCuda) { + cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose --features cuda } else { - cargo build --release --target x86_64-pc-windows-msvc -p goose-server + cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose } + $binaryPath = "./target/x86_64-pc-windows-msvc/release/goose.exe" # Verify build succeeded - if (-not (Test-Path "./target/x86_64-pc-windows-msvc/release/goosed.exe")) { - Write-Error "Windows binary not found." + if (-not (Test-Path $binaryPath)) { + Write-Error "Windows backend binary not found: $binaryPath" Get-ChildItem ./target/x86_64-pc-windows-msvc/release/ -ErrorAction SilentlyContinue exit 1 } - Write-Output "Windows binary found." - Get-Item ./target/x86_64-pc-windows-msvc/release/goosed.exe + Write-Output "Windows backend binary found." + Get-Item $binaryPath - name: Prepare Windows binary shell: bash run: | - if [ ! -f "./target/x86_64-pc-windows-msvc/release/goosed.exe" ]; then - echo "Windows binary not found." + BACKEND_BINARY="./target/x86_64-pc-windows-msvc/release/goose.exe" + + if [ ! -f "$BACKEND_BINARY" ]; then + echo "Windows backend binary not found: $BACKEND_BINARY" exit 1 fi @@ -142,13 +147,14 @@ jobs: rm -rf ./ui/desktop/src/bin mkdir -p ./ui/desktop/src/bin - echo "Copying Windows binary..." - cp -f ./target/x86_64-pc-windows-msvc/release/goosed.exe ./ui/desktop/src/bin/ + echo "Copying Windows backend binary..." + cp -f "$BACKEND_BINARY" ./ui/desktop/src/bin/ if [ -d "./ui/desktop/src/platform/windows/bin" ]; then echo "Copying Windows platform files..." for file in ./ui/desktop/src/platform/windows/bin/*.{exe,dll,cmd}; do - if [ -f "$file" ] && [ "$(basename "$file")" != "goosed.exe" ]; then + filename="$(basename "$file")" + if [ -f "$file" ] && [ "$filename" != "goose.exe" ]; then cp -f "$file" ./ui/desktop/src/bin/ fi done @@ -229,14 +235,14 @@ jobs: certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }} files: | ${{ github.workspace }}/dist-windows/Goose.exe - ${{ github.workspace }}/dist-windows/resources/bin/goosed.exe + ${{ github.workspace }}/dist-windows/resources/bin/goose.exe - name: Verify signed executables shell: pwsh run: | $files = @( "dist-windows/Goose.exe", - "dist-windows/resources/bin/goosed.exe" + "dist-windows/resources/bin/goose.exe" ) foreach ($file in $files) { Write-Output "Verifying signature: $file" diff --git a/.github/workflows/bundle-desktop.yml b/.github/workflows/bundle-desktop.yml index f892a8bb1e48..f431a34bc788 100644 --- a/.github/workflows/bundle-desktop.yml +++ b/.github/workflows/bundle-desktop.yml @@ -118,8 +118,10 @@ jobs: key: macos-deployment-target-12 # Build the project - - name: Build goosed - run: source ./bin/activate-hermit && cargo build --release -p goose-server + - name: Build desktop backend + run: | + source ./bin/activate-hermit + cargo build --release -p goose-cli --bin goose # Post-build cleanup to free space - name: Post-build cleanup @@ -134,9 +136,13 @@ jobs: # Check disk space after cleanup df -h - - name: Copy binaries into Electron folder + - name: Copy backend binary into Electron folder run: | - cp target/release/goosed ui/desktop/src/bin/goosed + mkdir -p ui/desktop/src/bin + rm -f ui/desktop/src/bin/goose + cp target/release/goose ui/desktop/src/bin/goose + chmod +x ui/desktop/src/bin/goose + ls -la ui/desktop/src/bin/ - name: Cache pnpm dependencies uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index a0273f286eb3..f93599880c84 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -67,7 +67,7 @@ jobs: path: download_cli.sh # ------------------------------------------------------------ - # 4) Bundle Desktop App (macOS only) - builds goosed and Electron app + # 4) Bundle Desktop App (macOS only) # ------------------------------------------------------------ bundle-desktop: needs: [prepare-version] @@ -80,7 +80,7 @@ jobs: signing: false # ------------------------------------------------------------ - # 5) Bundle Desktop App (macOS Intel) - builds goosed and Electron app + # 5) Bundle Desktop App (macOS Intel) # ------------------------------------------------------------ bundle-desktop-intel: needs: [prepare-version] @@ -93,7 +93,7 @@ jobs: signing: false # ------------------------------------------------------------ - # 6) Bundle Desktop App (Linux) - builds goosed and Electron app + # 6) Bundle Desktop App (Linux) # ------------------------------------------------------------ bundle-desktop-linux: needs: [prepare-version] @@ -102,7 +102,7 @@ jobs: version: ${{ needs.prepare-version.outputs.version }} # ------------------------------------------------------------ - # 6) Bundle Desktop App (Windows) - builds goosed and Electron app + # 6) Bundle Desktop App (Windows) # ------------------------------------------------------------ bundle-desktop-windows: needs: [prepare-version] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb062d22ddd2..1216340871d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -183,12 +183,6 @@ jobs: cd ui/desktop && pnpm install --frozen-lockfile cd ../sdk && pnpm install --frozen-lockfile - - name: Check OpenAPI Schema is Up-to-Date - run: | - source ./bin/activate-hermit - hermit uninstall rustup - just check-openapi-schema - - name: Check ACP Schema is Up-to-Date run: | source ./bin/activate-hermit diff --git a/.github/workflows/pr-smoke-test.yml b/.github/workflows/pr-smoke-test.yml index a0f263475345..ae83ad2afeeb 100644 --- a/.github/workflows/pr-smoke-test.yml +++ b/.github/workflows/pr-smoke-test.yml @@ -67,7 +67,7 @@ jobs: - name: Build Binary for Smoke Tests run: | - cargo build --bin goose --bin goosed + cargo build --bin goose - name: Upload goose binary uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -76,13 +76,6 @@ jobs: path: target/debug/goose retention-days: 1 - - name: Upload goosed binary - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: goosed-binary - path: target/debug/goosed - retention-days: 1 - smoke-tests: name: Smoke Tests runs-on: ubuntu-latest @@ -253,39 +246,3 @@ jobs: mkdir -p $HOME/.local/share/goose/sessions mkdir -p $HOME/.config/goose bash scripts/test_compaction.sh - - goosed-integration-tests: - name: goose server HTTP integration tests - runs-on: ubuntu-latest - needs: build-binary - steps: - - name: Checkout Code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.inputs.branch || github.ref }} - - - name: Download Binary - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: goosed-binary - path: target/debug - - - name: Make Binary Executable - run: chmod +x target/debug/goosed - - - name: Install Node.js Dependencies - run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile - working-directory: ui/desktop - - - name: Run Integration Tests - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - GOOSED_BINARY: ../../target/debug/goosed - GOOSE_PROVIDER: anthropic - GOOSE_MODEL: claude-sonnet-4-5-20250929 - SHELL: /bin/bash - SKIP_BUILD: 1 - run: | - echo 'export PATH=/some/fake/path:$PATH' >> $HOME/.bash_profile - source ../../bin/activate-hermit && pnpm run test:integration:goosed - working-directory: ui/desktop diff --git a/AGENTS.md b/AGENTS.md index e0fda6d5e075..e3ab069bd9db 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ cargo build ```bash cargo build # debug cargo build --release # release -just release-binary # release + openapi +just release-binary # release binary ``` ### Test @@ -33,8 +33,8 @@ cargo clippy --all-targets -- -D warnings ### UI ```bash -just generate-openapi # after server changes just run-ui # start desktop +cd ui/desktop && pnpm run typecheck cd ui/desktop && pnpm test # test UI ``` @@ -44,7 +44,6 @@ crates/ ├── goose # core logic ├── goose-acp-macros # ACP proc macros ├── goose-cli # CLI entry -├── goose-server # backend (binary: goosed) ├── goose-mcp # MCP extensions ├── goose-test # test utilities └── goose-test-support # test helpers @@ -65,7 +64,6 @@ ui/desktop/ # Electron app # 1. cargo build # 2. cargo test -p # 3. cargo clippy --all-targets -- -D warnings -# 4. [if server] just generate-openapi ``` ## Rules @@ -75,7 +73,7 @@ ui/desktop/ # Electron app - Error: Use anyhow::Result - Provider: Implement Provider trait see providers/base.rs - MCP: Extensions in crates/goose-mcp/ -- Server: Changes need just generate-openapi +- UI Desktop: Use ACP SDK types or local `src/types/*` types. Do not import generated OpenAPI types/client code from `ui/desktop/src/api` ## Code Quality @@ -107,7 +105,7 @@ remaining space for dynamic text. ## Never -- Never: Edit ui/desktop/openapi.json manually +- Never: Recreate `ui/desktop/src/api` or add `@hey-api/openapi-ts` to `ui/desktop` - Cargo.toml: For human-authored dependency changes, use `cargo add` instead of manually editing dependency entries unless there is a specific reason not to. - Cargo.toml: Automated dependency bump PRs are exempt; when manual edits are necessary, keep `Cargo.lock` consistent. - Never: Skip cargo fmt @@ -116,6 +114,5 @@ remaining space for dynamic text. ## Entry Points - CLI: crates/goose-cli/src/main.rs -- Server: crates/goose-server/src/main.rs - UI: ui/desktop/src/main.ts - Agent: crates/goose/src/agents/agent.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49e3dcd56ea9..66b96b14ea88 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -184,41 +184,27 @@ cd ui && pnpm install See #8757. -### Regenerating the OpenAPI schema - -The file `ui/desktop/openapi.json` is automatically generated during the build. -It is written by the `generate_schema` binary in `crates/goose-server`. -To update the spec without starting the UI, run: - -``` -just generate-openapi -``` - -This command regenerates `ui/desktop/openapi.json` and then runs the UI's -`generate-api` script to rebuild the TypeScript client from that spec. - -API changes should be made in the Rust source under `crates/goose-server/src/`. - ### Debugging -To debug the Goose server, run it from an IDE. The configuration will depend on the IDE. The command to run is: +To debug the external ACP backend, run it from an IDE. The configuration will depend on the IDE. The command to run is: ``` export GOOSE_SERVER__SECRET_KEY=test -cargo run --package goose-server --bin goosed -- agent # or: `just run-server` +cargo run --package goose-cli --bin goose -- serve --platform desktop --host 127.0.0.1 --port 3000 ``` -The server listens on port `3000` by default; this can be changed by setting the -`GOOSE_PORT` environment variable. +The `debug-ui` recipe connects to `http://127.0.0.1:3000` by default. If the +backend uses another port, set `GOOSE_PORT` when starting the UI, or set +`GOOSE_EXTERNAL_BACKEND_URL` to the backend's HTTP base URL. -Once the server is running, start a UI and connect it to the server by running: +Once the backend is running, start a UI and connect it to the backend by running: ``` just debug-ui ``` -The UI connects to the server started in the IDE, allowing breakpoints -and stepping through the server code while interacting with the UI. +The UI connects to the backend started in the IDE, allowing breakpoints +and stepping through the backend code while interacting with the UI. ## Creating a fork diff --git a/Justfile b/Justfile index 56f0fe330279..c08148357f7d 100644 --- a/Justfile +++ b/Justfile @@ -13,15 +13,13 @@ check-everything: cargo clippy --all-targets -- -D warnings @echo " → Checking UI code formatting..." cd ui/desktop && pnpm run lint:check - @echo " → Validating OpenAPI schema..." - ./scripts/check-openapi-schema.sh @echo "" @echo "✅ All style checks passed!" # Default release command release-binary: @echo "Building release version..." - cargo build --release + cargo build --release -p goose-cli --bin goose @just copy-binary @echo "Generating OpenAPI schema..." cargo run -p goose-server --bin generate_schema @@ -34,7 +32,7 @@ release-windows: [windows] release-windows: - @powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'rustup target add x86_64-pc-windows-msvc; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; cargo build --release --target x86_64-pc-windows-msvc -p goose-server; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; Write-Host "Windows executable created at ./target/x86_64-pc-windows-msvc/release/goosed.exe"' + @powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'rustup target add x86_64-pc-windows-msvc; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; Write-Host "Windows executable created at ./target/x86_64-pc-windows-msvc/release/goose.exe"' # Build for Intel Mac release-intel: @@ -43,14 +41,7 @@ release-intel: @just copy-binary-intel copy-binary BUILD_MODE="release": - @if [ -f ./target/{{BUILD_MODE}}/goosed ]; then \ - echo "Copying goosed binary from target/{{BUILD_MODE}}..."; \ - rm -f ./ui/desktop/src/bin/goosed; \ - cp -p ./target/{{BUILD_MODE}}/goosed ./ui/desktop/src/bin/; \ - else \ - echo "Binary not found in target/{{BUILD_MODE}}"; \ - exit 1; \ - fi + @rm -f ./ui/desktop/src/bin/goosed @if [ -f ./target/{{BUILD_MODE}}/goose ]; then \ echo "Copying goose CLI binary from target/{{BUILD_MODE}}..."; \ rm -f ./ui/desktop/src/bin/goose; \ @@ -62,14 +53,7 @@ copy-binary BUILD_MODE="release": # Copy binary command for Intel build copy-binary-intel: - @if [ -f ./target/x86_64-apple-darwin/release/goosed ]; then \ - echo "Copying Intel goosed binary to ui/desktop/src/bin with permissions preserved..."; \ - rm -f ./ui/desktop/src/bin/goosed; \ - cp -p ./target/x86_64-apple-darwin/release/goosed ./ui/desktop/src/bin/; \ - else \ - echo "Intel release binary not found."; \ - exit 1; \ - fi + @rm -f ./ui/desktop/src/bin/goosed @if [ -f ./target/x86_64-apple-darwin/release/goose ]; then \ echo "Copying Intel goose CLI binary to ui/desktop/src/bin..."; \ rm -f ./ui/desktop/src/bin/goose; \ @@ -87,10 +71,11 @@ copy-binary-windows: [windows] copy-binary-windows: - @powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'if (Test-Path ./target/x86_64-pc-windows-msvc/release/goosed.exe) { \ + @powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'if (Test-Path ./target/x86_64-pc-windows-msvc/release/goose.exe) { \ Write-Host "Copying Windows binary to ui/desktop/src/bin..."; \ New-Item -ItemType Directory -Force "./ui/desktop/src/bin" | Out-Null; \ - Copy-Item -Path "./target/x86_64-pc-windows-msvc/release/goosed.exe" -Destination "./ui/desktop/src/bin/" -Force; \ + Remove-Item -Path "./ui/desktop/src/bin/goosed.exe" -Force -ErrorAction SilentlyContinue; \ + Copy-Item -Path "./target/x86_64-pc-windows-msvc/release/goose.exe" -Destination "./ui/desktop/src/bin/" -Force; \ } else { \ Write-Host "Windows binary not found." -ForegroundColor Red; \ exit 1; \ @@ -116,7 +101,7 @@ run-ui-only: cd ui/desktop && pnpm install && pnpm run start-gui debug-ui: - @echo "🚀 Starting goose frontend in external backend mode" + @echo "🚀 Starting goose frontend in external ACP backend mode" cd ui/desktop && \ export GOOSE_EXTERNAL_BACKEND=true && \ export GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-test}" && \ @@ -161,19 +146,13 @@ run-docs: # Run server run-server: - @echo "Running server..." - cargo run -p goose-server --bin goosed agent - -# Check if OpenAPI schema is up-to-date -check-openapi-schema: generate-openapi - ./scripts/check-openapi-schema.sh + @echo "Running external ACP backend..." + GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-test}" cargo run -p goose-cli --bin goose -- serve --platform desktop --host 127.0.0.1 --port 3000 # Generate OpenAPI specification without starting the UI generate-openapi: @echo "Generating OpenAPI schema..." cargo run -p goose-server --bin generate_schema - @echo "Generating frontend API..." - cd ui/desktop && npx @hey-api/openapi-ts # Check if generated ACP schema and TypeScript types are up-to-date check-acp-schema: generate-acp-types @@ -404,6 +383,7 @@ win-app-deps: win-copy-win profile: copy target{{s}}{{profile}}{{s}}*.exe ui{{s}}desktop{{s}}src{{s}}bin copy target{{s}}{{profile}}{{s}}*.dll ui{{s}}desktop{{s}}src{{s}}bin + if exist ui{{s}}desktop{{s}}src{{s}}bin{{s}}goosed.exe del /f /q ui{{s}}desktop{{s}}src{{s}}bin{{s}}goosed.exe ### "Other" copy {release|debug} files to ui/desktop/src/bin ### s = os dependent file separator diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index e586de9efe30..20eab63033ed 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -51,6 +51,22 @@ fn generate_serve_secret_key() -> String { ) } +#[derive(clap::ValueEnum, Clone, Copy, Debug, Default, PartialEq, Eq)] +enum ServePlatform { + #[default] + Cli, + Desktop, +} + +impl From for GoosePlatform { + fn from(platform: ServePlatform) -> Self { + match platform { + ServePlatform::Cli => GoosePlatform::GooseCli, + ServePlatform::Desktop => GoosePlatform::GooseDesktop, + } + } +} + #[derive(Parser)] #[command(name = "goose", author, version, display_name = "", about, long_about = None)] pub struct Cli { @@ -840,6 +856,9 @@ enum Command { #[arg(long = "tls-key-path", value_name = "PATH")] tls_key_path: Option, + #[arg(long, value_enum, default_value_t = ServePlatform::Cli)] + platform: ServePlatform, + #[arg( long = "with-builtin", value_name = "NAME", @@ -1358,10 +1377,12 @@ async fn handle_mcp_command(server: McpCommand) -> Result<()> { struct ServeCommandArgs { host: String, + port: u16, tls: bool, tls_cert_path: Option, tls_key_path: Option, + platform: ServePlatform, builtins: Vec, dangerously_unauthenticated: bool, allowed_origins: Vec, @@ -1382,6 +1403,7 @@ async fn handle_serve_command(args: ServeCommandArgs) -> Result<()> { tls, tls_cert_path, tls_key_path, + platform, builtins, dangerously_unauthenticated, allowed_origins, @@ -1409,7 +1431,7 @@ async fn handle_serve_command(args: ServeCommandArgs) -> Result<()> { builtins, data_dir: Paths::data_dir(), config_dir: Paths::config_dir(), - goose_platform: GoosePlatform::GooseCli, + goose_platform: platform.into(), additional_source_roots, scheduler: None, })); @@ -2210,6 +2232,7 @@ pub async fn cli() -> anyhow::Result<()> { tls, tls_cert_path, tls_key_path, + platform, builtins, dangerously_unauthenticated, allowed_origins, @@ -2220,6 +2243,7 @@ pub async fn cli() -> anyhow::Result<()> { tls, tls_cert_path, tls_key_path, + platform, builtins, dangerously_unauthenticated, allowed_origins, diff --git a/documentation/src/pages/deeplink-generator.tsx b/documentation/src/pages/deeplink-generator.tsx index e1ab46beb3f7..350fe5d50c1e 100644 --- a/documentation/src/pages/deeplink-generator.tsx +++ b/documentation/src/pages/deeplink-generator.tsx @@ -68,7 +68,7 @@ export default function DeeplinkGenerator() { const urlParams = new URLSearchParams(window.location.search); if (urlParams.toString()) { try { - if (urlParams.get('cmd') === 'goosed' && urlParams.getAll('arg').includes('mcp')) { + if (urlParams.get('cmd') === 'goose' && urlParams.getAll('arg').includes('mcp')) { const args = urlParams.getAll('arg'); const extensionId = args[args.indexOf('mcp') + 1]; if (!extensionId) { @@ -187,7 +187,7 @@ export default function DeeplinkGenerator() { const generateDeeplink = (server: ServerConfig): string => { if (server.is_builtin) { const queryParams = [ - 'cmd=goosed', + 'cmd=goose', 'arg=mcp', `arg=${encodeURIComponent(server.id)}`, `description=${encodeURIComponent(server.id)}` diff --git a/documentation/src/utils/install-links.ts b/documentation/src/utils/install-links.ts index a70467748513..73a235e02987 100644 --- a/documentation/src/utils/install-links.ts +++ b/documentation/src/utils/install-links.ts @@ -3,7 +3,7 @@ import type { MCPServer } from "../types/server"; export function getGooseInstallLink(server: MCPServer): string { if (server.is_builtin) { const queryParams = [ - 'cmd=goosed', + 'cmd=goose', 'arg=mcp', `arg=${encodeURIComponent(server.id)}`, `description=${encodeURIComponent(server.id)}` @@ -53,4 +53,4 @@ export function getGooseInstallLink(server: MCPServer): string { ].join("&"); return `goose://extension?${queryParams}`; -} \ No newline at end of file +} diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index 4807e53e72db..25438ae5555f 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -42,7 +42,7 @@ Write-Host "" # Step 1: Clone or update repo Write-Host "[2/7] Building Rust backend (release)..." -ForegroundColor Yellow Write-Host " This may take 5-15 minutes on first build..." -cargo build --release -p goose-server +cargo build --release -p goose-cli --bin goose if ($LASTEXITCODE -ne 0) { Write-Host "Rust build failed!" -ForegroundColor Red exit 1 @@ -55,10 +55,12 @@ Write-Host "[3/7] Copying binaries to desktop app..." -ForegroundColor Yellow $binDir = "ui\desktop\src\bin" if (-not (Test-Path $binDir)) { New-Item -ItemType Directory -Path $binDir -Force | Out-Null } -Copy-Item "target\release\goosed.exe" "$binDir\" -Force -if (Test-Path "target\release\goose.exe") { - Copy-Item "target\release\goose.exe" "$binDir\" -Force +$gooseBinary = "target\release\goose.exe" +if (-not (Test-Path $gooseBinary)) { + Write-Host "Backend binary not found: $gooseBinary" -ForegroundColor Red + exit 1 } +Copy-Item $gooseBinary "$binDir\" -Force # Copy required DLLs if they exist (from cross-compilation) Get-ChildItem "target\release\*.dll" -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item $_.FullName "$binDir\" -Force @@ -78,20 +80,26 @@ if ($LASTEXITCODE -ne 0) { Write-Host " Dependencies installed." -ForegroundColor Green Write-Host "" -# Step 4: Generate API types -Write-Host "[5/7] Generating API types..." -ForegroundColor Yellow -pnpm run generate-api +# Step 4: Build desktop assets +Write-Host "[5/7] Building Goose SDK, clearing Vite cache, and compiling i18n messages..." -ForegroundColor Yellow +pnpm run build-goose-sdk +if ($LASTEXITCODE -ne 0) { + Write-Host "Goose SDK build or Vite cache cleanup failed!" -ForegroundColor Red + Pop-Location + exit 1 +} +pnpm run i18n:compile if ($LASTEXITCODE -ne 0) { - Write-Host "API type generation failed!" -ForegroundColor Red + Write-Host "i18n compilation failed!" -ForegroundColor Red Pop-Location exit 1 } -Write-Host " API types generated." -ForegroundColor Green +Write-Host " Desktop assets built." -ForegroundColor Green Write-Host "" # Step 5: Package Write-Host "[6/7] Packaging Goose Desktop..." -ForegroundColor Yellow -npx electron-forge package +pnpm exec electron-forge package if ($LASTEXITCODE -ne 0) { Write-Host "Packaging failed!" -ForegroundColor Red Pop-Location @@ -102,10 +110,10 @@ Write-Host "" # Step 6: Make installer Write-Host "[7/7] Creating Windows installer..." -ForegroundColor Yellow -npx electron-forge make +pnpm exec electron-forge make if ($LASTEXITCODE -ne 0) { Write-Host "Make failed! Trying with squirrel only..." -ForegroundColor Yellow - npx electron-forge make --targets=@electron-forge/maker-squirrel + pnpm exec electron-forge make --targets=@electron-forge/maker-squirrel if ($LASTEXITCODE -ne 0) { Write-Host "Fallback installer build also failed!" -ForegroundColor Red Pop-Location diff --git a/scripts/check-openapi-schema.sh b/scripts/check-openapi-schema.sh deleted file mode 100755 index d45f733f256c..000000000000 --- a/scripts/check-openapi-schema.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Check if OpenAPI schema is up-to-date -# This script generates the OpenAPI schema and compares it with the committed version - -echo "🔍 Checking OpenAPI schema is up-to-date..." - -# Check if the generated schema differs from the committed version -echo "🔍 Comparing generated schema with committed version..." -if ! git diff --ignore-space-change --exit-code ui/desktop/openapi.json ui/desktop/src/api/; then - echo "" - echo "❌ OpenAPI schema is out of date!" - echo "" - echo "The generated OpenAPI schema differs from the committed version." - echo "This usually means that API types were added or modified without updating the schema." - echo "" - echo "To fix this issue:" - echo "1. Run 'just generate-openapi' locally" - echo "2. Commit the changes to ui/desktop/openapi.json and ui/desktop/src/api/" - echo "3. Push your changes" - echo "" - echo "Changes detected:" - git diff ui/desktop/openapi.json ui/desktop/src/api/ - exit 1 -fi - -echo "✅ OpenAPI schema is up-to-date" diff --git a/ui/desktop/openapi-ts.config.ts b/ui/desktop/openapi-ts.config.ts deleted file mode 100644 index 992c5a4a0f54..000000000000 --- a/ui/desktop/openapi-ts.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from '@hey-api/openapi-ts'; - -export default defineConfig({ - input: './openapi.json', - output: './src/api', - plugins: [ - { - name: '@hey-api/client-fetch', - // Disable SSE support to avoid requiring SSE options on all requests - sse: false, - }, - ], -}); diff --git a/ui/desktop/package.json b/ui/desktop/package.json index f5ed7ebc016b..90940947ce08 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -9,24 +9,25 @@ }, "main": ".vite/build/main.js", "scripts": { - "postinstall": "pnpm --filter @aaif/goose-sdk run build", + "postinstall": "pnpm run build-goose-sdk", "typecheck": "tsc --noEmit", - "generate-api": "openapi-ts", - "start-gui": "pnpm run generate-api && pnpm run i18n:compile && electron-forge start", - "start-gui-debug": "pnpm run generate-api && pnpm run i18n:compile && electron-forge start -- --inspect=9229", + "build-goose-sdk": "pnpm --filter @aaif/goose-sdk run build && pnpm run clean-vite-cache", + "clean-vite-cache": "node scripts/clean-vite-cache.js", + "start-gui": "pnpm run build-goose-sdk && pnpm run i18n:compile && electron-forge start", + "start-gui-debug": "pnpm run build-goose-sdk && pnpm run i18n:compile && electron-forge start -- --inspect=9229", "start": "cd ../.. && just run-ui", "start:test-error": "GOOSE_TEST_ERROR=true electron-forge start", - "package": "pnpm run i18n:compile && electron-forge package", - "make": "pnpm run i18n:compile && electron-forge make", + "package": "pnpm run build-goose-sdk && pnpm run i18n:compile && electron-forge package", + "make": "pnpm run build-goose-sdk && pnpm run i18n:compile && electron-forge make", "bundle:default": "node scripts/prepare-platform-binaries.js && pnpm run make && BUNDLE_NAME=\"${GOOSE_BUNDLE_NAME:-Goose}\" && APP_DIR=\"out/${BUNDLE_NAME}-darwin-arm64\" && APP_BUNDLE=\"${APP_DIR}/${BUNDLE_NAME}.app\" && (cd \"$APP_DIR\" && ditto -c -k --sequesterRsrc --keepParent \"${BUNDLE_NAME}.app\" \"${BUNDLE_NAME}.zip\")", "bundle:intel": "node scripts/prepare-platform-binaries.js && pnpm run make --arch=x64 && BUNDLE_NAME=\"${GOOSE_BUNDLE_NAME:-Goose}\" && APP_DIR=\"out/${BUNDLE_NAME}-darwin-x64\" && APP_BUNDLE=\"${APP_DIR}/${BUNDLE_NAME}.app\" && (cd \"$APP_DIR\" && ditto -c -k --sequesterRsrc --keepParent \"${BUNDLE_NAME}.app\" \"${BUNDLE_NAME}_intel_mac.zip\")", "debug": "echo 'run --remote-debugging-port=8315' && BUNDLE_NAME=\"${GOOSE_BUNDLE_NAME:-Goose}\" && lldb \"out/${BUNDLE_NAME}-darwin-arm64/${BUNDLE_NAME}.app\"", - "test-e2e": "pnpm run generate-api && playwright test", - "test-e2e:dev": "pnpm run generate-api && playwright test --reporter=list --retries=0 --max-failures=1", - "test-e2e:ui": "pnpm run generate-api && playwright test --ui", - "test-e2e:debug": "pnpm run generate-api && playwright test --debug", + "test-e2e": "playwright test", + "test-e2e:dev": "playwright test --reporter=list --retries=0 --max-failures=1", + "test-e2e:ui": "playwright test --ui", + "test-e2e:debug": "playwright test --debug", "test-e2e:report": "playwright show-report", - "test-e2e:single": "pnpm run generate-api && playwright test -g", + "test-e2e:single": "playwright test -g", "lint": "eslint \"src/**/*.{ts,tsx}\" --fix --no-warn-ignored", "lint:check": "pnpm run typecheck && eslint \"src/**/*.{ts,tsx}\" --max-warnings 0 --no-warn-ignored && pnpm run i18n:check", "format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"", @@ -36,7 +37,6 @@ "test:ui": "vitest --ui", "test:coverage": "vitest run --coverage", "test:integration": "vitest run --config vitest.integration.config.ts", - "test:integration:goosed": "vitest run --config vitest.integration.config.ts tests/integration/goosed.test.ts", "test:integration:providers": "vitest run --config vitest.integration.config.ts tests/integration/test_providers.test.ts", "test:integration:providers-code-exec": "vitest run --config vitest.integration.config.ts tests/integration/test_providers_code_exec.test.ts", "test:integration:watch": "vitest --config vitest.integration.config.ts", @@ -121,7 +121,6 @@ "@eslint/js": "^9.39.2", "@formatjs/cli": "^6.14.0", "@formatjs/icu-messageformat-parser": "3.5.3", - "@hey-api/openapi-ts": "^0.93.0", "@modelcontextprotocol/sdk": "^1.27.0", "@playwright/test": "^1.58.2", "@tailwindcss/line-clamp": "^0.4.4", diff --git a/ui/desktop/scripts/clean-vite-cache.js b/ui/desktop/scripts/clean-vite-cache.js new file mode 100644 index 000000000000..a0438396e422 --- /dev/null +++ b/ui/desktop/scripts/clean-vite-cache.js @@ -0,0 +1,19 @@ +const fs = require('fs'); +const path = require('path'); + +const desktopRoot = path.resolve(__dirname, '..'); + +const pathsToRemove = [ + path.join(desktopRoot, 'node_modules', '.vite'), + path.join(desktopRoot, 'node_modules', '.vite-temp'), + path.join(desktopRoot, '.vite'), +]; + +for (const targetPath of pathsToRemove) { + if (!fs.existsSync(targetPath)) { + continue; + } + + fs.rmSync(targetPath, { recursive: true, force: true }); + console.log(`Removed ${path.relative(desktopRoot, targetPath)}`); +} diff --git a/ui/desktop/scripts/prepare-platform-binaries.js b/ui/desktop/scripts/prepare-platform-binaries.js index 9679d908b726..5f698bcc96bb 100644 --- a/ui/desktop/scripts/prepare-platform-binaries.js +++ b/ui/desktop/scripts/prepare-platform-binaries.js @@ -23,17 +23,6 @@ const windowsFiles = [ 'goose-npm/**/*' ]; -const macosFiles = [ - 'goosed', - 'goose', - 'jbang', - 'npx', - 'uvx', - '*.db', - '*.log', - '.gitkeep' -]; - // Helper function to check if file matches patterns function matchesPattern(filename, patterns) { return patterns.some(pattern => { @@ -174,9 +163,10 @@ function cleanBinDirectory(targetPlatform) { const filePath = path.join(srcBinDir, file.name); if (targetPlatform === 'darwin' || targetPlatform === 'linux') { - // For macOS/Linux, remove Windows-specific files - if (matchesPattern(file.name, windowsFiles)) { - console.log(`Removing Windows file: ${file.name}`); + const isLegacyBackendBinary = file.name === 'goosed'; + if (isLegacyBackendBinary || matchesPattern(file.name, windowsFiles)) { + const fileType = isLegacyBackendBinary ? 'legacy backend binary' : 'Windows file'; + console.log(`Removing ${fileType}: ${file.name}`); if (file.isDirectory()) { fs.rmSync(filePath, { recursive: true, force: true }); } else { diff --git a/ui/desktop/src/App.test.tsx b/ui/desktop/src/App.test.tsx index d820ac62dbd1..2da21b72b49a 100644 --- a/ui/desktop/src/App.test.tsx +++ b/ui/desktop/src/App.test.tsx @@ -34,15 +34,6 @@ vi.mock('./utils/costDatabase', () => ({ initializeCostDatabase: vi.fn().mockResolvedValue(undefined), })); -vi.mock('./api', () => { - return { - initConfig: vi.fn().mockResolvedValue(undefined), - backupConfig: vi.fn().mockResolvedValue(undefined), - recoverConfig: vi.fn().mockResolvedValue(undefined), - validateConfig: vi.fn().mockResolvedValue(undefined), - }; -}); - vi.mock('./sessions', () => ({ fetchSessionDetails: vi .fn() diff --git a/ui/desktop/src/acp/__tests__/url.test.ts b/ui/desktop/src/acp/__tests__/url.test.ts new file mode 100644 index 000000000000..b131401afdec --- /dev/null +++ b/ui/desktop/src/acp/__tests__/url.test.ts @@ -0,0 +1,133 @@ +import { describe, expect, it } from 'vitest'; +import { + acpHttpUrlFromHttpBase, + acpWebSocketUrlFromHttpBase, + httpBaseFromAcpWebSocketUrl, + isLoopbackAcpWebSocketUrl, + normalizeAcpHttpBaseUrl, + statusHttpUrlFromHttpBase, +} from '../url'; + +describe('httpBaseFromAcpWebSocketUrl', () => { + it('converts ws ACP URLs to HTTP bases', () => { + expect(httpBaseFromAcpWebSocketUrl('ws://127.0.0.1:64027/acp?token=secret')).toBe( + 'http://127.0.0.1:64027' + ); + }); + + it('converts wss ACP URLs to HTTPS bases', () => { + expect(httpBaseFromAcpWebSocketUrl('wss://example.com/acp?token=secret')).toBe( + 'https://example.com' + ); + }); + + it('preserves path prefixes before the ACP endpoint', () => { + expect(httpBaseFromAcpWebSocketUrl('wss://example.com/goose/acp?token=secret')).toBe( + 'https://example.com/goose' + ); + }); + + it('rejects non-WebSocket URLs', () => { + expect(() => httpBaseFromAcpWebSocketUrl('http://127.0.0.1:64027/acp')).toThrow( + 'ACP URL must use ws: or wss:' + ); + }); +}); + +describe('isLoopbackAcpWebSocketUrl', () => { + it('accepts IPv4 loopback ACP URLs', () => { + expect(isLoopbackAcpWebSocketUrl('ws://127.0.0.1:64027/acp?token=secret')).toBe(true); + expect(isLoopbackAcpWebSocketUrl('wss://127.12.0.1:64027/acp?token=secret')).toBe(true); + }); + + it('accepts localhost ACP URLs', () => { + expect(isLoopbackAcpWebSocketUrl('ws://localhost:64027/acp?token=secret')).toBe(true); + }); + + it('accepts IPv6 loopback ACP URLs', () => { + expect(isLoopbackAcpWebSocketUrl('ws://[::1]:64027/acp?token=secret')).toBe(true); + }); + + it('rejects remote ACP URLs', () => { + expect(isLoopbackAcpWebSocketUrl('wss://example.com/acp?token=secret')).toBe(false); + expect(isLoopbackAcpWebSocketUrl('ws://192.168.1.10:3284/acp?token=secret')).toBe(false); + }); + + it('rejects DNS hostnames that start with 127', () => { + expect(isLoopbackAcpWebSocketUrl('wss://127.evil.com/acp?token=secret')).toBe(false); + expect(isLoopbackAcpWebSocketUrl('wss://127.0.0.1.example.com/acp?token=secret')).toBe(false); + }); + + it('rejects non-WebSocket URLs', () => { + expect(() => isLoopbackAcpWebSocketUrl('http://127.0.0.1:64027/acp')).toThrow( + 'ACP URL must use ws: or wss:' + ); + }); +}); + +describe('normalizeAcpHttpBaseUrl', () => { + it('normalizes root HTTPS base URLs', () => { + expect(normalizeAcpHttpBaseUrl('https://example.com/')).toBe('https://example.com'); + }); + + it('normalizes prefixed HTTPS base URLs', () => { + expect(normalizeAcpHttpBaseUrl('https://example.com/goose/')).toBe('https://example.com/goose'); + }); + + it('rejects WebSocket URLs', () => { + expect(() => normalizeAcpHttpBaseUrl('wss://example.com/acp')).toThrow( + 'External ACP backend URL must use http: or https:' + ); + }); + + it('rejects direct ACP endpoint URLs', () => { + expect(() => normalizeAcpHttpBaseUrl('https://example.com/acp')).toThrow( + 'External ACP backend URL must be the base URL before /acp' + ); + }); + + it('rejects query parameters and fragments', () => { + expect(() => normalizeAcpHttpBaseUrl('https://example.com?token=secret')).toThrow( + 'External ACP backend URL must not include query parameters or fragments' + ); + expect(() => normalizeAcpHttpBaseUrl('https://example.com#section')).toThrow( + 'External ACP backend URL must not include query parameters or fragments' + ); + }); +}); + +describe('HTTP endpoint URLs from ACP HTTP base URLs', () => { + it('builds status URLs from root and prefixed bases', () => { + expect(statusHttpUrlFromHttpBase('https://example.com/')).toBe('https://example.com/status'); + expect(statusHttpUrlFromHttpBase('https://example.com/goose/')).toBe( + 'https://example.com/goose/status' + ); + }); + + it('builds ACP URLs from root and prefixed bases', () => { + expect(acpHttpUrlFromHttpBase('https://example.com/')).toBe('https://example.com/acp'); + expect(acpHttpUrlFromHttpBase('https://example.com/goose/')).toBe( + 'https://example.com/goose/acp' + ); + }); + + it('adds ACP query tokens when provided', () => { + expect(acpHttpUrlFromHttpBase('https://example.com/goose', 'test secret')).toBe( + 'https://example.com/goose/acp?token=test+secret' + ); + }); +}); + +describe('acpWebSocketUrlFromHttpBase', () => { + it('derives WSS ACP URLs from HTTPS base URLs', () => { + expect(acpWebSocketUrlFromHttpBase('https://example.com/goose', 'secret')).toBe( + 'wss://example.com/goose/acp?token=secret' + ); + }); + + it('derives WS ACP URLs from HTTP base URLs', () => { + expect(acpWebSocketUrlFromHttpBase('http://127.0.0.1:1234', 'secret')).toBe( + 'ws://127.0.0.1:1234/acp?token=secret' + ); + }); +}); diff --git a/ui/desktop/src/acp/acpConnection.ts b/ui/desktop/src/acp/acpConnection.ts index b7b6d5561e81..fd40944ea4cf 100644 --- a/ui/desktop/src/acp/acpConnection.ts +++ b/ui/desktop/src/acp/acpConnection.ts @@ -19,6 +19,8 @@ type InitializedAcpClient = { initializeResponse: InitializeResponse; }; +const ACP_INITIALIZE_TIMEOUT_MS = 10_000; + let clientPromise: Promise | null = null; let resolvedClient: InitializedAcpClient | null = null; @@ -44,6 +46,21 @@ function monitorConnection(client: GooseClient): void { }); } +async function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { + let timeoutId: ReturnType | null = null; + const timeout = new Promise((_, reject) => { + timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs); + }); + + try { + return await Promise.race([promise, timeout]); + } finally { + if (timeoutId !== null) { + clearTimeout(timeoutId); + } + } +} + async function initializeConnection(): Promise { const wsUrl = await window.electron.getAcpUrl(); if (!wsUrl) { @@ -53,26 +70,35 @@ async function initializeConnection(): Promise { const stream = createWebSocketStream(wsUrl); const client = new GooseClient(createClientCallbacks(), stream); - const initializeResponse = await client.initialize({ - protocolVersion: PROTOCOL_VERSION, - clientCapabilities: { - elicitation: { form: {} }, - _meta: { - goose: { - mcpHostCapabilities: DEFAULT_GOOSE_MCP_HOST_CAPABILITIES, - customNotifications: true, - recipeParameterRequests: true, + try { + const initializeResponse = await withTimeout( + client.initialize({ + protocolVersion: PROTOCOL_VERSION, + clientCapabilities: { + elicitation: { form: {} }, + _meta: { + goose: { + mcpHostCapabilities: DEFAULT_GOOSE_MCP_HOST_CAPABILITIES, + customNotifications: true, + recipeParameterRequests: true, + }, + }, }, - }, - }, - clientInfo: { - name: packageJson.name, - version: packageJson.version, - }, - }); + clientInfo: { + name: packageJson.name, + version: packageJson.version, + }, + }), + ACP_INITIALIZE_TIMEOUT_MS, + `ACP initialize timed out after ${ACP_INITIALIZE_TIMEOUT_MS}ms` + ); - monitorConnection(client); - return { client, initializeResponse }; + monitorConnection(client); + return { client, initializeResponse }; + } catch (error) { + stream.close(); + throw error; + } } export async function getAcpClient(): Promise { diff --git a/ui/desktop/src/acp/createWebSocketStream.ts b/ui/desktop/src/acp/createWebSocketStream.ts index 61b21c558f94..73d74481613a 100644 --- a/ui/desktop/src/acp/createWebSocketStream.ts +++ b/ui/desktop/src/acp/createWebSocketStream.ts @@ -1,6 +1,10 @@ import type { Stream } from '@aaif/goose-sdk'; -export function createWebSocketStream(wsUrl: string): Stream { +export type ClosableAcpStream = Stream & { + close: () => void; +}; + +export function createWebSocketStream(wsUrl: string): ClosableAcpStream { const ws = new window.WebSocket(wsUrl); const incoming: unknown[] = []; @@ -73,5 +77,9 @@ export function createWebSocketStream(wsUrl: string): Stream { }, }); - return { readable, writable } as Stream; + return { + readable, + writable, + close: () => ws.close(), + } as ClosableAcpStream; } diff --git a/ui/desktop/src/acp/url.ts b/ui/desktop/src/acp/url.ts new file mode 100644 index 000000000000..b3a2719a0fd3 --- /dev/null +++ b/ui/desktop/src/acp/url.ts @@ -0,0 +1,87 @@ +export function httpBaseFromAcpWebSocketUrl(acpUrl: string): string { + const url = new URL(acpUrl); + + if (url.protocol === 'ws:') { + url.protocol = 'http:'; + } else if (url.protocol === 'wss:') { + url.protocol = 'https:'; + } else { + throw new Error(`ACP URL must use ws: or wss:, got ${url.protocol}`); + } + + const pathname = url.pathname.replace(/\/+$/, ''); + const pathPrefix = pathname.endsWith('/acp') ? pathname.slice(0, -'/acp'.length) : pathname; + + return `${url.origin}${pathPrefix}`; +} + +export function isLoopbackAcpWebSocketUrl(acpUrl: string): boolean { + const url = new URL(acpUrl); + + if (url.protocol !== 'ws:' && url.protocol !== 'wss:') { + throw new Error(`ACP URL must use ws: or wss:, got ${url.protocol}`); + } + + const hostname = url.hostname.toLowerCase().replace(/^\[(.*)\]$/, '$1'); + return hostname === 'localhost' || hostname === '::1' || isIpv4LoopbackLiteral(hostname); +} + +function isIpv4LoopbackLiteral(hostname: string): boolean { + const octets = hostname.split('.'); + if (octets.length !== 4 || octets.some((octet) => !/^\d+$/.test(octet))) { + return false; + } + + return octets.every((octet) => Number(octet) <= 255) && Number(octets[0]) === 127; +} + +export function normalizeAcpHttpBaseUrl(rawBaseUrl: string): string { + const trimmed = rawBaseUrl.trim(); + if (!trimmed) { + throw new Error('External ACP backend URL is required'); + } + + const url = new URL(trimmed); + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + throw new Error(`External ACP backend URL must use http: or https:, got ${url.protocol}`); + } + + if (url.search || url.hash) { + throw new Error('External ACP backend URL must not include query parameters or fragments'); + } + + const pathname = url.pathname.replace(/\/+$/, ''); + if (pathname.endsWith('/acp')) { + throw new Error('External ACP backend URL must be the base URL before /acp'); + } + + return `${url.origin}${pathname}`; +} + +function httpEndpointUrlFromHttpBase(rawBaseUrl: string, endpoint: 'status' | 'acp'): string { + const baseUrl = normalizeAcpHttpBaseUrl(rawBaseUrl); + const url = new URL(baseUrl); + url.pathname = `${url.pathname.replace(/\/+$/, '')}/${endpoint}`; + return url.toString(); +} + +export function statusHttpUrlFromHttpBase(rawBaseUrl: string): string { + return httpEndpointUrlFromHttpBase(rawBaseUrl, 'status'); +} + +export function acpHttpUrlFromHttpBase(rawBaseUrl: string, token?: string): string { + const url = new URL(httpEndpointUrlFromHttpBase(rawBaseUrl, 'acp')); + if (token) { + url.searchParams.set('token', token); + } + return url.toString(); +} + +export function acpWebSocketUrlFromHttpBase(rawBaseUrl: string, token: string): string { + const baseUrl = normalizeAcpHttpBaseUrl(rawBaseUrl); + const url = new URL(baseUrl); + url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; + url.pathname = `${url.pathname.replace(/\/+$/, '')}/acp`; + url.searchParams.set('token', token); + return url.toString(); +} diff --git a/ui/desktop/src/api/client.gen.ts b/ui/desktop/src/api/client.gen.ts deleted file mode 100644 index d81ce3f8f717..000000000000 --- a/ui/desktop/src/api/client.gen.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { type ClientOptions, type Config, createClient, createConfig } from './client'; -import type { ClientOptions as ClientOptions2 } from './types.gen'; - -/** - * The `createClientConfig()` function will be called on client initialization - * and the returned object will become the client's initial configuration. - * - * You may want to initialize your client this way instead of calling - * `setConfig()`. This is useful for example if you're using Next.js - * to ensure your client always has the correct values. - */ -export type CreateClientConfig = (override?: Config) => Config & T> | Promise & T>>; - -export const client = createClient(createConfig()); diff --git a/ui/desktop/src/api/client/client.gen.ts b/ui/desktop/src/api/client/client.gen.ts deleted file mode 100644 index d2e55a14497d..000000000000 --- a/ui/desktop/src/api/client/client.gen.ts +++ /dev/null @@ -1,288 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createSseClient } from '../core/serverSentEvents.gen'; -import type { HttpMethod } from '../core/types.gen'; -import { getValidRequestBody } from '../core/utils.gen'; -import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen'; -import { - buildUrl, - createConfig, - createInterceptors, - getParseAs, - mergeConfigs, - mergeHeaders, - setAuthParams, -} from './utils.gen'; - -type ReqInit = Omit & { - body?: any; - headers: ReturnType; -}; - -export const createClient = (config: Config = {}): Client => { - let _config = mergeConfigs(createConfig(), config); - - const getConfig = (): Config => ({ ..._config }); - - const setConfig = (config: Config): Config => { - _config = mergeConfigs(_config, config); - return getConfig(); - }; - - const interceptors = createInterceptors(); - - const beforeRequest = async (options: RequestOptions) => { - const opts = { - ..._config, - ...options, - fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, - headers: mergeHeaders(_config.headers, options.headers), - serializedBody: undefined, - }; - - if (opts.security) { - await setAuthParams({ - ...opts, - security: opts.security, - }); - } - - if (opts.requestValidator) { - await opts.requestValidator(opts); - } - - if (opts.body !== undefined && opts.bodySerializer) { - opts.serializedBody = opts.bodySerializer(opts.body); - } - - // remove Content-Type header if body is empty to avoid sending invalid requests - if (opts.body === undefined || opts.serializedBody === '') { - opts.headers.delete('Content-Type'); - } - - const url = buildUrl(opts); - - return { opts, url }; - }; - - const request: Client['request'] = async (options) => { - // @ts-expect-error - const { opts, url } = await beforeRequest(options); - const requestInit: ReqInit = { - redirect: 'follow', - ...opts, - body: getValidRequestBody(opts), - }; - - let request = new Request(url, requestInit); - - for (const fn of interceptors.request.fns) { - if (fn) { - request = await fn(request, opts); - } - } - - // fetch must be assigned here, otherwise it would throw the error: - // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation - const _fetch = opts.fetch!; - let response: Response; - - try { - response = await _fetch(request); - } catch (error) { - // Handle fetch exceptions (AbortError, network errors, etc.) - let finalError = error; - - for (const fn of interceptors.error.fns) { - if (fn) { - finalError = (await fn(error, undefined as any, request, opts)) as unknown; - } - } - - finalError = finalError || ({} as unknown); - - if (opts.throwOnError) { - throw finalError; - } - - // Return error response - return opts.responseStyle === 'data' - ? undefined - : { - error: finalError, - request, - response: undefined as any, - }; - } - - for (const fn of interceptors.response.fns) { - if (fn) { - response = await fn(response, request, opts); - } - } - - const result = { - request, - response, - }; - - if (response.ok) { - const parseAs = - (opts.parseAs === 'auto' - ? getParseAs(response.headers.get('Content-Type')) - : opts.parseAs) ?? 'json'; - - if (response.status === 204 || response.headers.get('Content-Length') === '0') { - let emptyData: any; - switch (parseAs) { - case 'arrayBuffer': - case 'blob': - case 'text': - emptyData = await response[parseAs](); - break; - case 'formData': - emptyData = new FormData(); - break; - case 'stream': - emptyData = response.body; - break; - case 'json': - default: - emptyData = {}; - break; - } - return opts.responseStyle === 'data' - ? emptyData - : { - data: emptyData, - ...result, - }; - } - - let data: any; - switch (parseAs) { - case 'arrayBuffer': - case 'blob': - case 'formData': - case 'text': - data = await response[parseAs](); - break; - case 'json': { - // Some servers return 200 with no Content-Length and empty body. - // response.json() would throw; read as text and parse if non-empty. - const text = await response.text(); - data = text ? JSON.parse(text) : {}; - break; - } - case 'stream': - return opts.responseStyle === 'data' - ? response.body - : { - data: response.body, - ...result, - }; - } - - if (parseAs === 'json') { - if (opts.responseValidator) { - await opts.responseValidator(data); - } - - if (opts.responseTransformer) { - data = await opts.responseTransformer(data); - } - } - - return opts.responseStyle === 'data' - ? data - : { - data, - ...result, - }; - } - - const textError = await response.text(); - let jsonError: unknown; - - try { - jsonError = JSON.parse(textError); - } catch { - // noop - } - - const error = jsonError ?? textError; - let finalError = error; - - for (const fn of interceptors.error.fns) { - if (fn) { - finalError = (await fn(error, response, request, opts)) as string; - } - } - - finalError = finalError || ({} as string); - - if (opts.throwOnError) { - throw finalError; - } - - // TODO: we probably want to return error and improve types - return opts.responseStyle === 'data' - ? undefined - : { - error: finalError, - ...result, - }; - }; - - const makeMethodFn = (method: Uppercase) => (options: RequestOptions) => - request({ ...options, method }); - - const makeSseFn = (method: Uppercase) => async (options: RequestOptions) => { - const { opts, url } = await beforeRequest(options); - return createSseClient({ - ...opts, - body: opts.body as BodyInit | null | undefined, - headers: opts.headers as unknown as Record, - method, - onRequest: async (url, init) => { - let request = new Request(url, init); - for (const fn of interceptors.request.fns) { - if (fn) { - request = await fn(request, opts); - } - } - return request; - }, - serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, - url, - }); - }; - - return { - buildUrl, - connect: makeMethodFn('CONNECT'), - delete: makeMethodFn('DELETE'), - get: makeMethodFn('GET'), - getConfig, - head: makeMethodFn('HEAD'), - interceptors, - options: makeMethodFn('OPTIONS'), - patch: makeMethodFn('PATCH'), - post: makeMethodFn('POST'), - put: makeMethodFn('PUT'), - request, - setConfig, - sse: { - connect: makeSseFn('CONNECT'), - delete: makeSseFn('DELETE'), - get: makeSseFn('GET'), - head: makeSseFn('HEAD'), - options: makeSseFn('OPTIONS'), - patch: makeSseFn('PATCH'), - post: makeSseFn('POST'), - put: makeSseFn('PUT'), - trace: makeSseFn('TRACE'), - }, - trace: makeMethodFn('TRACE'), - } as Client; -}; diff --git a/ui/desktop/src/api/client/index.ts b/ui/desktop/src/api/client/index.ts deleted file mode 100644 index b295edeca0ca..000000000000 --- a/ui/desktop/src/api/client/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type { Auth } from '../core/auth.gen'; -export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; -export { - formDataBodySerializer, - jsonBodySerializer, - urlSearchParamsBodySerializer, -} from '../core/bodySerializer.gen'; -export { buildClientParams } from '../core/params.gen'; -export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; -export { createClient } from './client.gen'; -export type { - Client, - ClientOptions, - Config, - CreateClientConfig, - Options, - RequestOptions, - RequestResult, - ResolvedRequestOptions, - ResponseStyle, - TDataShape, -} from './types.gen'; -export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/ui/desktop/src/api/client/types.gen.ts b/ui/desktop/src/api/client/types.gen.ts deleted file mode 100644 index 8c0df2321e82..000000000000 --- a/ui/desktop/src/api/client/types.gen.ts +++ /dev/null @@ -1,214 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Auth } from '../core/auth.gen'; -import type { - ServerSentEventsOptions, - ServerSentEventsResult, -} from '../core/serverSentEvents.gen'; -import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen'; -import type { Middleware } from './utils.gen'; - -export type ResponseStyle = 'data' | 'fields'; - -export interface Config - extends Omit, CoreConfig { - /** - * Base URL for all requests made by this client. - */ - baseUrl?: T['baseUrl']; - /** - * Fetch API implementation. You can use this option to provide a custom - * fetch instance. - * - * @default globalThis.fetch - */ - fetch?: typeof fetch; - /** - * Please don't use the Fetch client for Next.js applications. The `next` - * options won't have any effect. - * - * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. - */ - next?: never; - /** - * Return the response data parsed in a specified format. By default, `auto` - * will infer the appropriate method from the `Content-Type` response header. - * You can override this behavior with any of the {@link Body} methods. - * Select `stream` if you don't want to parse response data at all. - * - * @default 'auto' - */ - parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text'; - /** - * Should we return only data or multiple fields (data, error, response, etc.)? - * - * @default 'fields' - */ - responseStyle?: ResponseStyle; - /** - * Throw an error instead of returning it in the response? - * - * @default false - */ - throwOnError?: T['throwOnError']; -} - -export interface RequestOptions< - TData = unknown, - TResponseStyle extends ResponseStyle = 'fields', - ThrowOnError extends boolean = boolean, - Url extends string = string, -> - extends - Config<{ - responseStyle: TResponseStyle; - throwOnError: ThrowOnError; - }>, - Pick< - ServerSentEventsOptions, - | 'onRequest' - | 'onSseError' - | 'onSseEvent' - | 'sseDefaultRetryDelay' - | 'sseMaxRetryAttempts' - | 'sseMaxRetryDelay' - > { - /** - * Any body that you want to add to your request. - * - * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} - */ - body?: unknown; - path?: Record; - query?: Record; - /** - * Security mechanism(s) to use for the request. - */ - security?: ReadonlyArray; - url: Url; -} - -export interface ResolvedRequestOptions< - TResponseStyle extends ResponseStyle = 'fields', - ThrowOnError extends boolean = boolean, - Url extends string = string, -> extends RequestOptions { - serializedBody?: string; -} - -export type RequestResult< - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = boolean, - TResponseStyle extends ResponseStyle = 'fields', -> = ThrowOnError extends true - ? Promise< - TResponseStyle extends 'data' - ? TData extends Record - ? TData[keyof TData] - : TData - : { - data: TData extends Record ? TData[keyof TData] : TData; - request: Request; - response: Response; - } - > - : Promise< - TResponseStyle extends 'data' - ? (TData extends Record ? TData[keyof TData] : TData) | undefined - : ( - | { - data: TData extends Record ? TData[keyof TData] : TData; - error: undefined; - } - | { - data: undefined; - error: TError extends Record ? TError[keyof TError] : TError; - } - ) & { - request: Request; - response: Response; - } - >; - -export interface ClientOptions { - baseUrl?: string; - responseStyle?: ResponseStyle; - throwOnError?: boolean; -} - -type MethodFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, - TResponseStyle extends ResponseStyle = 'fields', ->( - options: Omit, 'method'>, -) => RequestResult; - -type SseFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, - TResponseStyle extends ResponseStyle = 'fields', ->( - options: Omit, 'method'>, -) => Promise>; - -type RequestFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, - TResponseStyle extends ResponseStyle = 'fields', ->( - options: Omit, 'method'> & - Pick>, 'method'>, -) => RequestResult; - -type BuildUrlFn = < - TData extends { - body?: unknown; - path?: Record; - query?: Record; - url: string; - }, ->( - options: TData & Options, -) => string; - -export type Client = CoreClient & { - interceptors: Middleware; -}; - -/** - * The `createClientConfig()` function will be called on client initialization - * and the returned object will become the client's initial configuration. - * - * You may want to initialize your client this way instead of calling - * `setConfig()`. This is useful for example if you're using Next.js - * to ensure your client always has the correct values. - */ -export type CreateClientConfig = ( - override?: Config, -) => Config & T> | Promise & T>>; - -export interface TDataShape { - body?: unknown; - headers?: unknown; - path?: unknown; - query?: unknown; - url: string; -} - -type OmitKeys = Pick>; - -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean, - TResponse = unknown, - TResponseStyle extends ResponseStyle = 'fields', -> = OmitKeys< - RequestOptions, - 'body' | 'path' | 'query' | 'url' -> & - ([TData] extends [never] ? unknown : Omit); diff --git a/ui/desktop/src/api/client/utils.gen.ts b/ui/desktop/src/api/client/utils.gen.ts deleted file mode 100644 index b4bd2435ce0b..000000000000 --- a/ui/desktop/src/api/client/utils.gen.ts +++ /dev/null @@ -1,316 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { getAuthToken } from '../core/auth.gen'; -import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; -import { jsonBodySerializer } from '../core/bodySerializer.gen'; -import { - serializeArrayParam, - serializeObjectParam, - serializePrimitiveParam, -} from '../core/pathSerializer.gen'; -import { getUrl } from '../core/utils.gen'; -import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; - -export const createQuerySerializer = ({ - parameters = {}, - ...args -}: QuerySerializerOptions = {}) => { - const querySerializer = (queryParams: T) => { - const search: string[] = []; - if (queryParams && typeof queryParams === 'object') { - for (const name in queryParams) { - const value = queryParams[name]; - - if (value === undefined || value === null) { - continue; - } - - const options = parameters[name] || args; - - if (Array.isArray(value)) { - const serializedArray = serializeArrayParam({ - allowReserved: options.allowReserved, - explode: true, - name, - style: 'form', - value, - ...options.array, - }); - if (serializedArray) search.push(serializedArray); - } else if (typeof value === 'object') { - const serializedObject = serializeObjectParam({ - allowReserved: options.allowReserved, - explode: true, - name, - style: 'deepObject', - value: value as Record, - ...options.object, - }); - if (serializedObject) search.push(serializedObject); - } else { - const serializedPrimitive = serializePrimitiveParam({ - allowReserved: options.allowReserved, - name, - value: value as string, - }); - if (serializedPrimitive) search.push(serializedPrimitive); - } - } - } - return search.join('&'); - }; - return querySerializer; -}; - -/** - * Infers parseAs value from provided Content-Type header. - */ -export const getParseAs = (contentType: string | null): Exclude => { - if (!contentType) { - // If no Content-Type header is provided, the best we can do is return the raw response body, - // which is effectively the same as the 'stream' option. - return 'stream'; - } - - const cleanContent = contentType.split(';')[0]?.trim(); - - if (!cleanContent) { - return; - } - - if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) { - return 'json'; - } - - if (cleanContent === 'multipart/form-data') { - return 'formData'; - } - - if ( - ['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type)) - ) { - return 'blob'; - } - - if (cleanContent.startsWith('text/')) { - return 'text'; - } - - return; -}; - -const checkForExistence = ( - options: Pick & { - headers: Headers; - }, - name?: string, -): boolean => { - if (!name) { - return false; - } - if ( - options.headers.has(name) || - options.query?.[name] || - options.headers.get('Cookie')?.includes(`${name}=`) - ) { - return true; - } - return false; -}; - -export const setAuthParams = async ({ - security, - ...options -}: Pick, 'security'> & - Pick & { - headers: Headers; - }) => { - for (const auth of security) { - if (checkForExistence(options, auth.name)) { - continue; - } - - const token = await getAuthToken(auth, options.auth); - - if (!token) { - continue; - } - - const name = auth.name ?? 'Authorization'; - - switch (auth.in) { - case 'query': - if (!options.query) { - options.query = {}; - } - options.query[name] = token; - break; - case 'cookie': - options.headers.append('Cookie', `${name}=${token}`); - break; - case 'header': - default: - options.headers.set(name, token); - break; - } - } -}; - -export const buildUrl: Client['buildUrl'] = (options) => - getUrl({ - baseUrl: options.baseUrl as string, - path: options.path, - query: options.query, - querySerializer: - typeof options.querySerializer === 'function' - ? options.querySerializer - : createQuerySerializer(options.querySerializer), - url: options.url, - }); - -export const mergeConfigs = (a: Config, b: Config): Config => { - const config = { ...a, ...b }; - if (config.baseUrl?.endsWith('/')) { - config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); - } - config.headers = mergeHeaders(a.headers, b.headers); - return config; -}; - -const headersEntries = (headers: Headers): Array<[string, string]> => { - const entries: Array<[string, string]> = []; - headers.forEach((value, key) => { - entries.push([key, value]); - }); - return entries; -}; - -export const mergeHeaders = ( - ...headers: Array['headers'] | undefined> -): Headers => { - const mergedHeaders = new Headers(); - for (const header of headers) { - if (!header) { - continue; - } - - const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header); - - for (const [key, value] of iterator) { - if (value === null) { - mergedHeaders.delete(key); - } else if (Array.isArray(value)) { - for (const v of value) { - mergedHeaders.append(key, v as string); - } - } else if (value !== undefined) { - // assume object headers are meant to be JSON stringified, i.e. their - // content value in OpenAPI specification is 'application/json' - mergedHeaders.set( - key, - typeof value === 'object' ? JSON.stringify(value) : (value as string), - ); - } - } - } - return mergedHeaders; -}; - -type ErrInterceptor = ( - error: Err, - response: Res, - request: Req, - options: Options, -) => Err | Promise; - -type ReqInterceptor = (request: Req, options: Options) => Req | Promise; - -type ResInterceptor = ( - response: Res, - request: Req, - options: Options, -) => Res | Promise; - -class Interceptors { - fns: Array = []; - - clear(): void { - this.fns = []; - } - - eject(id: number | Interceptor): void { - const index = this.getInterceptorIndex(id); - if (this.fns[index]) { - this.fns[index] = null; - } - } - - exists(id: number | Interceptor): boolean { - const index = this.getInterceptorIndex(id); - return Boolean(this.fns[index]); - } - - getInterceptorIndex(id: number | Interceptor): number { - if (typeof id === 'number') { - return this.fns[id] ? id : -1; - } - return this.fns.indexOf(id); - } - - update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false { - const index = this.getInterceptorIndex(id); - if (this.fns[index]) { - this.fns[index] = fn; - return id; - } - return false; - } - - use(fn: Interceptor): number { - this.fns.push(fn); - return this.fns.length - 1; - } -} - -export interface Middleware { - error: Interceptors>; - request: Interceptors>; - response: Interceptors>; -} - -export const createInterceptors = (): Middleware< - Req, - Res, - Err, - Options -> => ({ - error: new Interceptors>(), - request: new Interceptors>(), - response: new Interceptors>(), -}); - -const defaultQuerySerializer = createQuerySerializer({ - allowReserved: false, - array: { - explode: true, - style: 'form', - }, - object: { - explode: true, - style: 'deepObject', - }, -}); - -const defaultHeaders = { - 'Content-Type': 'application/json', -}; - -export const createConfig = ( - override: Config & T> = {}, -): Config & T> => ({ - ...jsonBodySerializer, - headers: defaultHeaders, - parseAs: 'auto', - querySerializer: defaultQuerySerializer, - ...override, -}); diff --git a/ui/desktop/src/api/core/auth.gen.ts b/ui/desktop/src/api/core/auth.gen.ts deleted file mode 100644 index 3ebf9947883f..000000000000 --- a/ui/desktop/src/api/core/auth.gen.ts +++ /dev/null @@ -1,41 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type AuthToken = string | undefined; - -export interface Auth { - /** - * Which part of the request do we use to send the auth? - * - * @default 'header' - */ - in?: 'header' | 'query' | 'cookie'; - /** - * Header or query parameter name. - * - * @default 'Authorization' - */ - name?: string; - scheme?: 'basic' | 'bearer'; - type: 'apiKey' | 'http'; -} - -export const getAuthToken = async ( - auth: Auth, - callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, -): Promise => { - const token = typeof callback === 'function' ? await callback(auth) : callback; - - if (!token) { - return; - } - - if (auth.scheme === 'bearer') { - return `Bearer ${token}`; - } - - if (auth.scheme === 'basic') { - return `Basic ${btoa(token)}`; - } - - return token; -}; diff --git a/ui/desktop/src/api/core/bodySerializer.gen.ts b/ui/desktop/src/api/core/bodySerializer.gen.ts deleted file mode 100644 index 8ad92c9ffd6a..000000000000 --- a/ui/desktop/src/api/core/bodySerializer.gen.ts +++ /dev/null @@ -1,84 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen'; - -export type QuerySerializer = (query: Record) => string; - -export type BodySerializer = (body: any) => any; - -type QuerySerializerOptionsObject = { - allowReserved?: boolean; - array?: Partial>; - object?: Partial>; -}; - -export type QuerySerializerOptions = QuerySerializerOptionsObject & { - /** - * Per-parameter serialization overrides. When provided, these settings - * override the global array/object settings for specific parameter names. - */ - parameters?: Record; -}; - -const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { - if (typeof value === 'string' || value instanceof Blob) { - data.append(key, value); - } else if (value instanceof Date) { - data.append(key, value.toISOString()); - } else { - data.append(key, JSON.stringify(value)); - } -}; - -const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => { - if (typeof value === 'string') { - data.append(key, value); - } else { - data.append(key, JSON.stringify(value)); - } -}; - -export const formDataBodySerializer = { - bodySerializer: | Array>>( - body: T, - ): FormData => { - const data = new FormData(); - - Object.entries(body).forEach(([key, value]) => { - if (value === undefined || value === null) { - return; - } - if (Array.isArray(value)) { - value.forEach((v) => serializeFormDataPair(data, key, v)); - } else { - serializeFormDataPair(data, key, value); - } - }); - - return data; - }, -}; - -export const jsonBodySerializer = { - bodySerializer: (body: T): string => - JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)), -}; - -export const urlSearchParamsBodySerializer = { - bodySerializer: | Array>>(body: T): string => { - const data = new URLSearchParams(); - - Object.entries(body).forEach(([key, value]) => { - if (value === undefined || value === null) { - return; - } - if (Array.isArray(value)) { - value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); - } else { - serializeUrlSearchParamsPair(data, key, value); - } - }); - - return data.toString(); - }, -}; diff --git a/ui/desktop/src/api/core/params.gen.ts b/ui/desktop/src/api/core/params.gen.ts deleted file mode 100644 index 7955601a5cc0..000000000000 --- a/ui/desktop/src/api/core/params.gen.ts +++ /dev/null @@ -1,169 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -type Slot = 'body' | 'headers' | 'path' | 'query'; - -export type Field = - | { - in: Exclude; - /** - * Field name. This is the name we want the user to see and use. - */ - key: string; - /** - * Field mapped name. This is the name we want to use in the request. - * If omitted, we use the same value as `key`. - */ - map?: string; - } - | { - in: Extract; - /** - * Key isn't required for bodies. - */ - key?: string; - map?: string; - } - | { - /** - * Field name. This is the name we want the user to see and use. - */ - key: string; - /** - * Field mapped name. This is the name we want to use in the request. - * If `in` is omitted, `map` aliases `key` to the transport layer. - */ - map: Slot; - }; - -export interface Fields { - allowExtra?: Partial>; - args?: ReadonlyArray; -} - -export type FieldsConfig = ReadonlyArray; - -const extraPrefixesMap: Record = { - $body_: 'body', - $headers_: 'headers', - $path_: 'path', - $query_: 'query', -}; -const extraPrefixes = Object.entries(extraPrefixesMap); - -type KeyMap = Map< - string, - | { - in: Slot; - map?: string; - } - | { - in?: never; - map: Slot; - } ->; - -const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { - if (!map) { - map = new Map(); - } - - for (const config of fields) { - if ('in' in config) { - if (config.key) { - map.set(config.key, { - in: config.in, - map: config.map, - }); - } - } else if ('key' in config) { - map.set(config.key, { - map: config.map, - }); - } else if (config.args) { - buildKeyMap(config.args, map); - } - } - - return map; -}; - -interface Params { - body: unknown; - headers: Record; - path: Record; - query: Record; -} - -const stripEmptySlots = (params: Params) => { - for (const [slot, value] of Object.entries(params)) { - if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { - delete params[slot as Slot]; - } - } -}; - -export const buildClientParams = (args: ReadonlyArray, fields: FieldsConfig) => { - const params: Params = { - body: {}, - headers: {}, - path: {}, - query: {}, - }; - - const map = buildKeyMap(fields); - - let config: FieldsConfig[number] | undefined; - - for (const [index, arg] of args.entries()) { - if (fields[index]) { - config = fields[index]; - } - - if (!config) { - continue; - } - - if ('in' in config) { - if (config.key) { - const field = map.get(config.key)!; - const name = field.map || config.key; - if (field.in) { - (params[field.in] as Record)[name] = arg; - } - } else { - params.body = arg; - } - } else { - for (const [key, value] of Object.entries(arg ?? {})) { - const field = map.get(key); - - if (field) { - if (field.in) { - const name = field.map || key; - (params[field.in] as Record)[name] = value; - } else { - params[field.map] = value; - } - } else { - const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix)); - - if (extra) { - const [prefix, slot] = extra; - (params[slot] as Record)[key.slice(prefix.length)] = value; - } else if ('allowExtra' in config && config.allowExtra) { - for (const [slot, allowed] of Object.entries(config.allowExtra)) { - if (allowed) { - (params[slot as Slot] as Record)[key] = value; - break; - } - } - } - } - } - } - } - - stripEmptySlots(params); - - return params; -}; diff --git a/ui/desktop/src/api/core/pathSerializer.gen.ts b/ui/desktop/src/api/core/pathSerializer.gen.ts deleted file mode 100644 index 994b2848c63f..000000000000 --- a/ui/desktop/src/api/core/pathSerializer.gen.ts +++ /dev/null @@ -1,171 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -interface SerializeOptions extends SerializePrimitiveOptions, SerializerOptions {} - -interface SerializePrimitiveOptions { - allowReserved?: boolean; - name: string; -} - -export interface SerializerOptions { - /** - * @default true - */ - explode: boolean; - style: T; -} - -export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; -export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; -type MatrixStyle = 'label' | 'matrix' | 'simple'; -export type ObjectStyle = 'form' | 'deepObject'; -type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; - -interface SerializePrimitiveParam extends SerializePrimitiveOptions { - value: string; -} - -export const separatorArrayExplode = (style: ArraySeparatorStyle) => { - switch (style) { - case 'label': - return '.'; - case 'matrix': - return ';'; - case 'simple': - return ','; - default: - return '&'; - } -}; - -export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { - switch (style) { - case 'form': - return ','; - case 'pipeDelimited': - return '|'; - case 'spaceDelimited': - return '%20'; - default: - return ','; - } -}; - -export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { - switch (style) { - case 'label': - return '.'; - case 'matrix': - return ';'; - case 'simple': - return ','; - default: - return '&'; - } -}; - -export const serializeArrayParam = ({ - allowReserved, - explode, - name, - style, - value, -}: SerializeOptions & { - value: unknown[]; -}) => { - if (!explode) { - const joinedValues = ( - allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) - ).join(separatorArrayNoExplode(style)); - switch (style) { - case 'label': - return `.${joinedValues}`; - case 'matrix': - return `;${name}=${joinedValues}`; - case 'simple': - return joinedValues; - default: - return `${name}=${joinedValues}`; - } - } - - const separator = separatorArrayExplode(style); - const joinedValues = value - .map((v) => { - if (style === 'label' || style === 'simple') { - return allowReserved ? v : encodeURIComponent(v as string); - } - - return serializePrimitiveParam({ - allowReserved, - name, - value: v as string, - }); - }) - .join(separator); - return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; -}; - -export const serializePrimitiveParam = ({ - allowReserved, - name, - value, -}: SerializePrimitiveParam) => { - if (value === undefined || value === null) { - return ''; - } - - if (typeof value === 'object') { - throw new Error( - 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', - ); - } - - return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; -}; - -export const serializeObjectParam = ({ - allowReserved, - explode, - name, - style, - value, - valueOnly, -}: SerializeOptions & { - value: Record | Date; - valueOnly?: boolean; -}) => { - if (value instanceof Date) { - return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; - } - - if (style !== 'deepObject' && !explode) { - let values: string[] = []; - Object.entries(value).forEach(([key, v]) => { - values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]; - }); - const joinedValues = values.join(','); - switch (style) { - case 'form': - return `${name}=${joinedValues}`; - case 'label': - return `.${joinedValues}`; - case 'matrix': - return `;${name}=${joinedValues}`; - default: - return joinedValues; - } - } - - const separator = separatorObjectExplode(style); - const joinedValues = Object.entries(value) - .map(([key, v]) => - serializePrimitiveParam({ - allowReserved, - name: style === 'deepObject' ? `${name}[${key}]` : key, - value: v as string, - }), - ) - .join(separator); - return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; -}; diff --git a/ui/desktop/src/api/core/queryKeySerializer.gen.ts b/ui/desktop/src/api/core/queryKeySerializer.gen.ts deleted file mode 100644 index 5000df606f37..000000000000 --- a/ui/desktop/src/api/core/queryKeySerializer.gen.ts +++ /dev/null @@ -1,117 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -/** - * JSON-friendly union that mirrors what Pinia Colada can hash. - */ -export type JsonValue = - | null - | string - | number - | boolean - | JsonValue[] - | { [key: string]: JsonValue }; - -/** - * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. - */ -export const queryKeyJsonReplacer = (_key: string, value: unknown) => { - if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { - return undefined; - } - if (typeof value === 'bigint') { - return value.toString(); - } - if (value instanceof Date) { - return value.toISOString(); - } - return value; -}; - -/** - * Safely stringifies a value and parses it back into a JsonValue. - */ -export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { - try { - const json = JSON.stringify(input, queryKeyJsonReplacer); - if (json === undefined) { - return undefined; - } - return JSON.parse(json) as JsonValue; - } catch { - return undefined; - } -}; - -/** - * Detects plain objects (including objects with a null prototype). - */ -const isPlainObject = (value: unknown): value is Record => { - if (value === null || typeof value !== 'object') { - return false; - } - const prototype = Object.getPrototypeOf(value as object); - return prototype === Object.prototype || prototype === null; -}; - -/** - * Turns URLSearchParams into a sorted JSON object for deterministic keys. - */ -const serializeSearchParams = (params: URLSearchParams): JsonValue => { - const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); - const result: Record = {}; - - for (const [key, value] of entries) { - const existing = result[key]; - if (existing === undefined) { - result[key] = value; - continue; - } - - if (Array.isArray(existing)) { - (existing as string[]).push(value); - } else { - result[key] = [existing, value]; - } - } - - return result; -}; - -/** - * Normalizes any accepted value into a JSON-friendly shape for query keys. - */ -export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => { - if (value === null) { - return null; - } - - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return value; - } - - if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { - return undefined; - } - - if (typeof value === 'bigint') { - return value.toString(); - } - - if (value instanceof Date) { - return value.toISOString(); - } - - if (Array.isArray(value)) { - return stringifyToJsonValue(value); - } - - if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) { - return serializeSearchParams(value); - } - - if (isPlainObject(value)) { - return stringifyToJsonValue(value); - } - - return undefined; -}; diff --git a/ui/desktop/src/api/core/serverSentEvents.gen.ts b/ui/desktop/src/api/core/serverSentEvents.gen.ts deleted file mode 100644 index 6aa6cf02a4f4..000000000000 --- a/ui/desktop/src/api/core/serverSentEvents.gen.ts +++ /dev/null @@ -1,243 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Config } from './types.gen'; - -export type ServerSentEventsOptions = Omit & - Pick & { - /** - * Fetch API implementation. You can use this option to provide a custom - * fetch instance. - * - * @default globalThis.fetch - */ - fetch?: typeof fetch; - /** - * Implementing clients can call request interceptors inside this hook. - */ - onRequest?: (url: string, init: RequestInit) => Promise; - /** - * Callback invoked when a network or parsing error occurs during streaming. - * - * This option applies only if the endpoint returns a stream of events. - * - * @param error The error that occurred. - */ - onSseError?: (error: unknown) => void; - /** - * Callback invoked when an event is streamed from the server. - * - * This option applies only if the endpoint returns a stream of events. - * - * @param event Event streamed from the server. - * @returns Nothing (void). - */ - onSseEvent?: (event: StreamEvent) => void; - serializedBody?: RequestInit['body']; - /** - * Default retry delay in milliseconds. - * - * This option applies only if the endpoint returns a stream of events. - * - * @default 3000 - */ - sseDefaultRetryDelay?: number; - /** - * Maximum number of retry attempts before giving up. - */ - sseMaxRetryAttempts?: number; - /** - * Maximum retry delay in milliseconds. - * - * Applies only when exponential backoff is used. - * - * This option applies only if the endpoint returns a stream of events. - * - * @default 30000 - */ - sseMaxRetryDelay?: number; - /** - * Optional sleep function for retry backoff. - * - * Defaults to using `setTimeout`. - */ - sseSleepFn?: (ms: number) => Promise; - url: string; - }; - -export interface StreamEvent { - data: TData; - event?: string; - id?: string; - retry?: number; -} - -export type ServerSentEventsResult = { - stream: AsyncGenerator< - TData extends Record ? TData[keyof TData] : TData, - TReturn, - TNext - >; -}; - -export const createSseClient = ({ - onRequest, - onSseError, - onSseEvent, - responseTransformer, - responseValidator, - sseDefaultRetryDelay, - sseMaxRetryAttempts, - sseMaxRetryDelay, - sseSleepFn, - url, - ...options -}: ServerSentEventsOptions): ServerSentEventsResult => { - let lastEventId: string | undefined; - - const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); - - const createStream = async function* () { - let retryDelay: number = sseDefaultRetryDelay ?? 3000; - let attempt = 0; - const signal = options.signal ?? new AbortController().signal; - - while (true) { - if (signal.aborted) break; - - attempt++; - - const headers = - options.headers instanceof Headers - ? options.headers - : new Headers(options.headers as Record | undefined); - - if (lastEventId !== undefined) { - headers.set('Last-Event-ID', lastEventId); - } - - try { - const requestInit: RequestInit = { - redirect: 'follow', - ...options, - body: options.serializedBody, - headers, - signal, - }; - let request = new Request(url, requestInit); - if (onRequest) { - request = await onRequest(url, requestInit); - } - // fetch must be assigned here, otherwise it would throw the error: - // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation - const _fetch = options.fetch ?? globalThis.fetch; - const response = await _fetch(request); - - if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`); - - if (!response.body) throw new Error('No body in SSE response'); - - const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); - - let buffer = ''; - - const abortHandler = () => { - try { - reader.cancel(); - } catch { - // noop - } - }; - - signal.addEventListener('abort', abortHandler); - - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += value; - // Normalize line endings: CRLF -> LF, then CR -> LF - buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); - - const chunks = buffer.split('\n\n'); - buffer = chunks.pop() ?? ''; - - for (const chunk of chunks) { - const lines = chunk.split('\n'); - const dataLines: Array = []; - let eventName: string | undefined; - - for (const line of lines) { - if (line.startsWith('data:')) { - dataLines.push(line.replace(/^data:\s*/, '')); - } else if (line.startsWith('event:')) { - eventName = line.replace(/^event:\s*/, ''); - } else if (line.startsWith('id:')) { - lastEventId = line.replace(/^id:\s*/, ''); - } else if (line.startsWith('retry:')) { - const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10); - if (!Number.isNaN(parsed)) { - retryDelay = parsed; - } - } - } - - let data: unknown; - let parsedJson = false; - - if (dataLines.length) { - const rawData = dataLines.join('\n'); - try { - data = JSON.parse(rawData); - parsedJson = true; - } catch { - data = rawData; - } - } - - if (parsedJson) { - if (responseValidator) { - await responseValidator(data); - } - - if (responseTransformer) { - data = await responseTransformer(data); - } - } - - onSseEvent?.({ - data, - event: eventName, - id: lastEventId, - retry: retryDelay, - }); - - if (dataLines.length) { - yield data as any; - } - } - } - } finally { - signal.removeEventListener('abort', abortHandler); - reader.releaseLock(); - } - - break; // exit loop on normal completion - } catch (error) { - // connection failed or aborted; retry after delay - onSseError?.(error); - - if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) { - break; // stop after firing error - } - - // exponential backoff: double retry each attempt, cap at 30s - const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000); - await sleep(backoff); - } - } - }; - - const stream = createStream(); - - return { stream }; -}; diff --git a/ui/desktop/src/api/core/types.gen.ts b/ui/desktop/src/api/core/types.gen.ts deleted file mode 100644 index 97463257e43e..000000000000 --- a/ui/desktop/src/api/core/types.gen.ts +++ /dev/null @@ -1,104 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Auth, AuthToken } from './auth.gen'; -import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen'; - -export type HttpMethod = - | 'connect' - | 'delete' - | 'get' - | 'head' - | 'options' - | 'patch' - | 'post' - | 'put' - | 'trace'; - -export type Client< - RequestFn = never, - Config = unknown, - MethodFn = never, - BuildUrlFn = never, - SseFn = never, -> = { - /** - * Returns the final request URL. - */ - buildUrl: BuildUrlFn; - getConfig: () => Config; - request: RequestFn; - setConfig: (config: Config) => Config; -} & { - [K in HttpMethod]: MethodFn; -} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } }); - -export interface Config { - /** - * Auth token or a function returning auth token. The resolved value will be - * added to the request payload as defined by its `security` array. - */ - auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; - /** - * A function for serializing request body parameter. By default, - * {@link JSON.stringify()} will be used. - */ - bodySerializer?: BodySerializer | null; - /** - * An object containing any HTTP headers that you want to pre-populate your - * `Headers` object with. - * - * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} - */ - headers?: - | RequestInit['headers'] - | Record< - string, - string | number | boolean | (string | number | boolean)[] | null | undefined | unknown - >; - /** - * The request method. - * - * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} - */ - method?: Uppercase; - /** - * A function for serializing request query parameters. By default, arrays - * will be exploded in form style, objects will be exploded in deepObject - * style, and reserved characters are percent-encoded. - * - * This method will have no effect if the native `paramsSerializer()` Axios - * API function is used. - * - * {@link https://swagger.io/docs/specification/serialization/#query View examples} - */ - querySerializer?: QuerySerializer | QuerySerializerOptions; - /** - * A function validating request data. This is useful if you want to ensure - * the request conforms to the desired shape, so it can be safely sent to - * the server. - */ - requestValidator?: (data: unknown) => Promise; - /** - * A function transforming response data before it's returned. This is useful - * for post-processing data, e.g. converting ISO strings into Date objects. - */ - responseTransformer?: (data: unknown) => Promise; - /** - * A function validating response data. This is useful if you want to ensure - * the response conforms to the desired shape, so it can be safely passed to - * the transformers and returned to the user. - */ - responseValidator?: (data: unknown) => Promise; -} - -type IsExactlyNeverOrNeverUndefined = [T] extends [never] - ? true - : [T] extends [never | undefined] - ? [undefined] extends [T] - ? false - : true - : false; - -export type OmitNever> = { - [K in keyof T as IsExactlyNeverOrNeverUndefined extends true ? never : K]: T[K]; -}; diff --git a/ui/desktop/src/api/core/utils.gen.ts b/ui/desktop/src/api/core/utils.gen.ts deleted file mode 100644 index e7ddbe354117..000000000000 --- a/ui/desktop/src/api/core/utils.gen.ts +++ /dev/null @@ -1,140 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; -import { - type ArraySeparatorStyle, - serializeArrayParam, - serializeObjectParam, - serializePrimitiveParam, -} from './pathSerializer.gen'; - -export interface PathSerializer { - path: Record; - url: string; -} - -export const PATH_PARAM_RE = /\{[^{}]+\}/g; - -export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { - let url = _url; - const matches = _url.match(PATH_PARAM_RE); - if (matches) { - for (const match of matches) { - let explode = false; - let name = match.substring(1, match.length - 1); - let style: ArraySeparatorStyle = 'simple'; - - if (name.endsWith('*')) { - explode = true; - name = name.substring(0, name.length - 1); - } - - if (name.startsWith('.')) { - name = name.substring(1); - style = 'label'; - } else if (name.startsWith(';')) { - name = name.substring(1); - style = 'matrix'; - } - - const value = path[name]; - - if (value === undefined || value === null) { - continue; - } - - if (Array.isArray(value)) { - url = url.replace(match, serializeArrayParam({ explode, name, style, value })); - continue; - } - - if (typeof value === 'object') { - url = url.replace( - match, - serializeObjectParam({ - explode, - name, - style, - value: value as Record, - valueOnly: true, - }), - ); - continue; - } - - if (style === 'matrix') { - url = url.replace( - match, - `;${serializePrimitiveParam({ - name, - value: value as string, - })}`, - ); - continue; - } - - const replaceValue = encodeURIComponent( - style === 'label' ? `.${value as string}` : (value as string), - ); - url = url.replace(match, replaceValue); - } - } - return url; -}; - -export const getUrl = ({ - baseUrl, - path, - query, - querySerializer, - url: _url, -}: { - baseUrl?: string; - path?: Record; - query?: Record; - querySerializer: QuerySerializer; - url: string; -}) => { - const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; - let url = (baseUrl ?? '') + pathUrl; - if (path) { - url = defaultPathSerializer({ path, url }); - } - let search = query ? querySerializer(query) : ''; - if (search.startsWith('?')) { - search = search.substring(1); - } - if (search) { - url += `?${search}`; - } - return url; -}; - -export function getValidRequestBody(options: { - body?: unknown; - bodySerializer?: BodySerializer | null; - serializedBody?: unknown; -}) { - const hasBody = options.body !== undefined; - const isSerializedBody = hasBody && options.bodySerializer; - - if (isSerializedBody) { - if ('serializedBody' in options) { - const hasSerializedBody = - options.serializedBody !== undefined && options.serializedBody !== ''; - - return hasSerializedBody ? options.serializedBody : null; - } - - // not all clients implement a serializedBody property (i.e. client-axios) - return options.body !== '' ? options.body : null; - } - - // plain/text body - if (hasBody) { - return options.body; - } - - // no body was provided - return undefined; -} diff --git a/ui/desktop/src/api/index.ts b/ui/desktop/src/api/index.ts deleted file mode 100644 index afe1f8254327..000000000000 --- a/ui/desktop/src/api/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export { addExtension, agentAddExtension, agentRemoveExtension, cancelDownload, checkProvider, cleanupProviderCache, confirmToolAction, createCustomProvider, createSchedule, decodeRecipe, deleteModel, deleteProviderSecret, deleteRecipe, deleteSchedule, diagnostics, downloadModel, encodeRecipe, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModelInfo, getProviderModels, getSession, getSessionExtensions, getSlashCommands, getTools, inspectRunningJob, killRunningJob, listModels, listProviderSecrets, listRecipes, listSchedules, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, recipeToYaml, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, sendTelemetryEvent, sessionCancel, sessionEvents, sessionReply, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, status, stopAgent, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateSchedule, updateSession, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, validateConfig } from './sdk.gen'; -export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelRequest, ChatRequest, CheckProviderData, CheckProviderRequest, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponse, CleanupProviderCacheResponses, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponse2, CreateCustomProviderResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteProviderSecretData, DeleteProviderSecretErrors, DeleteProviderSecretResponse, DeleteProviderSecretResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DiagnosticsConfig, DiagnosticsData, DiagnosticsError, DiagnosticsErrors, DiagnosticsExtensions, DiagnosticsLevel, DiagnosticsLogs, DiagnosticsPrompt, DiagnosticsReport, DiagnosticsResponse, DiagnosticsResponses, DiagnosticsScheduledRecipe, DiagnosticsTextFile, DictationProvider, DictationProviderStatus, DownloadModelData, DownloadModelErrors, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCanonicalModelInfoData, GetCanonicalModelInfoResponse, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponse, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponse, GetProviderCatalogTemplateResponses, GetProviderModelInfoData, GetProviderModelInfoErrors, GetProviderModelInfoResponse, GetProviderModelInfoResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GooseApp, GooseMode, Icon, IconTheme, ImageContent, InferenceMetadata, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListProviderSecretsData, ListProviderSecretsErrors, ListProviderSecretsResponse, ListProviderSecretsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, LoadedProvider, McpAppResource, Message, MessageContent, MessageEvent, MessageMetadata, ModelCapabilities, ModelConfig, ModelInfo, ModelInfoData, ModelInfoQuery, ModelInfoResponse, ModelTemplate, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderCatalogEntry, ProviderDetails, ProviderEngine, ProviderMetadata, ProviderModelInfoQuery, ProvidersData, ProviderSecret, ProviderSecretsResponse, ProviderSecretStatus, ProviderSecretStorage, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionCancelData, SessionCancelResponses, SessionDisplayInfo, SessionEventsData, SessionEventsErrors, SessionEventsResponse, SessionEventsResponses, SessionExtensionsResponse, SessionReplyData, SessionReplyErrors, SessionReplyRequest, SessionReplyResponse, SessionReplyResponse2, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, SubRecipe, SuccessCheck, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, ThinkingEffort, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionRequest, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, Usage, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts deleted file mode 100644 index 4786dcf2704e..000000000000 --- a/ui/desktop/src/api/sdk.gen.ts +++ /dev/null @@ -1,466 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Client, Options as Options2, TDataShape } from './client'; -import { client } from './client.gen'; -import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CheckProviderData, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteProviderSecretData, DeleteProviderSecretErrors, DeleteProviderSecretResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ForkSessionData, ForkSessionErrors, ForkSessionResponses, GetCanonicalModelInfoData, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetPromptData, GetPromptErrors, GetPromptResponses, GetPromptsData, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponses, GetProviderModelInfoData, GetProviderModelInfoErrors, GetProviderModelInfoResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListModelsData, ListModelsResponses, ListProviderSecretsData, ListProviderSecretsErrors, ListProviderSecretsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SavePromptData, SavePromptErrors, SavePromptResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionCancelData, SessionCancelResponses, SessionEventsData, SessionEventsErrors, SessionEventsResponses, SessionReplyData, SessionReplyErrors, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; - -export type Options = Options2 & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; - /** - * You can pass arbitrary values through the `meta` object. This can be - * used to access values that aren't defined as part of the SDK function. - */ - meta?: Record; -}; - -export const confirmToolAction = (options: Options) => (options.client ?? client).post({ - url: '/action-required/tool-confirmation', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const agentAddExtension = (options: Options) => (options.client ?? client).post({ - url: '/agent/add_extension', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const agentRemoveExtension = (options: Options) => (options.client ?? client).post({ - url: '/agent/remove_extension', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const restartAgent = (options: Options) => (options.client ?? client).post({ - url: '/agent/restart', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const resumeAgent = (options: Options) => (options.client ?? client).post({ - url: '/agent/resume', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const startAgent = (options: Options) => (options.client ?? client).post({ - url: '/agent/start', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const stopAgent = (options: Options) => (options.client ?? client).post({ - url: '/agent/stop', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const getTools = (options: Options) => (options.client ?? client).get({ url: '/agent/tools', ...options }); - -export const updateFromSession = (options: Options) => (options.client ?? client).post({ - url: '/agent/update_from_session', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const updateAgentProvider = (options: Options) => (options.client ?? client).post({ - url: '/agent/update_provider', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const updateSession = (options: Options) => (options.client ?? client).post({ - url: '/agent/update_session', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const updateWorkingDir = (options: Options) => (options.client ?? client).post({ - url: '/agent/update_working_dir', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const readAllConfig = (options?: Options) => (options?.client ?? client).get({ url: '/config', ...options }); - -export const getCanonicalModelInfo = (options: Options) => (options.client ?? client).post({ - url: '/config/canonical-model-info', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const checkProvider = (options: Options) => (options.client ?? client).post({ - url: '/config/check_provider', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const createCustomProvider = (options: Options) => (options.client ?? client).post({ - url: '/config/custom-providers', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const removeCustomProvider = (options: Options) => (options.client ?? client).delete({ url: '/config/custom-providers/{id}', ...options }); - -export const getCustomProvider = (options: Options) => (options.client ?? client).get({ url: '/config/custom-providers/{id}', ...options }); - -export const updateCustomProvider = (options: Options) => (options.client ?? client).put({ - url: '/config/custom-providers/{id}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const getExtensions = (options?: Options) => (options?.client ?? client).get({ url: '/config/extensions', ...options }); - -export const addExtension = (options: Options) => (options.client ?? client).post({ - url: '/config/extensions', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const removeExtension = (options: Options) => (options.client ?? client).delete({ url: '/config/extensions/{name}', ...options }); - -export const getPrompts = (options?: Options) => (options?.client ?? client).get({ url: '/config/prompts', ...options }); - -export const resetPrompt = (options: Options) => (options.client ?? client).delete({ url: '/config/prompts/{name}', ...options }); - -export const getPrompt = (options: Options) => (options.client ?? client).get({ url: '/config/prompts/{name}', ...options }); - -export const savePrompt = (options: Options) => (options.client ?? client).put({ - url: '/config/prompts/{name}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const getProviderCatalog = (options?: Options) => (options?.client ?? client).get({ url: '/config/provider-catalog', ...options }); - -export const getProviderCatalogTemplate = (options: Options) => (options.client ?? client).get({ url: '/config/provider-catalog/{id}', ...options }); - -export const listProviderSecrets = (options?: Options) => (options?.client ?? client).get({ url: '/config/provider-secrets', ...options }); - -export const deleteProviderSecret = (options: Options) => (options.client ?? client).delete({ url: '/config/provider-secrets/{id}', ...options }); - -export const providers = (options?: Options) => (options?.client ?? client).get({ url: '/config/providers', ...options }); - -export const cleanupProviderCache = (options: Options) => (options.client ?? client).post({ url: '/config/providers/{name}/cleanup', ...options }); - -export const getProviderModelInfo = (options: Options) => (options.client ?? client).post({ - url: '/config/providers/{name}/model-info', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const getProviderModels = (options: Options) => (options.client ?? client).get({ url: '/config/providers/{name}/models', ...options }); - -export const readConfig = (options: Options) => (options.client ?? client).post({ - url: '/config/read', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const removeConfig = (options: Options) => (options.client ?? client).post({ - url: '/config/remove', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const setConfigProvider = (options: Options) => (options.client ?? client).post({ - url: '/config/set_provider', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const getSlashCommands = (options?: Options) => (options?.client ?? client).get({ url: '/config/slash_commands', ...options }); - -export const upsertConfig = (options: Options) => (options.client ?? client).post({ - url: '/config/upsert', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const validateConfig = (options?: Options) => (options?.client ?? client).get({ url: '/config/validate', ...options }); - -export const diagnostics = (options: Options) => (options.client ?? client).get({ url: '/diagnostics/{session_id}', ...options }); - -export const getDictationConfig = (options?: Options) => (options?.client ?? client).get({ url: '/dictation/config', ...options }); - -export const listModels = (options?: Options) => (options?.client ?? client).get({ url: '/dictation/models', ...options }); - -export const deleteModel = (options: Options) => (options.client ?? client).delete({ url: '/dictation/models/{model_id}', ...options }); - -export const cancelDownload = (options: Options) => (options.client ?? client).delete({ url: '/dictation/models/{model_id}/download', ...options }); - -export const getDownloadProgress = (options: Options) => (options.client ?? client).get({ url: '/dictation/models/{model_id}/download', ...options }); - -export const downloadModel = (options: Options) => (options.client ?? client).post({ url: '/dictation/models/{model_id}/download', ...options }); - -export const transcribeDictation = (options: Options) => (options.client ?? client).post({ - url: '/dictation/transcribe', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const decodeRecipe = (options: Options) => (options.client ?? client).post({ - url: '/recipes/decode', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const deleteRecipe = (options: Options) => (options.client ?? client).post({ - url: '/recipes/delete', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const encodeRecipe = (options: Options) => (options.client ?? client).post({ - url: '/recipes/encode', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const listRecipes = (options?: Options) => (options?.client ?? client).get({ url: '/recipes/list', ...options }); - -export const parseRecipe = (options: Options) => (options.client ?? client).post({ - url: '/recipes/parse', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const saveRecipe = (options: Options) => (options.client ?? client).post({ - url: '/recipes/save', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const scanRecipe = (options: Options) => (options.client ?? client).post({ - url: '/recipes/scan', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const scheduleRecipe = (options: Options) => (options.client ?? client).post({ - url: '/recipes/schedule', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const setRecipeSlashCommand = (options: Options) => (options.client ?? client).post({ - url: '/recipes/slash-command', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const recipeToYaml = (options: Options) => (options.client ?? client).post({ - url: '/recipes/to-yaml', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const reply = (options: Options) => (options.client ?? client).sse.post({ - url: '/reply', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const createSchedule = (options: Options) => (options.client ?? client).post({ - url: '/schedule/create', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const deleteSchedule = (options: Options) => (options.client ?? client).delete({ url: '/schedule/delete/{id}', ...options }); - -export const listSchedules = (options?: Options) => (options?.client ?? client).get({ url: '/schedule/list', ...options }); - -export const updateSchedule = (options: Options) => (options.client ?? client).put({ - url: '/schedule/{id}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const inspectRunningJob = (options: Options) => (options.client ?? client).get({ url: '/schedule/{id}/inspect', ...options }); - -export const killRunningJob = (options: Options) => (options.client ?? client).post({ url: '/schedule/{id}/kill', ...options }); - -export const pauseSchedule = (options: Options) => (options.client ?? client).post({ url: '/schedule/{id}/pause', ...options }); - -export const runNowHandler = (options: Options) => (options.client ?? client).post({ url: '/schedule/{id}/run_now', ...options }); - -export const sessionsHandler = (options: Options) => (options.client ?? client).get({ url: '/schedule/{id}/sessions', ...options }); - -export const unpauseSchedule = (options: Options) => (options.client ?? client).post({ url: '/schedule/{id}/unpause', ...options }); - -export const sessionCancel = (options: Options) => (options.client ?? client).post({ - url: '/sessions/{id}/cancel', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const sessionEvents = (options: Options) => (options.client ?? client).sse.get({ url: '/sessions/{id}/events', ...options }); - -export const sessionReply = (options: Options) => (options.client ?? client).post({ - url: '/sessions/{id}/reply', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const getSession = (options: Options) => (options.client ?? client).get({ url: '/sessions/{session_id}', ...options }); - -export const getSessionExtensions = (options: Options) => (options.client ?? client).get({ url: '/sessions/{session_id}/extensions', ...options }); - -export const forkSession = (options: Options) => (options.client ?? client).post({ - url: '/sessions/{session_id}/fork', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const updateSessionName = (options: Options) => (options.client ?? client).put({ - url: '/sessions/{session_id}/name', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const updateSessionUserRecipeValues = (options: Options) => (options.client ?? client).put({ - url: '/sessions/{session_id}/user_recipe_values', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); - -export const status = (options?: Options) => (options?.client ?? client).get({ url: '/status', ...options }); - -export const systemInfo = (options?: Options) => (options?.client ?? client).get({ url: '/system_info', ...options }); - -export const sendTelemetryEvent = (options: Options) => (options.client ?? client).post({ - url: '/telemetry/event', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts deleted file mode 100644 index 4e1d84d9e570..000000000000 --- a/ui/desktop/src/api/types.gen.ts +++ /dev/null @@ -1,3798 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type ClientOptions = { - baseUrl: `${string}://${string}` | (string & {}); -}; - -export type ActionRequired = { - data: ActionRequiredData; -}; - -export type ActionRequiredData = { - actionType: 'toolConfirmation'; - arguments: JsonObject; - id: string; - prompt?: string | null; - toolName: string; -} | { - actionType: 'elicitation'; - id: string; - message: string; - requested_schema: unknown; -} | { - action?: string; - actionType: 'elicitationResponse'; - id: string; - user_data: unknown; -}; - -export type AddExtensionRequest = { - config: ExtensionConfig; - session_id: string; -}; - -export type Annotations = { - audience?: Array; - lastModified?: string; - priority?: number; -}; - -export type Author = { - contact?: string | null; - metadata?: string | null; -}; - -export type CancelRequest = { - request_id: string; -}; - -export type ChatRequest = { - /** - * Override the server's conversation history. Only use this when you need absolute control - * over the conversation state (e.g., administrative tools). For normal operations, the server - * is the source of truth - use truncate/fork endpoints to modify conversation history instead. - */ - override_conversation?: Array | null; - recipe_name?: string | null; - recipe_version?: string | null; - session_id: string; - user_message: Message; -}; - -export type CheckProviderRequest = { - provider: string; -}; - -export type CommandType = 'Builtin' | 'Recipe' | 'Skill' | 'Agent'; - -/** - * Configuration key metadata for provider setup - */ -export type ConfigKey = { - /** - * Optional default value for the key - */ - default?: string | null; - /** - * Whether this OAuth flow uses the device code grant (RFC 8628) - * When true, the user must enter a verification code in the browser - */ - device_code_flow?: boolean; - /** - * The name of the configuration key (e.g., "API_KEY") - */ - name: string; - /** - * Whether this key should be configured using an OAuth flow - * When true, the provider's configure_oauth() method will be called instead of prompting for manual input - */ - oauth_flow: boolean; - /** - * Whether this key should be shown prominently during provider setup - * (onboarding, settings modal, CLI configure) - */ - primary?: boolean; - /** - * Whether this key is required for the provider to function - */ - required: boolean; - /** - * Whether this key should be stored securely (e.g., in keychain) - */ - secret: boolean; -}; - -export type ConfigKeyQuery = { - is_secret: boolean; - key: string; -}; - -export type ConfigResponse = { - config: { - [key: string]: unknown; - }; -}; - -export type ConfirmToolActionRequest = { - action: Permission; - id: string; - principalType?: PrincipalType; - sessionId: string; -}; - -export type Content = ({ - type: 'text'; -} & RawTextContent) | ({ - type: 'image'; -} & RawImageContent) | ({ - type: 'resource'; -} & RawEmbeddedResource) | ({ - type: 'audio'; -} & RawAudioContent) | ({ - type: 'resource_link'; -} & RawResource); - -export type ContentBlock = ({ - type: 'text'; -} & RawTextContent) | ({ - type: 'image'; -} & RawImageContent) | ({ - type: 'resource'; -} & RawEmbeddedResource) | ({ - type: 'audio'; -} & RawAudioContent) | ({ - type: 'resource_link'; -} & RawResource); - -export type Conversation = Array; - -export type CreateCustomProviderResponse = { - provider_name: string; -}; - -export type CreateScheduleRequest = { - cron: string; - id: string; - recipe: Recipe; -}; - -/** - * Content Security Policy metadata for MCP Apps - * Specifies allowed domains for network connections and resource loading - */ -export type CspMetadata = { - /** - * Domains allowed for base-uri - */ - baseUriDomains?: Array | null; - /** - * Domains allowed for connect-src (fetch, XHR, WebSocket) - */ - connectDomains?: Array | null; - /** - * Domains allowed for frame-src (nested iframes) - */ - frameDomains?: Array | null; - /** - * Domains allowed for resource loading (scripts, styles, images, fonts, media) - */ - resourceDomains?: Array | null; -}; - -export type DeclarativeProviderConfig = { - api_key_env?: string; - base_path?: string | null; - base_url: string; - catalog_provider_id?: string | null; - description?: string | null; - display_name: string; - /** - * Controls whether `fetch_supported_models` calls the provider's `/v1/models` - * endpoint or returns the static `models` list directly. - * - * - `Some(false)` + non-empty `models`: return the static list; no API call. - * Construction fails if `models` is empty. - * - `Some(true)` or `None`: try the API; fall back to `models` on 404. - */ - dynamic_models?: boolean | null; - engine: ProviderEngine; - env_vars?: Array | null; - fast_model?: string | null; - headers?: { - [key: string]: string; - } | null; - model_doc_link?: string | null; - models: Array; - name: string; - preserves_thinking?: boolean; - requires_auth?: boolean; - setup_steps?: Array; - skip_canonical_filtering?: boolean; - supports_streaming?: boolean | null; - timeout_seconds?: number | null; -}; - -export type DecodeRecipeRequest = { - deeplink: string; -}; - -export type DecodeRecipeResponse = { - recipe: Recipe; -}; - -export type DeleteRecipeRequest = { - id: string; -}; - -export type DiagnosticsConfig = { - configPath: string; - configYaml?: string | null; - truncated: boolean; -}; - -export type DiagnosticsError = { - message: string; - path?: string | null; -}; - -export type DiagnosticsExtensions = { - enabled: Array; -}; - -export type DiagnosticsLevel = 'summary' | 'full'; - -export type DiagnosticsLogs = { - llm: Array; - server?: DiagnosticsTextFile | null; -}; - -export type DiagnosticsPrompt = { - content: string; - name: string; -}; - -export type DiagnosticsReport = { - config?: DiagnosticsConfig | null; - errors: Array; - extensions: DiagnosticsExtensions; - generatedAt: string; - level: DiagnosticsLevel; - logs: DiagnosticsLogs; - prompts: Array; - schedule?: unknown; - scheduledRecipes: Array; - schemaVersion: number; - session?: unknown; - system: SystemInfo; -}; - -export type DiagnosticsScheduledRecipe = { - content: string; - path: string; -}; - -export type DiagnosticsTextFile = { - content: string; - path: string; - truncated: boolean; -}; - -export type DictationProvider = 'openai' | 'elevenlabs' | 'groq' | 'local'; - -export type DictationProviderStatus = { - /** - * Config key name if uses_provider_config is false - */ - config_key?: string | null; - /** - * Whether the provider is fully configured and ready to use - */ - configured: boolean; - /** - * Description of what this provider does - */ - description: string; - /** - * Custom host URL if configured (only for providers that support it) - */ - host?: string | null; - /** - * Path to settings if uses_provider_config is true - */ - settings_path?: string | null; - /** - * Whether this provider uses the main provider config (true) or has its own key (false) - */ - uses_provider_config: boolean; -}; - -export type DownloadProgress = { - /** - * Bytes downloaded so far - */ - bytes_downloaded: number; - /** - * Error message if failed - */ - error?: string | null; - /** - * Estimated time remaining in seconds - */ - eta_seconds?: number | null; - /** - * Model ID being downloaded - */ - model_id: string; - /** - * Download progress percentage (0-100) - */ - progress_percent: number; - /** - * Download speed in bytes per second - */ - speed_bps?: number | null; - status: DownloadStatus; - /** - * Total bytes to download - */ - total_bytes: number; -}; - -export type DownloadStatus = 'downloading' | 'completed' | 'failed' | 'cancelled'; - -export type EmbeddedResource = { - _meta?: { - [key: string]: unknown; - }; - annotations?: Annotations | { - [key: string]: unknown; - }; - resource: ResourceContents; -}; - -export type EncodeRecipeRequest = { - recipe: Recipe; -}; - -export type EncodeRecipeResponse = { - deeplink: string; -}; - -export type EnvVarConfig = { - default?: string | null; - description?: string | null; - name: string; - /** - * Defaults to the value of `required` if not specified. - * UIs may use this to feature this config value more prominently. - */ - primary?: boolean | null; - required?: boolean; - secret?: boolean; -}; - -export type Envs = { - [key: string]: string; -}; - -export type ErrorResponse = { - message: string; -}; - -/** - * Represents the different types of MCP extensions that can be added to the manager - */ -export type ExtensionConfig = { - description: string; - name: string; - type: 'sse'; - uri?: string | null; -} | { - args: Array; - available_tools?: Array; - bundled?: boolean | null; - cmd: string; - cwd?: string | null; - description: string; - env_keys?: Array; - envs?: Envs; - /** - * The name used to identify this extension - */ - name: string; - timeout?: number | null; - type: 'stdio'; -} | { - available_tools?: Array; - bundled?: boolean | null; - description: string; - display_name?: string | null; - /** - * The name used to identify this extension - */ - name: string; - timeout?: number | null; - type: 'builtin'; -} | { - available_tools?: Array; - bundled?: boolean | null; - description: string; - display_name?: string | null; - /** - * The name used to identify this extension - */ - name: string; - type: 'platform'; -} | { - available_tools?: Array; - bundled?: boolean | null; - description: string; - env_keys?: Array; - envs?: Envs; - headers?: { - [key: string]: string; - }; - /** - * The name used to identify this extension - */ - name: string; - /** - * Optional Unix domain socket path for HTTP-over-UDS transport. - * When set, the HTTP connection is routed through this socket while - * `uri` is used for the Host header and path. - * Use `@name` for Linux abstract sockets. - */ - socket?: string | null; - timeout?: number | null; - type: 'streamable_http'; - uri: string; -} | { - available_tools?: Array; - bundled?: boolean | null; - description: string; - /** - * Instructions for how to use these tools - */ - instructions?: string | null; - /** - * The name used to identify this extension - */ - name: string; - /** - * The tools provided by the frontend - */ - tools: Array; - type: 'frontend'; -} | { - available_tools?: Array; - /** - * The Python code to execute - */ - code: string; - /** - * Python package dependencies required by this extension - */ - dependencies?: Array | null; - description: string; - /** - * The name used to identify this extension - */ - name: string; - /** - * Timeout in seconds - */ - timeout?: number | null; - type: 'inline_python'; -}; - -/** - * Extension data containing all extension states - * Keys are in format "extension_name.version" (e.g., "todo.v0") - */ -export type ExtensionData = { - [key: string]: unknown; -}; - -export type ExtensionEntry = ExtensionConfig & { - enabled: boolean; -}; - -export type ExtensionLoadResult = { - error?: string | null; - name: string; - success: boolean; -}; - -export type ExtensionQuery = { - config: ExtensionConfig; - enabled: boolean; - name: string; -}; - -export type ExtensionResponse = { - extensions: Array; - warnings?: Array; -}; - -export type ForkRequest = { - copy: boolean; - timestamp?: number | null; - truncate: boolean; -}; - -export type ForkResponse = { - sessionId: string; -}; - -export type FrontendToolRequest = { - id: string; - toolCall: { - [key: string]: unknown; - }; -}; - -export type GetToolsQuery = { - extension_name?: string | null; - session_id: string; -}; - -export type GooseApp = McpAppResource & (WindowProps | null) & { - mcpServers?: Array; - prd?: string | null; -}; - -export type GooseMode = 'auto' | 'approve' | 'smart_approve' | 'chat'; - -export type Icon = { - mimeType?: string; - sizes?: Array; - src: string; - theme?: IconTheme | { - [key: string]: unknown; - }; -}; - -export type IconTheme = 'light' | 'dark'; - -export type ImageContent = { - _meta?: { - [key: string]: unknown; - }; - annotations?: Annotations | { - [key: string]: unknown; - }; - data: string; - mimeType: string; -}; - -export type InferenceMetadata = { - provider: string; - requestedModel: string; - resolvedModel?: string | null; -}; - -export type InspectJobResponse = { - processStartTime?: string | null; - runningDurationSeconds?: number | null; - sessionId?: string | null; -}; - -export type JsonObject = { - [key: string]: unknown; -}; - -export type KillJobResponse = { - message: string; -}; - -export type ListRecipeResponse = { - manifests: Array; -}; - -export type ListSchedulesResponse = { - jobs: Array; -}; - -export type LoadedProvider = { - config: DeclarativeProviderConfig; - is_editable: boolean; -}; - -/** - * MCP App Resource - * Represents a UI resource that can be rendered in an MCP App - */ -export type McpAppResource = { - _meta?: ResourceMetadata | null; - /** - * Base64-encoded binary content (alternative to text) - */ - blob?: string | null; - /** - * Optional description of what this resource does - */ - description?: string | null; - /** - * MIME type (should be "text/html;profile=mcp-app" for MCP Apps) - */ - mimeType: string; - /** - * Human-readable name of the resource - */ - name: string; - /** - * Text content of the resource (HTML for MCP Apps) - */ - text?: string | null; - /** - * URI of the resource (must use ui:// scheme) - */ - uri: string; -}; - -/** - * A message to or from an LLM - */ -export type Message = { - content: Array; - created: number; - id?: string | null; - metadata: MessageMetadata; - role: Role; -}; - -/** - * Content passed inside a message, which can be both simple content and tool content - */ -export type MessageContent = (TextContent & { - type: 'text'; -}) | (ImageContent & { - type: 'image'; -}) | (ToolRequest & { - type: 'toolRequest'; -}) | (ToolResponse & { - type: 'toolResponse'; -}) | (ToolConfirmationRequest & { - type: 'toolConfirmationRequest'; -}) | (ActionRequired & { - type: 'actionRequired'; -}) | (FrontendToolRequest & { - type: 'frontendToolRequest'; -}) | (ThinkingContent & { - type: 'thinking'; -}) | (RedactedThinkingContent & { - type: 'redactedThinking'; -}) | (SystemNotificationContent & { - type: 'systemNotification'; -}); - -export type MessageEvent = { - message: Message; - token_state: TokenState; - type: 'Message'; -} | { - error: string; - type: 'Error'; -} | { - reason: string; - token_state: TokenState; - type: 'Finish'; -} | { - message: { - [key: string]: unknown; - }; - request_id: string; - type: 'Notification'; -} | { - conversation: Conversation; - type: 'UpdateConversation'; -} | { - request_ids: Array; - type: 'ActiveRequests'; -} | { - type: 'Ping'; -}; - -/** - * Metadata for message visibility and model inference details - */ -export type MessageMetadata = { - /** - * Whether the message should be included in the agent's context window - */ - agentVisible: boolean; - inference?: InferenceMetadata | null; - /** - * Whether this message is a steer injected into an active run. UI-only: - * surfaced as `_meta.goose.steer` so clients can mark the steer boundary - * without matching user-visible text. Never sent to providers. - */ - steer?: boolean; - /** - * Whether the message should be visible to the user in the UI - */ - userVisible: boolean; -}; - -export type ModelCapabilities = { - attachment: boolean; - reasoning: boolean; - temperature: boolean; - tool_call: boolean; -}; - -export type ModelConfig = { - context_limit?: number | null; - max_tokens?: number | null; - model_name: string; - reasoning?: boolean | null; - /** - * Provider-specific request parameters (e.g., anthropic_beta headers) - */ - request_params?: { - [key: string]: unknown; - } | null; - temperature?: number | null; - toolshim: boolean; - toolshim_model?: string | null; -}; - -/** - * Information about a model's capabilities - */ -export type ModelInfo = { - /** - * The maximum context length this model supports - */ - context_limit: number; - /** - * Currency for the costs (default: "$") - */ - currency?: string | null; - /** - * Cost per token for input in USD (optional) - */ - input_token_cost?: number | null; - /** - * The name of the model - */ - name: string; - /** - * Cost per token for output in USD (optional) - */ - output_token_cost?: number | null; - /** - * Whether this model supports reasoning/thinking controls - */ - reasoning?: boolean; - /** - * The underlying model resolved from provider metadata, when the configured model is an alias or endpoint. - */ - resolved_model?: string | null; - /** - * Whether this model supports cache control - */ - supports_cache_control?: boolean | null; -}; - -export type ModelInfoData = { - cache_read_token_cost?: number | null; - cache_write_token_cost?: number | null; - context_limit: number; - currency: string; - input_token_cost?: number | null; - max_output_tokens?: number | null; - model: string; - output_token_cost?: number | null; - provider: string; - reasoning: boolean; -}; - -export type ModelInfoQuery = { - model: string; - provider: string; -}; - -export type ModelInfoResponse = { - model_info?: ModelInfoData | null; - source: string; -}; - -export type ModelTemplate = { - capabilities: ModelCapabilities; - context_limit: number; - deprecated: boolean; - id: string; - name: string; -}; - -export type ParseRecipeRequest = { - content: string; -}; - -export type ParseRecipeResponse = { - recipe: Recipe; -}; - -export type Permission = 'always_allow' | 'allow_once' | 'cancel' | 'deny_once' | 'always_deny'; - -/** - * Enum representing the possible permission levels for a tool. - */ -export type PermissionLevel = 'always_allow' | 'ask_before' | 'never_allow'; - -/** - * Sandbox permissions for MCP Apps - * Specifies which browser capabilities the UI needs access to. - * Maps to the iframe Permission Policy `allow` attribute. - */ -export type PermissionsMetadata = { - /** - * Request camera access (maps to Permission Policy `camera` feature) - */ - camera?: boolean; - /** - * Request clipboard write access (maps to Permission Policy `clipboard-write` feature) - */ - clipboardWrite?: boolean; - /** - * Request geolocation access (maps to Permission Policy `geolocation` feature) - */ - geolocation?: boolean; - /** - * Request microphone access (maps to Permission Policy `microphone` feature) - */ - microphone?: boolean; -}; - -export type PrincipalType = 'Extension' | 'Tool'; - -export type PromptContentResponse = { - content: string; - default_content: string; - is_customized: boolean; - name: string; -}; - -export type PromptsListResponse = { - prompts: Array